summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:37:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:37:08 +0000
commitd710a65c8b50bc3d4d0920dc6e865296f42edd5e (patch)
treed3bf9843448af9398b55f49a50a194bbaacd724e
parentInitial commit. (diff)
downloadnghttp2-d710a65c8b50bc3d4d0920dc6e865296f42edd5e.tar.xz
nghttp2-d710a65c8b50bc3d4d0920dc6e865296f42edd5e.zip
Adding upstream version 1.59.0.upstream/1.59.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.clang-format215
-rw-r--r--.github/dependabot.yml6
-rw-r--r--.github/workflows/build.yml530
-rw-r--r--.github/workflows/fuzz.yml24
-rw-r--r--.gitignore56
-rw-r--r--.gitmodules7
-rw-r--r--AUTHORS158
-rw-r--r--CMakeLists.txt491
-rw-r--r--CMakeOptions.txt28
-rw-r--r--CONTRIBUTION18
-rw-r--r--COPYING23
-rw-r--r--ChangeLog0
-rw-r--r--Dockerfile.android121
-rw-r--r--LICENSE1
-rw-r--r--Makefile.am61
-rw-r--r--NEWS0
-rw-r--r--README1
-rw-r--r--README.rst1474
-rwxr-xr-xandroid-config37
-rwxr-xr-xandroid-env40
-rwxr-xr-xauthor.py52
-rw-r--r--bpf/CMakeLists.txt13
-rw-r--r--bpf/Makefile.am40
-rw-r--r--bpf/reuseport_kern.c663
-rw-r--r--cmake/ExtractValidFlags.cmake31
-rw-r--r--cmake/FindCUnit.cmake40
-rw-r--r--cmake/FindJansson.cmake40
-rw-r--r--cmake/FindJemalloc.cmake40
-rw-r--r--cmake/FindLibbpf.cmake32
-rw-r--r--cmake/FindLibcares.cmake40
-rw-r--r--cmake/FindLibev.cmake38
-rw-r--r--cmake/FindLibevent.cmake97
-rw-r--r--cmake/FindLibnghttp3.cmake41
-rw-r--r--cmake/FindLibngtcp2.cmake41
-rw-r--r--cmake/FindLibngtcp2_crypto_quictls.cmake43
-rw-r--r--cmake/FindSystemd.cmake19
-rw-r--r--cmake/PickyWarningsC.cmake163
-rw-r--r--cmake/PickyWarningsCXX.cmake117
-rw-r--r--cmake/Version.cmake11
-rw-r--r--cmakeconfig.h.in101
-rw-r--r--configure.ac1185
-rw-r--r--contrib/.gitignore3
-rw-r--r--contrib/CMakeLists.txt12
-rw-r--r--contrib/Makefile.am51
-rwxr-xr-xcontrib/nghttpx-init.in164
-rw-r--r--contrib/nghttpx-logrotate11
-rw-r--r--contrib/nghttpx-upstart.conf.in8
-rw-r--r--contrib/nghttpx.service.in17
-rw-r--r--contrib/tlsticketupdate.go112
-rw-r--r--contrib/usr.sbin.nghttpx16
-rw-r--r--doc/.gitignore19
-rw-r--r--doc/CMakeLists.txt352
-rw-r--r--doc/Makefile.am366
-rw-r--r--doc/README.rst160
-rw-r--r--doc/_exts/rubydomain/LICENSE.rubydomain28
-rw-r--r--doc/_exts/rubydomain/__init__.py14
-rw-r--r--doc/_exts/rubydomain/rubydomain.py703
-rw-r--r--doc/bash_completion/h2load19
-rwxr-xr-xdoc/bash_completion/make_bash_completion.py75
-rw-r--r--doc/bash_completion/nghttp19
-rw-r--r--doc/bash_completion/nghttpd19
-rw-r--r--doc/bash_completion/nghttpx19
-rw-r--r--doc/building-android-binary.rst.in1
-rw-r--r--doc/conf.py.in252
-rw-r--r--doc/contribute.rst.in1
-rw-r--r--doc/docutils.conf2
-rw-r--r--doc/h2load-howto.rst.in1
-rw-r--r--doc/h2load.1530
-rw-r--r--doc/h2load.1.rst434
-rw-r--r--doc/h2load.h2r120
-rw-r--r--doc/index.rst.in1
-rw-r--r--doc/make.bat170
-rwxr-xr-xdoc/mkapiref.py343
-rw-r--r--doc/nghttp.1336
-rw-r--r--doc/nghttp.1.rst276
-rw-r--r--doc/nghttp.h2r57
-rw-r--r--doc/nghttp2.h.rst.in4
-rw-r--r--doc/nghttp2ver.h.rst.in4
-rw-r--r--doc/nghttpd.1236
-rw-r--r--doc/nghttpd.1.rst186
-rw-r--r--doc/nghttpd.h2r4
-rw-r--r--doc/nghttpx-howto.rst.in1
-rw-r--r--doc/nghttpx.12770
-rw-r--r--doc/nghttpx.1.rst2526
-rw-r--r--doc/nghttpx.h2r719
-rw-r--r--doc/package_README.rst.in1
-rw-r--r--doc/programmers-guide.rst526
-rw-r--r--doc/security.rst1
-rw-r--r--doc/sources/building-android-binary.rst127
-rw-r--r--doc/sources/contribute.rst56
-rw-r--r--doc/sources/h2load-howto.rst142
-rw-r--r--doc/sources/index.rst50
-rw-r--r--doc/sources/nghttpx-howto.rst642
-rw-r--r--doc/sources/security.rst33
-rw-r--r--doc/sources/tutorial-client.rst464
-rw-r--r--doc/sources/tutorial-hpack.rst126
-rw-r--r--doc/sources/tutorial-server.rst577
-rw-r--r--doc/tutorial-client.rst.in6
-rw-r--r--doc/tutorial-hpack.rst.in6
-rw-r--r--doc/tutorial-server.rst.in6
-rw-r--r--docker/Dockerfile78
-rw-r--r--docker/README.rst25
-rw-r--r--examples/.gitignore5
-rw-r--r--examples/CMakeLists.txt37
-rw-r--r--examples/Makefile.am54
-rw-r--r--examples/client.c699
-rw-r--r--examples/deflate.c206
-rw-r--r--examples/libevent-client.c595
-rw-r--r--examples/libevent-server.c787
-rw-r--r--fedora/spdylay.spec75
-rw-r--r--fuzz/README.rst33
-rw-r--r--fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8bin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5abin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66bin0 -> 16466 bytes
-rw-r--r--fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71abin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccdbin0 -> 82 bytes
-rw-r--r--fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f2
-rw-r--r--fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32bin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39bin0 -> 76 bytes
-rw-r--r--fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9bin0 -> 96 bytes
-rw-r--r--fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5bin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798abin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dcbin0 -> 3603 bytes
-rw-r--r--fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800bin0 -> 88 bytes
-rw-r--r--fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300bin0 -> 77 bytes
-rw-r--r--fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0bin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07abbin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8bin0 -> 78 bytes
-rw-r--r--fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76bin0 -> 84 bytes
-rw-r--r--fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248bin0 -> 98 bytes
-rw-r--r--fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6bin0 -> 98 bytes
-rw-r--r--fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678bin0 -> 2487 bytes
-rw-r--r--fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59dfbin0 -> 71 bytes
-rw-r--r--fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502fbin0 -> 77 bytes
-rw-r--r--fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132bin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbcebin0 -> 93 bytes
-rw-r--r--fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0bin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91bin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5bin0 -> 3603 bytes
-rw-r--r--fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933bin0 -> 114 bytes
-rw-r--r--fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93abin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13cbin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17dbin0 -> 75 bytes
-rw-r--r--fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8fbin0 -> 89 bytes
-rw-r--r--fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02cabin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92cbin0 -> 90 bytes
-rw-r--r--fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cfbin0 -> 17632 bytes
-rw-r--r--fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941bin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04abin0 -> 93 bytes
-rw-r--r--fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801bin0 -> 83 bytes
-rw-r--r--fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60bin0 -> 82 bytes
-rw-r--r--fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82bin0 -> 58 bytes
-rw-r--r--fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cabbin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193bin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16bin0 -> 84 bytes
-rw-r--r--fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937bin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13bin0 -> 91 bytes
-rw-r--r--fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3abin0 -> 3593 bytes
-rw-r--r--fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993ebin0 -> 72 bytes
-rw-r--r--fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aabin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878abin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63bin0 -> 82 bytes
-rw-r--r--fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8bin0 -> 96 bytes
-rw-r--r--fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3ebin0 -> 95 bytes
-rw-r--r--fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0cbin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2dbin0 -> 84 bytes
-rw-r--r--fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33bin0 -> 71 bytes
-rw-r--r--fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604bin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885bin0 -> 87 bytes
-rw-r--r--fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61ebin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbeabin0 -> 89 bytes
-rw-r--r--fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4fbin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2bin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0bin0 -> 87 bytes
-rw-r--r--fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5abin0 -> 92 bytes
-rw-r--r--fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707bin0 -> 74 bytes
-rw-r--r--fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18bin0 -> 77 bytes
-rw-r--r--fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45bin0 -> 60 bytes
-rw-r--r--fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fadabin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5bin0 -> 80 bytes
-rw-r--r--fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902abin0 -> 101 bytes
-rw-r--r--fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0bin0 -> 61 bytes
-rw-r--r--fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357bin0 -> 103 bytes
-rw-r--r--fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453bin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edbbin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eefbin0 -> 58 bytes
-rw-r--r--fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43bin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838bin0 -> 100 bytes
-rw-r--r--fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702bin0 -> 81 bytes
-rw-r--r--fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7bin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88bin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07bin0 -> 83 bytes
-rw-r--r--fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225bin0 -> 3606 bytes
-rw-r--r--fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1bin0 -> 84 bytes
-rw-r--r--fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250fbin0 -> 91 bytes
-rw-r--r--fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8bin0 -> 102 bytes
-rw-r--r--fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76bin0 -> 75 bytes
-rw-r--r--fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1bbin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5bin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1bebbin0 -> 71 bytes
-rw-r--r--fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7bin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7bin0 -> 104 bytes
-rw-r--r--fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9abin0 -> 39 bytes
-rw-r--r--fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9bin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423bin0 -> 87 bytes
-rw-r--r--fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738dbin0 -> 101 bytes
-rw-r--r--fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1ebin0 -> 117 bytes
-rw-r--r--fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9bin0 -> 98 bytes
-rw-r--r--fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64ccbin0 -> 70 bytes
-rw-r--r--fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477adbin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89bbin0 -> 86 bytes
-rw-r--r--fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11bin0 -> 72 bytes
-rw-r--r--fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7bin0 -> 58 bytes
-rw-r--r--fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0bin0 -> 102 bytes
-rw-r--r--fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3bin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6bin0 -> 62 bytes
-rw-r--r--fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105bin0 -> 3593 bytes
-rw-r--r--fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1dbin0 -> 81 bytes
-rw-r--r--fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cfbin0 -> 76 bytes
-rw-r--r--fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99abin0 -> 98 bytes
-rw-r--r--fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65bin0 -> 84 bytes
-rw-r--r--fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11bin0 -> 16465 bytes
-rw-r--r--fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120bin0 -> 62 bytes
-rw-r--r--fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7bin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4dfbin0 -> 102 bytes
-rw-r--r--fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629bin0 -> 81 bytes
-rw-r--r--fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14bin0 -> 60 bytes
-rw-r--r--fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433bin0 -> 79 bytes
-rw-r--r--fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73bin0 -> 73 bytes
-rw-r--r--fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3eebin0 -> 102 bytes
-rw-r--r--fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3abin0 -> 63 bytes
-rw-r--r--fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877bin0 -> 72 bytes
-rw-r--r--fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1ccbin0 -> 115 bytes
-rw-r--r--fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03bin0 -> 65 bytes
-rw-r--r--fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89ebin0 -> 72 bytes
-rw-r--r--fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98bin0 -> 90 bytes
-rw-r--r--fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116bin0 -> 85 bytes
-rw-r--r--fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6bin0 -> 91 bytes
-rw-r--r--fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5bin0 -> 3606 bytes
-rw-r--r--fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cbbin0 -> 191 bytes
-rw-r--r--fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911bin0 -> 5070 bytes
-rw-r--r--fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aabin0 -> 18740 bytes
-rw-r--r--fuzz/fuzz_frames.cc160
-rw-r--r--fuzz/fuzz_target.cc79
-rw-r--r--fuzz/fuzz_target_fdp.cc99
-rwxr-xr-xgenauthoritychartbl.py32
-rwxr-xr-xgendowncasetbl.py30
-rwxr-xr-xgenheaderfunc.py48
-rwxr-xr-xgenlibtokenlookup.py143
-rwxr-xr-xgenmethodchartbl.py29
-rwxr-xr-xgenmethodfunc.py52
-rwxr-xr-xgennghttpxfun.py242
-rwxr-xr-xgennmchartbl.py28
-rwxr-xr-xgenpathchartbl.py23
-rw-r--r--gentokenlookup.py69
-rwxr-xr-xgenvchartbl.py26
-rwxr-xr-xgit-clang-format484
-rw-r--r--go.mod24
-rw-r--r--go.sum57
-rwxr-xr-xhelp2rst.py192
-rw-r--r--integration-tests/.gitignore3
-rw-r--r--integration-tests/CMakeLists.txt45
-rw-r--r--integration-tests/Makefile.am52
-rw-r--r--integration-tests/alt-server.crt21
-rw-r--r--integration-tests/alt-server.key28
-rw-r--r--integration-tests/config.go.in6
-rw-r--r--integration-tests/nghttpx_http1_test.go1631
-rw-r--r--integration-tests/nghttpx_http2_test.go3740
-rw-r--r--integration-tests/nghttpx_http3_test.go393
-rw-r--r--integration-tests/req-return.rb12
-rw-r--r--integration-tests/req-set-header.rb7
-rw-r--r--integration-tests/resp-return.rb12
-rw-r--r--integration-tests/resp-set-header.rb7
-rw-r--r--integration-tests/server.crt21
-rw-r--r--integration-tests/server.key28
-rw-r--r--integration-tests/server_tester.go819
-rw-r--r--integration-tests/server_tester_http3.go90
-rw-r--r--integration-tests/setenv.in13
-rw-r--r--lib/.gitignore3
-rw-r--r--lib/CMakeLists.txt80
-rw-r--r--lib/Makefile.am81
-rw-r--r--lib/Makefile.msvc254
-rw-r--r--lib/includes/CMakeLists.txt4
-rw-r--r--lib/includes/Makefile.am26
-rw-r--r--lib/includes/nghttp2/nghttp2.h5941
-rw-r--r--lib/includes/nghttp2/nghttp2ver.h.in42
-rw-r--r--lib/libnghttp2.pc.in33
-rw-r--r--lib/nghttp2_alpn.c70
-rw-r--r--lib/nghttp2_alpn.h34
-rw-r--r--lib/nghttp2_buf.c527
-rw-r--r--lib/nghttp2_buf.h412
-rw-r--r--lib/nghttp2_callbacks.c175
-rw-r--r--lib/nghttp2_callbacks.h125
-rw-r--r--lib/nghttp2_debug.c60
-rw-r--r--lib/nghttp2_debug.h43
-rw-r--r--lib/nghttp2_extpri.c41
-rw-r--r--lib/nghttp2_extpri.h65
-rw-r--r--lib/nghttp2_frame.c1214
-rw-r--r--lib/nghttp2_frame.h637
-rw-r--r--lib/nghttp2_hd.c2357
-rw-r--r--lib/nghttp2_hd.h440
-rw-r--r--lib/nghttp2_hd_huffman.c144
-rw-r--r--lib/nghttp2_hd_huffman.h72
-rw-r--r--lib/nghttp2_hd_huffman_data.c4980
-rw-r--r--lib/nghttp2_helper.c803
-rw-r--r--lib/nghttp2_helper.h122
-rw-r--r--lib/nghttp2_http.c631
-rw-r--r--lib/nghttp2_http.h100
-rw-r--r--lib/nghttp2_int.h58
-rw-r--r--lib/nghttp2_map.c338
-rw-r--r--lib/nghttp2_map.h138
-rw-r--r--lib/nghttp2_mem.c74
-rw-r--r--lib/nghttp2_mem.h45
-rw-r--r--lib/nghttp2_net.h91
-rw-r--r--lib/nghttp2_option.c152
-rw-r--r--lib/nghttp2_option.h152
-rw-r--r--lib/nghttp2_outbound_item.c130
-rw-r--r--lib/nghttp2_outbound_item.h166
-rw-r--r--lib/nghttp2_pq.c183
-rw-r--r--lib/nghttp2_pq.h124
-rw-r--r--lib/nghttp2_priority_spec.c52
-rw-r--r--lib/nghttp2_priority_spec.h42
-rw-r--r--lib/nghttp2_queue.c85
-rw-r--r--lib/nghttp2_queue.h51
-rw-r--r--lib/nghttp2_ratelim.c75
-rw-r--r--lib/nghttp2_ratelim.h57
-rw-r--r--lib/nghttp2_rcbuf.c102
-rw-r--r--lib/nghttp2_rcbuf.h80
-rw-r--r--lib/nghttp2_session.c8395
-rw-r--r--lib/nghttp2_session.h974
-rw-r--r--lib/nghttp2_stream.c1016
-rw-r--r--lib/nghttp2_stream.h441
-rw-r--r--lib/nghttp2_submit.c900
-rw-r--r--lib/nghttp2_submit.h34
-rw-r--r--lib/nghttp2_time.c63
-rw-r--r--lib/nghttp2_time.h38
-rw-r--r--lib/nghttp2_version.c38
-rw-r--r--lib/sfparse.c1146
-rw-r--r--lib/sfparse.h409
-rw-r--r--lib/version.rc.in40
-rw-r--r--m4/ax_check_compile_flag.m474
-rw-r--r--m4/ax_cxx_compile_stdcxx.m41018
-rw-r--r--m4/libxml2.m4188
-rwxr-xr-xmakebashcompletion7
-rwxr-xr-xmakemanpages12
-rwxr-xr-xmakerelease.sh23
-rwxr-xr-xmkcipherlist.py325
-rwxr-xr-xmkhufftbl.py468
-rwxr-xr-xmkstatichdtbl.py36
-rw-r--r--nghttpx.conf.sample29
-rwxr-xr-xpre-commit27
-rw-r--r--proxy.pac.sample6
-rwxr-xr-xreleasechk6
-rw-r--r--script/CMakeLists.txt5
-rw-r--r--script/Makefile.am25
-rw-r--r--script/README.rst10
-rwxr-xr-xscript/fetch-ocsp-response253
-rw-r--r--src/.gitignore13
-rw-r--r--src/CMakeLists.txt243
-rw-r--r--src/HtmlParser.cc217
-rw-r--r--src/HtmlParser.h94
-rw-r--r--src/HttpServer.cc2248
-rw-r--r--src/HttpServer.h253
-rw-r--r--src/Makefile.am257
-rw-r--r--src/allocator.h273
-rw-r--r--src/app_helper.cc518
-rw-r--r--src/app_helper.h98
-rw-r--r--src/base64.h225
-rw-r--r--src/base64_test.cc121
-rw-r--r--src/base64_test.h39
-rw-r--r--src/buffer.h78
-rw-r--r--src/buffer_test.cc78
-rw-r--r--src/buffer_test.h38
-rw-r--r--src/ca-config.json17
-rw-r--r--src/ca.nghttp2.org-key.pem27
-rw-r--r--src/ca.nghttp2.org.csr17
-rw-r--r--src/ca.nghttp2.org.csr.json17
-rw-r--r--src/ca.nghttp2.org.pem22
-rw-r--r--src/comp_helper.c133
-rw-r--r--src/comp_helper.h57
-rw-r--r--src/deflatehd.cc450
-rw-r--r--src/h2load.cc3292
-rw-r--r--src/h2load.h510
-rw-r--r--src/h2load_http1_session.cc306
-rw-r--r--src/h2load_http1_session.h60
-rw-r--r--src/h2load_http2_session.cc314
-rw-r--r--src/h2load_http2_session.h54
-rw-r--r--src/h2load_http3_session.cc474
-rw-r--r--src/h2load_http3_session.h84
-rw-r--r--src/h2load_quic.cc844
-rw-r--r--src/h2load_quic.h38
-rw-r--r--src/h2load_session.h59
-rw-r--r--src/http-parser.patch28
-rw-r--r--src/http2.cc2096
-rw-r--r--src/http2.h457
-rw-r--r--src/http2_test.cc1249
-rw-r--r--src/http2_test.h54
-rw-r--r--src/http3.cc206
-rw-r--r--src/http3.h123
-rw-r--r--src/inflatehd.cc289
-rw-r--r--src/libevent_util.cc162
-rw-r--r--src/libevent_util.h75
-rw-r--r--src/memchunk.h664
-rw-r--r--src/memchunk_test.cc340
-rw-r--r--src/memchunk_test.h47
-rw-r--r--src/network.h67
-rw-r--r--src/nghttp.cc3115
-rw-r--r--src/nghttp.h310
-rw-r--r--src/nghttp2_config.h32
-rw-r--r--src/nghttp2_gzip.c87
-rw-r--r--src/nghttp2_gzip.h122
-rw-r--r--src/nghttp2_gzip_test.c111
-rw-r--r--src/nghttp2_gzip_test.h42
-rw-r--r--src/nghttpd.cc502
-rw-r--r--src/quic.cc60
-rw-r--r--src/quic.h56
-rw-r--r--src/shrpx-unittest.cc246
-rw-r--r--src/shrpx.cc5329
-rw-r--r--src/shrpx.h58
-rw-r--r--src/shrpx_accept_handler.cc111
-rw-r--r--src/shrpx_accept_handler.h54
-rw-r--r--src/shrpx_api_downstream_connection.cc478
-rw-r--r--src/shrpx_api_downstream_connection.h114
-rw-r--r--src/shrpx_client_handler.cc1703
-rw-r--r--src/shrpx_client_handler.h236
-rw-r--r--src/shrpx_config.cc4694
-rw-r--r--src/shrpx_config.h1450
-rw-r--r--src/shrpx_config_test.cc249
-rw-r--r--src/shrpx_config_test.h42
-rw-r--r--src/shrpx_connect_blocker.cc143
-rw-r--r--src/shrpx_connect_blocker.h86
-rw-r--r--src/shrpx_connection.cc1275
-rw-r--r--src/shrpx_connection.h203
-rw-r--r--src/shrpx_connection_handler.cc1319
-rw-r--r--src/shrpx_connection_handler.h322
-rw-r--r--src/shrpx_dns_resolver.cc353
-rw-r--r--src/shrpx_dns_resolver.h118
-rw-r--r--src/shrpx_dns_tracker.cc328
-rw-r--r--src/shrpx_dns_tracker.h121
-rw-r--r--src/shrpx_downstream.cc1189
-rw-r--r--src/shrpx_downstream.h628
-rw-r--r--src/shrpx_downstream_connection.cc48
-rw-r--r--src/shrpx_downstream_connection.h81
-rw-r--r--src/shrpx_downstream_connection_pool.cc66
-rw-r--r--src/shrpx_downstream_connection_pool.h53
-rw-r--r--src/shrpx_downstream_queue.cc175
-rw-r--r--src/shrpx_downstream_queue.h116
-rw-r--r--src/shrpx_downstream_test.cc231
-rw-r--r--src/shrpx_downstream_test.h44
-rw-r--r--src/shrpx_dual_dns_resolver.cc93
-rw-r--r--src/shrpx_dual_dns_resolver.h69
-rw-r--r--src/shrpx_error.h47
-rw-r--r--src/shrpx_exec.cc138
-rw-r--r--src/shrpx_exec.h47
-rw-r--r--src/shrpx_health_monitor_downstream_connection.cc116
-rw-r--r--src/shrpx_health_monitor_downstream_connection.h64
-rw-r--r--src/shrpx_http.cc280
-rw-r--r--src/shrpx_http.h96
-rw-r--r--src/shrpx_http2_downstream_connection.cc621
-rw-r--r--src/shrpx_http2_downstream_connection.h88
-rw-r--r--src/shrpx_http2_session.cc2426
-rw-r--r--src/shrpx_http2_session.h296
-rw-r--r--src/shrpx_http2_upstream.cc2404
-rw-r--r--src/shrpx_http2_upstream.h150
-rw-r--r--src/shrpx_http3_upstream.cc2906
-rw-r--r--src/shrpx_http3_upstream.h193
-rw-r--r--src/shrpx_http_downstream_connection.cc1617
-rw-r--r--src/shrpx_http_downstream_connection.h124
-rw-r--r--src/shrpx_http_test.cc168
-rw-r--r--src/shrpx_http_test.h42
-rw-r--r--src/shrpx_https_upstream.cc1582
-rw-r--r--src/shrpx_https_upstream.h113
-rw-r--r--src/shrpx_io_control.cc66
-rw-r--r--src/shrpx_io_control.h58
-rw-r--r--src/shrpx_live_check.cc792
-rw-r--r--src/shrpx_live_check.h125
-rw-r--r--src/shrpx_log.cc1008
-rw-r--r--src/shrpx_log.h318
-rw-r--r--src/shrpx_log_config.cc127
-rw-r--r--src/shrpx_log_config.h79
-rw-r--r--src/shrpx_memcached_connection.cc777
-rw-r--r--src/shrpx_memcached_connection.h155
-rw-r--r--src/shrpx_memcached_dispatcher.cc53
-rw-r--r--src/shrpx_memcached_dispatcher.h63
-rw-r--r--src/shrpx_memcached_request.h59
-rw-r--r--src/shrpx_memcached_result.h50
-rw-r--r--src/shrpx_mruby.cc238
-rw-r--r--src/shrpx_mruby.h89
-rw-r--r--src/shrpx_mruby_module.cc113
-rw-r--r--src/shrpx_mruby_module.h52
-rw-r--r--src/shrpx_mruby_module_env.cc500
-rw-r--r--src/shrpx_mruby_module_env.h42
-rw-r--r--src/shrpx_mruby_module_request.cc367
-rw-r--r--src/shrpx_mruby_module_request.h42
-rw-r--r--src/shrpx_mruby_module_response.cc398
-rw-r--r--src/shrpx_mruby_module_response.h42
-rw-r--r--src/shrpx_null_downstream_connection.cc88
-rw-r--r--src/shrpx_null_downstream_connection.h68
-rw-r--r--src/shrpx_process.h37
-rw-r--r--src/shrpx_quic.cc393
-rw-r--r--src/shrpx_quic.h138
-rw-r--r--src/shrpx_quic_connection_handler.cc761
-rw-r--r--src/shrpx_quic_connection_handler.h142
-rw-r--r--src/shrpx_quic_listener.cc132
-rw-r--r--src/shrpx_quic_listener.h51
-rw-r--r--src/shrpx_rate_limit.cc123
-rw-r--r--src/shrpx_rate_limit.h68
-rw-r--r--src/shrpx_router.cc420
-rw-r--r--src/shrpx_router.h110
-rw-r--r--src/shrpx_router_test.cc184
-rw-r--r--src/shrpx_router_test.h40
-rw-r--r--src/shrpx_signal.cc138
-rw-r--r--src/shrpx_signal.h60
-rw-r--r--src/shrpx_tls.cc2465
-rw-r--r--src/shrpx_tls.h321
-rw-r--r--src/shrpx_tls_test.cc339
-rw-r--r--src/shrpx_tls_test.h42
-rw-r--r--src/shrpx_upstream.h112
-rw-r--r--src/shrpx_worker.cc1347
-rw-r--r--src/shrpx_worker.h480
-rw-r--r--src/shrpx_worker_process.cc701
-rw-r--r--src/shrpx_worker_process.h67
-rw-r--r--src/shrpx_worker_test.cc247
-rw-r--r--src/shrpx_worker_test.h38
-rw-r--r--src/ssl_compat.h48
-rw-r--r--src/template.h548
-rw-r--r--src/template_test.cc204
-rw-r--r--src/template_test.h39
-rw-r--r--src/test.example.com-key.pem27
-rw-r--r--src/test.example.com.csr17
-rw-r--r--src/test.example.com.csr.json14
-rw-r--r--src/test.example.com.pem23
-rw-r--r--src/test.nghttp2.org-key.pem27
-rw-r--r--src/test.nghttp2.org.csr19
-rw-r--r--src/test.nghttp2.org.csr.json19
-rw-r--r--src/test.nghttp2.org.pem24
-rw-r--r--src/testdata/Makefile.am27
-rw-r--r--src/testdata/ipaddr.crt10
-rw-r--r--src/testdata/nosan.crt9
-rw-r--r--src/testdata/nosan_ip.crt9
-rw-r--r--src/testdata/verify_hostname.crt10
-rw-r--r--src/timegm.c88
-rw-r--r--src/timegm.h49
-rw-r--r--src/tls.cc125
-rw-r--r--src/tls.h104
-rw-r--r--src/util.cc1796
-rw-r--r--src/util.h971
-rw-r--r--src/util_test.cc707
-rw-r--r--src/util_test.h75
-rw-r--r--src/xsi_strerror.c50
-rw-r--r--src/xsi_strerror.h55
-rw-r--r--tests/.gitignore3
-rw-r--r--tests/CMakeLists.txt55
-rw-r--r--tests/Makefile.am93
-rw-r--r--tests/failmalloc.c79
-rw-r--r--tests/failmalloc_test.c576
-rw-r--r--tests/failmalloc_test.h38
-rw-r--r--tests/main.c473
-rw-r--r--tests/malloc_wrapper.c85
-rw-r--r--tests/malloc_wrapper.h65
-rw-r--r--tests/nghttp2_alpn_test.c95
-rw-r--r--tests/nghttp2_alpn_test.h34
-rw-r--r--tests/nghttp2_buf_test.c344
-rw-r--r--tests/nghttp2_buf_test.h42
-rw-r--r--tests/nghttp2_extpri_test.c52
-rw-r--r--tests/nghttp2_extpri_test.h35
-rw-r--r--tests/nghttp2_frame_test.c735
-rw-r--r--tests/nghttp2_frame_test.h47
-rw-r--r--tests/nghttp2_hd_test.c1577
-rw-r--r--tests/nghttp2_hd_test.h55
-rw-r--r--tests/nghttp2_helper_test.c195
-rw-r--r--tests/nghttp2_helper_test.h37
-rw-r--r--tests/nghttp2_http_test.c206
-rw-r--r--tests/nghttp2_http_test.h35
-rw-r--r--tests/nghttp2_map_test.c208
-rw-r--r--tests/nghttp2_map_test.h38
-rw-r--r--tests/nghttp2_pq_test.c228
-rw-r--r--tests/nghttp2_pq_test.h36
-rw-r--r--tests/nghttp2_queue_test.c50
-rw-r--r--tests/nghttp2_queue_test.h34
-rw-r--r--tests/nghttp2_ratelim_test.c101
-rw-r--r--tests/nghttp2_ratelim_test.h35
-rw-r--r--tests/nghttp2_session_test.c13438
-rw-r--r--tests/nghttp2_session_test.h184
-rw-r--r--tests/nghttp2_stream_test.c31
-rw-r--r--tests/nghttp2_stream_test.h32
-rw-r--r--tests/nghttp2_test_helper.c435
-rw-r--r--tests/nghttp2_test_helper.h158
-rw-r--r--tests/testdata/Makefile.am23
-rw-r--r--tests/testdata/cacert.pem14
-rw-r--r--tests/testdata/index.html1
-rw-r--r--tests/testdata/privkey.pem9
-rw-r--r--third-party/CMakeLists.txt81
-rw-r--r--third-party/Makefile.am593
-rw-r--r--third-party/build_config.rb34
-rw-r--r--third-party/llhttp/LICENSE-MIT22
-rw-r--r--third-party/llhttp/README.md482
-rw-r--r--third-party/llhttp/common.gypi46
-rw-r--r--third-party/llhttp/include/llhttp.h871
-rw-r--r--third-party/llhttp/llhttp.gyp22
-rw-r--r--third-party/llhttp/src/api.c494
-rw-r--r--third-party/llhttp/src/http.c150
-rw-r--r--third-party/llhttp/src/llhttp.c9537
-rw-r--r--third-party/url-parser/.gitignore1
-rw-r--r--third-party/url-parser/AUTHORS68
-rw-r--r--third-party/url-parser/LICENSE-MIT19
-rw-r--r--third-party/url-parser/url_parser.c652
-rw-r--r--third-party/url-parser/url_parser.h94
613 files changed, 179321 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..4aa455a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,215 @@
+---
+Language: Cpp
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveAssignments:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: true
+AlignConsecutiveBitFields:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: true
+AlignConsecutiveDeclarations:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: true
+AlignConsecutiveMacros:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: true
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+AttributeMacros:
+ - __capability
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: Always
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: NextLine
+BasedOnStyle: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^(<|"(gtest|isl|json)/)'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+IncludeIsMainRegex: '$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: AfterHash
+IndentExternBlock: AfterExternBlock
+IndentRequiresClause: false
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+InsertBraces: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Right
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: true
+RemoveBracesLLVM: false
+RequiresClausePosition: OwnLine
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+ AfterControlStatements: true
+ AfterForeachMacros: true
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: true
+ AfterOverloadedOperator: false
+ AfterRequiresInClause: false
+ AfterRequiresInExpression: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: Latest
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8c139c7
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+- package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..51dcdfe
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,530 @@
+name: build
+
+on: [push, pull_request]
+
+permissions: read-all
+
+env:
+ LIBBPF_VERSION: v1.3.0
+ OPENSSL1_VERSION: 1_1_1w+quic
+ OPENSSL3_VERSION: 3.1.4+quic
+ BORINGSSL_VERSION: f42be90d665b6a376177648ccbb76fbbd6497c13
+ AWSLC_VERSION: v1.20.0
+ NGHTTP3_VERSION: v1.1.0
+ NGTCP2_VERSION: v1.2.0
+
+jobs:
+ build-cache:
+ strategy:
+ matrix:
+ os: [ubuntu-22.04, macos-12]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Restore libbpf cache
+ id: cache-libbpf
+ uses: actions/cache@v3
+ if: runner.os == 'Linux'
+ with:
+ path: libbpf/build
+ key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }}
+ - name: Restore OpenSSL v1.1.1 cache
+ id: cache-openssl1
+ uses: actions/cache@v3
+ with:
+ path: openssl1/build
+ key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }}
+ - name: Restore OpenSSL v3.x cache
+ id: cache-openssl3
+ uses: actions/cache@v3
+ with:
+ path: openssl3/build
+ key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }}
+ - name: Restore BoringSSL cache
+ id: cache-boringssl
+ uses: actions/cache@v3
+ with:
+ path: |
+ boringssl/build/crypto/libcrypto.a
+ boringssl/build/ssl/libssl.a
+ boringssl/include
+ key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }}
+ - name: Restore aws-lc cache
+ id: cache-awslc
+ uses: actions/cache@v3
+ with:
+ path: |
+ aws-lc/build/crypto/libcrypto.a
+ aws-lc/build/ssl/libssl.a
+ aws-lc/include
+ key: ${{ runner.os }}-awslc-${{ env.AWSLC_VERSION }}
+ - name: Restore nghttp3 cache
+ id: cache-nghttp3
+ uses: actions/cache@v3
+ with:
+ path: nghttp3/build
+ key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }}
+ - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache
+ id: cache-ngtcp2-openssl1
+ uses: actions/cache@v3
+ with:
+ path: ngtcp2-openssl1/build
+ key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }}
+ - name: Restore ngtcp2 + quictls/openssl v3.x cache
+ id: cache-ngtcp2-openssl3
+ uses: actions/cache@v3
+ with:
+ path: ngtcp2-openssl3/build
+ key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }}
+ - id: settings
+ if: |
+ (steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux') ||
+ steps.cache-openssl1.outputs.cache-hit != 'true' ||
+ steps.cache-openssl3.outputs.cache-hit != 'true' ||
+ steps.cache-boringssl.outputs.cache-hit != 'true' ||
+ steps.cache-awslc.outputs.cache-hit != 'true' ||
+ steps.cache-nghttp3.outputs.cache-hit != 'true' ||
+ steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true' ||
+ steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true'
+ run: |
+ echo 'needs-build=true' >> $GITHUB_OUTPUT
+ - name: Linux setup
+ if: runner.os == 'Linux' && steps.settings.outputs.needs-build == 'true'
+ run: |
+ sudo apt-get install \
+ g++-12 \
+ clang-15 \
+ autoconf \
+ automake \
+ autotools-dev \
+ libtool \
+ pkg-config \
+ libelf-dev \
+ cmake \
+ cmake-data
+ - name: MacOS setup
+ if: runner.os == 'macOS' && steps.settings.outputs.needs-build == 'true'
+ run: |
+ brew install \
+ autoconf \
+ automake \
+ pkg-config \
+ libtool
+ - name: Build libbpf
+ if: steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux'
+ run: |
+ git clone -b ${{ env.LIBBPF_VERSION }} https://github.com/libbpf/libbpf
+ cd libbpf
+ make -C src install PREFIX=$PWD/build
+ - name: Build quictls/openssl v1.1.1
+ if: steps.cache-openssl1.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b OpenSSL_${{ env.OPENSSL1_VERSION }} https://github.com/quictls/openssl openssl1
+ cd openssl1
+ ./config --prefix=$PWD/build
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)"
+ make install_sw
+ - name: Build quictls/openssl v3.x
+ if: steps.cache-openssl3.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b openssl-${{ env.OPENSSL3_VERSION }} https://github.com/quictls/openssl openssl3
+ cd openssl3
+ ./config enable-ktls --prefix=$PWD/build --libdir=$PWD/build/lib
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)"
+ make install_sw
+ - name: Build BoringSSL
+ if: steps.cache-boringssl.outputs.cache-hit != 'true'
+ run: |
+ git clone https://boringssl.googlesource.com/boringssl
+ cd boringssl
+ git checkout ${{ env.BORINGSSL_VERSION }}
+ mkdir build
+ cd build
+ cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON ..
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)"
+ - name: Build aws-lc
+ if: steps.cache-awslc.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b "${AWSLC_VERSION}" https://github.com/aws/aws-lc
+ cd aws-lc
+ cmake -B build -DDISABLE_GO=ON
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" -C build
+ - name: Build nghttp3
+ if: steps.cache-nghttp3.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b ${{ env.NGHTTP3_VERSION}} https://github.com/ngtcp2/nghttp3
+ cd nghttp3
+ autoreconf -i
+ ./configure --prefix=$PWD/build --enable-lib-only
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check
+ make install
+ - name: Build ngtcp2 + quictls/openssl v1.1.1 + BoringSSL
+ if: steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl1
+ cd ngtcp2-openssl1
+ autoreconf -i
+ ./configure --prefix=$PWD/build --enable-lib-only \
+ PKG_CONFIG_PATH="../openssl1/build/lib/pkgconfig" \
+ BORINGSSL_CFLAGS="-I$PWD/../boringssl/include/" \
+ BORINGSSL_LIBS="-L$PWD/../boringssl/build/ssl -lssl -L$PWD/../boringssl/build/crypto -lcrypto" \
+ --with-boringssl
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check
+ make install
+ - name: Build ngtcp2 + quictls/openssl v3.x + aws-lc
+ if: steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl3
+ cd ngtcp2-openssl3
+ autoreconf -i
+ ./configure --prefix=$PWD/build --enable-lib-only \
+ PKG_CONFIG_PATH="../openssl3/build/lib/pkgconfig" \
+ BORINGSSL_CFLAGS="-I$PWD/../aws-lc/include/" \
+ BORINGSSL_LIBS="-L$PWD/../aws-lc/build/ssl -lssl -L$PWD/../aws-lc/build/crypto -lcrypto" \
+ --with-boringssl
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check
+ make install
+
+ build:
+ needs:
+ - build-cache
+
+ strategy:
+ matrix:
+ os: [ubuntu-22.04, macos-12]
+ compiler: [gcc, clang]
+ buildtool: [autotools, cmake]
+ http3: [http3, no-http3]
+ openssl: [openssl1, openssl3, boringssl, awslc]
+ exclude:
+ - os: macos-12
+ openssl: openssl3
+ - http3: no-http3
+ openssl: openssl3
+ - os: macos-12
+ compiler: gcc
+ - # disable macos cmake because of include path issue
+ os: macos-12
+ buildtool: cmake
+ - os: macos-12
+ openssl: boringssl
+ - openssl: boringssl
+ buildtool: cmake
+ - openssl: boringssl
+ compiler: gcc
+ - os: macos-12
+ openssl: awslc
+ - openssl: awslc
+ buildtool: cmake
+ - openssl: awslc
+ compiler: gcc
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Linux setup
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get install \
+ g++-12 \
+ clang-15 \
+ autoconf \
+ automake \
+ autotools-dev \
+ libtool \
+ pkg-config \
+ zlib1g-dev \
+ libcunit1-dev \
+ libssl-dev \
+ libxml2-dev \
+ libev-dev \
+ libevent-dev \
+ libjansson-dev \
+ libjemalloc-dev \
+ libc-ares-dev \
+ libelf-dev \
+ cmake \
+ cmake-data
+ echo 'CPPFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined -g' >> $GITHUB_ENV
+ echo 'LDFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined' >> $GITHUB_ENV
+ - name: MacOS setup
+ if: runner.os == 'macOS'
+ run: |
+ brew install \
+ libev \
+ libevent \
+ c-ares \
+ cunit \
+ libressl \
+ autoconf \
+ automake \
+ pkg-config \
+ libtool
+ echo 'PKG_CONFIG_PATH=/usr/local/opt/libressl/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig' >> $GITHUB_ENV
+ - name: Setup clang (Linux)
+ if: runner.os == 'Linux' && matrix.compiler == 'clang'
+ run: |
+ echo 'CC=clang-15' >> $GITHUB_ENV
+ echo 'CXX=clang++-15' >> $GITHUB_ENV
+ - name: Setup clang (MacOS)
+ if: runner.os == 'macOS' && matrix.compiler == 'clang'
+ run: |
+ echo 'CC=clang' >> $GITHUB_ENV
+ echo 'CXX=clang++' >> $GITHUB_ENV
+ - name: Setup gcc (Linux)
+ if: runner.os == 'Linux' && matrix.compiler == 'gcc'
+ run: |
+ echo 'CC=gcc-12' >> $GITHUB_ENV
+ echo 'CXX=g++-12' >> $GITHUB_ENV
+ - name: Setup gcc (MacOS)
+ if: runner.os == 'macOS' && matrix.compiler == 'gcc'
+ run: |
+ echo 'CC=gcc' >> $GITHUB_ENV
+ echo 'CXX=g++' >> $GITHUB_ENV
+ - name: Restore libbpf cache
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux'
+ with:
+ path: libbpf/build
+ key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }}
+ fail-on-cache-miss: true
+ - name: Set libbpf variables
+ if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux'
+ run: |
+ cd libbpf
+
+ EXTRA_AUTOTOOLS_OPTS="--with-libbpf"
+ EXTRA_CMAKE_OPTS="-DWITH_LIBBPF=1"
+
+ echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
+ echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV
+ - name: Restore quictls/openssl v1.1.1 cache
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3' && matrix.openssl == 'openssl1'
+ with:
+ path: openssl1/build
+ key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }}
+ fail-on-cache-miss: true
+ - name: Restore quictls/openssl v3.x cache
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3'
+ with:
+ path: openssl3/build
+ key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }}
+ fail-on-cache-miss: true
+ - name: Restore BoringSSL cache
+ uses: actions/cache/restore@v3
+ if: matrix.openssl == 'boringssl'
+ with:
+ path: |
+ boringssl/build/crypto/libcrypto.a
+ boringssl/build/ssl/libssl.a
+ boringssl/include
+ key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }}
+ fail-on-cache-miss: true
+ - name: Restore aws-lc cache
+ uses: actions/cache/restore@v3
+ if: matrix.openssl == 'awslc'
+ with:
+ path: |
+ aws-lc/build/crypto/libcrypto.a
+ aws-lc/build/ssl/libssl.a
+ aws-lc/include
+ key: ${{ runner.os }}-awslc-${{ env.AWSLC_VERSION }}
+ fail-on-cache-miss: true
+ - name: Set BoringSSL variables
+ if: matrix.openssl == 'boringssl'
+ run: |
+ cd boringssl
+
+ OPENSSL_CFLAGS="-I$PWD/include/"
+ OPENSSL_LIBS="-L$PWD/build/ssl -lssl -L$PWD/build/crypto -lcrypto -pthread"
+ EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --without-neverbleed --without-jemalloc"
+
+ echo 'OPENSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV
+ echo 'OPENSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV
+ echo 'BORINGSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV
+ echo 'BORINGSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV
+ echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
+ - name: Set aws-lc variables
+ if: matrix.openssl == 'awslc'
+ run: |
+ cd aws-lc
+
+ OPENSSL_CFLAGS="-I$PWD/include/"
+ OPENSSL_LIBS="-L$PWD/build/ssl -lssl -L$PWD/build/crypto -lcrypto -pthread"
+ EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --without-neverbleed --without-jemalloc"
+
+ echo 'OPENSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV
+ echo 'OPENSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV
+ echo 'BORINGSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV
+ echo 'BORINGSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV
+ echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
+ - name: Restore nghttp3 cache
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3'
+ with:
+ path: nghttp3/build
+ key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }}
+ fail-on-cache-miss: true
+ - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache + BoringSSL
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3' && (matrix.openssl == 'openssl1' || matrix.openssl == 'boringssl')
+ with:
+ path: ngtcp2-openssl1/build
+ key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }}
+ fail-on-cache-miss: true
+ - name: Restore ngtcp2 + quictls/openssl v3.x cache + aws-lc
+ uses: actions/cache/restore@v3
+ if: matrix.http3 == 'http3' && (matrix.openssl == 'openssl3' || matrix.openssl == 'awslc')
+ with:
+ path: ngtcp2-openssl3/build
+ key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }}
+ fail-on-cache-miss: true
+ - name: Setup extra environment variables for HTTP/3
+ if: matrix.http3 == 'http3'
+ run: |
+ PKG_CONFIG_PATH="$PWD/openssl1/build/lib/pkgconfig:$PWD/openssl3/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2-openssl1/build/lib/pkgconfig:$PWD/ngtcp2-openssl3/build/lib/pkgconfig:$PWD/libbpf/build/lib64/pkgconfig:$PKG_CONFIG_PATH"
+ LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl1/build/lib -Wl,-rpath,$PWD/openssl3/build/lib -Wl,-rpath,$PWD/libbpf/build/lib64"
+ EXTRA_AUTOTOOLS_OPTS="--enable-http3 $EXTRA_AUTOTOOLS_OPTS"
+ EXTRA_CMAKE_OPTS="-DENABLE_HTTP3=1 $EXTRA_CMAKE_OPTS"
+
+ echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV
+ echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV
+ echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
+ echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV
+ - name: Setup git submodules
+ run: |
+ git submodule update --init
+ - name: Configure autotools
+ run: |
+ autoreconf -i
+ ./configure
+ - name: Configure cmake (Linux)
+ if: matrix.buildtool == 'cmake' && runner.os == 'Linux'
+ run: |
+ make dist
+ VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"')
+ tar xf nghttp2-$VERSION.tar.gz
+ cd nghttp2-$VERSION
+ echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV
+
+ cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" .
+ - name: Configure cmake (MacOS)
+ if: matrix.buildtool == 'cmake' && runner.os == 'macOS'
+ run: |
+ make dist
+ VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"')
+ tar xf nghttp2-$VERSION.tar.gz
+ cd nghttp2-$VERSION
+ echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV
+
+ # This fixes infamous 'stdio.h not found' error.
+ echo 'SDKROOT='"$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV
+
+ cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" .
+ - name: Build nghttp2 with autotools (Linux)
+ if: matrix.buildtool == 'autotools' && runner.os == 'Linux'
+ run: |
+ make -j"$(nproc)" distcheck \
+ DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\""
+ - name: Build nghttp2 with autotools (MacOS)
+ if: matrix.buildtool == 'autotools' && runner.os == 'macOS'
+ run: |
+ make -j"$(sysctl -n hw.ncpu)" distcheck \
+ DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\""
+ - name: Build nghttp2 with cmake
+ if: matrix.buildtool == 'cmake'
+ run: |
+ cd $NGHTTP2_CMAKE_DIR
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)"
+ make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check
+ - uses: actions/setup-go@v5
+ if: matrix.buildtool == 'cmake'
+ with:
+ go-version-file: go.mod
+ - name: Integration test
+ # Integration tests for nghttpx; autotools erases build
+ # artifacts.
+ if: matrix.buildtool == 'cmake'
+ run: |
+ cd $NGHTTP2_CMAKE_DIR/integration-tests
+ make it
+
+ build-cross:
+ strategy:
+ matrix:
+ host: [x86_64-w64-mingw32, i686-w64-mingw32]
+
+ runs-on: ubuntu-22.04
+
+ env:
+ HOST: ${{ matrix.host }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Linux setup
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt-get update
+ sudo apt-get install \
+ gcc-mingw-w64 \
+ autoconf \
+ automake \
+ autotools-dev \
+ libtool \
+ pkg-config \
+ wine
+ - name: Build CUnit
+ run: |
+ curl -LO https://jaist.dl.sourceforge.net/project/cunit/CUnit/2.1-3/CUnit-2.1-3.tar.bz2
+ tar xf CUnit-2.1-3.tar.bz2
+ cd CUnit-2.1-3
+ ./bootstrap
+ ./configure --disable-shared --host="$HOST" --prefix="$PWD/build"
+ make -j$(nproc) install
+ - name: Configure autotools
+ run: |
+ autoreconf -i && \
+ ./configure --enable-werror --enable-lib-only --with-cunit \
+ --host="$HOST" PKG_CONFIG_PATH="$PWD/CUnit-2.1-3/build/lib/pkgconfig" \
+ CFLAGS="-g -O2 -D_WIN32_WINNT=0x0600"
+ - name: Build nghttp2
+ run: |
+ make -j$(nproc)
+ make -j$(nproc) check TESTS=""
+ - name: Run tests
+ if: matrix.host == 'x86_64-w64-mingw32'
+ run: |
+ cd tests
+ wine main.exe
+
+ build-windows:
+ strategy:
+ matrix:
+ arch: [x86, x64]
+ include:
+ - arch: x86
+ platform: Win32
+ - arch: x64
+ platform: x64
+
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: microsoft/setup-msbuild@v1
+ - run: |
+ vcpkg --triplet=${{ matrix.arch }}-windows install cunit
+ - name: Configure cmake
+ run: |
+ mkdir build
+ cd build
+ cmake -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_GENERATOR_PLATFORM=${{ matrix.platform }} -DVCPKG_TARGET_TRIPLET=${{ matrix.arch}}-windows ..
+ - name: Build nghttp2
+ run: |
+ cmake --build build
+ cmake --build build --target check
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
new file mode 100644
index 0000000..720b25f
--- /dev/null
+++ b/.github/workflows/fuzz.yml
@@ -0,0 +1,24 @@
+name: CIFuzz
+on: [pull_request]
+permissions: read-all
+jobs:
+ Fuzzing:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Build Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'nghttp2'
+ dry-run: false
+ - name: Run Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'nghttp2'
+ fuzz-seconds: 600
+ dry-run: false
+ - name: Upload Crash
+ uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: artifacts
+ path: ./out/artifacts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8c089da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,56 @@
+# emacs backup file
+*~
+
+# autotools
+*.la
+*.lo
+*.m4
+*.o
+*.pyc
+.deps/
+.libs/
+INSTALL
+Makefile
+Makefile.in
+autom4te.cache/
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+test-driver
+
+# cmake
+CMakeCache.txt
+CMakeFiles/
+cmake_install.cmake
+install_manifest.txt
+CTestTestfile.cmake
+build.ninja
+rules.ninja
+.ninja_deps
+.ninja_log
+lib*.so
+lib*.so.*
+lib*.a
+# generated by "make test" with cmake
+Testing/
+
+# test logs generated by `make check`
+*.log
+*.trs
+
+lib/MSVC_obj/
+_VC_ROOT/
+.depend.MSVC
+*.pyd
+*.egg-info/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..393c808
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "third-party/mruby"]
+ path = third-party/mruby
+ url = https://github.com/mruby/mruby
+[submodule "third-party/neverbleed"]
+ path = third-party/neverbleed
+ url = https://github.com/tatsuhiro-t/neverbleed.git
+ branch = nghttp2
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..c5c5eb5
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,158 @@
+nghttp2 project was started as a fork of spdylay project [1]. Both
+projects were started by Tatsuhiro Tsujikawa, who is still the main
+author of these projects. Meanwhile, we have many contributions, and
+we are not here without them. We sincerely thank you to all who made
+a contribution. Here is the all individuals/organizations who
+contributed to nghttp2 and spdylay project at which we forked. These
+names are retrieved from git commit log. If you have made a
+contribution, but you are missing in the list, please let us know via
+github issues [2].
+
+[1] https://github.com/tatsuhiro-t/spdylay
+[2] https://github.com/nghttp2/nghttp2/issues
+
+--------
+
+187j3x1
+Adam Gołębiowski
+Alek Storm
+Alex Nalivko
+Alexandr Vlasov
+Alexandros Konstantinakis-Karmis
+Alexis La Goutte
+Amir Livneh
+Amir Pakdel
+Anders Bakken
+Andreas Pohl
+Andrew Penkrat
+Andy Davies
+Angus Gratton
+Anna Henningsen
+Ant Bryan
+Anthony Alayo
+Asra Ali
+Benedikt Christoph Wolters
+Benjamin Peterson
+Bernard Spil
+Bernhard Walle
+Brendan Heinonen
+Brian Card
+Brian Suh
+Daniel Bevenius
+Daniel Evers
+Daniel Stenberg
+Dave Reisner
+David Beitey
+David Korczynski
+David Weekly
+Dimitris Apostolou
+Dmitri Tikhonov
+Dmitriy Vetutnev
+Don
+Dylan Plecki
+Etienne Cimon
+Fabian Möller
+Fabian Wiesel
+Fred Sundvik
+Gabi Davar
+Gaël PORTAY
+Geoff Hill
+George Liu
+Gitai
+Google Inc.
+Hajime Fujita
+Jacky Tian
+Jacky_Yin
+Jacob Champion
+James M Snell
+Jan Kundrát
+Jan-E
+Janusz Dziemidowicz
+Jay Satiro
+Jeff 'Raid' Baitis
+Jianqing Wang
+Jim Morrison
+Josh Braegger
+José F. Calcerrada
+Kamil Dudka
+Kazuho Oku
+Kenny (kang-yen) Peng
+Kenny Peng
+Kit Chan
+Kyle Schomp
+LazyHamster
+Leo Neat
+Lorenz Nickel
+Lucas Pardue
+MATSUMOTO Ryosuke
+Marc Bachmann
+Marcelo Trylesinski
+Matt Rudary
+Matt Way
+Michael Kaufmann
+Mike Conlen
+Mike Frysinger
+Mike Lothian
+Nicholas Hurley
+Nora Shoemaker
+Paweł Wegner
+Pedro Santos
+Peeyush Aggarwal
+Peter Wu
+Piotr Sikora
+PufferOverflow
+Raul Gutierrez Segales
+Remo E
+Renaud
+Reza Tavakoli
+Richard Wolfert
+Rick Lei
+Ross Smith II
+Rudi Heitbaum
+Ryo Ota
+Scott Mitchell
+Sebastiaan Deckers
+Shelley Vohr
+Simon Frankenberger
+Simone Basso
+Soham Sinha
+Stefan Eissing
+Stephen Ludin
+Sunpoet Po-Chuan Hsieh
+Svante Signell
+Syohei YOSHIDA
+Tapanito
+Tatsuhiko Kubo
+Tatsuhiro Tsujikawa
+Tobias Geerinckx-Rice
+Tom Harwood
+Tomas Krizek
+Tomasz Buchert
+Tomasz Torcz
+Vernon Tang
+Viacheslav Biriukov
+Viktor Szakats
+Viktor Szépe
+Wenfeng Liu
+William A Rowe Jr
+Xiaoguang Sun
+Zhuoyun Wei
+acesso
+ayanamist
+bxshi
+clemahieu
+dalf
+dawg
+es
+fangdingjun
+hrxi
+jwchoi
+kumagi
+lhuang04
+lstefani
+makovich
+mod-h2-dev
+moparisthebest
+robaho
+snnn
+yuuki-kodama
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..161a7ee
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,491 @@
+# nghttp2 - HTTP/2 C Library
+#
+# Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa
+# Copyright (c) 2016 Peter Wu <peter@lekensteyn.nl>
+#
+# 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.
+
+cmake_minimum_required(VERSION 3.5)
+# XXX using 1.8.90 instead of 1.9.0-DEV
+project(nghttp2 VERSION 1.59.0)
+
+# See versioning rule:
+# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+set(LT_CURRENT 40)
+set(LT_REVISION 0)
+set(LT_AGE 26)
+
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
+include(Version)
+
+math(EXPR LT_SOVERSION "${LT_CURRENT} - ${LT_AGE}")
+set(LT_VERSION "${LT_SOVERSION}.${LT_AGE}.${LT_REVISION}")
+set(PACKAGE_VERSION "${PROJECT_VERSION}")
+HexVersion(PACKAGE_VERSION_NUM ${PROJECT_VERSION_MAJOR} ${PROJECT_VERSION_MINOR} ${PROJECT_VERSION_PATCH})
+
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the build type" FORCE)
+
+ # Include "None" as option to disable any additional (optimization) flags,
+ # relying on just CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (which are empty by
+ # default). These strings are presented in cmake-gui.
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "None" "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
+endif()
+
+include(GNUInstallDirs)
+
+# For documentation
+find_package(Python3 COMPONENTS Interpreter)
+
+# Auto-detection of features that can be toggled
+if(NOT ENABLE_LIB_ONLY)
+ find_package(Libev 4.11)
+ find_package(Libcares 1.7.5)
+ find_package(ZLIB 1.2.3)
+endif()
+
+find_package(OpenSSL 1.1.1)
+find_package(Libngtcp2 1.0.0)
+find_package(Libngtcp2_crypto_quictls 1.0.0)
+if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND)
+ set(HAVE_LIBNGTCP2_CRYPTO_QUICTLS 1)
+endif()
+find_package(Libnghttp3 1.1.0)
+if(WITH_LIBBPF)
+ find_package(Libbpf 0.7.0)
+ if(NOT LIBBPF_FOUND)
+ message(FATAL_ERROR "libbpf was requested (WITH_LIBBPF=1) but not found.")
+ endif()
+endif()
+if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND)
+ set(ENABLE_APP_DEFAULT ON)
+else()
+ set(ENABLE_APP_DEFAULT OFF)
+endif()
+find_package(Systemd 209)
+find_package(Jansson 2.5)
+set(ENABLE_HPACK_TOOLS_DEFAULT ${JANSSON_FOUND})
+# 2.0.8 is required because we use evconnlistener_set_error_cb()
+find_package(Libevent 2.0.8 COMPONENTS core extra openssl)
+set(ENABLE_EXAMPLES_DEFAULT ${LIBEVENT_OPENSSL_FOUND})
+
+find_package(LibXml2 2.6.26)
+set(WITH_LIBXML2_DEFAULT ${LIBXML2_FOUND})
+find_package(Jemalloc)
+set(WITH_JEMALLOC_DEFAULT ${JEMALLOC_FOUND})
+
+include(CMakeOptions.txt)
+
+if(ENABLE_LIB_ONLY AND (ENABLE_APP OR ENABLE_HPACK_TOOLS OR ENABLE_EXAMPLES))
+ # Remember when disabled options are disabled for later diagnostics.
+ set(ENABLE_LIB_ONLY_DISABLED_OTHERS 1)
+else()
+ set(ENABLE_LIB_ONLY_DISABLED_OTHERS 0)
+endif()
+if(ENABLE_LIB_ONLY)
+ set(ENABLE_APP OFF)
+ set(ENABLE_HPACK_TOOLS OFF)
+ set(ENABLE_EXAMPLES OFF)
+endif()
+
+# Do not disable assertions based on CMAKE_BUILD_TYPE.
+foreach(_build_type "Release" "MinSizeRel" "RelWithDebInfo")
+ foreach(_lang C CXX)
+ string(TOUPPER "CMAKE_${_lang}_FLAGS_${_build_type}" _var)
+ string(REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " ${_var} "${${_var}}")
+ endforeach()
+endforeach()
+
+if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+ set(HINT_NORETURN "__attribute__((noreturn))")
+else()
+ set(HINT_NORETURN)
+endif()
+
+include(ExtractValidFlags)
+foreach(_cxx1x_flag -std=c++14)
+ extract_valid_cxx_flags(_cxx1x_flag_supported ${_cxx1x_flag})
+ if(_cxx1x_flag_supported)
+ set(CXX1XCXXFLAGS ${_cxx1x_flag})
+ break()
+ endif()
+endforeach()
+
+include(CMakePushCheckState)
+include(CheckCXXSourceCompiles)
+cmake_push_check_state()
+set(CMAKE_REQUIRED_DEFINITIONS "${CXX1XCXXFLAGS}")
+# Check that std::future is available.
+check_cxx_source_compiles("
+#include <vector>
+#include <future>
+int main() { std::vector<std::future<int>> v; }" HAVE_STD_FUTURE)
+# Check that std::map::emplace is available for g++-4.7.
+check_cxx_source_compiles("
+#include <map>
+int main() { std::map<int, int>().emplace(1, 2); }" HAVE_STD_MAP_EMPLACE)
+cmake_pop_check_state()
+
+
+# Checks for libraries.
+# Additional libraries required for programs under src directory.
+set(APP_LIBRARIES)
+
+set(CMAKE_THREAD_PREFER_PTHREAD 1)
+find_package(Threads)
+if(CMAKE_USE_PTHREADS_INIT)
+ list(APPEND APP_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
+endif()
+# XXX android and C++, is this still needed in cmake?
+# case "$host" in
+# *android*)
+# android_build=yes
+# # android does not need -pthread, but needs following 3 libs for C++
+# APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++"
+
+# dl: openssl requires libdl when it is statically linked.
+# XXX shouldn't ${CMAKE_DL_LIBS} be appended to OPENSSL_LIBRARIES instead of
+# APP_LIBRARIES if it is really specific to OpenSSL?
+
+find_package(CUnit 2.1)
+enable_testing()
+set(HAVE_CUNIT ${CUNIT_FOUND})
+if(HAVE_CUNIT)
+ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
+endif()
+
+# openssl (for src)
+include(CheckSymbolExists)
+set(HAVE_OPENSSL ${OPENSSL_FOUND})
+if(OPENSSL_FOUND)
+ set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR})
+ cmake_push_check_state()
+ set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}")
+ set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}")
+ if(WIN32)
+ set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}" "ws2_32" "bcrypt")
+ endif()
+ check_symbol_exists(SSL_provide_quic_data "openssl/ssl.h" HAVE_SSL_PROVIDE_QUIC_DATA)
+ if(NOT HAVE_SSL_PROVIDE_QUIC_DATA)
+ message(WARNING "OpenSSL in ${OPENSSL_LIBRARIES} does not have SSL_provide_quic_data. HTTP/3 support cannot be enabled")
+ endif()
+ cmake_pop_check_state()
+else()
+ set(OPENSSL_INCLUDE_DIRS "")
+ set(OPENSSL_LIBRARIES "")
+endif()
+# libev (for src)
+set(HAVE_LIBEV ${LIBEV_FOUND})
+set(HAVE_ZLIB ${ZLIB_FOUND})
+set(HAVE_SYSTEMD ${SYSTEMD_FOUND})
+set(HAVE_LIBEVENT_OPENSSL ${LIBEVENT_FOUND})
+if(LIBEVENT_FOUND)
+ # Must both link the core and openssl libraries.
+ set(LIBEVENT_OPENSSL_LIBRARIES ${LIBEVENT_LIBRARIES})
+endif()
+# libc-ares (for src)
+set(HAVE_LIBCARES ${LIBCARES_FOUND})
+if(LIBCARES_FOUND)
+ set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR})
+else()
+ set(LIBCARES_INCLUDE_DIRS "")
+ set(LIBCARES_LIBRARIES "")
+endif()
+# jansson (for src/nghttp, src/deflatehd and src/inflatehd)
+set(HAVE_JANSSON ${JANSSON_FOUND})
+# libxml2 (for src/nghttp)
+set(HAVE_LIBXML2 ${LIBXML2_FOUND})
+if(LIBXML2_FOUND)
+ set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR})
+else()
+ set(LIBXML2_INCLUDE_DIRS "")
+ set(LIBXML2_LIBRARIES "")
+endif()
+# jemalloc
+set(HAVE_JEMALLOC ${JEMALLOC_FOUND})
+
+# libbpf (for bpf)
+set(HAVE_LIBBPF ${LIBBPF_FOUND})
+if(LIBBPF_FOUND)
+ set(BPFCFLAGS -Wall -O2 -g)
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ # For Debian/Ubuntu
+ set(EXTRABPFCFLAGS -I/usr/include/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu)
+ endif()
+
+ check_c_source_compiles("
+#include <linux/bpf.h>
+int main() { enum bpf_stats_type foo; (void)foo; }" HAVE_BPF_STATS_TYPE)
+endif()
+
+# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL and libev
+if(ENABLE_APP AND NOT (ZLIB_FOUND AND OPENSSL_FOUND AND LIBEV_FOUND))
+ message(FATAL_ERROR "Applications were requested (ENABLE_APP=1) but dependencies are not met.")
+endif()
+
+# HTTP/3 requires quictls/openssl, libngtcp2, libngtcp2_crypto_quictls
+# and libnghttp3.
+if(ENABLE_HTTP3 AND NOT (HAVE_SSL_PROVIDE_QUIC_DATA AND LIBNGTCP2_FOUND AND LIBNGTCP2_CRYPTO_QUICTLS_FOUND AND LIBNGHTTP3_FOUND))
+ message(FATAL_ERROR "HTTP/3 was requested (ENABLE_HTTP3=1) but dependencies are not met.")
+endif()
+
+# HPACK tools requires jansson
+if(ENABLE_HPACK_TOOLS AND NOT HAVE_JANSSON)
+ message(FATAL_ERROR "HPACK tools were requested (ENABLE_HPACK_TOOLS=1) but dependencies are not met.")
+endif()
+
+# examples
+if(ENABLE_EXAMPLES AND NOT (OPENSSL_FOUND AND LIBEVENT_OPENSSL_FOUND))
+ message(FATAL_ERROR "examples were requested (ENABLE_EXAMPLES=1) but dependencies are not met.")
+endif()
+
+# third-party http-parser only be built when needed
+if(ENABLE_EXAMPLES OR ENABLE_APP OR ENABLE_HPACK_TOOLS)
+ set(ENABLE_THIRD_PARTY 1)
+ # mruby (for src/nghttpx)
+ set(HAVE_MRUBY ${WITH_MRUBY})
+ set(HAVE_NEVERBLEED ${WITH_NEVERBLEED})
+else()
+ set(HAVE_MRUBY 0)
+ set(HAVE_NEVERBLEED 0)
+endif()
+
+# Checks for header files.
+include(CheckIncludeFile)
+check_include_file("arpa/inet.h" HAVE_ARPA_INET_H)
+check_include_file("fcntl.h" HAVE_FCNTL_H)
+check_include_file("inttypes.h" HAVE_INTTYPES_H)
+check_include_file("limits.h" HAVE_LIMITS_H)
+check_include_file("netdb.h" HAVE_NETDB_H)
+check_include_file("netinet/in.h" HAVE_NETINET_IN_H)
+check_include_file("netinet/ip.h" HAVE_NETINET_IP_H)
+check_include_file("pwd.h" HAVE_PWD_H)
+check_include_file("sys/socket.h" HAVE_SYS_SOCKET_H)
+check_include_file("sys/time.h" HAVE_SYS_TIME_H)
+check_include_file("syslog.h" HAVE_SYSLOG_H)
+check_include_file("unistd.h" HAVE_UNISTD_H)
+check_include_file("windows.h" HAVE_WINDOWS_H)
+
+include(CheckTypeSize)
+# Checks for typedefs, structures, and compiler characteristics.
+# AC_TYPE_SIZE_T
+check_type_size("ssize_t" SIZEOF_SSIZE_T)
+if(SIZEOF_SSIZE_T STREQUAL "")
+ # ssize_t is a signed type in POSIX storing at least -1.
+ # Set it to "int" to match the behavior of AC_TYPE_SSIZE_T (autotools).
+ set(ssize_t int)
+endif()
+# AC_TYPE_UINT8_T
+# AC_TYPE_UINT16_T
+# AC_TYPE_UINT32_T
+# AC_TYPE_UINT64_T
+# AC_TYPE_INT8_T
+# AC_TYPE_INT16_T
+# AC_TYPE_INT32_T
+# AC_TYPE_INT64_T
+# AC_TYPE_OFF_T
+# AC_TYPE_PID_T
+# AC_TYPE_UID_T
+# XXX To support inline for crappy compilers, see https://cmake.org/Wiki/CMakeTestInline
+# AC_C_INLINE
+# XXX is AC_SYS_LARGEFILE still needed for modern systems?
+# add_definitions(-D_FILE_OFFSET_BITS=64)
+
+include(CheckStructHasMember)
+check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_STRUCT_TM_TM_GMTOFF)
+
+# Checks for library functions.
+include(CheckFunctionExists)
+check_function_exists(_Exit HAVE__EXIT)
+check_function_exists(accept4 HAVE_ACCEPT4)
+check_function_exists(clock_gettime HAVE_CLOCK_GETTIME)
+check_function_exists(mkostemp HAVE_MKOSTEMP)
+
+check_symbol_exists(GetTickCount64 sysinfoapi.h HAVE_GETTICKCOUNT64)
+
+include(CheckSymbolExists)
+# XXX does this correctly detect initgroups (un)availability on cygwin?
+check_symbol_exists(initgroups grp.h HAVE_DECL_INITGROUPS)
+if(NOT HAVE_DECL_INITGROUPS AND HAVE_UNISTD_H)
+ # FreeBSD declares initgroups() in unistd.h
+ check_symbol_exists(initgroups unistd.h HAVE_DECL_INITGROUPS2)
+ if(HAVE_DECL_INITGROUPS2)
+ set(HAVE_DECL_INITGROUPS 1)
+ endif()
+endif()
+
+check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_DECL_CLOCK_MONOTONIC)
+
+set(WARNCFLAGS)
+set(WARNCXXFLAGS)
+if(CMAKE_C_COMPILER_ID MATCHES "MSVC")
+ if(ENABLE_WERROR)
+ set(WARNCFLAGS /WX)
+ set(WARNCXXFLAGS /WX)
+ endif()
+else()
+ if(ENABLE_WERROR)
+ set(WARNCFLAGS "-Werror")
+ set(WARNCXXFLAGS "-Werror")
+ endif()
+
+ include(PickyWarningsC)
+ include(PickyWarningsCXX)
+endif()
+
+if(ENABLE_STATIC_CRT)
+ foreach(lang C CXX)
+ foreach(suffix "" _DEBUG _MINSIZEREL _RELEASE _RELWITHDEBINFO)
+ set(var "CMAKE_${lang}_FLAGS${suffix}")
+ string(REPLACE "/MD" "/MT" ${var} "${${var}}")
+ endforeach()
+ endforeach()
+endif()
+
+if(ENABLE_DEBUG)
+ set(DEBUGBUILD 1)
+endif()
+
+# Some platform does not have working std::future. We disable
+# threading for those platforms.
+if(NOT ENABLE_THREADS OR NOT HAVE_STD_FUTURE)
+ set(NOTHREADS 1)
+endif()
+
+add_definitions(-DHAVE_CONFIG_H)
+configure_file(cmakeconfig.h.in config.h)
+# autotools-compatible names
+# Sphinx expects relative paths in the .rst files. Use the fact that the files
+# below are all one directory level deep.
+file(RELATIVE_PATH top_srcdir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_SOURCE_DIR}")
+file(RELATIVE_PATH top_builddir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_BINARY_DIR}")
+set(abs_top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}")
+set(abs_top_builddir "${CMAKE_CURRENT_BINARY_DIR}")
+# libnghttp2.pc (pkg-config file)
+set(prefix "${CMAKE_INSTALL_PREFIX}")
+set(exec_prefix "${CMAKE_INSTALL_PREFIX}")
+set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}")
+set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}")
+set(VERSION "${PACKAGE_VERSION}")
+# For init scripts and systemd service file (in contrib/)
+set(bindir "${CMAKE_INSTALL_FULL_BINDIR}")
+set(sbindir "${CMAKE_INSTALL_FULL_SBINDIR}")
+foreach(name
+ lib/libnghttp2.pc
+ lib/includes/nghttp2/nghttp2ver.h
+ integration-tests/config.go
+ integration-tests/setenv
+ 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/building-android-binary.rst
+ doc/nghttp2.h.rst
+ doc/nghttp2ver.h.rst
+ doc/contribute.rst
+)
+ configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+if(APPLE)
+ add_definitions(-D__APPLE_USE_RFC_3542)
+endif()
+
+include_directories(
+ "${CMAKE_CURRENT_BINARY_DIR}" # for config.h
+)
+# For use in src/CMakeLists.txt
+set(PKGDATADIR "${CMAKE_INSTALL_FULL_DATADIR}/${CMAKE_PROJECT_NAME}")
+set(PKGLIBDIR "${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_PROJECT_NAME}")
+
+install(FILES README.rst DESTINATION "${CMAKE_INSTALL_DOCDIR}")
+
+add_subdirectory(lib)
+#add_subdirectory(lib/includes)
+add_subdirectory(third-party)
+add_subdirectory(src)
+add_subdirectory(examples)
+add_subdirectory(tests)
+#add_subdirectory(tests/testdata)
+add_subdirectory(integration-tests)
+if(ENABLE_DOC)
+ add_subdirectory(doc)
+endif()
+add_subdirectory(contrib)
+add_subdirectory(script)
+add_subdirectory(bpf)
+
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type)
+message(STATUS "summary of build options:
+
+ Package version: ${VERSION}
+ Library version: ${LT_CURRENT}:${LT_REVISION}:${LT_AGE}
+ Install prefix: ${CMAKE_INSTALL_PREFIX}
+ Target system: ${CMAKE_SYSTEM_NAME}
+ Compiler:
+ Build type: ${CMAKE_BUILD_TYPE}
+ C compiler: ${CMAKE_C_COMPILER}
+ CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS}
+ C++ compiler: ${CMAKE_CXX_COMPILER}
+ CXXFLAGS: ${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS}
+ WARNCFLAGS: ${WARNCFLAGS}
+ CXX1XCXXFLAGS: ${CXX1XCXXFLAGS}
+ WARNCXXFLAGS: ${WARNCXXFLAGS}
+ Python:
+ Python: ${Python3_EXECUTABLE}
+ Python3_VERSION: ${Python3_VERSION}
+ Test:
+ CUnit: ${HAVE_CUNIT} (LIBS='${CUNIT_LIBRARIES}')
+ Failmalloc: ${ENABLE_FAILMALLOC}
+ Libs:
+ OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}')
+ Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}')
+ Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}')
+ Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}')
+ Libngtcp2: ${HAVE_LIBNGTCP2} (LIBS='${LIBNGTCP2_LIBRARIES}')
+ Libngtcp2_crypto_quictls: ${HAVE_LIBNGTCP2_CRYPTO_QUICTLS} (LIBS='${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES}')
+ Libnghttp3: ${HAVE_LIBNGHTTP3} (LIBS='${LIBNGHTTP3_LIBRARIES}')
+ Libbpf: ${HAVE_LIBBPF} (LIBS='${LIBBPF_LIBRARIES}')
+ Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}')
+ Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}')
+ Jemalloc: ${HAVE_JEMALLOC} (LIBS='${JEMALLOC_LIBRARIES}')
+ Zlib: ${HAVE_ZLIB} (LIBS='${ZLIB_LIBRARIES}')
+ Systemd: ${HAVE_SYSTEMD} (LIBS='${SYSTEMD_LIBRARIES}')
+ Third-party:
+ http-parser: ${ENABLE_THIRD_PARTY}
+ MRuby: ${HAVE_MRUBY}
+ Neverbleed: ${HAVE_NEVERBLEED}
+ Features:
+ Applications: ${ENABLE_APP}
+ HPACK tools: ${ENABLE_HPACK_TOOLS}
+ Examples: ${ENABLE_EXAMPLES}
+ Threading: ${ENABLE_THREADS}
+ HTTP/3(EXPERIMENTAL): ${ENABLE_HTTP3}
+")
+if(ENABLE_LIB_ONLY_DISABLED_OTHERS)
+ message("Only the library will be built. To build other components "
+ "(such as applications and examples), set ENABLE_LIB_ONLY=OFF.")
+endif()
diff --git a/CMakeOptions.txt b/CMakeOptions.txt
new file mode 100644
index 0000000..238d5b1
--- /dev/null
+++ b/CMakeOptions.txt
@@ -0,0 +1,28 @@
+# Features that can be enabled for cmake (see CMakeLists.txt)
+
+option(ENABLE_WERROR "Turn on compile time warnings")
+option(ENABLE_DEBUG "Turn on debug output")
+option(ENABLE_THREADS "Turn on threading in apps" ON)
+option(ENABLE_APP "Build applications (nghttp, nghttpd, nghttpx and h2load)"
+ ${ENABLE_APP_DEFAULT})
+option(ENABLE_HPACK_TOOLS "Build HPACK tools"
+ ${ENABLE_HPACK_TOOLS_DEFAULT})
+option(ENABLE_EXAMPLES "Build examples"
+ ${ENABLE_EXAMPLES_DEFAULT})
+option(ENABLE_FAILMALLOC "Build failmalloc test program" ON)
+option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENABLE_APP=0 -DENABLE_EXAMPLES=0 -DENABLE_HPACK_TOOLS=0")
+option(ENABLE_STATIC_LIB "Build libnghttp2 in static mode also")
+option(ENABLE_SHARED_LIB "Build libnghttp2 as a shared library" ON)
+option(ENABLE_STATIC_CRT "Build libnghttp2 against the MS LIBCMT[d]")
+option(ENABLE_HTTP3 "Enable HTTP/3 support" OFF)
+option(ENABLE_DOC "Build documentation" ON)
+
+option(WITH_LIBXML2 "Use libxml2"
+ ${WITH_LIBXML2_DEFAULT})
+option(WITH_JEMALLOC "Use jemalloc"
+ ${WITH_JEMALLOC_DEFAULT})
+option(WITH_MRUBY "Use mruby")
+option(WITH_NEVERBLEED "Use neverbleed")
+option(WITH_LIBBPF "Use libbpf")
+
+# vim: ft=cmake:
diff --git a/CONTRIBUTION b/CONTRIBUTION
new file mode 100644
index 0000000..8a2aae3
--- /dev/null
+++ b/CONTRIBUTION
@@ -0,0 +1,18 @@
+[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/COPYING b/COPYING
new file mode 100644
index 0000000..8020179
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,23 @@
+The MIT License
+
+Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa
+Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
+
+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/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/Dockerfile.android b/Dockerfile.android
new file mode 100644
index 0000000..96ce7c4
--- /dev/null
+++ b/Dockerfile.android
@@ -0,0 +1,121 @@
+# vim: ft=dockerfile:
+# 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
+
+
+# Only use standalone-toolchain for reduce size
+FROM ubuntu:22.04
+MAINTAINER Tatsuhiro Tsujikawa
+
+ENV NDK_VERSION r25b
+ENV NDK /root/android-ndk-$NDK_VERSION
+ENV TOOLCHAIN $NDK/toolchains/llvm/prebuilt/linux-x86_64
+ENV TARGET aarch64-linux-android
+ENV API 33
+ENV AR $TOOLCHAIN/bin/llvm-ar
+ENV CC $TOOLCHAIN/bin/$TARGET$API-clang
+ENV CXX $TOOLCHAIN/bin/$TARGET$API-clang++
+ENV LD $TOOLCHAIN/bin/ld
+ENV RANDLIB $TOOLCHAIN/bin/llvm-ranlib
+ENV STRIP $TOOLCHAIN/bin/llvm-strip
+ENV PREFIX /root/usr/local
+
+WORKDIR /root
+RUN apt-get update && \
+ apt-get install -y unzip make binutils autoconf \
+ automake autotools-dev libtool pkg-config git \
+ curl dpkg-dev libxml2-dev genisoimage libc6-i386 \
+ lib32stdc++6 && \
+ rm -rf /var/cache/apt/*
+
+# Download NDK
+RUN curl -L -O https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-linux.zip && \
+ unzip -q android-ndk-$NDK_VERSION-linux.zip && \
+ rm android-ndk-$NDK_VERSION-linux.zip
+
+# Setup version of libraries
+ENV OPENSSL_VERSION 1.1.1q
+ENV LIBEV_VERSION 4.33
+ENV ZLIB_VERSION 1.2.13
+ENV CARES_VERSION 1.18.1
+ENV NGHTTP2_VERSION master
+
+WORKDIR /root/build
+RUN curl -L -O https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz && \
+ tar xf openssl-$OPENSSL_VERSION.tar.gz && \
+ rm openssl-$OPENSSL_VERSION.tar.gz
+
+WORKDIR /root/build/openssl-$OPENSSL_VERSION
+RUN export ANDROID_NDK_HOME=$NDK PATH=$TOOLCHAIN/bin:$PATH && \
+ ./Configure no-shared --prefix=$PREFIX android-arm64 && \
+ make && make install_sw
+
+WORKDIR /root/build
+RUN curl -L -O http://dist.schmorp.de/libev/Attic/libev-$LIBEV_VERSION.tar.gz && \
+ tar xf libev-$LIBEV_VERSION.tar.gz && \
+ rm libev-$LIBEV_VERSION.tar.gz
+
+WORKDIR /root/build/libev-$LIBEV_VERSION
+RUN ./configure \
+ --host=$TARGET \
+ --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 curl -L -O https://zlib.net/zlib-$ZLIB_VERSION.tar.gz && \
+ tar xf zlib-$ZLIB_VERSION.tar.gz && \
+ rm zlib-$ZLIB_VERSION.tar.gz
+
+WORKDIR /root/build/zlib-$ZLIB_VERSION
+RUN HOST=$TARGET \
+ ./configure \
+ --prefix=$PREFIX \
+ --libdir=$PREFIX/lib \
+ --includedir=$PREFIX/include \
+ --static && \
+ make install
+
+
+WORKDIR /root/build
+RUN curl -L -O https://c-ares.haxx.se/download/c-ares-$CARES_VERSION.tar.gz && \
+ tar xf c-ares-$CARES_VERSION.tar.gz && \
+ rm c-ares-$CARES_VERSION.tar.gz
+
+WORKDIR /root/build/c-ares-$CARES_VERSION
+RUN ./configure \
+ --host=$TARGET \
+ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+ --prefix=$PREFIX \
+ --disable-shared && \
+ make install
+
+WORKDIR /root/build
+RUN git clone https://github.com/nghttp2/nghttp2 -b $NGHTTP2_VERSION --depth 1
+WORKDIR /root/build/nghttp2
+RUN autoreconf -i && \
+ ./configure \
+ --enable-app \
+ --disable-shared \
+ --host=$TARGET \
+ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+ --without-libxml2 \
+ --disable-examples \
+ --disable-threads \
+ CPPFLAGS="-fPIE -I$PREFIX/include" \
+ PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+ LDFLAGS="-fPIE -pie -L$PREFIX/lib" && \
+ make && \
+ $STRIP src/nghttpx src/nghttpd src/nghttp
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..45d9408
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+See COPYING
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..683b989
--- /dev/null
+++ b/Makefile.am
@@ -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.
+SUBDIRS = lib third-party src bpf examples tests integration-tests \
+ doc contrib script
+
+ACLOCAL_AMFLAGS = -I m4
+
+dist_doc_DATA = README.rst
+
+EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-env \
+ Dockerfile.android \
+ cmakeconfig.h.in \
+ CMakeLists.txt \
+ CMakeOptions.txt \
+ cmake/ExtractValidFlags.cmake \
+ cmake/FindJemalloc.cmake \
+ cmake/FindLibev.cmake \
+ cmake/FindCUnit.cmake \
+ cmake/Version.cmake \
+ cmake/FindLibevent.cmake \
+ cmake/FindJansson.cmake \
+ cmake/FindLibcares.cmake \
+ cmake/FindSystemd.cmake \
+ cmake/FindLibbpf.cmake \
+ cmake/FindLibnghttp3.cmake \
+ cmake/FindLibngtcp2.cmake \
+ cmake/FindLibngtcp2_crypto_quictls.cmake \
+ cmake/PickyWarningsC.cmake \
+ cmake/PickyWarningsCXX.cmake
+
+.PHONY: clang-format
+
+# Format source files using clang-format. Don't format source files
+# under third-party directory since we are not responsible for their
+# 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} examples/*.c \
+ tests/*.{c,h} bpf/*.c fuzz/*.cc
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
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
index 0000000..2af1bcc
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,1474 @@
+nghttp2 - HTTP/2 C Library
+==========================
+
+This is an implementation of the Hypertext Transfer Protocol version 2
+in C.
+
+The framing layer of HTTP/2 is implemented as a reusable C library.
+On top of that, we have implemented an HTTP/2 client, server and
+proxy. We have also developed load test and benchmarking tools for
+HTTP/2.
+
+An HPACK encoder and decoder are available as a public API.
+
+Development Status
+------------------
+
+nghttp2 was originally developed based on `RFC 7540
+<https://tools.ietf.org/html/rfc7540>`_ HTTP/2 and `RFC 7541
+<https://tools.ietf.org/html/rfc7541>`_ HPACK - Header Compression for
+HTTP/2. Now we are updating our code to implement `RFC 9113
+<https://datatracker.ietf.org/doc/html/rfc9113>`_.
+
+The nghttp2 code base was forked from the spdylay
+(https://github.com/tatsuhiro-t/spdylay) project.
+
+Public Test Server
+------------------
+
+The following endpoints are available to try out our nghttp2
+implementation.
+
+* https://nghttp2.org/ (TLS + ALPN and HTTP/3)
+
+ This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and
+ ``http/1.1`` via ALPN and requires TLSv1.2 for HTTP/2
+ connection.
+
+ It also supports HTTP/3.
+
+* http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct)
+
+ ``h2c`` 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/)
+
+If you need libnghttp2 (C library) only, then the above packages are
+all you need. Use ``--enable-lib-only`` to ensure that only
+libnghttp2 is built. This avoids potential build error related to
+building bundled applications.
+
+To build and run the application programs (``nghttp``, ``nghttpd``,
+``nghttpx`` and ``h2load``) in the ``src`` directory, the following packages
+are required:
+
+* OpenSSL >= 1.1.1; or LibreSSL >= 3.8.1; or aws-lc >= 1.19.0; or
+ BoringSSL
+* libev >= 4.11
+* zlib >= 1.2.3
+* libc-ares >= 1.7.5
+
+To enable ``-a`` option (getting linked assets from the downloaded
+resource) in ``nghttp``, the following package is required:
+
+* libxml2 >= 2.6.26
+
+To enable systemd support in nghttpx, the following package is
+required:
+
+* libsystemd-dev >= 209
+
+The HPACK tools require the following package:
+
+* jansson >= 2.5
+
+To build sources under the 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
+
+ .. note::
+
+ Alpine Linux currently does not support malloc replacement
+ due to musl limitations. See details in issue `#762 <https://github.com/nghttp2/nghttp2/issues/762>`_.
+
+To enable mruby support for nghttpx, `mruby
+<https://github.com/mruby/mruby>`_ is required. We need to build
+mruby with C++ ABI explicitly turned on, and probably need other
+mrgems, mruby is managed by git submodule under third-party/mruby
+directory. Currently, mruby support for nghttpx is disabled by
+default. To enable mruby support, use ``--with-mruby`` configure
+option. Note that at the time of this writing, libmruby-dev and mruby
+packages in Debian/Ubuntu are not usable for nghttp2, since they do
+not enable C++ ABI. To build mruby, the following packages are
+required:
+
+* ruby
+* bison
+
+nghttpx supports `neverbleed <https://github.com/h2o/neverbleed>`_,
+privilege separation engine for OpenSSL. In short, it minimizes the
+risk of private key leakage when serious bug like Heartbleed is
+exploited. The neverbleed is disabled by default. To enable it, use
+``--with-neverbleed`` configure option.
+
+To enable the experimental HTTP/3 support for h2load and nghttpx, the
+following libraries are required:
+
+* `OpenSSL with QUIC support
+ <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1w+quic>`_; or
+ LibreSSL (does not support 0RTT); or aws-lc; or
+ `BoringSSL <https://boringssl.googlesource.com/boringssl/>`_ (commit
+ f42be90d665b6a376177648ccbb76fbbd6497c13)
+* `ngtcp2 <https://github.com/ngtcp2/ngtcp2>`_ >= 1.0.0
+* `nghttp3 <https://github.com/ngtcp2/nghttp3>`_ >= 1.1.0
+
+Use ``--enable-http3`` configure option to enable HTTP/3 feature for
+h2load and nghttpx.
+
+In order to build optional eBPF program to direct an incoming QUIC UDP
+datagram to a correct socket for nghttpx, the following libraries are
+required:
+
+* libbpf-dev >= 0.7.0
+
+Use ``--with-libbpf`` configure option to build eBPF program.
+libelf-dev is needed to build libbpf.
+
+For Ubuntu 20.04, you can build libbpf from `the source code
+<https://github.com/libbpf/libbpf/releases/tag/v1.3.0>`_. nghttpx
+requires eBPF program for reloading its configuration and hot swapping
+its executable.
+
+Compiling libnghttp2 C source code requires a C99 compiler. gcc 4.8
+is known to be adequate. In order to compile the C++ source code, gcc
+>= 6.0 or clang >= 6.0 is required. C++ source code requires C++14
+language features.
+
+.. note::
+
+ To enable mruby support in nghttpx, and use ``--with-mruby``
+ configure option.
+
+.. note::
+
+ Mac OS X users may need the ``--disable-threads`` configure option to
+ disable multi-threading in nghttpd, nghttpx and h2load to prevent
+ them from crashing. A patch is welcome to make multi threading work
+ on Mac OS X platform.
+
+.. note::
+
+ To compile the associated applications (nghttp, nghttpd, nghttpx
+ and h2load), you must use the ``--enable-app`` configure option and
+ ensure that the specified requirements above are met. Normally,
+ configure script checks required dependencies to build these
+ applications, and enable ``--enable-app`` automatically, so you
+ don't have to use it explicitly. But if you found that
+ applications were not built, then using ``--enable-app`` may find
+ that cause, such as the missing dependency.
+
+.. note::
+
+ In order to detect third party libraries, pkg-config is used
+ (however we don't use pkg-config for some libraries (e.g., libev)).
+ By default, pkg-config searches ``*.pc`` file in the standard
+ locations (e.g., /usr/lib/pkgconfig). If it is necessary to use
+ ``*.pc`` file in the custom location, specify paths to
+ ``PKG_CONFIG_PATH`` environment variable, and pass it to configure
+ script, like so:
+
+ .. code-block:: text
+
+ $ ./configure PKG_CONFIG_PATH=/path/to/pkgconfig
+
+ For pkg-config managed libraries, ``*_CFLAG`` and ``*_LIBS``
+ environment variables are defined (e.g., ``OPENSSL_CFLAGS``,
+ ``OPENSSL_LIBS``). Specifying non-empty string to these variables
+ completely overrides pkg-config. In other words, if they are
+ specified, pkg-config is not used for detection, and user is
+ responsible to specify the correct values to these variables. For
+ complete list of these variables, run ``./configure -h``.
+
+If you are using Ubuntu 22.04 LTS, run the following to install the
+required packages:
+
+.. code-block:: text
+
+ sudo apt-get install g++ clang make binutils autoconf automake \
+ autotools-dev libtool pkg-config \
+ zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev \
+ libevent-dev libjansson-dev \
+ libc-ares-dev libjemalloc-dev libsystemd-dev \
+ ruby-dev bison libelf-dev
+
+Building nghttp2 from release tar archive
+-----------------------------------------
+
+The nghttp2 project regularly releases tar archives which includes
+nghttp2 source code, and generated build files. They can be
+downloaded from `Releases
+<https://github.com/nghttp2/nghttp2/releases>`_ page.
+
+Building nghttp2 from git requires autotools development packages.
+Building from tar archives does not require them, and thus it is much
+easier. The usual build step is as follows:
+
+.. code-block:: text
+
+ $ tar xf nghttp2-X.Y.Z.tar.bz2
+ $ cd nghttp2-X.Y.Z
+ $ ./configure
+ $ make
+
+Building from git
+-----------------
+
+Building from git is easy, but please be sure that at least autoconf 2.68 is
+used:
+
+.. code-block:: text
+
+ $ git submodule update --init
+ $ autoreconf -i
+ $ automake
+ $ autoconf
+ $ ./configure
+ $ make
+
+Notes for building on Windows (MSVC)
+------------------------------------
+
+The easiest way to build native Windows nghttp2 dll is use `cmake
+<https://cmake.org/>`_. The free version of `Visual C++ Build Tools
+<http://landinghub.visualstudio.com/visual-cpp-build-tools>`_ works
+fine.
+
+1. Install cmake for windows
+2. Open "Visual C++ ... Native Build Tool Command Prompt", and inside
+ nghttp2 directly, run ``cmake``.
+3. Then run ``cmake --build`` to build library.
+4. nghttp2.dll, nghttp2.lib, nghttp2.exp are placed under lib directory.
+
+Note that the above steps most likely produce nghttp2 library only.
+No bundled applications are compiled.
+
+Notes for building on Windows (Mingw/Cygwin)
+--------------------------------------------
+
+Under Mingw environment, you can only compile the library, it's
+``libnghttp2-X.dll`` and ``libnghttp2.a``.
+
+If you want to compile the applications(``h2load``, ``nghttp``,
+``nghttpx``, ``nghttpd``), you need to use the Cygwin environment.
+
+Under Cygwin environment, to compile the applications you need to
+compile and install the libev first.
+
+Secondly, you need to undefine the macro ``__STRICT_ANSI__``, if you
+not, the functions ``fdopen``, ``fileno`` and ``strptime`` will not
+available.
+
+the sample command like this:
+
+.. code-block:: text
+
+ $ export CFLAGS="-U__STRICT_ANSI__ -I$libev_PREFIX/include -L$libev_PREFIX/lib"
+ $ export CXXFLAGS=$CFLAGS
+ $ ./configure
+ $ make
+
+If you want to compile the applications under ``examples/``, you need
+to remove or rename the ``event.h`` from libev's installation, because
+it conflicts with libevent's installation.
+
+Notes for installation on Linux systems
+--------------------------------------------
+After installing nghttp2 tool suite with ``make install`` one might experience a similar error:
+
+.. code-block:: text
+
+ nghttpx: error while loading shared libraries: libnghttp2.so.14: cannot open shared object file: No such file or directory
+
+This means that the tool is unable to locate the ``libnghttp2.so`` shared library.
+
+To update the shared library cache run ``sudo ldconfig``.
+
+Building the documentation
+--------------------------
+
+.. note::
+
+ Documentation is still incomplete.
+
+To build the documentation, run:
+
+.. code-block:: text
+
+ $ 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/
+
+Build HTTP/3 enabled h2load and nghttpx
+---------------------------------------
+
+To build h2load and nghttpx with HTTP/3 feature enabled, run the
+configure script with ``--enable-http3``.
+
+For nghttpx to reload configurations and swapping its executable while
+gracefully terminating old worker processes, eBPF is required. Run
+the configure script with ``--enable-http3 --with-libbpf`` to build
+eBPF program. The QUIC keying material must be set with
+``--frontend-quic-secret-file`` in order to keep the existing
+connections alive during reload.
+
+The detailed steps to build HTTP/3 enabled h2load and nghttpx follow.
+
+Build custom OpenSSL:
+
+.. code-block:: text
+
+ $ git clone --depth 1 -b OpenSSL_1_1_1w+quic https://github.com/quictls/openssl
+ $ cd openssl
+ $ ./config --prefix=$PWD/build --openssldir=/etc/ssl
+ $ make -j$(nproc)
+ $ make install_sw
+ $ cd ..
+
+Build nghttp3:
+
+.. code-block:: text
+
+ $ git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3
+ $ cd nghttp3
+ $ autoreconf -i
+ $ ./configure --prefix=$PWD/build --enable-lib-only
+ $ make -j$(nproc)
+ $ make install
+ $ cd ..
+
+Build ngtcp2:
+
+.. code-block:: text
+
+ $ git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/ngtcp2
+ $ cd ngtcp2
+ $ autoreconf -i
+ $ ./configure --prefix=$PWD/build --enable-lib-only \
+ PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig"
+ $ make -j$(nproc)
+ $ make install
+ $ cd ..
+
+If your Linux distribution does not have libbpf-dev >= 0.7.0, build
+from source:
+
+.. code-block:: text
+
+ $ git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf
+ $ cd libbpf
+ $ PREFIX=$PWD/build make -C src install
+ $ cd ..
+
+Build nghttp2:
+
+.. code-block:: text
+
+ $ git clone https://github.com/nghttp2/nghttp2
+ $ cd nghttp2
+ $ git submodule update --init
+ $ autoreconf -i
+ $ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \
+ CC=clang-14 CXX=clang++-14 \
+ PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \
+ LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/lib -Wl,-rpath,$PWD/../libbpf/build/lib64"
+ $ make -j$(nproc)
+
+The eBPF program ``reuseport_kern.o`` should be found under bpf
+directory. Pass ``--quic-bpf-program-file=bpf/reuseport_kern.o``
+option to nghttpx to load it. See also `HTTP/3 section in nghttpx -
+HTTP/2 proxy - HOW-TO
+<https://nghttp2.org/documentation/nghttpx-howto.html#http-3>`_.
+
+Unit tests
+----------
+
+Unit tests are done by simply running ``make check``.
+
+Integration tests
+-----------------
+
+We have the integration tests for the nghttpx proxy server. The tests are
+written in the `Go programming language <http://golang.org/>`_ and uses
+its testing framework. We depend on the following libraries:
+
+* golang.org/x/net/http2
+* golang.org/x/net/websocket
+* https://github.com/tatsuhiro-t/go-nghttp2
+
+Go modules will download these dependencies automatically.
+
+To run the tests, run the following command under
+``integration-tests`` directory:
+
+.. code-block:: text
+
+ $ make it
+
+Inside the tests, we use port 3009 to run the test subject server.
+
+Migration from v0.7.15 or earlier
+---------------------------------
+
+nghttp2 v1.0.0 introduced several backward incompatible changes. In
+this section, we describe these changes and how to migrate to v1.0.0.
+
+ALPN protocol ID is now ``h2`` and ``h2c``
+++++++++++++++++++++++++++++++++++++++++++
+
+Previously we announced ``h2-14`` and ``h2c-14``. v1.0.0 implements
+final protocol version, and we changed ALPN ID to ``h2`` and ``h2c``.
+The macros ``NGHTTP2_PROTO_VERSION_ID``,
+``NGHTTP2_PROTO_VERSION_ID_LEN``,
+``NGHTTP2_CLEARTEXT_PROTO_VERSION_ID``, and
+``NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN`` have been updated to
+reflect this change.
+
+Basically, existing applications do not have to do anything, just
+recompiling is enough for this change.
+
+Use word "client magic" where we use "client connection preface"
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+We use "client connection preface" to mean first 24 bytes of client
+connection preface. This is technically not correct, since client
+connection preface is composed of 24 bytes client magic byte string
+followed by SETTINGS frame. For clarification, we call "client magic"
+for this 24 bytes byte string and updated API.
+
+* ``NGHTTP2_CLIENT_CONNECTION_PREFACE`` was replaced with
+ ``NGHTTP2_CLIENT_MAGIC``.
+* ``NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN`` was replaced with
+ ``NGHTTP2_CLIENT_MAGIC_LEN``.
+* ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC``
+
+The already deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and
+``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed.
+
+If application uses these macros, just replace old ones with new ones.
+Since v1.0.0, client magic is sent by library (see next subsection),
+so client application may just remove these macro use.
+
+Client magic is sent by library
++++++++++++++++++++++++++++++++
+
+Previously nghttp2 library did not send client magic, which is first
+24 bytes byte string of client connection preface, and client
+applications have to send it by themselves. Since v1.0.0, client
+magic is sent by library via first call of ``nghttp2_session_send()``
+or ``nghttp2_session_mem_send()``.
+
+The client applications which send client magic must remove the
+relevant code.
+
+Remove HTTP Alternative Services (Alt-Svc) related code
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Alt-Svc specification is not finalized yet. To make our API stable,
+we have decided to remove all Alt-Svc related API from nghttp2.
+
+* ``NGHTTP2_EXT_ALTSVC`` was removed.
+* ``nghttp2_ext_altsvc`` was removed.
+
+We have already removed the functionality of Alt-Svc in v0.7 series
+and they have been essentially noop. The application using these
+macro and struct, remove those lines.
+
+Use nghttp2_error in nghttp2_on_invalid_frame_recv_callback
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Previously ``nghttp2_on_invalid_frame_recv_cb_called`` took the
+``error_code``, defined in ``nghttp2_error_code``, as parameter. But
+they are not detailed enough to debug. Therefore, we decided to use
+more detailed ``nghttp2_error`` values instead.
+
+The application using this callback should update the callback
+signature. If it treats ``error_code`` as HTTP/2 error code, update
+the code so that it is treated as ``nghttp2_error``.
+
+Receive client magic by default
++++++++++++++++++++++++++++++++
+
+Previously nghttp2 did not process client magic (24 bytes byte
+string). To make it deal with it, we had to use
+``nghttp2_option_set_recv_client_preface()``. Since v1.0.0, nghttp2
+processes client magic by default and
+``nghttp2_option_set_recv_client_preface()`` was removed.
+
+Some application may want to disable this behaviour, so we added
+``nghttp2_option_set_no_recv_client_magic()`` to achieve this.
+
+The application using ``nghttp2_option_set_recv_client_preface()``
+with nonzero value, just remove it.
+
+The application using ``nghttp2_option_set_recv_client_preface()``
+with zero value or not using it must use
+``nghttp2_option_set_no_recv_client_magic()`` with nonzero value.
+
+Client, Server and Proxy programs
+---------------------------------
+
+The ``src`` directory contains the 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 ALPN TLS extension.
+
+It has verbose output mode for framing information. Here is sample
+output from ``nghttp`` client:
+
+.. code-block:: text
+
+ $ nghttp -nv https://nghttp2.org
+ [ 0.190] Connected
+ The negotiated protocol: h2
+ [ 0.212] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+ [ 0.212] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+ [ 0.212] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
+ (dep_stream_id=0, weight=201, exclusive=0)
+ [ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
+ (dep_stream_id=0, weight=101, exclusive=0)
+ [ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
+ (dep_stream_id=0, weight=1, exclusive=0)
+ [ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
+ (dep_stream_id=7, weight=1, exclusive=0)
+ [ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
+ (dep_stream_id=3, weight=1, exclusive=0)
+ [ 0.212] send HEADERS frame <length=39, flags=0x25, stream_id=13>
+ ; END_STREAM | END_HEADERS | PRIORITY
+ (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
+ ; Open new stream
+ :method: GET
+ :path: /
+ :scheme: https
+ :authority: nghttp2.org
+ accept: */*
+ accept-encoding: gzip, deflate
+ user-agent: nghttp2/1.0.1-DEV
+ [ 0.221] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [ 0.221] recv (stream_id=13) :method: GET
+ [ 0.221] recv (stream_id=13) :scheme: https
+ [ 0.221] recv (stream_id=13) :path: /stylesheets/screen.css
+ [ 0.221] recv (stream_id=13) :authority: nghttp2.org
+ [ 0.221] recv (stream_id=13) accept-encoding: gzip, deflate
+ [ 0.222] recv (stream_id=13) user-agent: nghttp2/1.0.1-DEV
+ [ 0.222] recv PUSH_PROMISE frame <length=50, flags=0x04, stream_id=13>
+ ; END_HEADERS
+ (padlen=0, promised_stream_id=2)
+ [ 0.222] recv (stream_id=13) :status: 200
+ [ 0.222] recv (stream_id=13) date: Thu, 21 May 2015 16:38:14 GMT
+ [ 0.222] recv (stream_id=13) content-type: text/html
+ [ 0.222] recv (stream_id=13) last-modified: Fri, 15 May 2015 15:38:06 GMT
+ [ 0.222] recv (stream_id=13) etag: W/"555612de-19f6"
+ [ 0.222] recv (stream_id=13) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
+ [ 0.222] recv (stream_id=13) content-encoding: gzip
+ [ 0.222] recv (stream_id=13) server: nghttpx nghttp2/1.0.1-DEV
+ [ 0.222] recv (stream_id=13) via: 1.1 nghttpx
+ [ 0.222] recv (stream_id=13) strict-transport-security: max-age=31536000
+ [ 0.222] recv HEADERS frame <length=166, flags=0x04, stream_id=13>
+ ; END_HEADERS
+ (padlen=0)
+ ; First response header
+ [ 0.222] recv DATA frame <length=2601, flags=0x01, stream_id=13>
+ ; END_STREAM
+ [ 0.222] recv (stream_id=2) :status: 200
+ [ 0.222] recv (stream_id=2) date: Thu, 21 May 2015 16:38:14 GMT
+ [ 0.222] recv (stream_id=2) content-type: text/css
+ [ 0.222] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT
+ [ 0.222] recv (stream_id=2) etag: W/"555612de-9845"
+ [ 0.222] recv (stream_id=2) content-encoding: gzip
+ [ 0.222] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV
+ [ 0.222] recv (stream_id=2) via: 1.1 nghttpx
+ [ 0.222] recv (stream_id=2) strict-transport-security: max-age=31536000
+ [ 0.222] recv HEADERS frame <length=32, flags=0x04, stream_id=2>
+ ; END_HEADERS
+ (padlen=0)
+ ; First push response header
+ [ 0.228] recv DATA frame <length=8715, flags=0x01, stream_id=2>
+ ; END_STREAM
+ [ 0.228] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
+ (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
+
+The HTTP Upgrade is performed like so:
+
+.. code-block:: text
+
+ $ nghttp -nvu http://nghttp2.org
+ [ 0.011] Connected
+ [ 0.011] HTTP Upgrade request
+ GET / HTTP/1.1
+ Host: nghttp2.org
+ Connection: Upgrade, HTTP2-Settings
+ Upgrade: h2c
+ HTTP2-Settings: AAMAAABkAAQAAP__
+ Accept: */*
+ User-Agent: nghttp2/1.0.1-DEV
+
+
+ [ 0.018] HTTP Upgrade response
+ HTTP/1.1 101 Switching Protocols
+ Connection: Upgrade
+ Upgrade: h2c
+
+
+ [ 0.018] HTTP Upgrade success
+ [ 0.018] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+ [ 0.018] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+ [ 0.018] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
+ (dep_stream_id=0, weight=201, exclusive=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
+ (dep_stream_id=0, weight=101, exclusive=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
+ (dep_stream_id=0, weight=1, exclusive=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
+ (dep_stream_id=7, weight=1, exclusive=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
+ (dep_stream_id=3, weight=1, exclusive=0)
+ [ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=1>
+ (dep_stream_id=11, weight=16, exclusive=0)
+ [ 0.019] recv (stream_id=1) :method: GET
+ [ 0.019] recv (stream_id=1) :scheme: http
+ [ 0.019] recv (stream_id=1) :path: /stylesheets/screen.css
+ [ 0.019] recv (stream_id=1) host: nghttp2.org
+ [ 0.019] recv (stream_id=1) user-agent: nghttp2/1.0.1-DEV
+ [ 0.019] recv PUSH_PROMISE frame <length=49, flags=0x04, stream_id=1>
+ ; END_HEADERS
+ (padlen=0, promised_stream_id=2)
+ [ 0.019] recv (stream_id=1) :status: 200
+ [ 0.019] recv (stream_id=1) date: Thu, 21 May 2015 16:39:16 GMT
+ [ 0.019] recv (stream_id=1) content-type: text/html
+ [ 0.019] recv (stream_id=1) content-length: 6646
+ [ 0.019] recv (stream_id=1) last-modified: Fri, 15 May 2015 15:38:06 GMT
+ [ 0.019] recv (stream_id=1) etag: "555612de-19f6"
+ [ 0.019] recv (stream_id=1) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
+ [ 0.019] recv (stream_id=1) accept-ranges: bytes
+ [ 0.019] recv (stream_id=1) server: nghttpx nghttp2/1.0.1-DEV
+ [ 0.019] recv (stream_id=1) via: 1.1 nghttpx
+ [ 0.019] recv HEADERS frame <length=157, flags=0x04, stream_id=1>
+ ; END_HEADERS
+ (padlen=0)
+ ; First response header
+ [ 0.019] recv DATA frame <length=6646, flags=0x01, stream_id=1>
+ ; END_STREAM
+ [ 0.019] recv (stream_id=2) :status: 200
+ [ 0.019] recv (stream_id=2) date: Thu, 21 May 2015 16:39:16 GMT
+ [ 0.019] recv (stream_id=2) content-type: text/css
+ [ 0.019] recv (stream_id=2) content-length: 38981
+ [ 0.019] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT
+ [ 0.019] recv (stream_id=2) etag: "555612de-9845"
+ [ 0.019] recv (stream_id=2) accept-ranges: bytes
+ [ 0.019] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV
+ [ 0.019] recv (stream_id=2) via: 1.1 nghttpx
+ [ 0.019] recv HEADERS frame <length=36, flags=0x04, stream_id=2>
+ ; END_HEADERS
+ (padlen=0)
+ ; First push response header
+ [ 0.026] recv DATA frame <length=16384, flags=0x00, stream_id=2>
+ [ 0.027] recv DATA frame <length=7952, flags=0x00, stream_id=2>
+ [ 0.027] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
+ (window_size_increment=33343)
+ [ 0.032] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
+ (window_size_increment=33707)
+ [ 0.032] recv DATA frame <length=14645, flags=0x01, stream_id=2>
+ ; END_STREAM
+ [ 0.032] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [ 0.032] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
+ (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
+
+Using the ``-s`` option, ``nghttp`` prints out some timing information for
+requests, sorted by completion time:
+
+.. code-block:: text
+
+ $ nghttp -nas https://nghttp2.org/
+ ***** Statistics *****
+
+ Request timing:
+ responseEnd: the time when last byte of response was received
+ relative to connectEnd
+ requestStart: the time just before first byte of request was sent
+ relative to connectEnd. If '*' is shown, this was
+ pushed by server.
+ process: responseEnd - requestStart
+ code: HTTP status code
+ size: number of bytes received as response body without
+ inflation.
+ URI: request URI
+
+ see http://www.w3.org/TR/resource-timing/#processing-model
+
+ sorted by 'complete'
+
+ id responseEnd requestStart process code size request path
+ 13 +37.19ms +280us 36.91ms 200 2K /
+ 2 +72.65ms * +36.38ms 36.26ms 200 8K /stylesheets/screen.css
+ 17 +77.43ms +38.67ms 38.75ms 200 3K /javascripts/octopress.js
+ 15 +78.12ms +38.66ms 39.46ms 200 3K /javascripts/modernizr-2.0.js
+
+Using the ``-r`` option, ``nghttp`` writes more detailed timing data to
+the 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 HTTP/2 connections via ALPN or direct HTTP/2
+connections. No HTTP Upgrade is supported.
+
+The ``-p`` option allows users to configure server push.
+
+Just like ``nghttp``, it has a verbose output mode for framing
+information. Here is sample output from ``nghttpd``:
+
+.. code-block:: text
+
+ $ nghttpd --no-tls -v 8080
+ IPv4: listen 0.0.0.0:8080
+ IPv6: listen :::8080
+ [id=1] [ 1.521] send SETTINGS frame <length=6, flags=0x00, stream_id=0>
+ (niv=1)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [id=1] [ 1.521] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+ [id=1] [ 1.521] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=3>
+ (dep_stream_id=0, weight=201, exclusive=0)
+ [id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=5>
+ (dep_stream_id=0, weight=101, exclusive=0)
+ [id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=7>
+ (dep_stream_id=0, weight=1, exclusive=0)
+ [id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=9>
+ (dep_stream_id=7, weight=1, exclusive=0)
+ [id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=11>
+ (dep_stream_id=3, weight=1, exclusive=0)
+ [id=1] [ 1.521] recv (stream_id=13) :method: GET
+ [id=1] [ 1.521] recv (stream_id=13) :path: /
+ [id=1] [ 1.521] recv (stream_id=13) :scheme: http
+ [id=1] [ 1.521] recv (stream_id=13) :authority: localhost:8080
+ [id=1] [ 1.521] recv (stream_id=13) accept: */*
+ [id=1] [ 1.521] recv (stream_id=13) accept-encoding: gzip, deflate
+ [id=1] [ 1.521] recv (stream_id=13) user-agent: nghttp2/1.0.0-DEV
+ [id=1] [ 1.521] recv HEADERS frame <length=41, flags=0x25, stream_id=13>
+ ; END_STREAM | END_HEADERS | PRIORITY
+ (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
+ ; Open new stream
+ [id=1] [ 1.521] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+ ; ACK
+ (niv=0)
+ [id=1] [ 1.521] send HEADERS frame <length=86, flags=0x04, stream_id=13>
+ ; END_HEADERS
+ (padlen=0)
+ ; First response header
+ :status: 200
+ server: nghttpd nghttp2/1.0.0-DEV
+ content-length: 10
+ cache-control: max-age=3600
+ date: Fri, 15 May 2015 14:49:04 GMT
+ last-modified: Tue, 30 Sep 2014 12:40:52 GMT
+ [id=1] [ 1.522] send DATA frame <length=10, flags=0x01, stream_id=13>
+ ; END_STREAM
+ [id=1] [ 1.522] stream_id=13 closed
+ [id=1] [ 1.522] recv GOAWAY frame <length=8, flags=0x00, stream_id=0>
+ (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
+ [id=1] [ 1.522] closed
+
+nghttpx - proxy
++++++++++++++++
+
+``nghttpx`` is a multi-threaded reverse proxy for HTTP/3, HTTP/2, and
+HTTP/1.1, and powers http://nghttp2.org and supports HTTP/2 server
+push.
+
+We reworked ``nghttpx`` command-line interface, and as a result, there
+are several incompatibles from 1.8.0 or earlier. This is necessary to
+extend its capability, and secure the further feature enhancements in
+the future release. Please read `Migration from nghttpx v1.8.0 or
+earlier
+<https://nghttp2.org/documentation/nghttpx-howto.html#migration-from-nghttpx-v1-8-0-or-earlier>`_
+to know how to migrate from earlier releases.
+
+``nghttpx`` implements `important performance-oriented features
+<https://istlsfastyet.com/#server-performance>`_ in TLS, such as
+session IDs, session tickets (with automatic key rotation), OCSP
+stapling, dynamic record sizing, ALPN, forward secrecy and HTTP/2.
+``nghttpx`` also offers the functionality to share session cache and
+ticket keys among multiple ``nghttpx`` instances via memcached.
+
+``nghttpx`` has 2 operation modes:
+
+================== ======================== ================ =============
+Mode option Frontend Backend Note
+================== ======================== ================ =============
+default mode HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy
+``--http2-proxy`` HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy
+================== ======================== ================ =============
+
+The interesting mode at the moment is the default mode. It works like
+a reverse proxy and listens for HTTP/3, HTTP/2, and HTTP/1.1 and can
+be deployed as a SSL/TLS terminator for existing web server.
+
+In all modes, the frontend connections are encrypted by SSL/TLS by
+default. To disable encryption, use the ``no-tls`` keyword in
+``--frontend`` option. If encryption is disabled, incoming HTTP/1.1
+connections can be upgraded to HTTP/2 through HTTP Upgrade. On the
+other hard, backend connections are not encrypted by default. To
+encrypt backend connections, use ``tls`` keyword in ``--backend``
+option.
+
+``nghttpx`` supports a configuration file. See the ``--conf`` option and
+sample configuration file ``nghttpx.conf.sample``.
+
+In the default mode, ``nghttpx`` works as reverse proxy to the backend
+server:
+
+.. code-block:: text
+
+ Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server
+ [reverse proxy]
+
+With the ``--http2-proxy`` option, it works as forward proxy, and it
+is so called secure HTTP/2 proxy:
+
+.. code-block:: text
+
+ Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy
+ [secure proxy] (e.g., Squid, ATS)
+
+The ``Client`` in the above example needs to be configured to use
+``nghttpx`` as secure proxy.
+
+At the time of this writing, both Chrome and Firefox support secure
+HTTP/2 proxy. One way to configure Chrome to use a secure proxy is to
+create a 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 on. Please note that Chrome requires a valid
+certificate for secure proxy.
+
+Then run Chrome with the following arguments:
+
+.. code-block:: text
+
+ $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
+
+The backend HTTP/2 connections can be tunneled through an HTTP proxy.
+The proxy is specified using ``--backend-http-proxy-uri``. The
+following figure illustrates how nghttpx talks to the outside HTTP/2
+proxy through an HTTP proxy:
+
+.. code-block:: text
+
+ Client <-- (HTTP/3, HTTP/2, 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/3, HTTP/2, and
+HTTP/1.1. The UI of ``h2load`` is heavily inspired by ``weighttp``
+(https://github.com/lighttpd/weighttp). The typical usage is as
+follows:
+
+.. code-block:: text
+
+ $ 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
+ Server Temp Key: ECDH P-256 256 bits
+ 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 771.26ms, 129658 req/s, 4.71MB/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: 3812300 bytes total, 1009900 bytes headers, 1000000 bytes data
+ min max mean sd +/- sd
+ time for request: 25.12ms 124.55ms 51.07ms 15.36ms 84.87%
+ time for connect: 208.94ms 254.67ms 241.38ms 7.95ms 63.00%
+ time to 1st byte: 209.11ms 254.80ms 241.51ms 7.94ms 63.00%
+
+The above example issued total 100,000 requests, using 100 concurrent
+clients (in other words, 100 HTTP/2 sessions), and a maximum of 100 streams
+per client. With the ``-t`` option, ``h2load`` will use multiple native
+threads to avoid saturating a single core on client side.
+
+.. warning::
+
+ **Don't use this tool against publicly available servers.** That is
+ considered a DOS attack. Please only use it against your private
+ servers.
+
+If the experimental HTTP/3 is enabled, h2load can send requests to
+HTTP/3 server. To do this, specify ``h3`` to ``--alpn-list`` option
+like so:
+
+.. code-block:: text
+
+ $ h2load --alpn-list h3 https://127.0.0.1:4433
+
+For nghttp2 v1.58 or earlier, use ``--npn-list`` instead of
+``--alpn-list``.
+
+HPACK tools
+-----------
+
+The ``src`` directory contains the HPACK tools. The ``deflatehd`` program is a
+command-line header compression tool. The ``inflatehd`` program is a
+command-line header decompression tool. Both tools read input from
+stdin and write output to stdout. Errors are written to stderr.
+They take JSON as input and output. We (mostly) use the same JSON data
+format described at https://github.com/http2jp/hpack-test-case.
+
+deflatehd - header compressor
++++++++++++++++++++++++++++++
+
+The ``deflatehd`` program 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 a ``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 a ``headers`` key. Its value is an array of JSON objects,
+which includes exactly one name/value pair.
+
+Example:
+
+.. code-block:: json
+
+ {
+ "cases":
+ [
+ {
+ "headers": [
+ { ":method": "GET" },
+ { ":path": "/" }
+ ]
+ },
+ {
+ "headers": [
+ { ":method": "POST" },
+ { ":path": "/" }
+ ]
+ }
+ ]
+ }
+
+
+With the ``-t`` option, the program can accept more familiar HTTP/1 style
+header field blocks. Each header set is delimited by an empty line:
+
+Example:
+
+.. code-block:: text
+
+ :method: GET
+ :scheme: https
+ :path: /
+
+ :method: POST
+ user-agent: nghttp2
+
+The output is in JSON object. It should include a ``cases`` key and its
+value is an array of JSON objects, which has at least the following keys:
+
+seq
+ The index of header set in the input.
+
+input_length
+ The sum of the length of the name/value pairs in the input.
+
+output_length
+ The length of the compressed header block.
+
+percentage_of_original_size
+ ``output_length`` / ``input_length`` * 100
+
+wire
+ The compressed header block as a hex string.
+
+headers
+ The input header set.
+
+header_table_size
+ The header table size adjusted before deflating the 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 the ``-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 the encoder uses. This can be smaller
+ than ``max_size``. In this case, the 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 outside 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`` program reads JSON data from stdin and outputs decompressed
+name/value pairs in JSON.
+
+The root JSON object must include the ``cases`` key. Its value has to
+include the sequence of compressed header blocks. 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 a
+``wire`` key. Its value is a compressed header block as a hex string.
+
+Example:
+
+.. code-block:: json
+
+ {
+ "cases":
+ [
+ { "wire": "8285" },
+ { "wire": "8583" }
+ ]
+ }
+
+The output is a JSON object. It should include a ``cases`` key and its
+value is an array of JSON objects, which has at least following keys:
+
+seq
+ The index of the header set in the input.
+
+headers
+ A JSON array that includes decompressed name/value pairs.
+
+wire
+ The compressed header block as a 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 the ``-d`` option, the extra ``header_table`` key is added and its
+associated value includes the state of the dynamic header table after the
+corresponding header set was processed. The format is the same as
+``deflatehd``.
+
+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, 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 (or your employer, as
+the case may be) agree to assign the copyright of your submission to us.
+.. the above really needs to be reworded to pass legal muster.
+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.
+
+Reporting vulnerability
+-----------------------
+
+If you find a vulnerability in our software, please send the email to
+"tatsuhiro.t at gmail dot com" about its details instead of submitting
+issues on github issue page. It is a standard practice not to
+disclose vulnerability information publicly until a fixed version is
+released, or mitigation is worked out.
+
+In the future, we may setup a dedicated mail address for this purpose.
+
+Versioning
+----------
+
+In general, we follow `Semantic Versioning <http://semver.org/>`_.
+
+We may release PATCH releases between the regular releases, mainly for
+severe security bug fixes.
+
+We have no plan to break API compatibility changes involving soname
+bump, so MAJOR version will stay 1 for the foreseeable future.
+
+License
+-------
+
+The MIT License
diff --git a/android-config b/android-config
new file mode 100755
index 0000000..4a137de
--- /dev/null
+++ b/android-config
@@ -0,0 +1,37 @@
+#!/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.
+
+. ./android-env
+
+./configure \
+ --disable-shared \
+ --host=$TARGET \
+ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+ --without-libxml2 \
+ --disable-examples \
+ --disable-threads \
+ CPPFLAGS="-fPIE -I$PREFIX/include" \
+ PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+ LDFLAGS="-fPIE -pie -L$PREFIX/lib"
diff --git a/android-env b/android-env
new file mode 100755
index 0000000..4412fcf
--- /dev/null
+++ b/android-env
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# nghttp2 - HTTP/2 C Library
+#
+# Copyright (c) 2022 nghttp2 contributors
+#
+# 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 "$NDK" ]; then
+ echo 'No $NDK specified.'
+ exit 1
+fi
+
+export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
+export TARGET=aarch64-linux-android
+export API=33
+export AR=$TOOLCHAIN/bin/llvm-ar
+export CC=$TOOLCHAIN/bin/$TARGET$API-clang
+export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++
+export LD=$TOOLCHAIN/bin/ld
+export RANDLIB=$TOOLCHAIN/bin/llvm-ranlib
+export STRIP=$TOOLCHAIN/bin/llvm-strip
+export PREFIX=$NDK/usr/local
diff --git a/author.py b/author.py
new file mode 100755
index 0000000..4bf97c4
--- /dev/null
+++ b/author.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+# script to extract commit author's name from standard input. The
+# input should be <AUTHOR>:<EMAIL>, one per line.
+# This script expects the input is created by git-log command:
+#
+# git log --format=%aN:%aE
+#
+# This script removes duplicates based on email address, breaking a
+# tie with longer author name. Among the all author names extract the
+# previous step, we remove duplicate by case-insensitive match.
+#
+# So we can do this in one line:
+#
+# git log --format=%aN:%aE | sort | uniq | ./author.py > authors
+
+import sys
+
+edict = {}
+
+for line in sys.stdin:
+ author, email = line.strip().split(':', 1)
+ if email in edict:
+ an = edict[email]
+ if len(an) < len(author) or an > author:
+ sys.stderr.write(
+ 'eliminated {} in favor of {}\n'.format(an, author))
+ edict[email] = author
+ else:
+ sys.stderr.write(
+ 'eliminated {} in favor of {}\n'.format(author, an))
+ else:
+ edict[email] = author
+
+names = list(sorted(edict.values()))
+
+ndict = {}
+
+for name in names:
+ lowname = name.lower()
+ if lowname in ndict:
+ an = ndict[lowname]
+ if an > name:
+ sys.stderr.write('eliminated {} in favor of {}\n'.format(an, name))
+ ndict[lowname] = name
+ else:
+ sys.stderr.write('eliminated {} in favor of {}\n'.format(name, an))
+ else:
+ ndict[lowname] = name
+
+for name in sorted(ndict.values()):
+ print(name)
diff --git a/bpf/CMakeLists.txt b/bpf/CMakeLists.txt
new file mode 100644
index 0000000..904b821
--- /dev/null
+++ b/bpf/CMakeLists.txt
@@ -0,0 +1,13 @@
+if(LIBBPF_FOUND)
+ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o"
+ COMMAND ${CMAKE_C_COMPILER} ${BPFCFLAGS} ${EXTRABPFCFLAGS} -I${LIBBPF_INCLUDE_DIRS} -target bpf -c reuseport_kern.c -o "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ VERBATIM)
+
+ add_custom_target(bpf ALL
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o"
+ VERBATIM)
+
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CMAKE_PROJECT_NAME}")
+endif()
diff --git a/bpf/Makefile.am b/bpf/Makefile.am
new file mode 100644
index 0000000..9017fd9
--- /dev/null
+++ b/bpf/Makefile.am
@@ -0,0 +1,40 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2021 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 = CMakeLists.txt reuseport_kern.c
+
+if HAVE_LIBBPF
+
+bpf_pkglibdir = $(pkglibdir)
+bpf_pkglib_DATA = reuseport_kern.o
+
+all: $(builddir)/reuseport_kern.o
+
+$(builddir)/reuseport_kern.o: reuseport_kern.c
+ $(CC) @LIBBPF_CFLAGS@ @BPFCFLAGS@ @EXTRABPFCFLAGS@ \
+ -target bpf -c $< -o $@
+
+clean-local:
+ -rm -f reuseport_kern.o
+
+endif # HAVE_LIBBPF
diff --git a/bpf/reuseport_kern.c b/bpf/reuseport_kern.c
new file mode 100644
index 0000000..74c08c5
--- /dev/null
+++ b/bpf/reuseport_kern.c
@@ -0,0 +1,663 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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 <linux/udp.h>
+#include <linux/bpf.h>
+
+#include <bpf/bpf_helpers.h>
+
+/*
+ * How to compile:
+ *
+ * clang-12 -O2 -Wall -target bpf -g -c reuseport_kern.c -o reuseport_kern.o \
+ * -I/path/to/kernel/include
+ *
+ * See
+ * https://www.kernel.org/doc/Documentation/kbuild/headers_install.txt
+ * how to install kernel header files.
+ */
+
+/* AES_CBC_decrypt_buffer: https://github.com/kokke/tiny-AES-c
+ License is Public Domain. Commit hash:
+ 12e7744b4919e9d55de75b7ab566326a1c8e7a67 */
+
+#define AES_BLOCKLEN \
+ 16 /* Block length in bytes - AES is 128b block \
+ only */
+
+#define AES_KEYLEN 16 /* Key length in bytes */
+#define AES_keyExpSize 176
+
+struct AES_ctx {
+ __u8 RoundKey[AES_keyExpSize];
+};
+
+/* The number of columns comprising a state in AES. This is a constant
+ in AES. Value=4 */
+#define Nb 4
+
+#define Nk 4 /* The number of 32 bit words in a key. */
+#define Nr 10 /* The number of rounds in AES Cipher. */
+
+/* state - array holding the intermediate results during
+ decryption. */
+typedef __u8 state_t[4][4];
+
+/* The lookup-tables are marked const so they can be placed in
+ read-only storage instead of RAM The numbers below can be computed
+ dynamically trading ROM for RAM - This can be useful in (embedded)
+ bootloader applications, where ROM is often limited. */
+static const __u8 sbox[256] = {
+ /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
+ 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+ 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
+ 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
+ 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+ 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
+ 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
+ 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+ 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
+ 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
+ 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+ 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
+ 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
+ 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+ 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
+ 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
+ 0xb0, 0x54, 0xbb, 0x16};
+
+static const __u8 rsbox[256] = {
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
+ 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+ 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
+ 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
+ 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+ 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
+ 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
+ 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+ 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
+ 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
+ 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+ 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
+ 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
+ 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+ 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
+ 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
+ 0x55, 0x21, 0x0c, 0x7d};
+
+/* The round constant word array, Rcon[i], contains the values given
+ by x to the power (i-1) being powers of x (x is denoted as {02}) in
+ the field GF(2^8) */
+static const __u8 Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
+ 0x20, 0x40, 0x80, 0x1b, 0x36};
+
+#define getSBoxValue(num) (sbox[(num)])
+
+/* This function produces Nb(Nr+1) round keys. The round keys are used
+ in each round to decrypt the states. */
+static void KeyExpansion(__u8 *RoundKey, const __u8 *Key) {
+ unsigned i, j, k;
+ __u8 tempa[4]; /* Used for the column/row operations */
+
+ /* The first round key is the key itself. */
+ for (i = 0; i < Nk; ++i) {
+ RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
+ RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
+ RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
+ RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
+ }
+
+ /* All other round keys are found from the previous round keys. */
+ for (i = Nk; i < Nb * (Nr + 1); ++i) {
+ {
+ k = (i - 1) * 4;
+ tempa[0] = RoundKey[k + 0];
+ tempa[1] = RoundKey[k + 1];
+ tempa[2] = RoundKey[k + 2];
+ tempa[3] = RoundKey[k + 3];
+ }
+
+ if (i % Nk == 0) {
+ /* This function shifts the 4 bytes in a word to the left once.
+ [a0,a1,a2,a3] becomes [a1,a2,a3,a0] */
+
+ /* Function RotWord() */
+ {
+ const __u8 u8tmp = tempa[0];
+ tempa[0] = tempa[1];
+ tempa[1] = tempa[2];
+ tempa[2] = tempa[3];
+ tempa[3] = u8tmp;
+ }
+
+ /* SubWord() is a function that takes a four-byte input word and
+ applies the S-box to each of the four bytes to produce an
+ output word. */
+
+ /* Function Subword() */
+ {
+ tempa[0] = getSBoxValue(tempa[0]);
+ tempa[1] = getSBoxValue(tempa[1]);
+ tempa[2] = getSBoxValue(tempa[2]);
+ tempa[3] = getSBoxValue(tempa[3]);
+ }
+
+ tempa[0] = tempa[0] ^ Rcon[i / Nk];
+ }
+ j = i * 4;
+ k = (i - Nk) * 4;
+ RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
+ RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
+ RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
+ RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
+ }
+}
+
+static void AES_init_ctx(struct AES_ctx *ctx, const __u8 *key) {
+ KeyExpansion(ctx->RoundKey, key);
+}
+
+/* This function adds the round key to state. The round key is added
+ to the state by an XOR function. */
+static void AddRoundKey(__u8 round, state_t *state, const __u8 *RoundKey) {
+ __u8 i, j;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
+ }
+ }
+}
+
+static __u8 xtime(__u8 x) { return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); }
+
+#define Multiply(x, y) \
+ (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \
+ ((y >> 2 & 1) * xtime(xtime(x))) ^ \
+ ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \
+ ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x))))))
+
+#define getSBoxInvert(num) (rsbox[(num)])
+
+/* MixColumns function mixes the columns of the state matrix. The
+ method used to multiply may be difficult to understand for the
+ inexperienced. Please use the references to gain more
+ information. */
+static void InvMixColumns(state_t *state) {
+ int i;
+ __u8 a, b, c, d;
+ for (i = 0; i < 4; ++i) {
+ a = (*state)[i][0];
+ b = (*state)[i][1];
+ c = (*state)[i][2];
+ d = (*state)[i][3];
+
+ (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^
+ Multiply(d, 0x09);
+ (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^
+ Multiply(d, 0x0d);
+ (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^
+ Multiply(d, 0x0b);
+ (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^
+ Multiply(d, 0x0e);
+ }
+}
+
+extern __u32 LINUX_KERNEL_VERSION __kconfig;
+
+/* The SubBytes Function Substitutes the values in the state matrix
+ with values in an S-box. */
+static void InvSubBytes(state_t *state) {
+ __u8 i, j;
+ if (LINUX_KERNEL_VERSION < KERNEL_VERSION(5, 10, 0)) {
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ /* Ubuntu 20.04 LTS kernel 5.4.0 needs this workaround
+ otherwise "math between map_value pointer and register with
+ unbounded min value is not allowed". 5.10.0 is a kernel
+ version that works but it might not be the minimum
+ version. */
+ __u8 k = (*state)[j][i];
+ (*state)[j][i] = k ? getSBoxInvert(k) : getSBoxInvert(0);
+ }
+ }
+ } else {
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ (*state)[j][i] = getSBoxInvert((*state)[j][i]);
+ }
+ }
+ }
+}
+
+static void InvShiftRows(state_t *state) {
+ __u8 temp;
+
+ /* Rotate first row 1 columns to right */
+ temp = (*state)[3][1];
+ (*state)[3][1] = (*state)[2][1];
+ (*state)[2][1] = (*state)[1][1];
+ (*state)[1][1] = (*state)[0][1];
+ (*state)[0][1] = temp;
+
+ /* Rotate second row 2 columns to right */
+ temp = (*state)[0][2];
+ (*state)[0][2] = (*state)[2][2];
+ (*state)[2][2] = temp;
+
+ temp = (*state)[1][2];
+ (*state)[1][2] = (*state)[3][2];
+ (*state)[3][2] = temp;
+
+ /* Rotate third row 3 columns to right */
+ temp = (*state)[0][3];
+ (*state)[0][3] = (*state)[1][3];
+ (*state)[1][3] = (*state)[2][3];
+ (*state)[2][3] = (*state)[3][3];
+ (*state)[3][3] = temp;
+}
+
+static void InvCipher(state_t *state, const __u8 *RoundKey) {
+ /* Add the First round key to the state before starting the
+ rounds. */
+ AddRoundKey(Nr, state, RoundKey);
+
+ /* There will be Nr rounds. The first Nr-1 rounds are identical.
+ These Nr rounds are executed in the loop below. Last one without
+ InvMixColumn() */
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 1, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 2, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 3, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 4, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 5, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 6, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 7, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 8, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 9, state, RoundKey);
+ InvMixColumns(state);
+
+ InvShiftRows(state);
+ InvSubBytes(state);
+ AddRoundKey(Nr - 10, state, RoundKey);
+}
+
+static void AES_ECB_decrypt(const struct AES_ctx *ctx, __u8 *buf) {
+ /* The next function call decrypts the PlainText with the Key using
+ AES algorithm. */
+ InvCipher((state_t *)buf, ctx->RoundKey);
+}
+
+/* rol32: From linux kernel source code */
+
+/**
+ * rol32 - rotate a 32-bit value left
+ * @word: value to rotate
+ * @shift: bits to roll
+ */
+static inline __u32 rol32(__u32 word, unsigned int shift) {
+ return (word << shift) | (word >> ((-shift) & 31));
+}
+
+/* jhash.h: Jenkins hash support.
+ *
+ * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net)
+ *
+ * https://burtleburtle.net/bob/hash/
+ *
+ * These are the credits from Bob's sources:
+ *
+ * lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * These are functions for producing 32-bit hashes for hash table lookup.
+ * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+ * are externally useful functions. Routines to test the hash are included
+ * if SELF_TEST is defined. You can use this free for any purpose. It's in
+ * the public domain. It has no warranty.
+ *
+ * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu)
+ *
+ * I've modified Bob's hash to be useful in the Linux kernel, and
+ * any bugs present are my fault.
+ * Jozsef
+ */
+
+/* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */
+#define __jhash_final(a, b, c) \
+ { \
+ c ^= b; \
+ c -= rol32(b, 14); \
+ a ^= c; \
+ a -= rol32(c, 11); \
+ b ^= a; \
+ b -= rol32(a, 25); \
+ c ^= b; \
+ c -= rol32(b, 16); \
+ a ^= c; \
+ a -= rol32(c, 4); \
+ b ^= a; \
+ b -= rol32(a, 14); \
+ c ^= b; \
+ c -= rol32(b, 24); \
+ }
+
+/* __jhash_nwords - hash exactly 3, 2 or 1 word(s) */
+static inline __u32 __jhash_nwords(__u32 a, __u32 b, __u32 c, __u32 initval) {
+ a += initval;
+ b += initval;
+ c += initval;
+
+ __jhash_final(a, b, c);
+
+ return c;
+}
+
+/* An arbitrary initial parameter */
+#define JHASH_INITVAL 0xdeadbeef
+
+static inline __u32 jhash_2words(__u32 a, __u32 b, __u32 initval) {
+ return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2));
+}
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 255);
+ __type(key, __u64);
+ __type(value, __u32);
+} cid_prefix_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
+ __uint(max_entries, 255);
+ __type(key, __u32);
+ __type(value, __u32);
+} reuseport_array SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 3);
+ __type(key, __u32);
+ __type(value, __u64);
+} sk_info SEC(".maps");
+
+typedef struct quic_hd {
+ __u8 *dcid;
+ __u32 dcidlen;
+ __u32 dcid_offset;
+ __u8 type;
+} quic_hd;
+
+#define SV_DCIDLEN 20
+#define MAX_DCIDLEN 20
+#define MIN_DCIDLEN 8
+#define CID_PREFIXLEN 8
+#define CID_PREFIX_OFFSET 1
+
+enum {
+ NGTCP2_PKT_INITIAL = 0x0,
+ NGTCP2_PKT_0RTT = 0x1,
+ NGTCP2_PKT_HANDSHAKE = 0x2,
+ NGTCP2_PKT_SHORT = 0x40,
+};
+
+static inline int parse_quic(quic_hd *qhd, __u8 *data, __u8 *data_end) {
+ __u8 *p;
+ __u64 dcidlen;
+
+ if (*data & 0x80) {
+ p = data + 1 + 4;
+
+ /* Do not check the actual DCID length because we might not buffer
+ entire DCID here. */
+ dcidlen = *p;
+
+ if (dcidlen > MAX_DCIDLEN || dcidlen < MIN_DCIDLEN) {
+ return -1;
+ }
+
+ ++p;
+
+ qhd->type = (*data & 0x30) >> 4;
+ qhd->dcid = p;
+ qhd->dcidlen = dcidlen;
+ qhd->dcid_offset = 6;
+ } else {
+ qhd->type = NGTCP2_PKT_SHORT;
+ qhd->dcid = data + 1;
+ qhd->dcidlen = SV_DCIDLEN;
+ qhd->dcid_offset = 1;
+ }
+
+ return 0;
+}
+
+static __u32 hash(const __u8 *data, __u32 datalen, __u32 initval) {
+ __u32 a, b;
+
+ a = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+ b = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
+
+ return jhash_2words(a, b, initval);
+}
+
+static __u32 sk_index_from_dcid(const quic_hd *qhd,
+ const struct sk_reuseport_md *reuse_md,
+ __u64 num_socks) {
+ __u32 len = qhd->dcidlen;
+ __u32 h = reuse_md->hash;
+ __u8 hbuf[8];
+
+ if (len > 16) {
+ __builtin_memset(hbuf, 0, sizeof(hbuf));
+
+ switch (len) {
+ case 20:
+ __builtin_memcpy(hbuf, qhd->dcid + 16, 4);
+ break;
+ case 19:
+ __builtin_memcpy(hbuf, qhd->dcid + 16, 3);
+ break;
+ case 18:
+ __builtin_memcpy(hbuf, qhd->dcid + 16, 2);
+ break;
+ case 17:
+ __builtin_memcpy(hbuf, qhd->dcid + 16, 1);
+ break;
+ }
+
+ h = hash(hbuf, sizeof(hbuf), h);
+ len = 16;
+ }
+
+ if (len > 8) {
+ __builtin_memset(hbuf, 0, sizeof(hbuf));
+
+ switch (len) {
+ case 16:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 8);
+ break;
+ case 15:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 7);
+ break;
+ case 14:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 6);
+ break;
+ case 13:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 5);
+ break;
+ case 12:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 4);
+ break;
+ case 11:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 3);
+ break;
+ case 10:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 2);
+ break;
+ case 9:
+ __builtin_memcpy(hbuf, qhd->dcid + 8, 1);
+ break;
+ }
+
+ h = hash(hbuf, sizeof(hbuf), h);
+ len = 8;
+ }
+
+ return hash(qhd->dcid, len, h) % num_socks;
+}
+
+SEC("sk_reuseport")
+int select_reuseport(struct sk_reuseport_md *reuse_md) {
+ __u32 sk_index, *psk_index;
+ __u64 *pnum_socks, *pkey;
+ __u32 zero = 0, key_high_idx = 1, key_low_idx = 2;
+ int rv;
+ quic_hd qhd;
+ __u8 qpktbuf[6 + MAX_DCIDLEN];
+ struct AES_ctx aes_ctx;
+ __u8 key[AES_KEYLEN];
+ __u8 *cid_prefix;
+
+ if (bpf_skb_load_bytes(reuse_md, sizeof(struct udphdr), qpktbuf,
+ sizeof(qpktbuf)) != 0) {
+ return SK_DROP;
+ }
+
+ pnum_socks = bpf_map_lookup_elem(&sk_info, &zero);
+ if (pnum_socks == NULL) {
+ return SK_DROP;
+ }
+
+ pkey = bpf_map_lookup_elem(&sk_info, &key_high_idx);
+ if (pkey == NULL) {
+ return SK_DROP;
+ }
+
+ __builtin_memcpy(key, pkey, sizeof(*pkey));
+
+ pkey = bpf_map_lookup_elem(&sk_info, &key_low_idx);
+ if (pkey == NULL) {
+ return SK_DROP;
+ }
+
+ __builtin_memcpy(key + sizeof(*pkey), pkey, sizeof(*pkey));
+
+ rv = parse_quic(&qhd, qpktbuf, qpktbuf + sizeof(qpktbuf));
+ if (rv != 0) {
+ return SK_DROP;
+ }
+
+ AES_init_ctx(&aes_ctx, key);
+
+ switch (qhd.type) {
+ case NGTCP2_PKT_INITIAL:
+ case NGTCP2_PKT_0RTT:
+ if (qhd.dcidlen == SV_DCIDLEN) {
+ cid_prefix = qhd.dcid + CID_PREFIX_OFFSET;
+ AES_ECB_decrypt(&aes_ctx, cid_prefix);
+
+ psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix);
+ if (psk_index != NULL) {
+ sk_index = *psk_index;
+
+ break;
+ }
+ }
+
+ sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks);
+
+ break;
+ case NGTCP2_PKT_HANDSHAKE:
+ case NGTCP2_PKT_SHORT:
+ if (qhd.dcidlen != SV_DCIDLEN) {
+ return SK_DROP;
+ }
+
+ cid_prefix = qhd.dcid + CID_PREFIX_OFFSET;
+ AES_ECB_decrypt(&aes_ctx, cid_prefix);
+
+ psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix);
+ if (psk_index == NULL) {
+ sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks);
+
+ break;
+ }
+
+ sk_index = *psk_index;
+
+ break;
+ default:
+ return SK_DROP;
+ }
+
+ rv = bpf_sk_select_reuseport(reuse_md, &reuseport_array, &sk_index, 0);
+ if (rv != 0) {
+ return SK_DROP;
+ }
+
+ return SK_PASS;
+}
diff --git a/cmake/ExtractValidFlags.cmake b/cmake/ExtractValidFlags.cmake
new file mode 100644
index 0000000..ccd57dc
--- /dev/null
+++ b/cmake/ExtractValidFlags.cmake
@@ -0,0 +1,31 @@
+# Convenience function that checks the availability of certain
+# C or C++ compiler flags and returns valid ones as a string.
+
+include(CheckCCompilerFlag)
+include(CheckCXXCompilerFlag)
+
+function(extract_valid_c_flags varname)
+ set(valid_flags)
+ foreach(flag IN LISTS ARGN)
+ string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag})
+ set(flag_var "C_FLAG_${flag_var}")
+ check_c_compiler_flag("${flag}" "${flag_var}")
+ if(${flag_var})
+ set(valid_flags "${valid_flags} ${flag}")
+ endif()
+ endforeach()
+ set(${varname} "${valid_flags}" PARENT_SCOPE)
+endfunction()
+
+function(extract_valid_cxx_flags varname)
+ set(valid_flags)
+ foreach(flag IN LISTS ARGN)
+ string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag})
+ set(flag_var "CXX_FLAG_${flag_var}")
+ check_cxx_compiler_flag("${flag}" "${flag_var}")
+ if(${flag_var})
+ set(valid_flags "${valid_flags} ${flag}")
+ endif()
+ endforeach()
+ set(${varname} "${valid_flags}" PARENT_SCOPE)
+endfunction()
diff --git a/cmake/FindCUnit.cmake b/cmake/FindCUnit.cmake
new file mode 100644
index 0000000..ada87c1
--- /dev/null
+++ b/cmake/FindCUnit.cmake
@@ -0,0 +1,40 @@
+# - Try to find cunit
+# Once done this will define
+# CUNIT_FOUND - System has cunit
+# CUNIT_INCLUDE_DIRS - The cunit include directories
+# CUNIT_LIBRARIES - The libraries needed to use cunit
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_CUNIT QUIET cunit)
+
+find_path(CUNIT_INCLUDE_DIR
+ NAMES CUnit/CUnit.h
+ HINTS ${PC_CUNIT_INCLUDE_DIRS}
+)
+find_library(CUNIT_LIBRARY
+ NAMES cunit
+ HINTS ${PC_CUNIT_LIBRARY_DIRS}
+)
+
+if(CUNIT_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+CU_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${CUNIT_INCLUDE_DIR}/CUnit/CUnit.h"
+ CUNIT_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ CUNIT_VERSION "${CUNIT_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set CUNIT_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(CUnit REQUIRED_VARS
+ CUNIT_LIBRARY CUNIT_INCLUDE_DIR
+ VERSION_VAR CUNIT_VERSION)
+
+if(CUNIT_FOUND)
+ set(CUNIT_LIBRARIES ${CUNIT_LIBRARY})
+ set(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(CUNIT_INCLUDE_DIR CUNIT_LIBRARY)
diff --git a/cmake/FindJansson.cmake b/cmake/FindJansson.cmake
new file mode 100644
index 0000000..4c4bcb7
--- /dev/null
+++ b/cmake/FindJansson.cmake
@@ -0,0 +1,40 @@
+# - Try to find jansson
+# Once done this will define
+# JANSSON_FOUND - System has jansson
+# JANSSON_INCLUDE_DIRS - The jansson include directories
+# JANSSON_LIBRARIES - The libraries needed to use jansson
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_JANSSON QUIET jansson)
+
+find_path(JANSSON_INCLUDE_DIR
+ NAMES jansson.h
+ HINTS ${PC_JANSSON_INCLUDE_DIRS}
+)
+find_library(JANSSON_LIBRARY
+ NAMES jansson
+ HINTS ${PC_JANSSON_LIBRARY_DIRS}
+)
+
+if(JANSSON_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+JANSSON_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${JANSSON_INCLUDE_DIR}/jansson.h"
+ JANSSON_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ JANSSON_VERSION "${JANSSON_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set JANSSON_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(Jansson REQUIRED_VARS
+ JANSSON_LIBRARY JANSSON_INCLUDE_DIR
+ VERSION_VAR JANSSON_VERSION)
+
+if(JANSSON_FOUND)
+ set(JANSSON_LIBRARIES ${JANSSON_LIBRARY})
+ set(JANSSON_INCLUDE_DIRS ${JANSSON_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(JANSSON_INCLUDE_DIR JANSSON_LIBRARY)
diff --git a/cmake/FindJemalloc.cmake b/cmake/FindJemalloc.cmake
new file mode 100644
index 0000000..b7815fa
--- /dev/null
+++ b/cmake/FindJemalloc.cmake
@@ -0,0 +1,40 @@
+# - Try to find jemalloc
+# Once done this will define
+# JEMALLOC_FOUND - System has jemalloc
+# JEMALLOC_INCLUDE_DIRS - The jemalloc include directories
+# JEMALLOC_LIBRARIES - The libraries needed to use jemalloc
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_JEMALLOC QUIET jemalloc)
+
+find_path(JEMALLOC_INCLUDE_DIR
+ NAMES jemalloc/jemalloc.h
+ HINTS ${PC_JEMALLOC_INCLUDE_DIRS}
+)
+find_library(JEMALLOC_LIBRARY
+ NAMES jemalloc
+ HINTS ${PC_JEMALLOC_LIBRARY_DIRS}
+)
+
+if(JEMALLOC_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+JEMALLOC_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${JEMALLOC_INCLUDE_DIR}/jemalloc/jemalloc.h"
+ JEMALLOC_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ JEMALLOC_VERSION "${JEMALLOC_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(Jemalloc REQUIRED_VARS
+ JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR
+ VERSION_VAR JEMALLOC_VERSION)
+
+if(JEMALLOC_FOUND)
+ set(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY})
+ set(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY)
diff --git a/cmake/FindLibbpf.cmake b/cmake/FindLibbpf.cmake
new file mode 100644
index 0000000..7f76255
--- /dev/null
+++ b/cmake/FindLibbpf.cmake
@@ -0,0 +1,32 @@
+# - Try to find libbpf
+# Once done this will define
+# LIBBPF_FOUND - System has libbpf
+# LIBBPF_INCLUDE_DIRS - The libbpf include directories
+# LIBBPF_LIBRARIES - The libraries needed to use libbpf
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBBPF QUIET libbpf)
+
+find_path(LIBBPF_INCLUDE_DIR
+ NAMES bpf/bpf.h
+ HINTS ${PC_LIBBPF_INCLUDE_DIRS}
+)
+find_library(LIBBPF_LIBRARY
+ NAMES bpf
+ HINTS ${PC_LIBBPF_LIBRARY_DIRS}
+)
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBBPF_FOUND
+# to TRUE if all listed variables are TRUE and the requested version
+# matches.
+find_package_handle_standard_args(Libbpf REQUIRED_VARS
+ LIBBPF_LIBRARY LIBBPF_INCLUDE_DIR
+ VERSION_VAR LIBBPF_VERSION)
+
+if(LIBBPF_FOUND)
+ set(LIBBPF_LIBRARIES ${LIBBPF_LIBRARY})
+ set(LIBBPF_INCLUDE_DIRS ${LIBBPF_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBBPF_INCLUDE_DIR LIBBPF_LIBRARY)
diff --git a/cmake/FindLibcares.cmake b/cmake/FindLibcares.cmake
new file mode 100644
index 0000000..1fe56ce
--- /dev/null
+++ b/cmake/FindLibcares.cmake
@@ -0,0 +1,40 @@
+# - Try to find libcares
+# Once done this will define
+# LIBCARES_FOUND - System has libcares
+# LIBCARES_INCLUDE_DIRS - The libcares include directories
+# LIBCARES_LIBRARIES - The libraries needed to use libcares
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBCARES QUIET libcares)
+
+find_path(LIBCARES_INCLUDE_DIR
+ NAMES ares.h
+ HINTS ${PC_LIBCARES_INCLUDE_DIRS}
+)
+find_library(LIBCARES_LIBRARY
+ NAMES cares
+ HINTS ${PC_LIBCARES_LIBRARY_DIRS}
+)
+
+if(LIBCARES_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+ARES_VERSION_STR[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${LIBCARES_INCLUDE_DIR}/ares_version.h"
+ LIBCARES_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ LIBCARES_VERSION "${LIBCARES_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBCARES_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(Libcares REQUIRED_VARS
+ LIBCARES_LIBRARY LIBCARES_INCLUDE_DIR
+ VERSION_VAR LIBCARES_VERSION)
+
+if(LIBCARES_FOUND)
+ set(LIBCARES_LIBRARIES ${LIBCARES_LIBRARY})
+ set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBCARES_INCLUDE_DIR LIBCARES_LIBRARY)
diff --git a/cmake/FindLibev.cmake b/cmake/FindLibev.cmake
new file mode 100644
index 0000000..71e4508
--- /dev/null
+++ b/cmake/FindLibev.cmake
@@ -0,0 +1,38 @@
+# - Try to find libev
+# Once done this will define
+# LIBEV_FOUND - System has libev
+# LIBEV_INCLUDE_DIRS - The libev include directories
+# LIBEV_LIBRARIES - The libraries needed to use libev
+
+find_path(LIBEV_INCLUDE_DIR
+ NAMES ev.h
+)
+find_library(LIBEV_LIBRARY
+ NAMES ev
+)
+
+if(LIBEV_INCLUDE_DIR)
+ file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h"
+ LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+")
+ file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h"
+ LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+")
+ string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}")
+ string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}")
+ set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}")
+ unset(LIBEV_VERSION_MINOR)
+ unset(LIBEV_VERSION_MAJOR)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(Libev REQUIRED_VARS
+ LIBEV_LIBRARY LIBEV_INCLUDE_DIR
+ VERSION_VAR LIBEV_VERSION)
+
+if(LIBEV_FOUND)
+ set(LIBEV_LIBRARIES ${LIBEV_LIBRARY})
+ set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY)
diff --git a/cmake/FindLibevent.cmake b/cmake/FindLibevent.cmake
new file mode 100644
index 0000000..e8a3cef
--- /dev/null
+++ b/cmake/FindLibevent.cmake
@@ -0,0 +1,97 @@
+# - Try to find libevent
+#.rst
+# FindLibevent
+# ------------
+#
+# Find Libevent include directories and libraries. Invoke as::
+#
+# find_package(Libevent
+# [version] [EXACT] # Minimum or exact version
+# [REQUIRED] # Fail if Libevent is not found
+# [COMPONENT <C>...]) # Libraries to look for
+#
+# Valid components are one or more of:: libevent core extra pthreads openssl.
+# Note that 'libevent' contains both core and extra. You must specify one of
+# them for the other components.
+#
+# This module will define the following variables::
+#
+# LIBEVENT_FOUND - True if headers and requested libraries were found
+# LIBEVENT_INCLUDE_DIRS - Libevent include directories
+# LIBEVENT_LIBRARIES - Libevent libraries to be linked
+# LIBEVENT_<C>_FOUND - Component <C> was found (<C> is uppercase)
+# LIBEVENT_<C>_LIBRARY - Library to be linked for Libevent component <C>.
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBEVENT QUIET libevent)
+
+# Look for the Libevent 2.0 or 1.4 headers
+find_path(LIBEVENT_INCLUDE_DIR
+ NAMES
+ event2/event-config.h
+ event-config.h
+ HINTS
+ ${PC_LIBEVENT_INCLUDE_DIRS}
+)
+
+if(LIBEVENT_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+_EVENT_VERSION[ \t]+\"([^\"]+)\".*")
+ if(EXISTS "${LIBEVENT_INCLUDE_DIR}/event2/event-config.h")
+ # Libevent 2.0
+ file(STRINGS "${LIBEVENT_INCLUDE_DIR}/event2/event-config.h"
+ LIBEVENT_VERSION REGEX "${_version_regex}")
+ if("${LIBEVENT_VERSION}" STREQUAL "")
+ set(LIBEVENT_VERSION ${PC_LIBEVENT_VERSION})
+ endif()
+ else()
+ # Libevent 1.4
+ file(STRINGS "${LIBEVENT_INCLUDE_DIR}/event-config.h"
+ LIBEVENT_VERSION REGEX "${_version_regex}")
+ endif()
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ LIBEVENT_VERSION "${LIBEVENT_VERSION}")
+ unset(_version_regex)
+endif()
+
+set(_LIBEVENT_REQUIRED_VARS)
+foreach(COMPONENT ${Libevent_FIND_COMPONENTS})
+ set(_LIBEVENT_LIBNAME libevent)
+ # Note: compare two variables to avoid a CMP0054 policy warning
+ if(COMPONENT STREQUAL _LIBEVENT_LIBNAME)
+ set(_LIBEVENT_LIBNAME event)
+ else()
+ set(_LIBEVENT_LIBNAME "event_${COMPONENT}")
+ endif()
+ string(TOUPPER "${COMPONENT}" COMPONENT_UPPER)
+ find_library(LIBEVENT_${COMPONENT_UPPER}_LIBRARY
+ NAMES ${_LIBEVENT_LIBNAME}
+ HINTS ${PC_LIBEVENT_LIBRARY_DIRS}
+ )
+ if(LIBEVENT_${COMPONENT_UPPER}_LIBRARY)
+ set(Libevent_${COMPONENT}_FOUND 1)
+ endif()
+ list(APPEND _LIBEVENT_REQUIRED_VARS LIBEVENT_${COMPONENT_UPPER}_LIBRARY)
+endforeach()
+unset(_LIBEVENT_LIBNAME)
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBEVENT_FOUND to TRUE
+# if all listed variables are TRUE and the requested version matches.
+find_package_handle_standard_args(Libevent REQUIRED_VARS
+ ${_LIBEVENT_REQUIRED_VARS}
+ LIBEVENT_INCLUDE_DIR
+ VERSION_VAR LIBEVENT_VERSION
+ HANDLE_COMPONENTS)
+
+if(LIBEVENT_FOUND)
+ set(LIBEVENT_INCLUDE_DIRS ${LIBEVENT_INCLUDE_DIR})
+ set(LIBEVENT_LIBRARIES)
+ foreach(COMPONENT ${Libevent_FIND_COMPONENTS})
+ string(TOUPPER "${COMPONENT}" COMPONENT_UPPER)
+ list(APPEND LIBEVENT_LIBRARIES ${LIBEVENT_${COMPONENT_UPPER}_LIBRARY})
+ set(LIBEVENT_${COMPONENT_UPPER}_FOUND ${Libevent_${COMPONENT}_FOUND})
+ endforeach()
+endif()
+
+mark_as_advanced(LIBEVENT_INCLUDE_DIR ${_LIBEVENT_REQUIRED_VARS})
+unset(_LIBEVENT_REQUIRED_VARS)
diff --git a/cmake/FindLibnghttp3.cmake b/cmake/FindLibnghttp3.cmake
new file mode 100644
index 0000000..ecd01f6
--- /dev/null
+++ b/cmake/FindLibnghttp3.cmake
@@ -0,0 +1,41 @@
+# - Try to find libnghttp3
+# Once done this will define
+# LIBNGHTTP3_FOUND - System has libnghttp3
+# LIBNGHTTP3_INCLUDE_DIRS - The libnghttp3 include directories
+# LIBNGHTTP3_LIBRARIES - The libraries needed to use libnghttp3
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBNGHTTP3 QUIET libnghttp3)
+
+find_path(LIBNGHTTP3_INCLUDE_DIR
+ NAMES nghttp3/nghttp3.h
+ HINTS ${PC_LIBNGHTTP3_INCLUDE_DIRS}
+)
+find_library(LIBNGHTTP3_LIBRARY
+ NAMES nghttp3
+ HINTS ${PC_LIBNGHTTP3_LIBRARY_DIRS}
+)
+
+if(LIBNGHTTP3_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+NGHTTP3_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${LIBNGHTTP3_INCLUDE_DIR}/nghttp3/version.h"
+ LIBNGHTTP3_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ LIBNGHTTP3_VERSION "${LIBNGHTTP3_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBNGHTTP3_FOUND
+# to TRUE if all listed variables are TRUE and the requested version
+# matches.
+find_package_handle_standard_args(Libnghttp3 REQUIRED_VARS
+ LIBNGHTTP3_LIBRARY LIBNGHTTP3_INCLUDE_DIR
+ VERSION_VAR LIBNGHTTP3_VERSION)
+
+if(LIBNGHTTP3_FOUND)
+ set(LIBNGHTTP3_LIBRARIES ${LIBNGHTTP3_LIBRARY})
+ set(LIBNGHTTP3_INCLUDE_DIRS ${LIBNGHTTP3_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBNGHTTP3_INCLUDE_DIR LIBNGHTTP3_LIBRARY)
diff --git a/cmake/FindLibngtcp2.cmake b/cmake/FindLibngtcp2.cmake
new file mode 100644
index 0000000..c670114
--- /dev/null
+++ b/cmake/FindLibngtcp2.cmake
@@ -0,0 +1,41 @@
+# - Try to find libngtcp2
+# Once done this will define
+# LIBNGTCP2_FOUND - System has libngtcp2
+# LIBNGTCP2_INCLUDE_DIRS - The libngtcp2 include directories
+# LIBNGTCP2_LIBRARIES - The libraries needed to use libngtcp2
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBNGTCP2 QUIET libngtcp2)
+
+find_path(LIBNGTCP2_INCLUDE_DIR
+ NAMES ngtcp2/ngtcp2.h
+ HINTS ${PC_LIBNGTCP2_INCLUDE_DIRS}
+)
+find_library(LIBNGTCP2_LIBRARY
+ NAMES ngtcp2
+ HINTS ${PC_LIBNGTCP2_LIBRARY_DIRS}
+)
+
+if(LIBNGTCP2_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${LIBNGTCP2_INCLUDE_DIR}/ngtcp2/version.h"
+ LIBNGTCP2_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ LIBNGTCP2_VERSION "${LIBNGTCP2_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBNGTCP2_FOUND
+# to TRUE if all listed variables are TRUE and the requested version
+# matches.
+find_package_handle_standard_args(Libngtcp2 REQUIRED_VARS
+ LIBNGTCP2_LIBRARY LIBNGTCP2_INCLUDE_DIR
+ VERSION_VAR LIBNGTCP2_VERSION)
+
+if(LIBNGTCP2_FOUND)
+ set(LIBNGTCP2_LIBRARIES ${LIBNGTCP2_LIBRARY})
+ set(LIBNGTCP2_INCLUDE_DIRS ${LIBNGTCP2_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBNGTCP2_INCLUDE_DIR LIBNGTCP2_LIBRARY)
diff --git a/cmake/FindLibngtcp2_crypto_quictls.cmake b/cmake/FindLibngtcp2_crypto_quictls.cmake
new file mode 100644
index 0000000..3d55b63
--- /dev/null
+++ b/cmake/FindLibngtcp2_crypto_quictls.cmake
@@ -0,0 +1,43 @@
+# - Try to find libngtcp2_crypto_quictls
+# Once done this will define
+# LIBNGTCP2_CRYPTO_QUICTLS_FOUND - System has libngtcp2_crypto_quictls
+# LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS - The libngtcp2_crypto_quictls include directories
+# LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES - The libraries needed to use libngtcp2_crypto_quictls
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_LIBNGTCP2_CRYPTO_QUICTLS QUIET libngtcp2_crypto_quictls)
+
+find_path(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR
+ NAMES ngtcp2/ngtcp2_crypto_quictls.h
+ HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS}
+)
+find_library(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY
+ NAMES ngtcp2_crypto_quictls
+ HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY_DIRS}
+)
+
+if(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR)
+ set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*")
+ file(STRINGS "${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR}/ngtcp2/version.h"
+ LIBNGTCP2_CRYPTO_QUICTLS_VERSION REGEX "${_version_regex}")
+ string(REGEX REPLACE "${_version_regex}" "\\1"
+ LIBNGTCP2_CRYPTO_QUICTLS_VERSION "${LIBNGTCP2_CRYPTO_QUICTLS_VERSION}")
+ unset(_version_regex)
+endif()
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set
+# LIBNGTCP2_CRYPTO_QUICTLS_FOUND to TRUE if all listed variables are
+# TRUE and the requested version matches.
+find_package_handle_standard_args(Libngtcp2_crypto_quictls REQUIRED_VARS
+ LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY
+ LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR
+ VERSION_VAR LIBNGTCP2_CRYPTO_QUICTLS_VERSION)
+
+if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND)
+ set(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY})
+ set(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR
+ LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY)
diff --git a/cmake/FindSystemd.cmake b/cmake/FindSystemd.cmake
new file mode 100644
index 0000000..e7534e5
--- /dev/null
+++ b/cmake/FindSystemd.cmake
@@ -0,0 +1,19 @@
+# - Try to find systemd
+# Once done this will define
+# SYSTEMD_FOUND - System has systemd
+# SYSTEMD_INCLUDE_DIRS - The systemd include directories
+# SYSTEMD_LIBRARIES - The libraries needed to use systemd
+
+include(FeatureSummary)
+set_package_properties(Systemd PROPERTIES
+ URL "http://freedesktop.org/wiki/Software/systemd/"
+ DESCRIPTION "System and Service Manager")
+
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_SYSTEMD QUIET libsystemd)
+find_library(SYSTEMD_LIBRARIES NAMES systemd ${PC_SYSTEMD_LIBRARY_DIRS})
+find_path(SYSTEMD_INCLUDE_DIRS systemd/sd-login.h HINTS ${PC_SYSTEMD_INCLUDE_DIRS})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Systemd DEFAULT_MSG SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES)
+mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES)
diff --git a/cmake/PickyWarningsC.cmake b/cmake/PickyWarningsC.cmake
new file mode 100644
index 0000000..50eb789
--- /dev/null
+++ b/cmake/PickyWarningsC.cmake
@@ -0,0 +1,163 @@
+# nghttp2
+#
+# Copyright (c) 2023 nghttp2 contributors
+#
+# 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.
+
+# C
+
+include(CheckCCompilerFlag)
+
+if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+
+ # https://clang.llvm.org/docs/DiagnosticsReference.html
+ # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+
+ # WPICKY_ENABLE = Options we want to enable as-is.
+ # WPICKY_DETECT = Options we want to test first and enable if available.
+
+ # Prefer the -Wextra alias with clang.
+ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ set(WPICKY_ENABLE "-Wextra")
+ else()
+ set(WPICKY_ENABLE "-W")
+ endif()
+
+ list(APPEND WPICKY_ENABLE
+ -Wall
+ )
+
+ # ----------------------------------
+ # Add new options here, if in doubt:
+ # ----------------------------------
+ set(WPICKY_DETECT
+ )
+
+ # Assume these options always exist with both clang and gcc.
+ # Require clang 3.0 / gcc 2.95 or later.
+ list(APPEND WPICKY_ENABLE
+ -Wconversion # clang 3.0 gcc 2.95
+ -Winline # clang 1.0 gcc 1.0
+ -Wmissing-declarations # clang 1.0 gcc 2.7
+ -Wmissing-prototypes # clang 1.0 gcc 1.0
+ -Wnested-externs # clang 1.0 gcc 2.7
+ -Wpointer-arith # clang 1.0 gcc 1.4
+ -Wshadow # clang 1.0 gcc 2.95
+ -Wundef # clang 1.0 gcc 2.95
+ -Wwrite-strings # clang 1.0 gcc 1.4
+ )
+
+ # Always enable with clang, version dependent with gcc
+ set(WPICKY_COMMON_OLD
+ -Waddress # clang 3.0 gcc 4.3
+ -Wattributes # clang 3.0 gcc 4.1
+ -Wcast-align # clang 1.0 gcc 4.2
+ -Wdeclaration-after-statement # clang 1.0 gcc 3.4
+ -Wdiv-by-zero # clang 3.0 gcc 4.1
+ -Wempty-body # clang 3.0 gcc 4.3
+ -Wendif-labels # clang 1.0 gcc 3.3
+ -Wfloat-equal # clang 1.0 gcc 2.96 (3.0)
+ -Wformat-nonliteral # clang 3.0 gcc 4.1
+ -Wformat-security # clang 3.0 gcc 4.1
+ -Wmissing-field-initializers # clang 3.0 gcc 4.1
+ -Wmissing-noreturn # clang 3.0 gcc 4.1
+ -Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0) # This is required because we pass format string as "const char*"
+ # -Wpadded # clang 3.0 gcc 4.1 # Not used because we cannot change public structs
+ -Wredundant-decls # clang 3.0 gcc 4.1
+ -Wsign-conversion # clang 3.0 gcc 4.3
+ -Wstrict-prototypes # clang 1.0 gcc 3.3
+ # -Wswitch-enum # clang 3.0 gcc 4.1 # Not used because this basically disallows default case
+ -Wunreachable-code # clang 3.0 gcc 4.1
+ -Wunused-macros # clang 3.0 gcc 4.1
+ -Wunused-parameter # clang 3.0 gcc 4.1
+ -Wvla # clang 2.8 gcc 4.3
+ )
+
+ set(WPICKY_COMMON
+ -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0
+ )
+
+ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON_OLD}
+ -Wshorten-64-to-32 # clang 1.0
+ -Wlanguage-extension-token # clang 3.0
+ )
+ # Enable based on compiler version
+ if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR
+ (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3))
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON}
+ -Wunreachable-code-break # clang 3.5 appleclang 6.0
+ -Wheader-guard # clang 3.4 appleclang 5.1
+ )
+ endif()
+ if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR
+ (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3))
+ list(APPEND WPICKY_ENABLE
+ -Wmissing-variable-declarations # clang 3.2 appleclang 4.6
+ )
+ endif()
+ if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) OR
+ (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 9.4))
+ list(APPEND WPICKY_ENABLE
+ )
+ endif()
+ if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR
+ (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3))
+ list(APPEND WPICKY_ENABLE
+ )
+ endif()
+ else() # gcc
+ list(APPEND WPICKY_DETECT
+ ${WPICKY_COMMON}
+ )
+ # Enable based on compiler version
+ if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3)
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON_OLD}
+ -Wclobbered # gcc 4.3
+ )
+ endif()
+ endif()
+
+ #
+
+ unset(_wpicky)
+
+ foreach(_CCOPT IN LISTS WPICKY_ENABLE)
+ set(_wpicky "${_wpicky} ${_CCOPT}")
+ endforeach()
+
+ foreach(_CCOPT IN LISTS WPICKY_DETECT)
+ # surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new
+ # test result in.
+ string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname)
+ # GCC only warns about unknown -Wno- options if there are also other diagnostic messages,
+ # so test for the positive form instead
+ string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}")
+ check_c_compiler_flag(${_CCOPT_ON} ${_optvarname})
+ if(${_optvarname})
+ set(_wpicky "${_wpicky} ${_CCOPT}")
+ endif()
+ endforeach()
+
+ set(WARNCFLAGS "${WARNCFLAGS} ${_wpicky}")
+endif()
diff --git a/cmake/PickyWarningsCXX.cmake b/cmake/PickyWarningsCXX.cmake
new file mode 100644
index 0000000..4699733
--- /dev/null
+++ b/cmake/PickyWarningsCXX.cmake
@@ -0,0 +1,117 @@
+# nghttp2
+#
+# Copyright (c) 2023 nghttp2 contributors
+#
+# 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.
+
+# C++
+
+include(CheckCXXCompilerFlag)
+
+if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+
+ # https://clang.llvm.org/docs/DiagnosticsReference.html
+ # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+
+ # WPICKY_ENABLE = Options we want to enable as-is.
+ # WPICKY_DETECT = Options we want to test first and enable if available.
+
+ set(WPICKY_ENABLE "-Wall")
+
+ # ----------------------------------
+ # Add new options here, if in doubt:
+ # ----------------------------------
+ set(WPICKY_DETECT
+ )
+
+ # Assume these options always exist with both clang and gcc.
+ # Require clang 3.0 / gcc 2.95 or later.
+ list(APPEND WPICKY_ENABLE
+ )
+
+ # Always enable with clang, version dependent with gcc
+ set(WPICKY_COMMON_OLD
+ -Wformat-security # clang 3.0 gcc 4.1
+ )
+
+ set(WPICKY_COMMON
+ )
+
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON_OLD}
+ )
+ # Enable based on compiler version
+ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6) OR
+ (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.3))
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON}
+ )
+ endif()
+ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.9) OR
+ (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.3))
+ list(APPEND WPICKY_ENABLE
+ )
+ endif()
+ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) OR
+ (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.4))
+ list(APPEND WPICKY_ENABLE
+ )
+ endif()
+ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) OR
+ (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.3))
+ list(APPEND WPICKY_ENABLE
+ )
+ endif()
+ else() # gcc
+ list(APPEND WPICKY_DETECT
+ ${WPICKY_COMMON}
+ )
+ # Enable based on compiler version
+ if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.3)
+ list(APPEND WPICKY_ENABLE
+ ${WPICKY_COMMON_OLD}
+ )
+ endif()
+ endif()
+
+ #
+
+ unset(_wpicky)
+
+ foreach(_CCOPT IN LISTS WPICKY_ENABLE)
+ set(_wpicky "${_wpicky} ${_CCOPT}")
+ endforeach()
+
+ foreach(_CCOPT IN LISTS WPICKY_DETECT)
+ # surprisingly, CHECK_CXX_COMPILER_FLAG needs a new variable to store each new
+ # test result in.
+ string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname)
+ # GCC only warns about unknown -Wno- options if there are also other diagnostic messages,
+ # so test for the positive form instead
+ string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}")
+ check_cxx_compiler_flag(${_CCOPT_ON} ${_optvarname})
+ if(${_optvarname})
+ set(_wpicky "${_wpicky} ${_CCOPT}")
+ endif()
+ endforeach()
+
+ set(WARNCXXFLAGS "${WARNCXXFLAGS} ${_wpicky}")
+endif()
diff --git a/cmake/Version.cmake b/cmake/Version.cmake
new file mode 100644
index 0000000..8ac4849
--- /dev/null
+++ b/cmake/Version.cmake
@@ -0,0 +1,11 @@
+# Converts a version such as 1.2.255 to 0x0102ff
+function(HexVersion version_hex_var major minor patch)
+ math(EXPR version_dec "${major} * 256 * 256 + ${minor} * 256 + ${patch}")
+ set(version_hex "0x")
+ foreach(i RANGE 5 0 -1)
+ math(EXPR num "(${version_dec} >> (4 * ${i})) & 15")
+ string(SUBSTRING "0123456789abcdef" ${num} 1 num_hex)
+ set(version_hex "${version_hex}${num_hex}")
+ endforeach()
+ set(${version_hex_var} "${version_hex}" PARENT_SCOPE)
+endfunction()
diff --git a/cmakeconfig.h.in b/cmakeconfig.h.in
new file mode 100644
index 0000000..a011d00
--- /dev/null
+++ b/cmakeconfig.h.in
@@ -0,0 +1,101 @@
+/* Hint to the compiler that a function never returns */
+#define NGHTTP2_NORETURN @HINT_NORETURN@
+
+/* Define to `int' if <sys/types.h> does not define. */
+#cmakedefine ssize_t @ssize_t@
+
+/* Define to 1 if you have the `std::map::emplace`. */
+#cmakedefine HAVE_STD_MAP_EMPLACE 1
+
+/* Define to 1 if you have `libjansson` library. */
+#cmakedefine HAVE_JANSSON 1
+
+/* Define to 1 if you have `libxml2` library. */
+#cmakedefine HAVE_LIBXML2 1
+
+/* Define to 1 if you have `mruby` library. */
+#cmakedefine HAVE_MRUBY 1
+
+/* Define to 1 if you have `neverbleed` library. */
+#cmakedefine HAVE_NEVERBLEED 1
+
+/* Define to 1 if you have the `_Exit` function. */
+#cmakedefine HAVE__EXIT 1
+
+/* Define to 1 if you have the `accept4` function. */
+#cmakedefine HAVE_ACCEPT4 1
+
+/* Define to 1 if you have the `clock_gettime` function. */
+#cmakedefine HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the `mkostemp` function. */
+#cmakedefine HAVE_MKOSTEMP 1
+
+/* Define to 1 if you have the `GetTickCount64` function. */
+#cmakedefine HAVE_GETTICKCOUNT64 1
+
+/* Define to 1 if you have the `initgroups` function. */
+#cmakedefine01 HAVE_DECL_INITGROUPS
+
+/* Define to 1 if you have the `CLOCK_MONOTONIC` defined. */
+#cmakedefine01 HAVE_DECL_CLOCK_MONOTONIC
+
+/* Define to 1 to enable debug output. */
+#cmakedefine DEBUGBUILD 1
+
+/* Define to 1 if you want to disable threads. */
+#cmakedefine NOTHREADS 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#cmakedefine HAVE_ARPA_INET_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#cmakedefine HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#cmakedefine HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#cmakedefine HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#cmakedefine HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#cmakedefine HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/ip.h> header file. */
+#cmakedefine HAVE_NETINET_IP_H 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#cmakedefine HAVE_PWD_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#cmakedefine HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#cmakedefine HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#cmakedefine HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#cmakedefine HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <windows.h> header file. */
+#cmakedefine HAVE_WINDOWS_H 1
+
+/* Define to 1 if HTTP/3 is enabled. */
+#cmakedefine ENABLE_HTTP3 1
+
+/* Define to 1 if you have `libbpf` library. */
+#cmakedefine HAVE_LIBBPF 1
+
+/* Define to 1 if you have enum bpf_stats_type in linux/bpf.h. */
+#cmakedefine HAVE_BPF_STATS_TYPE 1
+
+/* Define to 1 if you have `libngtcp2_crypto_quictls` library. */
+#cmakedefine HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+
+/* Define to 1 if you have `libev` library. */
+#cmakedefine HAVE_LIBEV 1
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..defc113
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,1185 @@
+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 https://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
+
+AC_PREREQ(2.61)
+AC_INIT([nghttp2], [1.59.0], [t-tujikawa@users.sourceforge.net])
+AC_CONFIG_AUX_DIR([.])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS([config.h])
+AC_USE_SYSTEM_EXTENSIONS
+
+LT_PREREQ([2.2.6])
+LT_INIT()
+
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE([subdir-objects])
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl See versioning rule:
+dnl https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+AC_SUBST(LT_CURRENT, 40)
+AC_SUBST(LT_REVISION, 0)
+AC_SUBST(LT_AGE, 26)
+
+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)
+
+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, nghttpx and h2load) [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([examples],
+ [AS_HELP_STRING([--enable-examples],
+ [Build examples [default=check]])],
+ [request_examples=$enableval], [request_examples=check])
+
+AC_ARG_ENABLE([failmalloc],
+ [AS_HELP_STRING([--disable-failmalloc],
+ [Do not build failmalloc test program])],
+ [request_failmalloc=$enableval], [request_failmalloc=yes])
+
+AC_ARG_ENABLE([lib-only],
+ [AS_HELP_STRING([--enable-lib-only],
+ [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools])],
+ [request_lib_only=$enableval], [request_lib_only=no])
+
+AC_ARG_ENABLE([http3],
+ [AS_HELP_STRING([--enable-http3],
+ [(EXPERIMENTAL) Enable HTTP/3. This requires ngtcp2, nghttp3, and a custom OpenSSL.])],
+ [request_http3=$enableval], [request_http3=no])
+
+AC_ARG_WITH([libxml2],
+ [AS_HELP_STRING([--with-libxml2],
+ [Use libxml2 [default=check]])],
+ [request_libxml2=$withval], [request_libxml2=check])
+
+AC_ARG_WITH([jansson],
+ [AS_HELP_STRING([--with-jansson],
+ [Use jansson [default=check]])],
+ [request_jansson=$withval], [request_jansson=check])
+
+AC_ARG_WITH([zlib],
+ [AS_HELP_STRING([--with-zlib],
+ [Use zlib [default=check]])],
+ [request_zlib=$withval], [request_zlib=check])
+
+AC_ARG_WITH([libevent-openssl],
+ [AS_HELP_STRING([--with-libevent-openssl],
+ [Use libevent_openssl [default=check]])],
+ [request_libevent_openssl=$withval], [request_libevent_openssl=check])
+
+AC_ARG_WITH([libcares],
+ [AS_HELP_STRING([--with-libcares],
+ [Use libc-ares [default=check]])],
+ [request_libcares=$withval], [request_libcares=check])
+
+AC_ARG_WITH([openssl],
+ [AS_HELP_STRING([--with-openssl],
+ [Use openssl [default=check]])],
+ [request_openssl=$withval], [request_openssl=check])
+
+AC_ARG_WITH([libev],
+ [AS_HELP_STRING([--with-libev],
+ [Use libev [default=check]])],
+ [request_libev=$withval], [request_libev=check])
+
+AC_ARG_WITH([cunit],
+ [AS_HELP_STRING([--with-cunit],
+ [Use cunit [default=check]])],
+ [request_cunit=$withval], [request_cunit=check])
+
+AC_ARG_WITH([jemalloc],
+ [AS_HELP_STRING([--with-jemalloc],
+ [Use jemalloc [default=check]])],
+ [request_jemalloc=$withval], [request_jemalloc=check])
+
+AC_ARG_WITH([systemd],
+ [AS_HELP_STRING([--with-systemd],
+ [Enable systemd support in nghttpx [default=check]])],
+ [request_systemd=$withval], [request_systemd=check])
+
+AC_ARG_WITH([mruby],
+ [AS_HELP_STRING([--with-mruby],
+ [Use mruby [default=no]])],
+ [request_mruby=$withval], [request_mruby=no])
+
+AC_ARG_WITH([neverbleed],
+ [AS_HELP_STRING([--with-neverbleed],
+ [Use neverbleed [default=no]])],
+ [request_neverbleed=$withval], [request_neverbleed=no])
+
+AC_ARG_WITH([libngtcp2],
+ [AS_HELP_STRING([--with-libngtcp2],
+ [Use libngtcp2 [default=check]])],
+ [request_libngtcp2=$withval], [request_libngtcp2=check])
+
+AC_ARG_WITH([libnghttp3],
+ [AS_HELP_STRING([--with-libnghttp3],
+ [Use libnghttp3 [default=check]])],
+ [request_libnghttp3=$withval], [request_libnghttp3=check])
+
+AC_ARG_WITH([libbpf],
+ [AS_HELP_STRING([--with-libbpf],
+ [Use libbpf [default=no]])],
+ [request_libbpf=$withval], [request_libbpf=no])
+
+dnl Define variables
+AC_ARG_VAR([LIBEV_CFLAGS], [C compiler flags for libev, skipping any checks])
+AC_ARG_VAR([LIBEV_LIBS], [linker flags for libev, skipping any checks])
+
+AC_ARG_VAR([JEMALLOC_CFLAGS],
+ [C compiler flags for jemalloc, skipping any checks])
+AC_ARG_VAR([JEMALLOC_LIBS], [linker flags for jemalloc, skipping any checks])
+
+AC_ARG_VAR([LIBTOOL_LDFLAGS],
+ [libtool specific flags (e.g., -static-libtool-libs)])
+
+AC_ARG_VAR([BPFCFLAGS], [C compiler flags for bpf program])
+
+dnl Checks for programs
+AC_PROG_CC
+AC_PROG_CXX
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AC_PROG_MKDIR_P
+
+PKG_PROG_PKG_CONFIG([0.20])
+
+AM_PATH_PYTHON([3.8],, [:])
+
+if [test "x$request_lib_only" = "xyes"]; then
+ request_app=no
+ request_hpack_tools=no
+ request_examples=no
+ request_http3=no
+ request_libxml2=no
+ request_jansson=no
+ request_zlib=no
+ request_libevent_openssl=no
+ request_libcares=no
+ request_openssl=no
+ request_libev=no
+ request_jemalloc=no
+ request_systemd=no
+ request_mruby=no
+ request_neverbleed=no
+ request_libngtcp2=no
+ request_libnghttp3=no
+ request_libbpf=no
+fi
+
+if test "x$GCC" = "xyes" -o "x$CC" = "xclang" ; then
+ AC_DEFINE([NGHTTP2_NORETURN], [__attribute__((noreturn))], [Hint to the compiler that a function never return])
+else
+ AC_DEFINE([NGHTTP2_NORETURN], , [Hint to the compiler that a function never return])
+fi
+
+save_CXXFLAGS="$CXXFLAGS"
+CXXFLAGS=
+
+AX_CXX_COMPILE_STDCXX([14], [], [optional])
+
+CXX1XCXXFLAGS="$CXXFLAGS"
+CXXFLAGS="$save_CXXFLAGS"
+AC_SUBST([CXX1XCXXFLAGS])
+
+AC_LANG_PUSH(C++)
+
+save_CXXFLAGS="$CXXFLAGS"
+CXXFLAGS="$CXXFLAGS $CXX1XCXXFLAGS"
+
+# 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;
+(void)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 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])])
+
+# Check that std::atomic_* overloads for std::shared_ptr are
+# available.
+AC_MSG_CHECKING([whether std::atomic_* overloads for std::shared_ptr are available])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <memory>
+]],
+[[
+auto a = std::make_shared<int>(1000000007);
+auto p = std::atomic_load(&a);
+++*p;
+std::atomic_store(&a, p);
+]])],
+ [AC_DEFINE([HAVE_ATOMIC_STD_SHARED_PTR], [1],
+ [Define to 1 if you have the std::atomic_* overloads for std::shared_ptr.])
+ have_atomic_std_shared_ptr=yes
+ AC_MSG_RESULT([yes])],
+ [have_atomic_std_shared_ptr=no
+ AC_MSG_RESULT([no])])
+
+# Check that thread_local storage specifier is available
+AC_MSG_CHECKING([whether thread_local storage class specifier is available.])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+,
+[[
+thread_local int a = 0;
+(void)a;
+]])],
+ [AC_DEFINE([HAVE_THREAD_LOCAL], [1],
+ [Define to 1 if you have thread_local storage specifier.])
+ have_thread_local=yes
+ AC_MSG_RESULT([yes])],
+ [have_Thread_local=no
+ AC_MSG_RESULT([no])])
+
+CXXFLAGS=$save_CXXFLAGS
+
+AC_LANG_POP()
+
+# Checks for libraries.
+
+# Additional libraries required for tests.
+TESTLDADD=
+
+# Additional libraries required for programs under src directory.
+APPLDFLAGS=
+
+case "$host_os" in
+ *android*)
+ android_build=yes
+ # android does not need -pthread, but needs following 2 libs for C++
+ APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic"
+ ;;
+ *)
+ PTHREAD_LDFLAGS="-pthread"
+ APPLDFLAGS="$APPLDFLAGS $PTHREAD_LDFLAGS"
+ ;;
+esac
+
+case "$host_os" in
+ *solaris*)
+ APPLDFLAGS="$APPLDFLAGS -lsocket -lnsl"
+ ;;
+esac
+
+case "${build}" in
+ *-apple-darwin*)
+ EXTRA_DEFS="-D__APPLE_USE_RFC_3542"
+ AC_SUBST([EXTRA_DEFS])
+ ;;
+esac
+
+# zlib
+have_zlib=no
+if test "x${request_zlib}" != "xno"; then
+ 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
+
+if test "x${request_zlib}" = "xyes" &&
+ test "x${have_zlib}" != "xyes"; then
+ AC_MSG_ERROR([zlib was requested (--with-zlib) but not found])
+fi
+
+# dl: openssl requires libdl when it is statically linked.
+case "${host_os}" in
+ *bsd*)
+ # dlopen is in libc on *BSD
+ ;;
+ *)
+ save_LIBS=$LIBS
+ AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
+ LIBS=$save_LIBS
+ ;;
+esac
+
+# cunit
+have_cunit=no
+if test "x${request_cunit}" != "xno"; then
+ 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
+fi
+
+if test "x${request_cunit}" = "xyes" &&
+ test "x${have_cunit}" != "xyes"; then
+ AC_MSG_ERROR([cunit was requested (--with-cunit) but not found])
+fi
+
+AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
+
+# libev (for src)
+have_libev=no
+if test "x${request_libev}" != "xno"; then
+ if test "x${LIBEV_LIBS}" = "x" && test "x${LIBEV_CFLAGS}" = "x"; then
+ # libev does not have pkg-config file. Check it in an old way.
+ save_LIBS=$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=
+ fi
+ fi
+ LIBS=$save_LIBS
+ else
+ have_libev=yes
+ fi
+
+ if test "x${have_libev}" = "xyes"; then
+ AC_DEFINE([HAVE_LIBEV], [1], [Define to 1 if you have `libev` library.])
+ fi
+fi
+
+if test "x${request_libev}" = "xyes" &&
+ test "x${have_libev}" != "xyes"; then
+ AC_MSG_ERROR([libev was requested (--with-libev) but not found])
+fi
+
+# openssl (for src)
+have_openssl=no
+if test "x${request_openssl}" != "xno"; then
+ PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.1.1],
+ [have_openssl=yes], [have_openssl=no])
+ if test "x${have_openssl}" = "xno"; then
+ AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
+ else
+ save_CFLAGS="$CFLAGS"
+ save_LIBS="$LIBS"
+ CFLAGS="$OPENSSL_CFLAGS $CFLAGS"
+ LIBS="$OPENSSL_LIBS $LIBS"
+
+ # quictls/openssl has SSL_provide_quic_data. boringssl also has
+ # it. We will deal with it later.
+ have_ssl_provide_quic_data=no
+ AC_MSG_CHECKING([for SSL_provide_quic_data])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #include <openssl/ssl.h>
+ ]], [[
+ SSL_provide_quic_data(NULL, 0, NULL, 0);
+ ]])],
+ [AC_MSG_RESULT([yes]); have_ssl_provide_quic_data=yes],
+ [AC_MSG_RESULT([no]); have_ssl_provide_quic_data=no])
+
+ # boringssl has SSL_set_quic_early_data_context.
+ AC_MSG_CHECKING([for SSL_set_quic_early_data_context])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #include <openssl/ssl.h>
+ ]], [[
+ SSL *ssl = NULL;
+ SSL_set_quic_early_data_context(ssl, NULL, 0);
+ ]])],
+ [AC_MSG_RESULT([yes]); have_boringssl_quic=yes],
+ [AC_MSG_RESULT([no]); have_boringssl_quic=no])
+
+ CFLAGS="$save_CFLAGS"
+ LIBS="$save_LIBS"
+ fi
+fi
+
+if test "x${request_openssl}" = "xyes" &&
+ test "x${have_openssl}" != "xyes"; then
+ AC_MSG_ERROR([openssl was requested (--with-openssl) but not found])
+fi
+
+# c-ares (for src)
+have_libcares=no
+if test "x${request_libcares}" != "xno"; then
+ PKG_CHECK_MODULES([LIBCARES], [libcares >= 1.7.5], [have_libcares=yes],
+ [have_libcares=no])
+ if test "x${have_libcares}" = "xno"; then
+ AC_MSG_NOTICE($LIBCARES_PKG_ERRORS)
+ fi
+fi
+
+if test "x${request_libcares}" = "xyes" &&
+ test "x${have_libcares}" != "xyes"; then
+ AC_MSG_ERROR([libcares was requested (--with-libcares) but not found])
+fi
+
+# ngtcp2 (for src)
+have_libngtcp2=no
+if test "x${request_libngtcp2}" != "xno"; then
+ PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 1.0.0], [have_libngtcp2=yes],
+ [have_libngtcp2=no])
+ if test "x${have_libngtcp2}" = "xno"; then
+ AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS)
+ fi
+fi
+
+if test "x${request_libngtcp2}" = "xyes" &&
+ test "x${have_libngtcp2}" != "xyes"; then
+ AC_MSG_ERROR([libngtcp2 was requested (--with-libngtcp2) but not found])
+fi
+
+# ngtcp2_crypto_quictls (for src)
+have_libngtcp2_crypto_quictls=no
+if test "x${have_ssl_provide_quic_data}" = "xyes" &&
+ test "x${have_boringssl_quic}" != "xyes" &&
+ test "x${request_libngtcp2}" != "xno"; then
+ PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_QUICTLS],
+ [libngtcp2_crypto_quictls >= 1.0.0],
+ [have_libngtcp2_crypto_quictls=yes],
+ [have_libngtcp2_crypto_quictls=no])
+ if test "x${have_libngtcp2_crypto_quictls}" = "xno"; then
+ AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_QUICTLS_PKG_ERRORS)
+ else
+ AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_QUICTLS], [1],
+ [Define to 1 if you have `libngtcp2_crypto_quictls` library.])
+ fi
+fi
+
+if test "x${have_ssl_provide_quic_data}" = "xyes" &&
+ test "x${have_boringssl_quic}" != "xyes" &&
+ test "x${request_libngtcp2}" = "xyes" &&
+ test "x${have_libngtcp2_crypto_quictls}" != "xyes"; then
+ AC_MSG_ERROR([libngtcp2_crypto_quictls was requested (--with-libngtcp2) but not found])
+fi
+
+# ngtcp2_crypto_boringssl (for src)
+have_libngtcp2_crypto_boringssl=no
+if test "x${have_boringssl_quic}" = "xyes" &&
+ test "x${request_libngtcp2}" != "xno"; then
+ PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_BORINGSSL],
+ [libngtcp2_crypto_boringssl >= 0.0.0],
+ [have_libngtcp2_crypto_boringssl=yes],
+ [have_libngtcp2_crypto_boringssl=no])
+ if test "x${have_libngtcp2_crypto_boringssl}" = "xno"; then
+ AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_BORINGSSL_PKG_ERRORS)
+ else
+ AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_BORINGSSL], [1],
+ [Define to 1 if you have `libngtcp2_crypto_boringssl` library.])
+ fi
+fi
+
+if test "x${have_boringssl_quic}" = "xyes" &&
+ test "x${request_libngtcp2}" = "xyes" &&
+ test "x${have_libngtcp2_crypto_boringssl}" != "xyes"; then
+ AC_MSG_ERROR([libngtcp2_crypto_boringssl was requested (--with-libngtcp2) but not found])
+fi
+
+# nghttp3 (for src)
+have_libnghttp3=no
+if test "x${request_libnghttp3}" != "xno"; then
+ PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 1.1.0], [have_libnghttp3=yes],
+ [have_libnghttp3=no])
+ if test "x${have_libnghttp3}" = "xno"; then
+ AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS)
+ fi
+fi
+
+if test "x${request_libnghttp3}" = "xyes" &&
+ test "x${have_libnghttp3}" != "xyes"; then
+ AC_MSG_ERROR([libnghttp3 was requested (--with-libnghttp3) but not found])
+fi
+
+# libbpf (for src)
+have_libbpf=no
+if test "x${request_libbpf}" != "xno"; then
+ PKG_CHECK_MODULES([LIBBPF], [libbpf >= 0.7.0], [have_libbpf=yes],
+ [have_libbpf=no])
+ if test "x${have_libbpf}" = "xyes"; then
+ AC_DEFINE([HAVE_LIBBPF], [1], [Define to 1 if you have `libbpf` library.])
+ if test "x${BPFCFLAGS}" = "x"; then
+ BPFCFLAGS="-Wall -O2 -g"
+ fi
+ # Add the include path for Debian
+ EXTRABPFCFLAGS="-I/usr/include/$host_cpu-$host_os"
+ AC_SUBST([EXTRABPFCFLAGS])
+
+ AC_MSG_CHECKING([whether enum bpf_stats_type is defined in linux/bpf.h])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+ [[
+ #include <linux/bpf.h>
+ ]],
+ [[
+ enum bpf_stats_type foo;
+ (void)foo;
+ ]])],
+ [have_bpf_stats_type=yes],
+ [have_bpf_stats_type=no])
+
+ if test "x${have_bpf_stats_type}" = "xyes"; then
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([HAVE_BPF_STATS_TYPE], [1],
+ [Define to 1 if you have enum bpf_stats_type in linux/bpf.h.])
+ else
+ AC_MSG_RESULT([no])
+ fi
+ else
+ AC_MSG_NOTICE($LIBBPF_PKG_ERRORS)
+ fi
+fi
+
+if test "x${request_libbpf}" = "xyes" &&
+ test "x${have_libbpf}" != "xyes"; then
+ AC_MSG_ERROR([libbpf was requested (--with-libbpf) but not found])
+fi
+
+AM_CONDITIONAL([HAVE_LIBBPF], [ test "x${have_libbpf}" = "xyes" ])
+
+# libevent_openssl (for examples)
+# 2.0.8 is required because we use evconnlistener_set_error_cb()
+have_libevent_openssl=no
+if test "x${request_libevent_openssl}" != "xno"; then
+ 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
+fi
+
+if test "x${request_libevent_openssl}" = "xyes" &&
+ test "x${have_libevent_openssl}" != "xyes"; then
+ AC_MSG_ERROR([libevent_openssl was requested (--with-libevent) but not found])
+fi
+
+# jansson (for src/nghttp, src/deflatehd and src/inflatehd)
+have_jansson=no
+if test "x${request_jansson}" != "xno"; then
+ 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
+fi
+
+if test "x${request_jansson}" = "xyes" &&
+ test "x${have_jansson}" != "xyes"; then
+ AC_MSG_ERROR([jansson was requested (--with-jansson) but not found])
+fi
+
+# libsystemd (for src/nghttpx)
+have_libsystemd=no
+if test "x${request_systemd}" != "xno"; then
+ PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 209], [have_libsystemd=yes],
+ [have_libsystemd=no])
+ if test "x${have_libsystemd}" = "xyes"; then
+ AC_DEFINE([HAVE_LIBSYSTEMD], [1],
+ [Define to 1 if you have `libsystemd` library.])
+ else
+ AC_MSG_NOTICE($SYSTEMD_PKG_ERRORS)
+ fi
+fi
+
+if test "x${request_systemd}" = "xyes" &&
+ test "x${have_libsystemd}" != "xyes"; then
+ AC_MSG_ERROR([systemd was requested (--with-systemd) but not found])
+fi
+
+# libxml2 (for src/nghttp)
+have_libxml2=no
+if test "x${request_libxml2}" != "xno"; then
+ PKG_CHECK_MODULES([LIBXML2], [libxml-2.0 >= 2.6.26],
+ [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.])
+ else
+ AC_MSG_NOTICE($LIBXML2_PKG_ERRORS)
+ 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
+ if test "x${JEMALLOC_LIBS}" = "x" && test "x${JEMALLOC_CFLAGS}" = "x"; then
+ save_LIBS=$LIBS
+ AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
+ [$PTHREAD_LDFLAGS])
+
+ if test "x${have_jemalloc}" = "xyes"; then
+ jemalloc_libs=${ac_cv_search_malloc_stats_print}
+ else
+ # On Darwin, malloc_stats_print is je_malloc_stats_print
+ AC_SEARCH_LIBS([je_malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
+ [$PTHREAD_LDFLAGS])
+
+ if test "x${have_jemalloc}" = "xyes"; then
+ jemalloc_libs=${ac_cv_search_je_malloc_stats_print}
+ fi
+ fi
+
+ LIBS=$save_LIBS
+
+ if test "x${have_jemalloc}" = "xyes" &&
+ test "x${jemalloc_libs}" != "xnone required"; then
+ JEMALLOC_LIBS=${jemalloc_libs}
+ fi
+ else
+ have_jemalloc=yes
+ 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
+
+# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL,
+# libev, and libc-ares.
+enable_app=no
+if test "x${request_app}" != "xno" &&
+ test "x${have_zlib}" = "xyes" &&
+ test "x${have_openssl}" = "xyes" &&
+ test "x${have_libev}" = "xyes" &&
+ test "x${have_libcares}" = "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" ])
+
+# Check HTTP/3 support
+enable_http3=no
+if test "x${request_http3}" != "xno" &&
+ test "x${have_libngtcp2}" = "xyes" &&
+ (test "x${have_libngtcp2_crypto_quictls}" = "xyes" ||
+ test "x${have_libngtcp2_crypto_boringssl}" = "xyes") &&
+ test "x${have_libnghttp3}" = "xyes"; then
+ enable_http3=yes
+ AC_DEFINE([ENABLE_HTTP3], [1], [Define to 1 if HTTP/3 is enabled.])
+fi
+
+if test "x${request_http3}" = "xyes" &&
+ test "x${enable_http3}" != "xyes"; then
+ AC_MSG_ERROR([HTTP/3 was requested (--enable-http3) but dependencies are not met.])
+fi
+
+AM_CONDITIONAL([ENABLE_HTTP3], [ test "x${enable_http3}" = "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" ])
+
+# 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" ])
+
+# third-party only be built when needed
+
+enable_third_party=no
+have_mruby=no
+have_neverbleed=no
+if test "x${enable_examples}" = "xyes" ||
+ test "x${enable_app}" = "xyes" ||
+ test "x${enable_hpack_tools}" = "xyes"; then
+ enable_third_party=yes
+
+ # mruby (for src/nghttpx)
+ if test "x${request_mruby}" = "xyes"; then
+ # We are going to build mruby
+ have_mruby=yes
+ AC_DEFINE([HAVE_MRUBY], [1], [Define to 1 if you have `mruby` library.])
+ LIBMRUBY_LIBS="-lmruby -lm"
+ LIBMRUBY_CFLAGS=
+ AC_SUBST([LIBMRUBY_LIBS])
+ AC_SUBST([LIBMRUBY_CFLAGS])
+ fi
+
+ # neverbleed (for src/nghttpx)
+ if test "x${request_neverbleed}" = "xyes"; then
+ have_neverbleed=yes
+ AC_DEFINE([HAVE_NEVERBLEED], [1], [Define to 1 if you have `neverbleed` library.])
+ fi
+fi
+
+AM_CONDITIONAL([ENABLE_THIRD_PARTY], [ test "x${enable_third_party}" = "xyes" ])
+AM_CONDITIONAL([HAVE_MRUBY], [test "x${have_mruby}" = "xyes"])
+AM_CONDITIONAL([HAVE_NEVERBLEED], [test "x${have_neverbleed}" = "xyes"])
+
+# 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 \
+ fcntl.h \
+ inttypes.h \
+ limits.h \
+ netdb.h \
+ netinet/in.h \
+ netinet/ip.h \
+ pwd.h \
+ stddef.h \
+ stdint.h \
+ stdlib.h \
+ string.h \
+ sys/socket.h \
+ sys/time.h \
+ syslog.h \
+ unistd.h \
+ windows.h \
+])
+
+# 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_TYPE_INT8_T
+AC_TYPE_INT16_T
+AC_TYPE_INT32_T
+AC_TYPE_INT64_T
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_UID_T
+AC_CHECK_TYPES([ptrdiff_t])
+AC_C_BIGENDIAN
+AC_C_INLINE
+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>]])
+
+AC_CHECK_MEMBER([struct sockaddr_in.sin_len],
+ [AC_DEFINE([HAVE_SOCKADDR_IN_SIN_LEN],[1],
+ [Define to 1 if struct sockaddr_in has sin_len member.])],
+ [],
+ [[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+]])
+
+AC_CHECK_MEMBER([struct sockaddr_in6.sin6_len],
+ [AC_DEFINE([HAVE_SOCKADDR_IN6_SIN6_LEN],[1],
+ [Define to 1 if struct sockaddr_in6 has sin6_len member.])],
+ [],
+ [[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.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.
+
+# Don't check malloc, since it does not play nicely with C++ stdlib
+# AC_FUNC_MALLOC
+
+AC_FUNC_CHOWN
+AC_FUNC_ERROR_AT_LINE
+AC_FUNC_FORK
+# Don't check realloc, since LeakSanitizer detects memory leak during check
+# AC_FUNC_REALLOC
+AC_FUNC_STRERROR_R
+AC_FUNC_STRNLEN
+
+AC_CHECK_FUNCS([ \
+ _Exit \
+ accept4 \
+ clock_gettime \
+ dup2 \
+ getcwd \
+ getpwnam \
+ localtime_r \
+ memchr \
+ memmove \
+ memset \
+ mkostemp \
+ socket \
+ sqrt \
+ strchr \
+ strdup \
+ strerror \
+ strndup \
+ strstr \
+ strtol \
+ strtoul \
+ timegm \
+])
+
+# timerfd_create was added in linux kernel 2.6.25
+
+AC_CHECK_FUNC([timerfd_create],
+ [have_timerfd_create=yes], [have_timerfd_create=no])
+
+AC_MSG_CHECKING([checking for GetTickCount64])
+AC_LINK_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <windows.h>
+]],
+[[
+GetTickCount64();
+]])],
+[have_gettickcount64=yes],
+[have_gettickcount64=no])
+
+if test "x${have_gettickcount64}" = "xyes"; then
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([HAVE_GETTICKCOUNT64], [1],
+ [Define to 1 if you have `GetTickCount64` function.])
+else
+ AC_MSG_RESULT([no])
+fi
+
+# For cygwin: we can link initgroups, so AC_CHECK_FUNCS succeeds, but
+# cygwin disables initgroups due to feature test macro magic with our
+# configuration. FreeBSD declares initgroups() in unistd.h.
+AC_CHECK_DECLS([initgroups], [], [], [[
+ #ifdef HAVE_UNISTD_H
+ # include <unistd.h>
+ #endif
+ #include <grp.h>
+]])
+
+AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[
+#include <time.h>
+]])
+
+save_CFLAGS=$CFLAGS
+save_CXXFLAGS=$CXXFLAGS
+
+CFLAGS=
+CXXFLAGS=
+
+if test "x$werror" != "xno"; then
+ # For C compiler
+ 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"])
+
+ AX_CHECK_COMPILE_FLAG([-Wconversion], [CFLAGS="$CFLAGS -Wconversion"])
+ AX_CHECK_COMPILE_FLAG([-Wextended-offsetof], [CFLAGS="$CFLAGS -Wextended-offsetof"])
+ AX_CHECK_COMPILE_FLAG([-Wformat-nonliteral], [CFLAGS="$CFLAGS -Wformat-nonliteral"])
+ AX_CHECK_COMPILE_FLAG([-Wlanguage-extension-token], [CFLAGS="$CFLAGS -Wlanguage-extension-token"])
+ AX_CHECK_COMPILE_FLAG([-Wmissing-field-initializers], [CFLAGS="$CFLAGS -Wmissing-field-initializers"])
+ AX_CHECK_COMPILE_FLAG([-Wmissing-noreturn], [CFLAGS="$CFLAGS -Wmissing-noreturn"])
+ AX_CHECK_COMPILE_FLAG([-Wmissing-variable-declarations], [CFLAGS="$CFLAGS -Wmissing-variable-declarations"])
+ # Not used because we cannot change public structs
+ # AX_CHECK_COMPILE_FLAG([-Wpadded], [CFLAGS="$CFLAGS -Wpadded"])
+ AX_CHECK_COMPILE_FLAG([-Wsign-conversion], [CFLAGS="$CFLAGS -Wsign-conversion"])
+ # Not used because this basically disallows default case
+ # AX_CHECK_COMPILE_FLAG([-Wswitch-enum], [CFLAGS="$CFLAGS -Wswitch-enum"])
+ AX_CHECK_COMPILE_FLAG([-Wunreachable-code-break], [CFLAGS="$CFLAGS -Wunreachable-code-break"])
+ AX_CHECK_COMPILE_FLAG([-Wunused-macros], [CFLAGS="$CFLAGS -Wunused-macros"])
+ AX_CHECK_COMPILE_FLAG([-Wunused-parameter], [CFLAGS="$CFLAGS -Wunused-parameter"])
+ AX_CHECK_COMPILE_FLAG([-Wredundant-decls], [CFLAGS="$CFLAGS -Wredundant-decls"])
+ # Only work with Clang for the moment
+ AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"])
+ AX_CHECK_COMPILE_FLAG([-Wsometimes-uninitialized], [CFLAGS="$CFLAGS -Wsometimes-uninitialized"])
+
+ # This is required because we pass format string as "const char*.
+ AX_CHECK_COMPILE_FLAG([-Wno-format-nonliteral], [CFLAGS="$CFLAGS -Wno-format-nonliteral"])
+
+ # For C++ compiler
+ AC_LANG_PUSH(C++)
+ AX_CHECK_COMPILE_FLAG([-Wall], [CXXFLAGS="$CXXFLAGS -Wall"])
+ AX_CHECK_COMPILE_FLAG([-Werror], [CXXFLAGS="$CXXFLAGS -Werror"])
+ AX_CHECK_COMPILE_FLAG([-Wformat-security], [CXXFLAGS="$CXXFLAGS -Wformat-security"])
+ AX_CHECK_COMPILE_FLAG([-Wsometimes-uninitialized], [CXXFLAGS="$CXXFLAGS -Wsometimes-uninitialized"])
+ # Disable noexcept-type warning of g++-7. This is not harmful as
+ # long as all source files are compiled with the same compiler.
+ AX_CHECK_COMPILE_FLAG([-Wno-noexcept-type], [CXXFLAGS="$CXXFLAGS -Wno-noexcept-type"])
+ AC_LANG_POP()
+fi
+
+WARNCFLAGS=$CFLAGS
+WARNCXXFLAGS=$CXXFLAGS
+
+CFLAGS=$save_CFLAGS
+CXXFLAGS=$save_CXXFLAGS
+
+AC_SUBST([WARNCFLAGS])
+AC_SUBST([WARNCXXFLAGS])
+
+EXTRACFLAG=
+AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [EXTRACFLAG="-fvisibility=hidden"])
+
+AC_SUBST([EXTRACFLAG])
+
+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. We disable
+# threading for those platforms.
+if test "x$threads" != "xyes" ||
+ test "x$have_std_future" != "xyes"; then
+ enable_threads=no
+ AC_DEFINE([NOTHREADS], [1], [Define to 1 if you want to disable threads.])
+fi
+
+# propagate $enable_static to tests/Makefile.am
+AM_CONDITIONAL([ENABLE_STATIC], [test "x$enable_static" = "xyes"])
+
+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/testdata/Makefile
+ bpf/Makefile
+ examples/Makefile
+ 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/building-android-binary.rst
+ doc/nghttp2.h.rst
+ doc/nghttp2ver.h.rst
+ doc/contribute.rst
+ contrib/Makefile
+ script/Makefile
+])
+AC_OUTPUT
+
+AC_MSG_NOTICE([summary of build options:
+
+ Package version: ${VERSION}
+ Library version: $LT_CURRENT:$LT_REVISION:$LT_AGE
+ Install prefix: ${prefix}
+ System types:
+ Build: ${build}
+ Host: ${host}
+ Target: ${target}
+ Compiler:
+ C compiler: ${CC}
+ CFLAGS: ${CFLAGS}
+ LDFLAGS: ${LDFLAGS}
+ C++ compiler: ${CXX}
+ CXXFLAGS: ${CXXFLAGS}
+ CXXCPP: ${CXXCPP}
+ C preprocessor: ${CPP}
+ CPPFLAGS: ${CPPFLAGS}
+ WARNCFLAGS: ${WARNCFLAGS}
+ WARNCXXFLAGS: ${WARNCXXFLAGS}
+ CXX1XCXXFLAGS: ${CXX1XCXXFLAGS}
+ EXTRACFLAG: ${EXTRACFLAG}
+ BPFCFLAGS: ${BPFCFLAGS}
+ EXTRABPFCFLAGS: ${EXTRABPFCFLAGS}
+ LIBS: ${LIBS}
+ DEFS: ${DEFS}
+ EXTRA_DEFS: ${EXTRA_DEFS}
+ Library:
+ Shared: ${enable_shared}
+ Static: ${enable_static}
+ Libtool:
+ LIBTOOL_LDFLAGS: ${LIBTOOL_LDFLAGS}
+ Python:
+ Python: ${PYTHON}
+ PYTHON_VERSION: ${PYTHON_VERSION}
+ Test:
+ CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}')
+ Failmalloc: ${enable_failmalloc}
+ Libs:
+ OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}')
+ Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}')
+ Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
+ Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}')
+ libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}')
+ libngtcp2_crypto_quictls: ${have_libngtcp2_crypto_quictls} (CFLAGS='${LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_QUICTLS_LIBS}')
+ libngtcp2_crypto_boringssl: ${have_libngtcp2_crypto_boringssl} (CFLAGS='${LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_BORINGSSL_LIBS}')
+ libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}')
+ libbpf: ${have_libbpf} (CFLAGS='${LIBBPF_CFLAGS}' LIBS='${LIBBPF_LIBS}')
+ Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')
+ Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
+ Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}')
+ Zlib: ${have_zlib} (CFLAGS='${ZLIB_CFLAGS}' LIBS='${ZLIB_LIBS}')
+ Systemd: ${have_libsystemd} (CFLAGS='${SYSTEMD_CFLAGS}' LIBS='${SYSTEMD_LIBS}')
+ Third-party:
+ http-parser: ${enable_third_party}
+ MRuby: ${have_mruby} (CFLAGS='${LIBMRUBY_CFLAGS}' LIBS='${LIBMRUBY_LIBS}')
+ Neverbleed: ${have_neverbleed}
+ Features:
+ Applications: ${enable_app}
+ HPACK tools: ${enable_hpack_tools}
+ Examples: ${enable_examples}
+ Threading: ${enable_threads}
+ HTTP/3 (EXPERIMENTAL): ${enable_http3}
+])
diff --git a/contrib/.gitignore b/contrib/.gitignore
new file mode 100644
index 0000000..85acde3
--- /dev/null
+++ b/contrib/.gitignore
@@ -0,0 +1,3 @@
+nghttpx-init
+nghttpx.service
+nghttpx-upstart.conf
diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt
new file mode 100644
index 0000000..f598f7b
--- /dev/null
+++ b/contrib/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(CONFIGFILES
+ nghttpx-init
+ nghttpx.service
+ nghttpx-upstart.conf
+)
+
+# Note that the execute permissions of nghttpx-init is preserved
+foreach(name IN LISTS CONFIGFILES)
+ configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+# set(EXTRA_DIST ${CONFIGFILES} nghttpx-logrotate tlsticketupdate.go)
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 0000000..5a02e2f
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1,51 @@
+# 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.
+
+configfiles = nghttpx-init nghttpx.service nghttpx-upstart.conf
+
+EXTRA_DIST = \
+ CMakeLists.txt \
+ $(configfiles:%=%.in) \
+ nghttpx-logrotate \
+ tlsticketupdate.go
+
+edit = sed -e 's|@bindir[@]|$(bindir)|g'
+
+nghttpx-init: $(srcdir)/nghttpx-init.in
+ rm -f $@ $@.tmp
+ $(edit) $< > $@.tmp
+ chmod +x $@.tmp
+ mv $@.tmp $@
+
+nghttpx.service: $(srcdir)/nghttpx.service.in
+ $(edit) $< > $@
+
+nghttpx-upstart.conf: $(srcdir)/nghttpx-upstart.conf.in
+ $(edit) $< > $@
+
+$(configfiles): Makefile
+
+all-local: $(configfiles)
+
+clean-local:
+ -rm -f nghttpx-init.tmp $(configfiles)
diff --git a/contrib/nghttpx-init.in b/contrib/nghttpx-init.in
new file mode 100755
index 0000000..2b3c76e
--- /dev/null
+++ b/contrib/nghttpx-init.in
@@ -0,0 +1,164 @@
+#! /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 --daemon"
+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"
+}
+
+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 "Upgrading $DESC" "$NAME"
+ oldpid=`pidofproc -p $PIDFILE $NAME`
+ case "$?" in
+ 0)
+ log_progress_msg "Sending SIGUSR2 to $oldpid..."
+ kill -USR2 $oldpid
+ log_progress_msg "Waiting for new binary..."
+ for i in 1 2 3 4 5 ; do
+ sleep 1
+ newpid=`pidofproc -p $PIDFILE $NAME`
+ if [ "$newpid" != "$oldpid" ] ; then
+ break
+ fi
+ done
+ if [ "$newpid" != "$oldpid" ] ; then
+ log_progress_msg "Sending SIGQUIT to $oldpid..."
+ kill -QUIT $oldpid
+ log_end_msg 0
+ else
+ log_progress_msg "New binary failed to start"
+ log_end_msg 1
+ fi
+ ;;
+ *)
+ log_progress_msg "pidofproc() failed"
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ 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
index 0000000..c715ced
--- /dev/null
+++ b/contrib/nghttpx-logrotate
@@ -0,0 +1,11 @@
+/var/log/nghttpx/*.log {
+ weekly
+ rotate 52
+ missingok
+ compress
+ delaycompress
+ notifempty
+ postrotate
+ [ -s /var/run/nghttpx.pid ] && kill -USR1 `cat /var/run/nghttpx.pid` 2> /dev/null || true
+ endscript
+}
diff --git a/contrib/nghttpx-upstart.conf.in b/contrib/nghttpx-upstart.conf.in
new file mode 100644
index 0000000..0b79916
--- /dev/null
+++ b/contrib/nghttpx-upstart.conf.in
@@ -0,0 +1,8 @@
+# vim: ft=upstart:
+
+description "HTTP/2 reverse proxy"
+
+start on runlevel [2]
+stop on runlevel [016]
+
+exec @bindir@/nghttpx
diff --git a/contrib/nghttpx.service.in b/contrib/nghttpx.service.in
new file mode 100644
index 0000000..06fb736
--- /dev/null
+++ b/contrib/nghttpx.service.in
@@ -0,0 +1,17 @@
+[Unit]
+Description=HTTP/2 proxy
+Documentation=man:nghttpx
+After=network.target
+
+[Service]
+Type=notify
+ExecStart=@bindir@/nghttpx --conf=/etc/nghttpx/nghttpx.conf
+ExecReload=/bin/kill --signal HUP $MAINPID
+KillSignal=SIGQUIT
+PrivateTmp=yes
+ProtectHome=yes
+ProtectSystem=full
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/tlsticketupdate.go b/contrib/tlsticketupdate.go
new file mode 100644
index 0000000..ff3d759
--- /dev/null
+++ b/contrib/tlsticketupdate.go
@@ -0,0 +1,112 @@
+//
+// 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.
+//
+package main
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "flag"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/bradfitz/gomemcache/memcache"
+)
+
+func makeKey(len int) []byte {
+ b := make([]byte, len)
+ if _, err := rand.Read(b); err != nil {
+ log.Fatalf("rand.Read: %v", err)
+ }
+ return b
+}
+
+func main() {
+ var host = flag.String("host", "127.0.0.1", "memcached host")
+ var port = flag.Int("port", 11211, "memcached port")
+ var cipher = flag.String("cipher", "aes-128-cbc", "cipher for TLS ticket encryption")
+ var interval = flag.Int("interval", 3600, "interval to update TLS ticket keys")
+
+ flag.Parse()
+
+ var keylen int
+ switch *cipher {
+ case "aes-128-cbc":
+ keylen = 48
+ case "aes-256-cbc":
+ keylen = 80
+ default:
+ log.Fatalf("cipher: unknown cipher %v", cipher)
+ }
+
+ mc := memcache.New(fmt.Sprintf("%v:%v", *host, *port))
+
+ keys := [][]byte{
+ makeKey(keylen), // current encryption key
+ makeKey(keylen), // next encryption key; now decryption only
+ }
+
+ for {
+ buf := new(bytes.Buffer)
+ if err := binary.Write(buf, binary.BigEndian, uint32(1)); err != nil {
+ log.Fatalf("failed to write version: %v", err)
+ }
+
+ for _, key := range keys {
+ if err := binary.Write(buf, binary.BigEndian, uint16(keylen)); err != nil {
+ log.Fatalf("failed to write length: %v", err)
+ }
+ if _, err := buf.Write(key); err != nil {
+ log.Fatalf("buf.Write: %v", err)
+ }
+ }
+
+ mc.Set(&memcache.Item{
+ Key: "nghttpx:tls-ticket-key",
+ Value: buf.Bytes(),
+ Expiration: int32((*interval) + 300),
+ })
+
+ <-time.After(time.Duration(*interval) * time.Second)
+
+ // rotate keys. the last key is now encryption key.
+ // generate new key and append it to the last, so that
+ // we can at least decrypt TLS ticket encrypted by new
+ // key on the host which does not get new key yet.
+ // keep at most past 11 keys as decryption only key
+ n := len(keys) + 1
+ if n > 13 {
+ n = 13
+ }
+ newKeys := make([][]byte, n)
+ newKeys[0] = keys[len(keys)-1]
+ copy(newKeys[1:], keys[0:n-2])
+ newKeys[n-1] = makeKey(keylen)
+
+ keys = newKeys
+ }
+
+}
diff --git a/contrib/usr.sbin.nghttpx b/contrib/usr.sbin.nghttpx
new file mode 100644
index 0000000..891ff52
--- /dev/null
+++ b/contrib/usr.sbin.nghttpx
@@ -0,0 +1,16 @@
+#include <tunables/global>
+
+/usr/sbin/nghttpx {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+ #include <abstractions/openssl>
+
+ capability setgid,
+ capability setuid,
+
+ /usr/sbin/nghttpx rmix, # allow to run itself
+ /etc/nghttpx/nghttpx.conf r, # allow to read the config file
+ /etc/ssl/** r, # give access to ssl keys
+
+ /{,var/}run/nghttpx.pid lw, # allow to store a pid file
+}
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..ed9e634
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1,19 @@
+# generated files
+apiref.rst
+building-android-binary.rst
+conf.py
+contribute.rst
+enums.rst
+h2load-howto.rst
+index.rst
+macros.rst
+manual/
+nghttp2.h.rst
+nghttp2_*.rst
+nghttp2ver.h.rst
+nghttpx-howto.rst
+package_README.rst
+tutorial-client.rst
+tutorial-hpack.rst
+tutorial-server.rst
+types.rst
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644
index 0000000..531791f
--- /dev/null
+++ b/doc/CMakeLists.txt
@@ -0,0 +1,352 @@
+# Generated documents
+set(APIDOCS
+ macros.rst
+ enums.rst
+ types.rst
+ nghttp2_check_header_name.rst
+ nghttp2_check_header_value.rst
+ nghttp2_hd_deflate_bound.rst
+ nghttp2_hd_deflate_change_table_size.rst
+ nghttp2_hd_deflate_del.rst
+ nghttp2_hd_deflate_get_dynamic_table_size.rst
+ nghttp2_hd_deflate_get_max_dynamic_table_size.rst
+ nghttp2_hd_deflate_get_num_table_entries.rst
+ nghttp2_hd_deflate_get_table_entry.rst
+ nghttp2_hd_deflate_hd.rst
+ nghttp2_hd_deflate_hd_vec.rst
+ nghttp2_hd_deflate_new.rst
+ nghttp2_hd_deflate_new2.rst
+ nghttp2_hd_inflate_change_table_size.rst
+ nghttp2_hd_inflate_del.rst
+ nghttp2_hd_inflate_end_headers.rst
+ nghttp2_hd_inflate_get_dynamic_table_size.rst
+ nghttp2_hd_inflate_get_max_dynamic_table_size.rst
+ nghttp2_hd_inflate_get_num_table_entries.rst
+ nghttp2_hd_inflate_get_table_entry.rst
+ nghttp2_hd_inflate_hd.rst
+ nghttp2_hd_inflate_hd2.rst
+ nghttp2_hd_inflate_new.rst
+ nghttp2_hd_inflate_new2.rst
+ nghttp2_http2_strerror.rst
+ nghttp2_is_fatal.rst
+ nghttp2_nv_compare_name.rst
+ nghttp2_option_del.rst
+ nghttp2_option_new.rst
+ nghttp2_option_set_builtin_recv_extension_type.rst
+ nghttp2_option_set_max_deflate_dynamic_table_size.rst
+ nghttp2_option_set_max_reserved_remote_streams.rst
+ nghttp2_option_set_max_send_header_block_length.rst
+ nghttp2_option_set_no_auto_ping_ack.rst
+ nghttp2_option_set_no_auto_window_update.rst
+ nghttp2_option_set_no_http_messaging.rst
+ nghttp2_option_set_no_recv_client_magic.rst
+ nghttp2_option_set_peer_max_concurrent_streams.rst
+ nghttp2_option_set_user_recv_extension_type.rst
+ nghttp2_option_set_max_settings.rst
+ nghttp2_pack_settings_payload.rst
+ nghttp2_priority_spec_check_default.rst
+ nghttp2_priority_spec_default_init.rst
+ nghttp2_priority_spec_init.rst
+ nghttp2_rcbuf_decref.rst
+ nghttp2_rcbuf_get_buf.rst
+ nghttp2_rcbuf_incref.rst
+ nghttp2_rcbuf_is_static.rst
+ nghttp2_select_next_protocol.rst
+ nghttp2_session_callbacks_del.rst
+ nghttp2_session_callbacks_new.rst
+ nghttp2_session_callbacks_set_before_frame_send_callback.rst
+ nghttp2_session_callbacks_set_data_source_read_length_callback.rst
+ nghttp2_session_callbacks_set_error_callback.rst
+ nghttp2_session_callbacks_set_on_begin_frame_callback.rst
+ nghttp2_session_callbacks_set_on_begin_headers_callback.rst
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst
+ nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst
+ nghttp2_session_callbacks_set_on_frame_not_send_callback.rst
+ nghttp2_session_callbacks_set_on_frame_recv_callback.rst
+ nghttp2_session_callbacks_set_on_frame_send_callback.rst
+ nghttp2_session_callbacks_set_on_header_callback.rst
+ nghttp2_session_callbacks_set_on_header_callback2.rst
+ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst
+ nghttp2_session_callbacks_set_on_invalid_header_callback.rst
+ nghttp2_session_callbacks_set_on_invalid_header_callback2.rst
+ nghttp2_session_callbacks_set_on_stream_close_callback.rst
+ nghttp2_session_callbacks_set_pack_extension_callback.rst
+ nghttp2_session_callbacks_set_recv_callback.rst
+ nghttp2_session_callbacks_set_select_padding_callback.rst
+ nghttp2_session_callbacks_set_send_callback.rst
+ nghttp2_session_callbacks_set_send_data_callback.rst
+ nghttp2_session_callbacks_set_unpack_extension_callback.rst
+ nghttp2_session_change_stream_priority.rst
+ nghttp2_session_check_request_allowed.rst
+ nghttp2_session_check_server_session.rst
+ nghttp2_session_client_new.rst
+ nghttp2_session_client_new2.rst
+ nghttp2_session_client_new3.rst
+ nghttp2_session_consume.rst
+ nghttp2_session_consume_connection.rst
+ nghttp2_session_consume_stream.rst
+ nghttp2_session_create_idle_stream.rst
+ nghttp2_session_del.rst
+ nghttp2_session_find_stream.rst
+ nghttp2_session_get_effective_local_window_size.rst
+ nghttp2_session_get_effective_recv_data_length.rst
+ nghttp2_session_get_hd_deflate_dynamic_table_size.rst
+ nghttp2_session_get_hd_inflate_dynamic_table_size.rst
+ nghttp2_session_get_last_proc_stream_id.rst
+ nghttp2_session_get_local_settings.rst
+ nghttp2_session_get_local_window_size.rst
+ nghttp2_session_get_next_stream_id.rst
+ nghttp2_session_get_outbound_queue_size.rst
+ nghttp2_session_get_remote_settings.rst
+ nghttp2_session_get_remote_window_size.rst
+ nghttp2_session_get_root_stream.rst
+ nghttp2_session_get_stream_effective_local_window_size.rst
+ nghttp2_session_get_stream_effective_recv_data_length.rst
+ nghttp2_session_get_stream_local_close.rst
+ nghttp2_session_get_stream_local_window_size.rst
+ nghttp2_session_get_stream_remote_close.rst
+ nghttp2_session_get_stream_remote_window_size.rst
+ nghttp2_session_get_stream_user_data.rst
+ nghttp2_session_mem_recv.rst
+ nghttp2_session_mem_send.rst
+ nghttp2_session_recv.rst
+ nghttp2_session_resume_data.rst
+ nghttp2_session_send.rst
+ nghttp2_session_server_new.rst
+ nghttp2_session_server_new2.rst
+ nghttp2_session_server_new3.rst
+ nghttp2_session_set_local_window_size.rst
+ nghttp2_session_set_next_stream_id.rst
+ nghttp2_session_set_stream_user_data.rst
+ nghttp2_session_terminate_session.rst
+ nghttp2_session_terminate_session2.rst
+ nghttp2_session_upgrade.rst
+ nghttp2_session_upgrade2.rst
+ nghttp2_session_want_read.rst
+ nghttp2_session_want_write.rst
+ nghttp2_set_debug_vprintf_callback.rst
+ nghttp2_stream_get_first_child.rst
+ nghttp2_stream_get_next_sibling.rst
+ nghttp2_stream_get_parent.rst
+ nghttp2_stream_get_previous_sibling.rst
+ nghttp2_stream_get_state.rst
+ nghttp2_stream_get_sum_dependency_weight.rst
+ nghttp2_stream_get_weight.rst
+ nghttp2_strerror.rst
+ nghttp2_submit_altsvc.rst
+ nghttp2_submit_data.rst
+ nghttp2_submit_extension.rst
+ nghttp2_submit_goaway.rst
+ nghttp2_submit_headers.rst
+ nghttp2_submit_ping.rst
+ nghttp2_submit_priority.rst
+ nghttp2_submit_push_promise.rst
+ nghttp2_submit_request.rst
+ nghttp2_submit_response.rst
+ nghttp2_submit_rst_stream.rst
+ nghttp2_submit_settings.rst
+ nghttp2_submit_shutdown_notice.rst
+ nghttp2_submit_trailer.rst
+ nghttp2_submit_window_update.rst
+ nghttp2_version.rst
+)
+
+set(MAN_PAGES
+ nghttp.1
+ nghttpd.1
+ nghttpx.1
+ h2load.1
+)
+
+# Other .rst files from the source tree that need to be copied
+# XXX move them to sources/ and create .in files?
+set(RST_FILES
+ README.rst
+ programmers-guide.rst
+ nghttp.1.rst
+ nghttpd.1.rst
+ nghttpx.1.rst
+ h2load.1.rst
+)
+
+# XXX unused for now
+set(EXTRA_DIST
+ mkapiref.py
+ ${RST_FILES}
+ ${APIDOCS}
+ sources/index.rst
+ sources/tutorial-client.rst
+ sources/tutorial-server.rst
+ sources/tutorial-hpack.rst
+ sources/nghttpx-howto.rst
+ sources/h2load-howto.rst
+ sources/building-android-binary.rst
+ sources/contribute.rst
+ _exts/rubydomain/LICENSE.rubydomain
+ _exts/rubydomain/__init__.py
+ _exts/rubydomain/rubydomain.py
+ _themes/sphinx_rtd_theme/__init__.py
+ _themes/sphinx_rtd_theme/breadcrumbs.html
+ _themes/sphinx_rtd_theme/footer.html
+ _themes/sphinx_rtd_theme/layout.html
+ _themes/sphinx_rtd_theme/layout_old.html
+ _themes/sphinx_rtd_theme/search.html
+ _themes/sphinx_rtd_theme/searchbox.html
+ _themes/sphinx_rtd_theme/static/css/badge_only.css
+ _themes/sphinx_rtd_theme/static/css/theme.css
+ _themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf
+ _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot
+ _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
+ _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf
+ _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff
+ _themes/sphinx_rtd_theme/static/js/theme.js
+ _themes/sphinx_rtd_theme/theme.conf
+ _themes/sphinx_rtd_theme/versions.html
+ ${MAN_PAGES}
+ bash_completion/nghttp
+ bash_completion/nghttpd
+ bash_completion/nghttpx
+ bash_completion/h2load
+)
+
+# Based on Makefile for Sphinx documentation
+
+# You can set these variables from the command line.
+set(SPHINXOPTS)
+set(SPHINXBUILD sphinx-build)
+set(PAPER)
+set(BUILDDIR manual)
+
+# Internal variables.
+set(PAPEROPT_a4 -D latex_paper_size=a4)
+set(PAPEROPT_letter -D latex_paper_size=letter)
+set(ALLSPHINXOPTS -d ${BUILDDIR}/doctrees ${PAPEROPT_${PAPER}} ${SPHINXOPTS} .)
+
+# "Please use `make <target>' where <target> is one of"
+# " html to make standalone HTML files"
+# " dirhtml to make HTML files named index.html in directories"
+# " singlehtml to make a single large HTML file"
+# " pickle to make pickle files"
+# " json to make JSON files"
+# " htmlhelp to make HTML files and a HTML help project"
+# " qthelp to make HTML files and a qthelp project"
+# " devhelp to make HTML files and a Devhelp project"
+# " epub to make an epub"
+# " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+# " latexpdf to make LaTeX files and run them through pdflatex"
+# " text to make text files"
+# " man to make manual pages"
+# " changes to make an overview of all changed/added/deprecated items"
+# " linkcheck to check all external links for integrity"
+# " doctest to run all doctests embedded in the documentation (if enabled)"
+
+
+# Copy files for out-of-tree builds
+if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
+ set(RST_BUILD_FILES)
+ foreach(rstfile IN LISTS RST_FILES)
+ set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${rstfile}")
+ add_custom_command(OUTPUT "${outfile}"
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CMAKE_CURRENT_SOURCE_DIR}/${rstfile}" "${outfile}"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${rstfile}"
+ )
+ list(APPEND RST_BUILD_FILES "${outfile}")
+ endforeach()
+else()
+ set(RST_BUILD_FILES "${RST_FILES}")
+endif()
+
+set(apiref_SOURCES
+ ${CMAKE_BINARY_DIR}/lib/includes/nghttp2/nghttp2ver.h
+ ${CMAKE_SOURCE_DIR}/lib/includes/nghttp2/nghttp2.h
+)
+# Generates apiref.rst and other files
+add_custom_command(
+ OUTPUT
+ apiref.rst
+ ${APIDOCS}
+ COMMAND
+ "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mkapiref.py"
+ apiref.rst macros.rst enums.rst types.rst .
+ ${apiref_SOURCES}
+ DEPENDS
+ ${RST_BUILD_FILES}
+ ${apiref_SOURCES}
+)
+
+
+
+set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${BUILDDIR}")
+
+# Invokes sphinx-build and prints the given messages when completed
+function(sphinxbuild builder)
+ set(echo_commands)
+ foreach(message IN LISTS ARGN)
+ list(APPEND echo_commands COMMAND ${CMAKE_COMMAND} -E echo "${message}")
+ endforeach()
+ add_custom_target(${builder}
+ COMMAND "${SPHINXBUILD}" -b ${builder} ${ALLSPHINXOPTS} "${BUILDDIR}/${builder}"
+ COMMAND ${CMAKE_COMMAND} -E echo
+ ${echo_commands}
+ VERBATIM
+ DEPENDS apiref.rst
+ )
+endfunction()
+
+foreach(builder html dirhtml singlehtml)
+ sphinxbuild(${builder}
+ "Build finished. The HTML pages are in ${BUILDDIR}/${builder}.")
+endforeach()
+sphinxbuild(pickle "Build finished; now you can process the pickle files.")
+sphinxbuild(json "Build finished; now you can process the JSON files.")
+sphinxbuild(htmlhelp
+ "Build finished; now you can run HTML Help Workshop with the"
+ ".hhp project file in ${BUILDDIR}/htmlhelp."
+)
+sphinxbuild(qthelp
+ "Build finished; now you can run \"qcollectiongenerator\" with the"
+ ".qhcp project file in ${BUILDDIR}/qthelp, like this:"
+ "# qcollectiongenerator ${BUILDDIR}/qthelp/nghttp2.qhcp"
+ "To view the help file:"
+ "# assistant -collectionFile ${BUILDDIR}/qthelp/nghttp2.qhc"
+)
+sphinxbuild(devhelp
+ "Build finished."
+ "To view the help file:"
+ "# mkdir -p ~/.local/share/devhelp/nghttp2"
+ "# ln -s ${BUILDDIR}/devhelp ~/.local/share/devhelp/nghttp2"
+ "# devhelp"
+)
+sphinxbuild(epub "Build finished. The epub file is in ${BUILDDIR}/epub.")
+sphinxbuild(latex
+ "Build finished; the LaTeX files are in ${BUILDDIR}/latex."
+ "Run `make' in that directory to run these through (pdf)latex"
+ "(use `make latexpdf' here to do that automatically)."
+)
+
+# Invoke the Makefile generated by sphinx
+add_custom_target(latexpdf
+ COMMAND ${CMAKE_COMMAND} -E echo "Running LaTeX files through pdflatex..."
+ COMMAND make -C "${BUILDDIR}/latex" all-pdf
+ COMMAND ${CMAKE_COMMAND} -E echo "pdflatex finished; the PDF files are in ${BUILDDIR}/latex."
+ DEPENDS latex
+)
+
+sphinxbuild(text "Build finished. The text files are in ${BUILDDIR}/text.")
+sphinxbuild(man "Build finished. The manual pages are in ${BUILDDIR}/man.")
+sphinxbuild(changes "The overview file is in ${BUILDDIR}/changes.")
+sphinxbuild(linkcheck
+ "Link check complete; look for any errors in the above output"
+ "or in ${BUILDDIR}/linkcheck/output.txt."
+)
+sphinxbuild(doctest
+ "Testing of doctests in the sources finished, look at the"
+ "results in ${BUILDDIR}/doctest/output.txt."
+)
+
+foreach(_man_page IN LISTS MAN_PAGES)
+ install(FILES ${_man_page}
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1"
+ )
+endforeach()
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..7d7f31c
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,366 @@
+# 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
+
+APIDOCS= \
+ macros.rst \
+ enums.rst \
+ types.rst \
+ nghttp2_check_authority.rst \
+ nghttp2_check_header_name.rst \
+ nghttp2_check_header_value.rst \
+ nghttp2_check_header_value_rfc9113.rst \
+ nghttp2_check_method.rst \
+ nghttp2_check_path.rst \
+ nghttp2_extpri_parse_priority.rst \
+ nghttp2_hd_deflate_bound.rst \
+ nghttp2_hd_deflate_change_table_size.rst \
+ nghttp2_hd_deflate_del.rst \
+ nghttp2_hd_deflate_get_dynamic_table_size.rst \
+ nghttp2_hd_deflate_get_max_dynamic_table_size.rst \
+ nghttp2_hd_deflate_get_num_table_entries.rst \
+ nghttp2_hd_deflate_get_table_entry.rst \
+ nghttp2_hd_deflate_hd.rst \
+ nghttp2_hd_deflate_hd_vec.rst \
+ nghttp2_hd_deflate_new.rst \
+ nghttp2_hd_deflate_new2.rst \
+ nghttp2_hd_inflate_change_table_size.rst \
+ nghttp2_hd_inflate_del.rst \
+ nghttp2_hd_inflate_end_headers.rst \
+ nghttp2_hd_inflate_get_dynamic_table_size.rst \
+ nghttp2_hd_inflate_get_max_dynamic_table_size.rst \
+ nghttp2_hd_inflate_get_num_table_entries.rst \
+ nghttp2_hd_inflate_get_table_entry.rst \
+ nghttp2_hd_inflate_hd.rst \
+ nghttp2_hd_inflate_hd2.rst \
+ nghttp2_hd_inflate_new.rst \
+ nghttp2_hd_inflate_new2.rst \
+ nghttp2_http2_strerror.rst \
+ nghttp2_is_fatal.rst \
+ nghttp2_nv_compare_name.rst \
+ nghttp2_option_del.rst \
+ nghttp2_option_new.rst \
+ nghttp2_option_set_builtin_recv_extension_type.rst \
+ nghttp2_option_set_max_deflate_dynamic_table_size.rst \
+ nghttp2_option_set_max_reserved_remote_streams.rst \
+ nghttp2_option_set_max_send_header_block_length.rst \
+ nghttp2_option_set_no_auto_ping_ack.rst \
+ nghttp2_option_set_no_auto_window_update.rst \
+ nghttp2_option_set_no_closed_streams.rst \
+ nghttp2_option_set_no_http_messaging.rst \
+ nghttp2_option_set_no_recv_client_magic.rst \
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation.rst \
+ nghttp2_option_set_peer_max_concurrent_streams.rst \
+ nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
+ nghttp2_option_set_user_recv_extension_type.rst \
+ nghttp2_option_set_max_outbound_ack.rst \
+ nghttp2_option_set_max_settings.rst \
+ nghttp2_option_set_stream_reset_rate_limit.rst \
+ nghttp2_pack_settings_payload.rst \
+ nghttp2_priority_spec_check_default.rst \
+ nghttp2_priority_spec_default_init.rst \
+ nghttp2_priority_spec_init.rst \
+ nghttp2_rcbuf_decref.rst \
+ nghttp2_rcbuf_get_buf.rst \
+ nghttp2_rcbuf_incref.rst \
+ nghttp2_rcbuf_is_static.rst \
+ nghttp2_select_next_protocol.rst \
+ nghttp2_select_alpn.rst \
+ nghttp2_session_callbacks_del.rst \
+ nghttp2_session_callbacks_new.rst \
+ nghttp2_session_callbacks_set_before_frame_send_callback.rst \
+ nghttp2_session_callbacks_set_data_source_read_length_callback.rst \
+ nghttp2_session_callbacks_set_error_callback.rst \
+ nghttp2_session_callbacks_set_error_callback2.rst \
+ nghttp2_session_callbacks_set_on_begin_frame_callback.rst \
+ nghttp2_session_callbacks_set_on_begin_headers_callback.rst \
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst \
+ nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst \
+ nghttp2_session_callbacks_set_on_frame_not_send_callback.rst \
+ nghttp2_session_callbacks_set_on_frame_recv_callback.rst \
+ nghttp2_session_callbacks_set_on_frame_send_callback.rst \
+ nghttp2_session_callbacks_set_on_header_callback.rst \
+ nghttp2_session_callbacks_set_on_header_callback2.rst \
+ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \
+ nghttp2_session_callbacks_set_on_invalid_header_callback.rst \
+ nghttp2_session_callbacks_set_on_invalid_header_callback2.rst \
+ nghttp2_session_callbacks_set_on_stream_close_callback.rst \
+ nghttp2_session_callbacks_set_pack_extension_callback.rst \
+ nghttp2_session_callbacks_set_recv_callback.rst \
+ nghttp2_session_callbacks_set_select_padding_callback.rst \
+ nghttp2_session_callbacks_set_send_callback.rst \
+ nghttp2_session_callbacks_set_send_data_callback.rst \
+ nghttp2_session_callbacks_set_unpack_extension_callback.rst \
+ nghttp2_session_change_extpri_stream_priority.rst \
+ nghttp2_session_change_stream_priority.rst \
+ nghttp2_session_check_request_allowed.rst \
+ nghttp2_session_check_server_session.rst \
+ nghttp2_session_client_new.rst \
+ nghttp2_session_client_new2.rst \
+ nghttp2_session_client_new3.rst \
+ nghttp2_session_consume.rst \
+ nghttp2_session_consume_connection.rst \
+ nghttp2_session_consume_stream.rst \
+ nghttp2_session_create_idle_stream.rst \
+ nghttp2_session_del.rst \
+ nghttp2_session_find_stream.rst \
+ nghttp2_session_get_effective_local_window_size.rst \
+ nghttp2_session_get_effective_recv_data_length.rst \
+ nghttp2_session_get_extpri_stream_priority.rst \
+ nghttp2_session_get_hd_deflate_dynamic_table_size.rst \
+ nghttp2_session_get_hd_inflate_dynamic_table_size.rst \
+ nghttp2_session_get_last_proc_stream_id.rst \
+ nghttp2_session_get_local_settings.rst \
+ nghttp2_session_get_local_window_size.rst \
+ nghttp2_session_get_next_stream_id.rst \
+ nghttp2_session_get_outbound_queue_size.rst \
+ nghttp2_session_get_remote_settings.rst \
+ nghttp2_session_get_remote_window_size.rst \
+ nghttp2_session_get_root_stream.rst \
+ nghttp2_session_get_stream_effective_local_window_size.rst \
+ nghttp2_session_get_stream_effective_recv_data_length.rst \
+ nghttp2_session_get_stream_local_close.rst \
+ nghttp2_session_get_stream_local_window_size.rst \
+ nghttp2_session_get_stream_remote_close.rst \
+ nghttp2_session_get_stream_remote_window_size.rst \
+ nghttp2_session_get_stream_user_data.rst \
+ nghttp2_session_mem_recv.rst \
+ nghttp2_session_mem_send.rst \
+ nghttp2_session_recv.rst \
+ nghttp2_session_resume_data.rst \
+ nghttp2_session_send.rst \
+ nghttp2_session_server_new.rst \
+ nghttp2_session_server_new2.rst \
+ nghttp2_session_server_new3.rst \
+ nghttp2_session_set_local_window_size.rst \
+ nghttp2_session_set_next_stream_id.rst \
+ nghttp2_session_set_stream_user_data.rst \
+ nghttp2_session_set_user_data.rst \
+ nghttp2_session_terminate_session.rst \
+ nghttp2_session_terminate_session2.rst \
+ nghttp2_session_upgrade.rst \
+ nghttp2_session_upgrade2.rst \
+ nghttp2_session_want_read.rst \
+ nghttp2_session_want_write.rst \
+ nghttp2_set_debug_vprintf_callback.rst \
+ nghttp2_stream_get_first_child.rst \
+ nghttp2_stream_get_next_sibling.rst \
+ nghttp2_stream_get_parent.rst \
+ nghttp2_stream_get_previous_sibling.rst \
+ nghttp2_stream_get_state.rst \
+ nghttp2_stream_get_sum_dependency_weight.rst \
+ nghttp2_stream_get_weight.rst \
+ nghttp2_strerror.rst \
+ nghttp2_submit_altsvc.rst \
+ nghttp2_submit_data.rst \
+ nghttp2_submit_extension.rst \
+ nghttp2_submit_goaway.rst \
+ nghttp2_submit_headers.rst \
+ nghttp2_submit_origin.rst \
+ nghttp2_submit_ping.rst \
+ nghttp2_submit_priority.rst \
+ nghttp2_submit_priority_update.rst \
+ nghttp2_submit_push_promise.rst \
+ nghttp2_submit_request.rst \
+ nghttp2_submit_response.rst \
+ nghttp2_submit_rst_stream.rst \
+ nghttp2_submit_settings.rst \
+ nghttp2_submit_shutdown_notice.rst \
+ nghttp2_submit_trailer.rst \
+ nghttp2_submit_window_update.rst \
+ nghttp2_version.rst
+
+RST_FILES = \
+ README.rst \
+ programmers-guide.rst \
+ nghttp.1.rst \
+ nghttpd.1.rst \
+ nghttpx.1.rst \
+ h2load.1.rst
+
+EXTRA_DIST = \
+ CMakeLists.txt \
+ mkapiref.py \
+ $(RST_FILES) \
+ $(APIDOCS) \
+ sources/index.rst \
+ sources/tutorial-client.rst \
+ sources/tutorial-server.rst \
+ sources/tutorial-hpack.rst \
+ sources/nghttpx-howto.rst \
+ sources/h2load-howto.rst \
+ sources/building-android-binary.rst \
+ sources/contribute.rst \
+ sources/security.rst \
+ _exts/rubydomain/LICENSE.rubydomain \
+ _exts/rubydomain/__init__.py \
+ _exts/rubydomain/rubydomain.py \
+ $(man_MANS) \
+ bash_completion/nghttp \
+ bash_completion/nghttpd \
+ bash_completion/nghttpx \
+ bash_completion/h2load
+
+# 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_srcdir)/lib/includes/nghttp2/nghttp2.h
+ for i in $(RST_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done
+ $(PYTHON) $(top_srcdir)/doc/mkapiref.py \
+ apiref.rst macros.rst enums.rst types.rst . $^
+
+$(APIDOCS): apiref.rst
+
+clean-local:
+ if [ $(srcdir) != $(builddir) ]; then for i in $(RST_FILES); do rm -f $(builddir)/$$i; done fi
+ -rm -f apiref.rst
+ -rm -f $(APIDOCS)
+ -rm -rf $(BUILDDIR)/*
+
+html-local: 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
index 0000000..549e550
--- /dev/null
+++ b/doc/README.rst
@@ -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-called 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/_exts/rubydomain/LICENSE.rubydomain b/doc/_exts/rubydomain/LICENSE.rubydomain
new file mode 100644
index 0000000..a560d75
--- /dev/null
+++ b/doc/_exts/rubydomain/LICENSE.rubydomain
@@ -0,0 +1,28 @@
+If not otherwise noted, the extensions in this package are licensed
+under the following license.
+
+Copyright (c) 2010 by the contributors (see AUTHORS file).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/doc/_exts/rubydomain/__init__.py b/doc/_exts/rubydomain/__init__.py
new file mode 100644
index 0000000..b5a7dc2
--- /dev/null
+++ b/doc/_exts/rubydomain/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinxcontrib
+ ~~~~~~~~~~~~~
+
+ This package is a namespace package that contains all extensions
+ distributed in the ``sphinx-contrib`` distribution.
+
+ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+__import__('pkg_resources').declare_namespace(__name__)
+
diff --git a/doc/_exts/rubydomain/rubydomain.py b/doc/_exts/rubydomain/rubydomain.py
new file mode 100644
index 0000000..db35233
--- /dev/null
+++ b/doc/_exts/rubydomain/rubydomain.py
@@ -0,0 +1,703 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.domains.ruby
+ ~~~~~~~~~~~~~~~~~~~
+
+ The Ruby domain.
+
+ :copyright: Copyright 2010 by SHIBUKAWA Yoshiki
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive
+
+from sphinx import addnodes
+from sphinx import version_info
+from sphinx.roles import XRefRole
+from sphinx.locale import _
+from sphinx.domains import Domain, ObjType, Index
+from sphinx.directives import ObjectDescription
+from sphinx.util.nodes import make_refnode
+from sphinx.util.docfields import Field, GroupedField, TypedField
+
+# REs for Ruby signatures
+rb_sig_re = re.compile(
+ r'''^ ([\w.]*\.)? # class name(s)
+ (\$?\w+\??!?) \s* # thing name
+ (?: \((.*)\) # optional: arguments
+ (?:\s* -> \s* (.*))? # return annotation
+ )? $ # and nothing more
+ ''', re.VERBOSE)
+
+rb_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
+
+separators = {
+ 'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#',
+ 'function':'.', 'classmethod':'.', 'class':'::', 'module':'::',
+ 'global':'', 'const':'::'}
+
+rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?")
+
+
+def _iteritems(d):
+
+ for k in d:
+ yield k, d[k]
+
+
+def ruby_rsplit(fullname):
+ items = [item for item in rb_separator.findall(fullname)]
+ return ''.join(items[:-2]), items[-1]
+
+
+class RubyObject(ObjectDescription):
+ """
+ Description of a general Ruby object.
+ """
+ option_spec = {
+ 'noindex': directives.flag,
+ 'module': directives.unchanged,
+ }
+
+ doc_field_types = [
+ TypedField('parameter', label=_('Parameters'),
+ names=('param', 'parameter', 'arg', 'argument'),
+ typerolename='obj', typenames=('paramtype', 'type')),
+ TypedField('variable', label=_('Variables'), rolename='obj',
+ names=('var', 'ivar', 'cvar'),
+ typerolename='obj', typenames=('vartype',)),
+ GroupedField('exceptions', label=_('Raises'), rolename='exc',
+ names=('raises', 'raise', 'exception', 'except'),
+ can_collapse=True),
+ Field('returnvalue', label=_('Returns'), has_arg=False,
+ names=('returns', 'return')),
+ Field('returntype', label=_('Return type'), has_arg=False,
+ names=('rtype',)),
+ ]
+
+ def get_signature_prefix(self, sig):
+ """
+ May return a prefix to put before the object name in the signature.
+ """
+ return ''
+
+ def needs_arglist(self):
+ """
+ May return true if an empty argument list is to be generated even if
+ the document contains none.
+ """
+ return False
+
+ def handle_signature(self, sig, signode):
+ """
+ Transform a Ruby signature into RST nodes.
+ Returns (fully qualified name of the thing, classname if any).
+
+ If inside a class, the current class name is handled intelligently:
+ * it is stripped from the displayed name if present
+ * it is added to the full name (return value) if not present
+ """
+ m = rb_sig_re.match(sig)
+ if m is None:
+ raise ValueError
+ name_prefix, name, arglist, retann = m.groups()
+ if not name_prefix:
+ name_prefix = ""
+ # determine module and class name (if applicable), as well as full name
+ modname = self.options.get(
+ 'module', self.env.temp_data.get('rb:module'))
+ classname = self.env.temp_data.get('rb:class')
+ if self.objtype == 'global':
+ add_module = False
+ modname = None
+ classname = None
+ fullname = name
+ elif classname:
+ add_module = False
+ if name_prefix and name_prefix.startswith(classname):
+ fullname = name_prefix + name
+ # class name is given again in the signature
+ name_prefix = name_prefix[len(classname):].lstrip('.')
+ else:
+ separator = separators[self.objtype]
+ fullname = classname + separator + name_prefix + name
+ else:
+ add_module = True
+ if name_prefix:
+ classname = name_prefix.rstrip('.')
+ fullname = name_prefix + name
+ else:
+ classname = ''
+ fullname = name
+
+ signode['module'] = modname
+ signode['class'] = self.class_name = classname
+ signode['fullname'] = fullname
+
+ sig_prefix = self.get_signature_prefix(sig)
+ if sig_prefix:
+ signode += addnodes.desc_annotation(sig_prefix, sig_prefix)
+
+ if name_prefix:
+ signode += addnodes.desc_addname(name_prefix, name_prefix)
+ # exceptions are a special case, since they are documented in the
+ # 'exceptions' module.
+ elif add_module and self.env.config.add_module_names:
+ if self.objtype == 'global':
+ nodetext = ''
+ signode += addnodes.desc_addname(nodetext, nodetext)
+ else:
+ modname = self.options.get(
+ 'module', self.env.temp_data.get('rb:module'))
+ if modname and modname != 'exceptions':
+ nodetext = modname + separators[self.objtype]
+ signode += addnodes.desc_addname(nodetext, nodetext)
+
+ signode += addnodes.desc_name(name, name)
+ if not arglist:
+ if self.needs_arglist():
+ # for callables, add an empty parameter list
+ signode += addnodes.desc_parameterlist()
+ if retann:
+ signode += addnodes.desc_returns(retann, retann)
+ return fullname, name_prefix
+ signode += addnodes.desc_parameterlist()
+
+ stack = [signode[-1]]
+ for token in rb_paramlist_re.split(arglist):
+ if token == '[':
+ opt = addnodes.desc_optional()
+ stack[-1] += opt
+ stack.append(opt)
+ elif token == ']':
+ try:
+ stack.pop()
+ except IndexError:
+ raise ValueError
+ elif not token or token == ',' or token.isspace():
+ pass
+ else:
+ token = token.strip()
+ stack[-1] += addnodes.desc_parameter(token, token)
+ if len(stack) != 1:
+ raise ValueError
+ if retann:
+ signode += addnodes.desc_returns(retann, retann)
+ return fullname, name_prefix
+
+ def get_index_text(self, modname, name):
+ """
+ Return the text for the index entry of the object.
+ """
+ raise NotImplementedError('must be implemented in subclasses')
+
+ def _is_class_member(self):
+ return self.objtype.endswith('method') or self.objtype.startswith('attr')
+
+ def add_target_and_index(self, name_cls, sig, signode):
+ if self.objtype == 'global':
+ modname = ''
+ else:
+ modname = self.options.get(
+ 'module', self.env.temp_data.get('rb:module'))
+ separator = separators[self.objtype]
+ if self._is_class_member():
+ if signode['class']:
+ prefix = modname and modname + '::' or ''
+ else:
+ prefix = modname and modname + separator or ''
+ else:
+ prefix = modname and modname + separator or ''
+ fullname = prefix + name_cls[0]
+ # note target
+ if fullname not in self.state.document.ids:
+ signode['names'].append(fullname)
+ signode['ids'].append(fullname)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+ objects = self.env.domaindata['rb']['objects']
+ if fullname in objects:
+ self.env.warn(
+ self.env.docname,
+ 'duplicate object description of %s, ' % fullname +
+ 'other instance in ' +
+ self.env.doc2path(objects[fullname][0]),
+ self.lineno)
+ objects[fullname] = (self.env.docname, self.objtype)
+
+ indextext = self.get_index_text(modname, name_cls)
+ if indextext:
+ self.indexnode['entries'].append(
+ _make_index('single', indextext, fullname, fullname))
+
+ def before_content(self):
+ # needed for automatic qualification of members (reset in subclasses)
+ self.clsname_set = False
+
+ def after_content(self):
+ if self.clsname_set:
+ self.env.temp_data['rb:class'] = None
+
+
+class RubyModulelevel(RubyObject):
+ """
+ Description of an object on module level (functions, data).
+ """
+
+ def needs_arglist(self):
+ return self.objtype == 'function'
+
+ def get_index_text(self, modname, name_cls):
+ if self.objtype == 'function':
+ if not modname:
+ return _('%s() (global function)') % name_cls[0]
+ return _('%s() (module function in %s)') % (name_cls[0], modname)
+ else:
+ return ''
+
+
+class RubyGloballevel(RubyObject):
+ """
+ Description of an object on module level (functions, data).
+ """
+
+ def get_index_text(self, modname, name_cls):
+ if self.objtype == 'global':
+ return _('%s (global variable)') % name_cls[0]
+ else:
+ return ''
+
+
+class RubyEverywhere(RubyObject):
+ """
+ Description of a class member (methods, attributes).
+ """
+
+ def needs_arglist(self):
+ return self.objtype == 'method'
+
+ def get_index_text(self, modname, name_cls):
+ name, cls = name_cls
+ add_modules = self.env.config.add_module_names
+ if self.objtype == 'method':
+ try:
+ clsname, methname = ruby_rsplit(name)
+ except ValueError:
+ if modname:
+ return _('%s() (in module %s)') % (name, modname)
+ else:
+ return '%s()' % name
+ if modname and add_modules:
+ return _('%s() (%s::%s method)') % (methname, modname,
+ clsname)
+ else:
+ return _('%s() (%s method)') % (methname, clsname)
+ else:
+ return ''
+
+
+class RubyClasslike(RubyObject):
+ """
+ Description of a class-like object (classes, exceptions).
+ """
+
+ def get_signature_prefix(self, sig):
+ return self.objtype + ' '
+
+ def get_index_text(self, modname, name_cls):
+ if self.objtype == 'class':
+ if not modname:
+ return _('%s (class)') % name_cls[0]
+ return _('%s (class in %s)') % (name_cls[0], modname)
+ elif self.objtype == 'exception':
+ return name_cls[0]
+ else:
+ return ''
+
+ def before_content(self):
+ RubyObject.before_content(self)
+ if self.names:
+ self.env.temp_data['rb:class'] = self.names[0][0]
+ self.clsname_set = True
+
+
+class RubyClassmember(RubyObject):
+ """
+ Description of a class member (methods, attributes).
+ """
+
+ def needs_arglist(self):
+ return self.objtype.endswith('method')
+
+ def get_signature_prefix(self, sig):
+ if self.objtype == 'classmethod':
+ return "classmethod %s." % self.class_name
+ elif self.objtype == 'attr_reader':
+ return "attribute [R] "
+ elif self.objtype == 'attr_writer':
+ return "attribute [W] "
+ elif self.objtype == 'attr_accessor':
+ return "attribute [R/W] "
+ return ''
+
+ def get_index_text(self, modname, name_cls):
+ name, cls = name_cls
+ add_modules = self.env.config.add_module_names
+ if self.objtype == 'classmethod':
+ try:
+ clsname, methname = ruby_rsplit(name)
+ except ValueError:
+ return '%s()' % name
+ if modname:
+ return _('%s() (%s.%s class method)') % (methname, modname,
+ clsname)
+ else:
+ return _('%s() (%s class method)') % (methname, clsname)
+ elif self.objtype.startswith('attr'):
+ try:
+ clsname, attrname = ruby_rsplit(name)
+ except ValueError:
+ return name
+ if modname and add_modules:
+ return _('%s (%s.%s attribute)') % (attrname, modname, clsname)
+ else:
+ return _('%s (%s attribute)') % (attrname, clsname)
+ else:
+ return ''
+
+ def before_content(self):
+ RubyObject.before_content(self)
+ lastname = self.names and self.names[-1][1]
+ if lastname and not self.env.temp_data.get('rb:class'):
+ self.env.temp_data['rb:class'] = lastname.strip('.')
+ self.clsname_set = True
+
+
+class RubyModule(Directive):
+ """
+ Directive to mark description of a new module.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {
+ 'platform': lambda x: x,
+ 'synopsis': lambda x: x,
+ 'noindex': directives.flag,
+ 'deprecated': directives.flag,
+ }
+
+ def run(self):
+ env = self.state.document.settings.env
+ modname = self.arguments[0].strip()
+ noindex = 'noindex' in self.options
+ env.temp_data['rb:module'] = modname
+ env.domaindata['rb']['modules'][modname] = \
+ (env.docname, self.options.get('synopsis', ''),
+ self.options.get('platform', ''), 'deprecated' in self.options)
+ targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
+ self.state.document.note_explicit_target(targetnode)
+ ret = [targetnode]
+ # XXX this behavior of the module directive is a mess...
+ if 'platform' in self.options:
+ platform = self.options['platform']
+ node = nodes.paragraph()
+ node += nodes.emphasis('', _('Platforms: '))
+ node += nodes.Text(platform, platform)
+ ret.append(node)
+ # the synopsis isn't printed; in fact, it is only used in the
+ # modindex currently
+ if not noindex:
+ indextext = _('%s (module)') % modname
+ inode = addnodes.index(entries=[_make_index(
+ 'single', indextext, 'module-' + modname, modname)])
+ ret.append(inode)
+ return ret
+
+def _make_index(entrytype, entryname, target, ignored, key=None):
+ # Sphinx 1.4 introduced backward incompatible changes, it now
+ # requires 5 tuples. Last one is categorization key. See
+ # http://www.sphinx-doc.org/en/stable/extdev/nodes.html#sphinx.addnodes.index
+ if version_info >= (1, 4, 0, '', 0):
+ return (entrytype, entryname, target, ignored, key)
+ else:
+ return (entrytype, entryname, target, ignored)
+
+class RubyCurrentModule(Directive):
+ """
+ This directive is just to tell Sphinx that we're documenting
+ stuff in module foo, but links to module foo won't lead here.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {}
+
+ def run(self):
+ env = self.state.document.settings.env
+ modname = self.arguments[0].strip()
+ if modname == 'None':
+ env.temp_data['rb:module'] = None
+ else:
+ env.temp_data['rb:module'] = modname
+ return []
+
+
+class RubyXRefRole(XRefRole):
+ def process_link(self, env, refnode, has_explicit_title, title, target):
+ if not has_explicit_title:
+ title = title.lstrip('.') # only has a meaning for the target
+ title = title.lstrip('#')
+ if title.startswith("::"):
+ title = title[2:]
+ target = target.lstrip('~') # only has a meaning for the title
+ # if the first character is a tilde, don't display the module/class
+ # parts of the contents
+ if title[0:1] == '~':
+ m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title)
+ if m:
+ title = m.group(1)
+ if not title.startswith("$"):
+ refnode['rb:module'] = env.temp_data.get('rb:module')
+ refnode['rb:class'] = env.temp_data.get('rb:class')
+ # if the first character is a dot, search more specific namespaces first
+ # else search builtins first
+ if target[0:1] == '.':
+ target = target[1:]
+ refnode['refspecific'] = True
+ return title, target
+
+
+class RubyModuleIndex(Index):
+ """
+ Index subclass to provide the Ruby module index.
+ """
+
+ name = 'modindex'
+ localname = _('Ruby Module Index')
+ shortname = _('modules')
+
+ def generate(self, docnames=None):
+ content = {}
+ # list of prefixes to ignore
+ ignores = self.domain.env.config['modindex_common_prefix']
+ ignores = sorted(ignores, key=len, reverse=True)
+ # list of all modules, sorted by module name
+ modules = sorted(_iteritems(self.domain.data['modules']),
+ key=lambda x: x[0].lower())
+ # sort out collapsible modules
+ prev_modname = ''
+ num_toplevels = 0
+ for modname, (docname, synopsis, platforms, deprecated) in modules:
+ if docnames and docname not in docnames:
+ continue
+
+ for ignore in ignores:
+ if modname.startswith(ignore):
+ modname = modname[len(ignore):]
+ stripped = ignore
+ break
+ else:
+ stripped = ''
+
+ # we stripped the whole module name?
+ if not modname:
+ modname, stripped = stripped, ''
+
+ entries = content.setdefault(modname[0].lower(), [])
+
+ package = modname.split('::')[0]
+ if package != modname:
+ # it's a submodule
+ if prev_modname == package:
+ # first submodule - make parent a group head
+ entries[-1][1] = 1
+ elif not prev_modname.startswith(package):
+ # submodule without parent in list, add dummy entry
+ entries.append([stripped + package, 1, '', '', '', '', ''])
+ subtype = 2
+ else:
+ num_toplevels += 1
+ subtype = 0
+
+ qualifier = deprecated and _('Deprecated') or ''
+ entries.append([stripped + modname, subtype, docname,
+ 'module-' + stripped + modname, platforms,
+ qualifier, synopsis])
+ prev_modname = modname
+
+ # apply heuristics when to collapse modindex at page load:
+ # only collapse if number of toplevel modules is larger than
+ # number of submodules
+ collapse = len(modules) - num_toplevels < num_toplevels
+
+ # sort by first letter
+ content = sorted(_iteritems(content))
+
+ return content, collapse
+
+
+class RubyDomain(Domain):
+ """Ruby language domain."""
+ name = 'rb'
+ label = 'Ruby'
+ object_types = {
+ 'function': ObjType(_('function'), 'func', 'obj'),
+ 'global': ObjType(_('global variable'), 'global', 'obj'),
+ 'method': ObjType(_('method'), 'meth', 'obj'),
+ 'class': ObjType(_('class'), 'class', 'obj'),
+ 'exception': ObjType(_('exception'), 'exc', 'obj'),
+ 'classmethod': ObjType(_('class method'), 'meth', 'obj'),
+ 'attr_reader': ObjType(_('attribute'), 'attr', 'obj'),
+ 'attr_writer': ObjType(_('attribute'), 'attr', 'obj'),
+ 'attr_accessor': ObjType(_('attribute'), 'attr', 'obj'),
+ 'const': ObjType(_('const'), 'const', 'obj'),
+ 'module': ObjType(_('module'), 'mod', 'obj'),
+ }
+
+ directives = {
+ 'function': RubyModulelevel,
+ 'global': RubyGloballevel,
+ 'method': RubyEverywhere,
+ 'const': RubyEverywhere,
+ 'class': RubyClasslike,
+ 'exception': RubyClasslike,
+ 'classmethod': RubyClassmember,
+ 'attr_reader': RubyClassmember,
+ 'attr_writer': RubyClassmember,
+ 'attr_accessor': RubyClassmember,
+ 'module': RubyModule,
+ 'currentmodule': RubyCurrentModule,
+ }
+
+ roles = {
+ 'func': RubyXRefRole(fix_parens=False),
+ 'global':RubyXRefRole(),
+ 'class': RubyXRefRole(),
+ 'exc': RubyXRefRole(),
+ 'meth': RubyXRefRole(fix_parens=False),
+ 'attr': RubyXRefRole(),
+ 'const': RubyXRefRole(),
+ 'mod': RubyXRefRole(),
+ 'obj': RubyXRefRole(),
+ }
+ initial_data = {
+ 'objects': {}, # fullname -> docname, objtype
+ 'modules': {}, # modname -> docname, synopsis, platform, deprecated
+ }
+ indices = [
+ RubyModuleIndex,
+ ]
+
+ def clear_doc(self, docname):
+ for fullname, (fn, _) in list(self.data['objects'].items()):
+ if fn == docname:
+ del self.data['objects'][fullname]
+ for modname, (fn, _, _, _) in list(self.data['modules'].items()):
+ if fn == docname:
+ del self.data['modules'][modname]
+
+ def find_obj(self, env, modname, classname, name, type, searchorder=0):
+ """
+ Find a Ruby object for "name", perhaps using the given module and/or
+ classname.
+ """
+ # skip parens
+ if name[-2:] == '()':
+ name = name[:-2]
+
+ if not name:
+ return None, None
+
+ objects = self.data['objects']
+
+ newname = None
+ if searchorder == 1:
+ if modname and classname and \
+ modname + '::' + classname + '#' + name in objects:
+ newname = modname + '::' + classname + '#' + name
+ elif modname and classname and \
+ modname + '::' + classname + '.' + name in objects:
+ newname = modname + '::' + classname + '.' + name
+ elif modname and modname + '::' + name in objects:
+ newname = modname + '::' + name
+ elif modname and modname + '#' + name in objects:
+ newname = modname + '#' + name
+ elif modname and modname + '.' + name in objects:
+ newname = modname + '.' + name
+ elif classname and classname + '.' + name in objects:
+ newname = classname + '.' + name
+ elif classname and classname + '#' + name in objects:
+ newname = classname + '#' + name
+ elif name in objects:
+ newname = name
+ else:
+ if name in objects:
+ newname = name
+ elif classname and classname + '.' + name in objects:
+ newname = classname + '.' + name
+ elif classname and classname + '#' + name in objects:
+ newname = classname + '#' + name
+ elif modname and modname + '::' + name in objects:
+ newname = modname + '::' + name
+ elif modname and modname + '#' + name in objects:
+ newname = modname + '#' + name
+ elif modname and modname + '.' + name in objects:
+ newname = modname + '.' + name
+ elif modname and classname and \
+ modname + '::' + classname + '#' + name in objects:
+ newname = modname + '::' + classname + '#' + name
+ elif modname and classname and \
+ modname + '::' + classname + '.' + name in objects:
+ newname = modname + '::' + classname + '.' + name
+ # special case: object methods
+ elif type in ('func', 'meth') and '.' not in name and \
+ 'object.' + name in objects:
+ newname = 'object.' + name
+ if newname is None:
+ return None, None
+ return newname, objects[newname]
+
+ def resolve_xref(self, env, fromdocname, builder,
+ typ, target, node, contnode):
+ if (typ == 'mod' or
+ typ == 'obj' and target in self.data['modules']):
+ docname, synopsis, platform, deprecated = \
+ self.data['modules'].get(target, ('','','', ''))
+ if not docname:
+ return None
+ else:
+ title = '%s%s%s' % ((platform and '(%s) ' % platform),
+ synopsis,
+ (deprecated and ' (deprecated)' or ''))
+ return make_refnode(builder, fromdocname, docname,
+ 'module-' + target, contnode, title)
+ else:
+ modname = node.get('rb:module')
+ clsname = node.get('rb:class')
+ searchorder = node.hasattr('refspecific') and 1 or 0
+ name, obj = self.find_obj(env, modname, clsname,
+ target, typ, searchorder)
+ if not obj:
+ return None
+ else:
+ return make_refnode(builder, fromdocname, obj[0], name,
+ contnode, name)
+
+ def get_objects(self):
+ for modname, info in _iteritems(self.data['modules']):
+ yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
+ for refname, (docname, type) in _iteritems(self.data['objects']):
+ yield (refname, refname, type, docname, refname, 1)
+
+
+def setup(app):
+ app.add_domain(RubyDomain)
diff --git a/doc/bash_completion/h2load b/doc/bash_completion/h2load
new file mode 100644
index 0000000..2b2d4ab
--- /dev/null
+++ b/doc/bash_completion/h2load
@@ -0,0 +1,19 @@
+_h2load()
+{
+ local cur prev split=false
+ COMPREPLY=()
+ COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
+
+ cmd=${COMP_WORDS[0]}
+ _get_comp_words_by_ref cur prev
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W '--requests --clients --threads --input-file --max-concurrent-streams --max-frame-size --window-bits --connection-window-bits --header --ciphers --tls13-ciphers --no-tls-proto --data --rate --rate-period --duration --warm-up-time --connection-active-timeout --connection-inactivity-timeout --timing-script-file --base-uri --alpn-list --h1 --header-table-size --encoder-header-table-size --log-file --qlog-file-base --connect-to --rps --groups --no-udp-gso --max-udp-payload-size --ktls --verbose --version --help ' -- "$cur" ) )
+ ;;
+ *)
+ _filedir
+ return 0
+ esac
+ return 0
+}
+complete -F _h2load h2load
diff --git a/doc/bash_completion/make_bash_completion.py b/doc/bash_completion/make_bash_completion.py
new file mode 100755
index 0000000..e3a535b
--- /dev/null
+++ b/doc/bash_completion/make_bash_completion.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import subprocess
+import io
+import re
+import sys
+import os.path
+
+class Option:
+ def __init__(self, long_opt, short_opt):
+ self.long_opt = long_opt
+ self.short_opt = short_opt
+
+def get_all_options(cmd):
+ opt_pattern = re.compile(r' (?:(-.), )?(--[^\s\[=]+)(\[)?')
+ proc = subprocess.Popen([cmd, "--help"], stdout=subprocess.PIPE)
+ stdoutdata, _ = proc.communicate()
+ cur_option = None
+ opts = {}
+ for line in io.StringIO(stdoutdata.decode('utf-8')):
+ match = opt_pattern.match(line)
+ if not match:
+ continue
+ long_opt = match.group(2)
+ short_opt = match.group(1)
+ opts[long_opt] = Option(long_opt, short_opt)
+
+ return opts
+
+def output_case(out, name, opts):
+ out.write('''\
+_{name}()
+{{
+ local cur prev split=false
+ COMPREPLY=()
+ COMP_WORDBREAKS=${{COMP_WORDBREAKS//=}}
+
+ cmd=${{COMP_WORDS[0]}}
+ _get_comp_words_by_ref cur prev
+'''.format(name=name))
+
+ # Complete option name.
+ out.write('''\
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W '\
+''')
+ for opt in opts.values():
+ out.write(opt.long_opt)
+ out.write(' ')
+
+ out.write('''\
+' -- "$cur" ) )
+ ;;
+''')
+ # If no option found for completion then complete with files.
+ out.write('''\
+ *)
+ _filedir
+ return 0
+ esac
+ return 0
+}}
+complete -F _{name} {name}
+'''.format(name=name))
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Generates bash_completion using `/path/to/cmd --help'")
+ print("Usage: make_bash_completion.py /path/to/cmd")
+ exit(1)
+ name = os.path.basename(sys.argv[1])
+ opts = get_all_options(sys.argv[1])
+ output_case(sys.stdout, name, opts)
diff --git a/doc/bash_completion/nghttp b/doc/bash_completion/nghttp
new file mode 100644
index 0000000..dd48403
--- /dev/null
+++ b/doc/bash_completion/nghttp
@@ -0,0 +1,19 @@
+_nghttp()
+{
+ local cur prev split=false
+ COMPREPLY=()
+ COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
+
+ cmd=${COMP_WORDS[0]}
+ _get_comp_words_by_ref cur prev
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W '--verbose --null-out --remote-name --timeout --window-bits --connection-window-bits --get-assets --stat --header --trailer --cert --key --data --multiply --upgrade --weight --peer-max-concurrent-streams --header-table-size --encoder-header-table-size --padding --har --color --continuation --no-content-length --no-dep --hexdump --no-push --max-concurrent-streams --expect-continue --no-verify-peer --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) )
+ ;;
+ *)
+ _filedir
+ return 0
+ esac
+ return 0
+}
+complete -F _nghttp nghttp
diff --git a/doc/bash_completion/nghttpd b/doc/bash_completion/nghttpd
new file mode 100644
index 0000000..7dd4d9b
--- /dev/null
+++ b/doc/bash_completion/nghttpd
@@ -0,0 +1,19 @@
+_nghttpd()
+{
+ local cur prev split=false
+ COMPREPLY=()
+ COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
+
+ cmd=${COMP_WORDS[0]}
+ _get_comp_words_by_ref cur prev
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W '--address --daemon --verify-client --htdocs --verbose --no-tls --header-table-size --encoder-header-table-size --color --push --padding --max-concurrent-streams --workers --error-gzip --window-bits --connection-window-bits --dh-param-file --early-response --trailer --hexdump --echo-upload --mime-types-file --no-content-length --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) )
+ ;;
+ *)
+ _filedir
+ return 0
+ esac
+ return 0
+}
+complete -F _nghttpd nghttpd
diff --git a/doc/bash_completion/nghttpx b/doc/bash_completion/nghttpx
new file mode 100644
index 0000000..a735cd2
--- /dev/null
+++ b/doc/bash_completion/nghttpx
@@ -0,0 +1,19 @@
+_nghttpx()
+{
+ local cur prev split=false
+ COMPREPLY=()
+ COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
+
+ cmd=${COMP_WORDS[0]}
+ _get_comp_words_by_ref cur prev
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W '--backend --frontend --backlog --backend-address-family --backend-http-proxy-uri --workers --single-thread --read-rate --read-burst --write-rate --write-burst --worker-read-rate --worker-read-burst --worker-write-rate --worker-write-burst --worker-frontend-connections --backend-connections-per-host --backend-connections-per-frontend --rlimit-nofile --rlimit-memlock --backend-request-buffer --backend-response-buffer --fastopen --no-kqueue --frontend-http2-read-timeout --frontend-http3-read-timeout --frontend-read-timeout --frontend-write-timeout --frontend-keep-alive-timeout --stream-read-timeout --stream-write-timeout --backend-read-timeout --backend-write-timeout --backend-connect-timeout --backend-keep-alive-timeout --listener-disable-timeout --frontend-http2-setting-timeout --backend-http2-settings-timeout --backend-max-backoff --ciphers --tls13-ciphers --client-ciphers --tls13-client-ciphers --ecdh-curves --insecure --cacert --private-key-passwd-file --subcert --dh-param-file --alpn-list --verify-client --verify-client-cacert --verify-client-tolerate-expired --client-private-key-file --client-cert-file --tls-min-proto-version --tls-max-proto-version --tls-ticket-key-file --tls-ticket-key-memcached --tls-ticket-key-memcached-address-family --tls-ticket-key-memcached-interval --tls-ticket-key-memcached-max-retry --tls-ticket-key-memcached-max-fail --tls-ticket-key-cipher --tls-ticket-key-memcached-cert-file --tls-ticket-key-memcached-private-key-file --fetch-ocsp-response-file --ocsp-update-interval --ocsp-startup --no-verify-ocsp --no-ocsp --tls-session-cache-memcached --tls-session-cache-memcached-address-family --tls-session-cache-memcached-cert-file --tls-session-cache-memcached-private-key-file --tls-dyn-rec-warmup-threshold --tls-dyn-rec-idle-timeout --no-http2-cipher-block-list --client-no-http2-cipher-block-list --tls-sct-dir --psk-secrets --client-psk-secrets --tls-no-postpone-early-data --tls-max-early-data --tls-ktls --frontend-http2-max-concurrent-streams --backend-http2-max-concurrent-streams --frontend-http2-window-size --frontend-http2-connection-window-size --backend-http2-window-size --backend-http2-connection-window-size --http2-no-cookie-crumbling --padding --no-server-push --frontend-http2-optimize-write-buffer-size --frontend-http2-optimize-window-size --frontend-http2-encoder-dynamic-table-size --frontend-http2-decoder-dynamic-table-size --backend-http2-encoder-dynamic-table-size --backend-http2-decoder-dynamic-table-size --http2-proxy --log-level --accesslog-file --accesslog-syslog --accesslog-format --accesslog-write-early --errorlog-file --errorlog-syslog --syslog-facility --add-x-forwarded-for --strip-incoming-x-forwarded-for --no-add-x-forwarded-proto --no-strip-incoming-x-forwarded-proto --add-forwarded --strip-incoming-forwarded --forwarded-by --forwarded-for --no-via --no-strip-incoming-early-data --no-location-rewrite --host-rewrite --altsvc --http2-altsvc --add-request-header --add-response-header --request-header-field-buffer --max-request-header-fields --response-header-field-buffer --max-response-header-fields --error-page --server-name --no-server-rewrite --redirect-https-port --require-http-scheme --api-max-request-body --dns-cache-timeout --dns-lookup-timeout --dns-max-try --frontend-max-requests --frontend-http2-dump-request-header --frontend-http2-dump-response-header --frontend-frame-debug --daemon --pid-file --user --single-process --max-worker-processes --worker-process-grace-shutdown-period --mruby-file --ignore-per-pattern-mruby-error --frontend-quic-idle-timeout --frontend-quic-debug-log --quic-bpf-program-file --frontend-quic-early-data --frontend-quic-qlog-dir --frontend-quic-require-token --frontend-quic-congestion-controller --frontend-quic-secret-file --quic-server-id --frontend-quic-initial-rtt --no-quic-bpf --frontend-http3-window-size --frontend-http3-connection-window-size --frontend-http3-max-window-size --frontend-http3-max-connection-window-size --frontend-http3-max-concurrent-streams --conf --include --version --help ' -- "$cur" ) )
+ ;;
+ *)
+ _filedir
+ return 0
+ esac
+ return 0
+}
+complete -F _nghttpx nghttpx
diff --git a/doc/building-android-binary.rst.in b/doc/building-android-binary.rst.in
new file mode 100644
index 0000000..2c77c98
--- /dev/null
+++ b/doc/building-android-binary.rst.in
@@ -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
index 0000000..228bdb1
--- /dev/null
+++ b/doc/conf.py.in
@@ -0,0 +1,252 @@
+# -*- 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('.'))
+
+sys.path.insert(0, os.path.abspath('@top_srcdir@/doc/_exts'))
+
+# -- 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 = ['rubydomain.rubydomain']
+
+# 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, 2016, 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 client', [u'Tatsuhiro Tsujikawa'], 1),
+ ('nghttpd.1', 'nghttpd', u'HTTP/2 server', [u'Tatsuhiro Tsujikawa'], 1),
+ ('nghttpx.1', 'nghttpx', u'HTTP/2 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
index 0000000..6a229d4
--- /dev/null
+++ b/doc/contribute.rst.in
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/contribute.rst
diff --git a/doc/docutils.conf b/doc/docutils.conf
new file mode 100644
index 0000000..f5208a8
--- /dev/null
+++ b/doc/docutils.conf
@@ -0,0 +1,2 @@
+[parsers]
+smart_quotes=no
diff --git a/doc/h2load-howto.rst.in b/doc/h2load-howto.rst.in
new file mode 100644
index 0000000..252069d
--- /dev/null
+++ b/doc/h2load-howto.rst.in
@@ -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
index 0000000..df052ab
--- /dev/null
+++ b/doc/h2load.1
@@ -0,0 +1,530 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.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
+..
+.TH "H2LOAD" "1" "Jan 21, 2024" "1.59.0" "nghttp2"
+.SH NAME
+h2load \- HTTP/2 benchmarking tool
+.SH SYNOPSIS
+.sp
+\fBh2load\fP [OPTIONS]... [URI]...
+.SH DESCRIPTION
+.sp
+benchmarking tool for HTTP/2 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. Definition of a base URI overrides all
+scheme, host or port values.
+.UNINDENT
+.SH OPTIONS
+.INDENT 0.0
+.TP
+.B \-n, \-\-requests=<N>
+Number of requests across all clients. If it is used
+with \fI\%\-\-timing\-script\-file\fP option, this option specifies
+the number of requests each client performs rather than
+the number of requests across all clients. This option
+is ignored if timing\-based benchmarking is enabled (see
+\fI\%\-\-duration\fP option).
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-c, \-\-clients=<N>
+Number of concurrent clients. With \fI\%\-r\fP option, this
+specifies the maximum number of connections to be made.
+.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=<PATH>
+Path of a file with multiple URIs are separated by EOLs.
+This option will disable URIs getting from command\-line.
+If \(aq\-\(aq is given as <PATH>, 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. Definition of a base URI overrides all
+scheme, host or port values.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-m, \-\-max\-concurrent\-streams=<N>
+Max concurrent streams to issue per session. When
+http/1.1 is used, this specifies the number of HTTP
+pipelining requests in\-flight.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-f, \-\-max\-frame\-size=<SIZE>
+Maximum frame size that the local endpoint is willing to
+receive.
+.sp
+Default: \fB16K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-w, \-\-window\-bits=<N>
+Sets the stream level initial window size to (2**<N>)\-1.
+For QUIC, <N> is capped to 26 (roughly 64MiB).
+.sp
+Default: \fB30\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-W, \-\-connection\-window\-bits=<N>
+Sets the connection level initial window size to
+(2**<N>)\-1.
+.sp
+Default: \fB30\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-H, \-\-header=<HEADER>
+Add/Override a header to the requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ciphers=<SUITE>
+Set allowed cipher list for TLSv1.2 or earlier. The
+format of the string is described in OpenSSL ciphers(1).
+.sp
+Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls13\-ciphers=<SUITE>
+Set allowed cipher list for TLSv1.3. The format of the
+string is described in OpenSSL ciphers(1).
+.sp
+Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256\fP
+.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: h2c and http/1.1
+.sp
+Default: \fBh2c\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-d, \-\-data=<PATH>
+Post FILE to server. The request method is changed to
+POST. For http/1.1 connection, if \fI\%\-d\fP is used, the
+maximum number of in\-flight pipelined requests is set to
+1.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-r, \-\-rate=<N>
+Specifies the fixed rate at which connections are
+created. The rate must be a positive integer,
+representing the number of connections to be made per
+rate period. The maximum number of connections to be
+made is given in \fI\%\-c\fP option. This rate will be
+distributed among threads as evenly as possible. For
+example, with \fI\%\-t\fP2 and \fI\%\-r\fP4, each thread gets 2
+connections per period. When the rate is 0, the program
+will run as it normally does, creating connections at
+whatever variable rate it wants. The default value for
+this option is 0. \fI\%\-r\fP and \fI\%\-D\fP are mutually exclusive.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-rate\-period=<DURATION>
+Specifies the time period between creating connections.
+The period must be a positive number, representing the
+length of the period in time. This option is ignored if
+the rate option is not used. The default value for this
+option is 1s.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-D, \-\-duration=<DURATION>
+Specifies the main duration for the measurements in case
+of timing\-based benchmarking. \fI\%\-D\fP and \fI\%\-r\fP are mutually
+exclusive.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-warm\-up\-time=<DURATION>
+Specifies the time period before starting the actual
+measurements, in case of timing\-based benchmarking.
+Needs to provided along with \fI\%\-D\fP option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-T, \-\-connection\-active\-timeout=<DURATION>
+Specifies the maximum time that h2load is willing to
+keep a connection open, regardless of the activity on
+said connection. <DURATION> must be a positive integer,
+specifying the amount of time to wait. When no timeout
+value is set (either active or inactive), h2load will
+keep a connection open indefinitely, waiting for a
+response.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-N, \-\-connection\-inactivity\-timeout=<DURATION>
+Specifies the amount of time that h2load is willing to
+wait to see activity on a given connection. <DURATION>
+must be a positive integer, specifying the amount of
+time to wait. When no timeout value is set (either
+active or inactive), h2load will keep a connection open
+indefinitely, waiting for a response.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-timing\-script\-file=<PATH>
+Path of a file containing one or more lines separated by
+EOLs. Each script line is composed of two tab\-separated
+fields. The first field represents the time offset from
+the start of execution, expressed as a positive value of
+milliseconds with microsecond resolution. The second
+field represents the URI. This option will disable URIs
+getting from command\-line. If \(aq\-\(aq is given as <PATH>,
+script lines will be read from stdin. Script lines are
+used in order for each client. If \fI\%\-n\fP is given, it must
+be less than or equal to the number of script lines,
+larger values are clamped to the number of script lines.
+If \fI\%\-n\fP is not given, the number of requests will default
+to the number of script lines. The scheme, host and
+port defined in the first URI are used solely. Values
+contained in other URIs, if present, are ignored.
+Definition of a base URI overrides all scheme, host or
+port values. \fI\%\-\-timing\-script\-file\fP and \fI\%\-\-rps\fP are
+mutually exclusive.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-B, \-\-base\-uri=(<URI>|unix:<PATH>)
+Specify URI from which the scheme, host and port will be
+used for all requests. The base URI overrides all
+values defined either at the command line or inside
+input files. If argument starts with \(dqunix:\(dq, then the
+rest of the argument will be treated as UNIX domain
+socket path. The connection is made through that path
+instead of TCP. In this case, scheme is inferred from
+the first URI appeared in the command line or inside
+input files as usual.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-alpn\-list=<LIST>
+Comma delimited list of ALPN protocol identifier sorted
+in the order of preference. That means most desirable
+protocol comes first. 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,h2\-16,h2\-14,http/1.1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-h1
+Short hand for \fI\%\-\-alpn\-list\fP=http/1.1
+\fI\%\-\-no\-tls\-proto\fP=http/1.1, which effectively force
+http/1.1 for both http and https URI.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-header\-table\-size=<SIZE>
+Specify decoder header table size.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-encoder\-header\-table\-size=<SIZE>
+Specify encoder header table size. The decoder (server)
+specifies the maximum dynamic table size it accepts.
+Then the negotiated dynamic table size is the minimum of
+this option value and the value which server specified.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-log\-file=<PATH>
+Write per\-request information to a file as tab\-separated
+columns: start time as microseconds since epoch; HTTP
+status code; microseconds until end of response. More
+columns may be added later. Rows are ordered by end\-of\-
+response time when using one worker thread, but may
+appear slightly out of order with multiple threads due
+to buffering. Status code is \-1 for failed streams.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-qlog\-file\-base=<PATH>
+Enable qlog output and specify base file name for qlogs.
+Qlog is emitted for each connection. For a given base
+name \(dqbase\(dq, each output file name becomes
+\(dqbase.M.N.sqlog\(dq where M is worker ID and N is client ID
+(e.g. \(dqbase.0.3.sqlog\(dq). Only effective in QUIC runs.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-connect\-to=<HOST>[:<PORT>]
+Host and port to connect instead of using the authority
+in <URI>.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-rps=<N>
+Specify request per second for each client. \fI\%\-\-rps\fP and
+\fI\%\-\-timing\-script\-file\fP are mutually exclusive.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-groups=<GROUPS>
+Specify the supported groups.
+.sp
+Default: \fBX25519:P\-256:P\-384:P\-521\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-udp\-gso
+Disable UDP GSO.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-max\-udp\-payload\-size=<SIZE>
+Specify the maximum outgoing UDP datagram payload size.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ktls
+Enable ktls.
+.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
+.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 h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). If a unit
+is omitted, a second is used as unit.
+.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.
+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.
+.TP
+.B timeout
+The number of requests whose connection timed out before they were
+completed. This is the subset of the number reported in
+\fBerrored\fP\&.
+.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 \(dqon the wire\(dq. If
+requests were made via TLS, this value is the number of decrypted
+bytes.
+.TP
+.B headers
+The number of response header bytes from the server without
+decompression. The \fBspace savings\fP shows efficiency of header
+compression. Let \fBdecompressed(headers)\fP to the number of bytes
+used for header fields after decompression. The \fBspace savings\fP
+is calculated by (1 \- \fBheaders\fP / \fBdecompressed(headers)\fP) *
+100. For HTTP/1.1, this is usually 0.00%, since it does not have
+header compression. For HTTP/2, it shows some insightful numbers.
+.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 taken 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
+.TP
+.B time for connect
+.INDENT 7.0
+.TP
+.B min
+The minimum time taken to connect to a server including TLS
+handshake.
+.TP
+.B max
+The maximum time taken to connect to a server including TLS
+handshake.
+.TP
+.B mean
+The mean time taken to connect to a server including TLS
+handshake.
+.TP
+.B sd
+The standard deviation of the time taken to connect to a server.
+.TP
+.B +/\- sd
+The fraction of the number of connections within standard
+deviation range (mean +/\- sd) against total number of successful
+connections.
+.UNINDENT
+.TP
+.B time for 1st byte (of (decrypted in case of TLS) application data)
+.INDENT 7.0
+.TP
+.B min
+The minimum time taken to get 1st byte from a server.
+.TP
+.B max
+The maximum time taken to get 1st byte from a server.
+.TP
+.B mean
+The mean time taken to get 1st byte from a server.
+.TP
+.B sd
+The standard deviation of the time taken to get 1st byte from a
+server.
+.TP
+.B +/\- sd
+The fraction of the number of connections within standard
+deviation range (mean +/\- sd) against total number of successful
+connections.
+.UNINDENT
+.TP
+.B req/s
+.INDENT 7.0
+.TP
+.B min
+The minimum request per second among all clients.
+.TP
+.B max
+The maximum request per second among all clients.
+.TP
+.B mean
+The mean request per second among all clients.
+.TP
+.B sd
+The standard deviation of request per second among all clients.
+server.
+.TP
+.B +/\- sd
+The fraction of the number of connections within standard
+deviation range (mean +/\- sd) against total number of successful
+connections.
+.UNINDENT
+.UNINDENT
+.SH FLOW CONTROL
+.sp
+h2load sets large flow control window by default, and effectively
+disables flow control to avoid under utilization of server
+performance. To set smaller flow control window, use \fI\%\-w\fP and
+\fI\%\-W\fP options. For example, use \fB\-w16 \-W16\fP to set default
+window size described in HTTP/2 protocol specification.
+.SH SEE ALSO
+.sp
+\fBnghttp(1)\fP, \fBnghttpd(1)\fP, \fBnghttpx(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, 2016, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/h2load.1.rst b/doc/h2load.1.rst
new file mode 100644
index 0000000..85ed651
--- /dev/null
+++ b/doc/h2load.1.rst
@@ -0,0 +1,434 @@
+
+.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
+
+.. program:: h2load
+
+h2load(1)
+=========
+
+SYNOPSIS
+--------
+
+**h2load** [OPTIONS]... [URI]...
+
+DESCRIPTION
+-----------
+
+benchmarking tool for HTTP/2 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. Definition of a base URI overrides all
+ scheme, host or port values.
+
+OPTIONS
+-------
+
+.. option:: -n, --requests=<N>
+
+ Number of requests across all clients. If it is used
+ with :option:`--timing-script-file` option, this option specifies
+ the number of requests each client performs rather than
+ the number of requests across all clients. This option
+ is ignored if timing-based benchmarking is enabled (see
+ :option:`--duration` option).
+
+ Default: ``1``
+
+.. option:: -c, --clients=<N>
+
+ Number of concurrent clients. With :option:`-r` option, this
+ specifies the maximum number of connections to be made.
+
+ Default: ``1``
+
+.. option:: -t, --threads=<N>
+
+ Number of native threads.
+
+ Default: ``1``
+
+.. option:: -i, --input-file=<PATH>
+
+ Path of a file with multiple URIs are separated by EOLs.
+ This option will disable URIs getting from command-line.
+ If '-' is given as <PATH>, 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. Definition of a base URI overrides all
+ scheme, host or port values.
+
+.. option:: -m, --max-concurrent-streams=<N>
+
+ Max concurrent streams to issue per session. When
+ http/1.1 is used, this specifies the number of HTTP
+ pipelining requests in-flight.
+
+ Default: ``1``
+
+.. option:: -f, --max-frame-size=<SIZE>
+
+ Maximum frame size that the local endpoint is willing to
+ receive.
+
+ Default: ``16K``
+
+.. option:: -w, --window-bits=<N>
+
+ Sets the stream level initial window size to (2\*\*<N>)-1.
+ For QUIC, <N> is capped to 26 (roughly 64MiB).
+
+ Default: ``30``
+
+.. option:: -W, --connection-window-bits=<N>
+
+ Sets the connection level initial window size to
+ (2\*\*<N>)-1.
+
+ Default: ``30``
+
+.. option:: -H, --header=<HEADER>
+
+ Add/Override a header to the requests.
+
+.. option:: --ciphers=<SUITE>
+
+ Set allowed cipher list for TLSv1.2 or earlier. The
+ format of the string is described in OpenSSL ciphers(1).
+
+ Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384``
+
+.. option:: --tls13-ciphers=<SUITE>
+
+ Set allowed cipher list for TLSv1.3. The format of the
+ string is described in OpenSSL ciphers(1).
+
+ Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256``
+
+.. option:: -p, --no-tls-proto=<PROTOID>
+
+ Specify ALPN identifier of the protocol to be used when
+ accessing http URI without SSL/TLS.
+ Available protocols: h2c and http/1.1
+
+ Default: ``h2c``
+
+.. option:: -d, --data=<PATH>
+
+ Post FILE to server. The request method is changed to
+ POST. For http/1.1 connection, if :option:`-d` is used, the
+ maximum number of in-flight pipelined requests is set to
+ 1.
+
+.. option:: -r, --rate=<N>
+
+ Specifies the fixed rate at which connections are
+ created. The rate must be a positive integer,
+ representing the number of connections to be made per
+ rate period. The maximum number of connections to be
+ made is given in :option:`-c` option. This rate will be
+ distributed among threads as evenly as possible. For
+ example, with :option:`-t`\2 and :option:`-r`\4, each thread gets 2
+ connections per period. When the rate is 0, the program
+ will run as it normally does, creating connections at
+ whatever variable rate it wants. The default value for
+ this option is 0. :option:`-r` and :option:`\-D` are mutually exclusive.
+
+.. option:: --rate-period=<DURATION>
+
+ Specifies the time period between creating connections.
+ The period must be a positive number, representing the
+ length of the period in time. This option is ignored if
+ the rate option is not used. The default value for this
+ option is 1s.
+
+.. option:: -D, --duration=<DURATION>
+
+ Specifies the main duration for the measurements in case
+ of timing-based benchmarking. :option:`-D` and :option:`\-r` are mutually
+ exclusive.
+
+.. option:: --warm-up-time=<DURATION>
+
+ Specifies the time period before starting the actual
+ measurements, in case of timing-based benchmarking.
+ Needs to provided along with :option:`-D` option.
+
+.. option:: -T, --connection-active-timeout=<DURATION>
+
+ Specifies the maximum time that h2load is willing to
+ keep a connection open, regardless of the activity on
+ said connection. <DURATION> must be a positive integer,
+ specifying the amount of time to wait. When no timeout
+ value is set (either active or inactive), h2load will
+ keep a connection open indefinitely, waiting for a
+ response.
+
+.. option:: -N, --connection-inactivity-timeout=<DURATION>
+
+ Specifies the amount of time that h2load is willing to
+ wait to see activity on a given connection. <DURATION>
+ must be a positive integer, specifying the amount of
+ time to wait. When no timeout value is set (either
+ active or inactive), h2load will keep a connection open
+ indefinitely, waiting for a response.
+
+.. option:: --timing-script-file=<PATH>
+
+ Path of a file containing one or more lines separated by
+ EOLs. Each script line is composed of two tab-separated
+ fields. The first field represents the time offset from
+ the start of execution, expressed as a positive value of
+ milliseconds with microsecond resolution. The second
+ field represents the URI. This option will disable URIs
+ getting from command-line. If '-' is given as <PATH>,
+ script lines will be read from stdin. Script lines are
+ used in order for each client. If :option:`-n` is given, it must
+ be less than or equal to the number of script lines,
+ larger values are clamped to the number of script lines.
+ If :option:`-n` is not given, the number of requests will default
+ to the number of script lines. The scheme, host and
+ port defined in the first URI are used solely. Values
+ contained in other URIs, if present, are ignored.
+ Definition of a base URI overrides all scheme, host or
+ port values. :option:`--timing-script-file` and :option:`\--rps` are
+ mutually exclusive.
+
+.. option:: -B, --base-uri=(<URI>|unix:<PATH>)
+
+ Specify URI from which the scheme, host and port will be
+ used for all requests. The base URI overrides all
+ values defined either at the command line or inside
+ input files. If argument starts with "unix:", then the
+ rest of the argument will be treated as UNIX domain
+ socket path. The connection is made through that path
+ instead of TCP. In this case, scheme is inferred from
+ the first URI appeared in the command line or inside
+ input files as usual.
+
+.. option:: --alpn-list=<LIST>
+
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+
+ Default: ``h2,h2-16,h2-14,http/1.1``
+
+.. option:: --h1
+
+ Short hand for :option:`--alpn-list`\=http/1.1
+ :option:`--no-tls-proto`\=http/1.1, which effectively force
+ http/1.1 for both http and https URI.
+
+.. option:: --header-table-size=<SIZE>
+
+ Specify decoder header table size.
+
+ Default: ``4K``
+
+.. option:: --encoder-header-table-size=<SIZE>
+
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+
+ Default: ``4K``
+
+.. option:: --log-file=<PATH>
+
+ Write per-request information to a file as tab-separated
+ columns: start time as microseconds since epoch; HTTP
+ status code; microseconds until end of response. More
+ columns may be added later. Rows are ordered by end-of-
+ response time when using one worker thread, but may
+ appear slightly out of order with multiple threads due
+ to buffering. Status code is -1 for failed streams.
+
+.. option:: --qlog-file-base=<PATH>
+
+ Enable qlog output and specify base file name for qlogs.
+ Qlog is emitted for each connection. For a given base
+ name "base", each output file name becomes
+ "base.M.N.sqlog" where M is worker ID and N is client ID
+ (e.g. "base.0.3.sqlog"). Only effective in QUIC runs.
+
+.. option:: --connect-to=<HOST>[:<PORT>]
+
+ Host and port to connect instead of using the authority
+ in <URI>.
+
+.. option:: --rps=<N>
+
+ Specify request per second for each client. :option:`--rps` and
+ :option:`--timing-script-file` are mutually exclusive.
+
+.. option:: --groups=<GROUPS>
+
+ Specify the supported groups.
+
+ Default: ``X25519:P-256:P-384:P-521``
+
+.. option:: --no-udp-gso
+
+ Disable UDP GSO.
+
+.. option:: --max-udp-payload-size=<SIZE>
+
+ Specify the maximum outgoing UDP datagram payload size.
+
+.. option:: --ktls
+
+ Enable ktls.
+
+.. option:: -v, --verbose
+
+ Output debug information.
+
+.. 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).
+
+The <DURATION> argument is an integer and an optional unit (e.g., 1s
+is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). If a unit
+is omitted, a second is used as unit.
+
+.. _h2load-1-output:
+
+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.
+ This is the subset of the number reported in ``failed`` and most
+ likely the network level failures or stream was reset by
+ RST_STREAM.
+ timeout
+ The number of requests whose connection timed out before they were
+ completed. This is the subset of the number reported in
+ ``errored``.
+
+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 decrypted
+ bytes.
+ headers
+ The number of response header bytes from the server without
+ decompression. The ``space savings`` shows efficiency of header
+ compression. Let ``decompressed(headers)`` to the number of bytes
+ used for header fields after decompression. The ``space savings``
+ is calculated by (1 - ``headers`` / ``decompressed(headers)``) *
+ 100. For HTTP/1.1, this is usually 0.00%, since it does not have
+ header compression. For HTTP/2, it shows some insightful numbers.
+ 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 taken for request and response.
+ +/- sd
+ The fraction of the number of requests within standard deviation
+ range (mean +/- sd) against total number of successful requests.
+
+time for connect
+ min
+ The minimum time taken to connect to a server including TLS
+ handshake.
+ max
+ The maximum time taken to connect to a server including TLS
+ handshake.
+ mean
+ The mean time taken to connect to a server including TLS
+ handshake.
+ sd
+ The standard deviation of the time taken to connect to a server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+time for 1st byte (of (decrypted in case of TLS) application data)
+ min
+ The minimum time taken to get 1st byte from a server.
+ max
+ The maximum time taken to get 1st byte from a server.
+ mean
+ The mean time taken to get 1st byte from a server.
+ sd
+ The standard deviation of the time taken to get 1st byte from a
+ server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+req/s
+ min
+ The minimum request per second among all clients.
+ max
+ The maximum request per second among all clients.
+ mean
+ The mean request per second among all clients.
+ sd
+ The standard deviation of request per second among all clients.
+ server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+FLOW CONTROL
+------------
+
+h2load sets large flow control window by default, and effectively
+disables flow control to avoid under utilization of server
+performance. To set smaller flow control window, use :option:`-w` and
+:option:`-W` options. For example, use ``-w16 -W16`` to set default
+window size described in HTTP/2 protocol specification.
+
+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
index 0000000..50d9977
--- /dev/null
+++ b/doc/h2load.h2r
@@ -0,0 +1,120 @@
+.. _h2load-1-output:
+
+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.
+ This is the subset of the number reported in ``failed`` and most
+ likely the network level failures or stream was reset by
+ RST_STREAM.
+ timeout
+ The number of requests whose connection timed out before they were
+ completed. This is the subset of the number reported in
+ ``errored``.
+
+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 decrypted
+ bytes.
+ headers
+ The number of response header bytes from the server without
+ decompression. The ``space savings`` shows efficiency of header
+ compression. Let ``decompressed(headers)`` to the number of bytes
+ used for header fields after decompression. The ``space savings``
+ is calculated by (1 - ``headers`` / ``decompressed(headers)``) *
+ 100. For HTTP/1.1, this is usually 0.00%, since it does not have
+ header compression. For HTTP/2, it shows some insightful numbers.
+ 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 taken for request and response.
+ +/- sd
+ The fraction of the number of requests within standard deviation
+ range (mean +/- sd) against total number of successful requests.
+
+time for connect
+ min
+ The minimum time taken to connect to a server including TLS
+ handshake.
+ max
+ The maximum time taken to connect to a server including TLS
+ handshake.
+ mean
+ The mean time taken to connect to a server including TLS
+ handshake.
+ sd
+ The standard deviation of the time taken to connect to a server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+time for 1st byte (of (decrypted in case of TLS) application data)
+ min
+ The minimum time taken to get 1st byte from a server.
+ max
+ The maximum time taken to get 1st byte from a server.
+ mean
+ The mean time taken to get 1st byte from a server.
+ sd
+ The standard deviation of the time taken to get 1st byte from a
+ server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+req/s
+ min
+ The minimum request per second among all clients.
+ max
+ The maximum request per second among all clients.
+ mean
+ The mean request per second among all clients.
+ sd
+ The standard deviation of request per second among all clients.
+ server.
+ +/- sd
+ The fraction of the number of connections within standard
+ deviation range (mean +/- sd) against total number of successful
+ connections.
+
+FLOW CONTROL
+------------
+
+h2load sets large flow control window by default, and effectively
+disables flow control to avoid under utilization of server
+performance. To set smaller flow control window, use :option:`-w` and
+:option:`-W` options. For example, use ``-w16 -W16`` to set default
+window size described in HTTP/2 protocol specification.
+
+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
index 0000000..2a49369
--- /dev/null
+++ b/doc/index.rst.in
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/index.rst
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 0000000..2e0500d
--- /dev/null
+++ b/doc/make.bat
@@ -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
index 0000000..df59fbc
--- /dev/null
+++ b/doc/mkapiref.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# nghttp2 - HTTP/2 C Library
+#
+# Copyright (c) 2020 nghttp2 contributors
+# Copyright (c) 2020 ngtcp2 contributors
+# 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.
+
+import re, sys, argparse, os.path
+
+class FunctionDoc:
+ def __init__(self, name, content, domain, filename):
+ self.name = name
+ self.content = content
+ self.domain = domain
+ if self.domain == 'function':
+ self.funcname = re.search(r'(nghttp2_[^ )]+)\(', self.name).group(1)
+ self.filename = filename
+
+ def write(self, out):
+ out.write('.. {}:: {}\n'.format(self.domain, self.name))
+ out.write('\n')
+ for line in self.content:
+ out.write(' {}\n'.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:
+ out.write('.. type:: {}\n'.format(self.name))
+ out.write('\n')
+ for line in self.content:
+ out.write(' {}\n'.format(line))
+ out.write('\n')
+ for name, content in self.members:
+ out.write(' .. {}:: {}\n'.format(self.member_domain, name))
+ out.write('\n')
+ for line in content:
+ out.write(' {}\n'.format(line))
+ out.write('\n')
+
+class EnumDoc:
+ def __init__(self, name, content, members):
+ self.name = name
+ self.content = content
+ self.members = members
+
+ def write(self, out):
+ if self.name:
+ out.write('.. type:: {}\n'.format(self.name))
+ out.write('\n')
+ for line in self.content:
+ out.write(' {}\n'.format(line))
+ out.write('\n')
+ for name, content in self.members:
+ out.write(' .. enum:: {}\n'.format(name))
+ out.write('\n')
+ for line in content:
+ out.write(' {}\n'.format(line))
+ out.write('\n')
+
+class MacroDoc:
+ def __init__(self, name, content):
+ self.name = name
+ self.content = content
+
+ def write(self, out):
+ out.write('''.. macro:: {}\n'''.format(self.name))
+ out.write('\n')
+ for line in self.content:
+ out.write(' {}\n'.format(line))
+
+class MacroSectionDoc:
+ def __init__(self, content):
+ self.content = content
+
+ def write(self, out):
+ out.write('\n')
+ c = ' '.join(self.content).strip()
+ out.write(c)
+ out.write('\n')
+ out.write('-' * len(c))
+ out.write('\n\n')
+
+class TypedefDoc:
+ def __init__(self, name, content):
+ self.name = name
+ self.content = content
+
+ def write(self, out):
+ out.write('''.. type:: {}\n'''.format(self.name))
+ out.write('\n')
+ for line in self.content:
+ out.write(' {}\n'.format(line))
+
+def make_api_ref(infile):
+ macros = []
+ enums = []
+ types = []
+ functions = []
+ 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))
+ elif doctype == '@macrosection':
+ macros.append(process_macrosection(infile))
+ elif doctype == '@typedef':
+ types.append(process_typedef(infile))
+ return macros, enums, types, functions
+
+def output(
+ title, indexfile, macrosfile, enumsfile, typesfile, funcsdir,
+ macros, enums, types, functions):
+ indexfile.write('''
+{title}
+{titledecoration}
+
+.. toctree::
+ :maxdepth: 1
+
+ {macros}
+ {enums}
+ {types}
+'''.format(
+ title=title, titledecoration='='*len(title),
+ macros=os.path.splitext(os.path.basename(macrosfile.name))[0],
+ enums=os.path.splitext(os.path.basename(enumsfile.name))[0],
+ types=os.path.splitext(os.path.basename(typesfile.name))[0],
+))
+
+ for doc in functions:
+ indexfile.write(' {}\n'.format(doc.funcname))
+
+ macrosfile.write('''
+Macros
+======
+''')
+ for doc in macros:
+ doc.write(macrosfile)
+
+ enumsfile.write('''
+Enums
+=====
+''')
+ for doc in enums:
+ doc.write(enumsfile)
+
+ typesfile.write('''
+Types (structs, unions and typedefs)
+====================================
+''')
+ for doc in types:
+ doc.write(typesfile)
+
+ for doc in functions:
+ with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f:
+ f.write('''
+{funcname}
+{secul}
+
+Synopsis
+--------
+
+*#include <nghttp2/{filename}>*
+
+'''.format(funcname=doc.funcname, secul='='*len(doc.funcname),
+ filename=doc.filename))
+ doc.write(f)
+
+def process_macro(infile):
+ content = read_content(infile)
+ line = infile.readline()
+ macro_name = line.split()[1]
+ return MacroDoc(macro_name, content)
+
+def process_macrosection(infile):
+ content = read_content(infile)
+ return MacroSectionDoc(content)
+
+def process_typedef(infile):
+ content = read_content(infile)
+ typedef = infile.readline()
+ typedef = re.sub(r';\n$', '', typedef)
+ typedef = re.sub(r'typedef ', '', typedef)
+ return TypedefDoc(typedef, 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].rstrip(',')
+ 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 EnumDoc(enum_name, content, members)
+
+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)
+ func_proto = re.sub(r'NGHTTP2_EXTERN ', '', func_proto)
+ func_proto = re.sub(r'typedef ', '', func_proto)
+ filename = os.path.basename(infile.name)
+ return FunctionDoc(func_proto, content, domain, filename)
+
+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)
+ return content
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="Generate API reference")
+ parser.add_argument('--title', default='API Reference',
+ help='title of index page')
+ parser.add_argument('index', type=argparse.FileType('w'),
+ help='index output file')
+ parser.add_argument('macros', type=argparse.FileType('w'),
+ help='macros section output file. The filename should be macros.rst')
+ parser.add_argument('enums', type=argparse.FileType('w'),
+ help='enums section output file. The filename should be enums.rst')
+ parser.add_argument('types', type=argparse.FileType('w'),
+ help='types section output file. The filename should be types.rst')
+ parser.add_argument('funcsdir',
+ help='functions doc output dir')
+ parser.add_argument('files', nargs='+', type=argparse.FileType('r'),
+ help='source file')
+ args = parser.parse_args()
+ macros = []
+ enums = []
+ types = []
+ funcs = []
+ for infile in args.files:
+ m, e, t, f = make_api_ref(infile)
+ macros.extend(m)
+ enums.extend(e)
+ types.extend(t)
+ funcs.extend(f)
+ funcs.sort(key=lambda x: x.funcname)
+ output(
+ args.title,
+ args.index, args.macros, args.enums, args.types, args.funcsdir,
+ macros, enums, types, funcs)
diff --git a/doc/nghttp.1 b/doc/nghttp.1
new file mode 100644
index 0000000..332d9c6
--- /dev/null
+++ b/doc/nghttp.1
@@ -0,0 +1,336 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.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
+..
+.TH "NGHTTP" "1" "Jan 21, 2024" "1.59.0" "nghttp2"
+.SH NAME
+nghttp \- HTTP/2 client
+.SH SYNOPSIS
+.sp
+\fBnghttp\fP [OPTIONS]... <URI>...
+.SH DESCRIPTION
+.sp
+HTTP/2 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 derived 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=<DURATION>
+Timeout each request after <DURATION>. Set 0 to disable
+timeout.
+.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 \-\-trailer=<HEADER>
+Add a trailer header to the requests. <HEADER> must not
+include pseudo header field (header field name starting
+with \(aq:\(aq). To send trailer, one must use \fI\%\-d\fP option to
+send request body. Example: \fI\%\-\-trailer\fP \(aqfoo: bar\(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=<PATH>
+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 weight of given URI. This option can be used
+multiple times, and N\-th \fI\%\-p\fP option sets weight of N\-th
+URI in the command line. If the number of \fI\%\-p\fP option is
+less than the number of URI, the last \fI\%\-p\fP option value is
+repeated. If there is no \fI\%\-p\fP option, default weight, 16,
+is assumed. The valid value range is
+[1, 256], inclusive.
+.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.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-c, \-\-header\-table\-size=<SIZE>
+Specify decoder header table size. If this option is
+used multiple times, and the minimum value among the
+given values except for last one is strictly less than
+the last value, that minimum value is set in SETTINGS
+frame payload before the last value, to simulate
+multiple header table size change.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-encoder\-header\-table\-size=<SIZE>
+Specify encoder header table size. The decoder (server)
+specifies the maximum dynamic table size it accepts.
+Then the negotiated dynamic table size is the minimum of
+this option value and the value which server specified.
+.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=<PATH>
+Output HTTP transactions <PATH> 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 \-\-hexdump
+Display the incoming traffic in hexadecimal (Canonical
+hex+ASCII display). If SSL/TLS is used, decrypted data
+are used.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-push
+Disable server push.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-max\-concurrent\-streams=<N>
+The number of concurrent pushed streams this client
+accepts.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-expect\-continue
+Perform an Expect/Continue handshake: wait to send DATA
+(up to a short timeout) until the server sends a 100
+Continue interim response. This option is ignored unless
+combined with the \fI\%\-d\fP option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-y, \-\-no\-verify\-peer
+Suppress warning on server certificate verification
+failure.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ktls
+Enable ktls.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-rfc7540\-pri
+Disable RFC7540 priorities.
+.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).
+.sp
+The <DURATION> argument is an integer and an optional unit (e.g., 1s
+is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). If a unit
+is omitted, a second is used as unit.
+.SH DEPENDENCY BASED PRIORITY
+.sp
+nghttp sends priority hints to server by default unless
+\fI\%\-\-no\-dep\fP is used. nghttp mimics the way Firefox employs to
+manages dependency using idle streams. We follows the behaviour of
+Firefox Nightly as of April, 2015, and nghttp\(aqs behaviour is very
+static and could be different from Firefox in detail. But reproducing
+the same behaviour of Firefox is not our goal. The goal is provide
+the easy way to test out the dependency priority in server
+implementation.
+.sp
+When connection is established, nghttp sends 5 PRIORITY frames to idle
+streams 3, 5, 7, 9 and 11 to create \(dqanchor\(dq nodes in dependency
+tree:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+ +\-\-\-\-\-+
+ |id=0 |
+ +\-\-\-\-\-+
+ ^ ^ ^
+ w=201 / | \e w=1
+ / | \e
+ / w=101| \e
+ +\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+
+ |id=3 | |id=5 | |id=7 |
+ +\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+
+ ^ ^
+w=1 | w=1 |
+ | |
+ +\-\-\-\-\-+ +\-\-\-\-\-+
+ |id=11| |id=9 |
+ +\-\-\-\-\-+ +\-\-\-\-\-+
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+In the above figure, \fBid\fP means stream ID, and \fBw\fP means weight.
+The stream 0 is non\-existence stream, and forms the root of the tree.
+The stream 7 and 9 are not used for now.
+.sp
+The URIs given in the command\-line depend on stream 11 with the weight
+given in \fI\%\-p\fP option, which defaults to 16.
+.sp
+If \fI\%\-a\fP option is used, nghttp parses the resource pointed by
+URI given in command\-line as html, and extracts resource links from
+it. When requesting those resources, nghttp uses dependency according
+to its resource type.
+.sp
+For CSS, and Javascript files inside \(dqhead\(dq element, they depend on
+stream 3 with the weight 2. The Javascript files outside \(dqhead\(dq
+element depend on stream 5 with the weight 2. The mages depend on
+stream 11 with the weight 12. The other resources (e.g., icon) depend
+on stream 11 with the weight 2.
+.SH SEE ALSO
+.sp
+\fBnghttpd(1)\fP, \fBnghttpx(1)\fP, \fBh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, 2016, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttp.1.rst b/doc/nghttp.1.rst
new file mode 100644
index 0000000..e10f3ee
--- /dev/null
+++ b/doc/nghttp.1.rst
@@ -0,0 +1,276 @@
+
+.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
+
+.. program:: nghttp
+
+nghttp(1)
+=========
+
+SYNOPSIS
+--------
+
+**nghttp** [OPTIONS]... <URI>...
+
+DESCRIPTION
+-----------
+
+HTTP/2 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 derived from URI. If URI ends with '*/*',
+ 'index.html' is used as a filename. Not implemented
+ yet.
+
+.. option:: -t, --timeout=<DURATION>
+
+ Timeout each request after <DURATION>. Set 0 to disable
+ timeout.
+
+.. 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:: --trailer=<HEADER>
+
+ Add a trailer header to the requests. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). To send trailer, one must use :option:`-d` option to
+ send request body. Example: :option:`--trailer` 'foo: bar'.
+
+.. 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=<PATH>
+
+ 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 weight of given URI. This option can be used
+ multiple times, and N-th :option:`-p` option sets weight of N-th
+ URI in the command line. If the number of :option:`-p` option is
+ less than the number of URI, the last :option:`-p` option value is
+ repeated. If there is no :option:`-p` option, default weight, 16,
+ is assumed. The valid value range is
+ [1, 256], inclusive.
+
+.. 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.
+
+ Default: ``100``
+
+.. option:: -c, --header-table-size=<SIZE>
+
+ Specify decoder header table size. If this option is
+ used multiple times, and the minimum value among the
+ given values except for last one is strictly less than
+ the last value, that minimum value is set in SETTINGS
+ frame payload before the last value, to simulate
+ multiple header table size change.
+
+.. option:: --encoder-header-table-size=<SIZE>
+
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+
+.. option:: -b, --padding=<N>
+
+ Add at most <N> bytes to a frame payload as padding.
+ Specify 0 to disable padding.
+
+.. option:: -r, --har=<PATH>
+
+ Output HTTP transactions <PATH> 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:: --hexdump
+
+ Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+
+.. option:: --no-push
+
+ Disable server push.
+
+.. option:: --max-concurrent-streams=<N>
+
+ The number of concurrent pushed streams this client
+ accepts.
+
+.. option:: --expect-continue
+
+ Perform an Expect/Continue handshake: wait to send DATA
+ (up to a short timeout) until the server sends a 100
+ Continue interim response. This option is ignored unless
+ combined with the :option:`-d` option.
+
+.. option:: -y, --no-verify-peer
+
+ Suppress warning on server certificate verification
+ failure.
+
+.. option:: --ktls
+
+ Enable ktls.
+
+.. option:: --no-rfc7540-pri
+
+ Disable RFC7540 priorities.
+
+.. 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).
+
+The <DURATION> argument is an integer and an optional unit (e.g., 1s
+is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). If a unit
+is omitted, a second is used as unit.
+
+DEPENDENCY BASED PRIORITY
+-------------------------
+
+nghttp sends priority hints to server by default unless
+:option:`--no-dep` is used. nghttp mimics the way Firefox employs to
+manages dependency using idle streams. We follows the behaviour of
+Firefox Nightly as of April, 2015, and nghttp's behaviour is very
+static and could be different from Firefox in detail. But reproducing
+the same behaviour of Firefox is not our goal. The goal is provide
+the easy way to test out the dependency priority in server
+implementation.
+
+When connection is established, nghttp sends 5 PRIORITY frames to idle
+streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency
+tree:
+
+.. code-block:: text
+
+ +-----+
+ |id=0 |
+ +-----+
+ ^ ^ ^
+ w=201 / | \ w=1
+ / | \
+ / w=101| \
+ +-----+ +-----+ +-----+
+ |id=3 | |id=5 | |id=7 |
+ +-----+ +-----+ +-----+
+ ^ ^
+ w=1 | w=1 |
+ | |
+ +-----+ +-----+
+ |id=11| |id=9 |
+ +-----+ +-----+
+
+In the above figure, ``id`` means stream ID, and ``w`` means weight.
+The stream 0 is non-existence stream, and forms the root of the tree.
+The stream 7 and 9 are not used for now.
+
+The URIs given in the command-line depend on stream 11 with the weight
+given in :option:`-p` option, which defaults to 16.
+
+If :option:`-a` option is used, nghttp parses the resource pointed by
+URI given in command-line as html, and extracts resource links from
+it. When requesting those resources, nghttp uses dependency according
+to its resource type.
+
+For CSS, and Javascript files inside "head" element, they depend on
+stream 3 with the weight 2. The Javascript files outside "head"
+element depend on stream 5 with the weight 2. The mages depend on
+stream 11 with the weight 12. The other resources (e.g., icon) depend
+on stream 11 with the weight 2.
+
+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
index 0000000..9d2a90e
--- /dev/null
+++ b/doc/nghttp.h2r
@@ -0,0 +1,57 @@
+DEPENDENCY BASED PRIORITY
+-------------------------
+
+nghttp sends priority hints to server by default unless
+:option:`--no-dep` is used. nghttp mimics the way Firefox employs to
+manages dependency using idle streams. We follows the behaviour of
+Firefox Nightly as of April, 2015, and nghttp's behaviour is very
+static and could be different from Firefox in detail. But reproducing
+the same behaviour of Firefox is not our goal. The goal is provide
+the easy way to test out the dependency priority in server
+implementation.
+
+When connection is established, nghttp sends 5 PRIORITY frames to idle
+streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency
+tree:
+
+.. code-block:: text
+
+ +-----+
+ |id=0 |
+ +-----+
+ ^ ^ ^
+ w=201 / | \ w=1
+ / | \
+ / w=101| \
+ +-----+ +-----+ +-----+
+ |id=3 | |id=5 | |id=7 |
+ +-----+ +-----+ +-----+
+ ^ ^
+ w=1 | w=1 |
+ | |
+ +-----+ +-----+
+ |id=11| |id=9 |
+ +-----+ +-----+
+
+In the above figure, ``id`` means stream ID, and ``w`` means weight.
+The stream 0 is non-existence stream, and forms the root of the tree.
+The stream 7 and 9 are not used for now.
+
+The URIs given in the command-line depend on stream 11 with the weight
+given in :option:`-p` option, which defaults to 16.
+
+If :option:`-a` option is used, nghttp parses the resource pointed by
+URI given in command-line as html, and extracts resource links from
+it. When requesting those resources, nghttp uses dependency according
+to its resource type.
+
+For CSS, and Javascript files inside "head" element, they depend on
+stream 3 with the weight 2. The Javascript files outside "head"
+element depend on stream 5 with the weight 2. The mages depend on
+stream 11 with the weight 12. The other resources (e.g., icon) depend
+on stream 11 with the weight 2.
+
+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
index 0000000..29e641d
--- /dev/null
+++ b/doc/nghttp2.h.rst.in
@@ -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
index 0000000..c6aa779
--- /dev/null
+++ b/doc/nghttp2ver.h.rst.in
@@ -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
index 0000000..219a365
--- /dev/null
+++ b/doc/nghttpd.1
@@ -0,0 +1,236 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.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
+..
+.TH "NGHTTPD" "1" "Jan 21, 2024" "1.59.0" "nghttp2"
+.SH NAME
+nghttpd \- HTTP/2 server
+.SH SYNOPSIS
+.sp
+\fBnghttpd\fP [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]
+.SH DESCRIPTION
+.sp
+HTTP/2 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 \-a, \-\-address=<ADDR>
+The address to bind to. If not specified the default IP
+address determined by getaddrinfo is used.
+.UNINDENT
+.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 \-\-encoder\-header\-table\-size=<SIZE>
+Specify encoder header table size. The decoder (client)
+specifies the maximum dynamic table size it accepts.
+Then the negotiated dynamic table size is the minimum of
+this option value and the value which client specified.
+.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 \-m, \-\-max\-concurrent\-streams=<N>
+Set the maximum number of the concurrent streams in one
+HTTP/2 session.
+.sp
+Default: \fB100\fP
+.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 \-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 \-\-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 \-\-trailer=<HEADER>
+Add a trailer header to a response. <HEADER> must not
+include pseudo header field (header field name starting
+with \(aq:\(aq). The trailer is sent only if a response has
+body part. Example: \fI\%\-\-trailer\fP \(aqfoo: bar\(aq.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-hexdump
+Display the incoming traffic in hexadecimal (Canonical
+hex+ASCII display). If SSL/TLS is used, decrypted data
+are used.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-echo\-upload
+Send back uploaded content if method is POST or PUT.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-mime\-types\-file=<PATH>
+Path to file that contains MIME media types and the
+extensions that represent them.
+.sp
+Default: \fB/etc/mime.types\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-content\-length
+Don\(aqt send content\-length header field.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ktls
+Enable ktls.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-rfc7540\-pri
+Disable RFC7540 priorities.
+.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
+\fBnghttp(1)\fP, \fBnghttpx(1)\fP, \fBh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, 2016, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttpd.1.rst b/doc/nghttpd.1.rst
new file mode 100644
index 0000000..654a025
--- /dev/null
+++ b/doc/nghttpd.1.rst
@@ -0,0 +1,186 @@
+
+.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
+
+.. program:: nghttpd
+
+nghttpd(1)
+==========
+
+SYNOPSIS
+--------
+
+**nghttpd** [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]
+
+DESCRIPTION
+-----------
+
+HTTP/2 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:: -a, --address=<ADDR>
+
+ The address to bind to. If not specified the default IP
+ address determined by getaddrinfo is used.
+
+.. 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:: --encoder-header-table-size=<SIZE>
+
+ Specify encoder header table size. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+
+.. 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:: -m, --max-concurrent-streams=<N>
+
+ Set the maximum number of the concurrent streams in one
+ HTTP/2 session.
+
+ Default: ``100``
+
+.. option:: -n, --workers=<N>
+
+ Set the number of worker threads.
+
+ Default: ``1``
+
+.. option:: -e, --error-gzip
+
+ Make error response gzipped.
+
+.. 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:: --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:: --trailer=<HEADER>
+
+ Add a trailer header to a response. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). The trailer is sent only if a response has
+ body part. Example: :option:`--trailer` 'foo: bar'.
+
+.. option:: --hexdump
+
+ Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+
+.. option:: --echo-upload
+
+ Send back uploaded content if method is POST or PUT.
+
+.. option:: --mime-types-file=<PATH>
+
+ Path to file that contains MIME media types and the
+ extensions that represent them.
+
+ Default: ``/etc/mime.types``
+
+.. option:: --no-content-length
+
+ Don't send content-length header field.
+
+.. option:: --ktls
+
+ Enable ktls.
+
+.. option:: --no-rfc7540-pri
+
+ Disable RFC7540 priorities.
+
+.. 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
index 0000000..e346cd1
--- /dev/null
+++ b/doc/nghttpd.h2r
@@ -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
index 0000000..082ce51
--- /dev/null
+++ b/doc/nghttpx-howto.rst.in
@@ -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
index 0000000..e9742a5
--- /dev/null
+++ b/doc/nghttpx.1
@@ -0,0 +1,2770 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.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
+..
+.TH "NGHTTPX" "1" "Jan 21, 2024" "1.59.0" "nghttp2"
+.SH NAME
+nghttpx \- HTTP/2 proxy
+.SH SYNOPSIS
+.sp
+\fBnghttpx\fP [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+.SH DESCRIPTION
+.sp
+A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.
+.INDENT 0.0
+.TP
+.B <PRIVATE_KEY>
+Set path to server\(aqs private key. Required unless
+\(dqno\-tls\(dq parameter is used in \fI\%\-\-frontend\fP option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B <CERT>
+Set path to server\(aqs certificate. Required unless
+\(dqno\-tls\(dq parameter is used in \fI\%\-\-frontend\fP option. To
+make OCSP stapling work, this must be an absolute path.
+.UNINDENT
+.SH OPTIONS
+.sp
+The options are categorized into several groups.
+.SS Connections
+.INDENT 0.0
+.TP
+.B \-b, \-\-backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...]
+Set backend host and port. The multiple backend
+addresses are accepted by repeating this option. UNIX
+domain socket can be specified by prefixing path name
+with \(dqunix:\(dq (e.g., unix:/var/run/backend.sock).
+.sp
+Optionally, if <PATTERN>s are given, the backend address
+is only used if request matches the pattern. The
+pattern matching is closely designed to ServeMux in
+net/http package of Go programming language. <PATTERN>
+consists of path, host + path or just host. The path
+must start with \(dq\fI/\fP\(dq. If it ends with \(dq\fI/\fP\(dq, it matches
+all request path in its subtree. To deal with the
+request to the directory without trailing slash, the
+path which ends with \(dq\fI/\fP\(dq also matches the request path
+which only lacks trailing \(aq\fI/\fP\(aq (e.g., path \(dq\fI/foo/\fP\(dq
+matches request path \(dq\fI/foo\fP\(dq). If it does not end with
+\(dq\fI/\fP\(dq, it performs exact match against the request path.
+If host is given, it performs a match against the
+request host. For a request received on the frontend
+listener with \(dqsni\-fwd\(dq parameter enabled, SNI host is
+used instead of a request host. If host alone is given,
+\(dq\fI/\fP\(dq is appended to it, so that it matches all request
+paths under the host (e.g., specifying \(dqnghttp2.org\(dq
+equals to \(dqnghttp2.org/\(dq). CONNECT method is treated
+specially. It does not have path, and we don\(aqt allow
+empty path. To workaround this, we assume that CONNECT
+method has \(dq\fI/\fP\(dq as path.
+.sp
+Patterns with host take precedence over patterns with
+just path. Then, longer patterns take precedence over
+shorter ones.
+.sp
+Host can include \(dq*\(dq in the left most position to
+indicate wildcard match (only suffix match is done).
+The \(dq*\(dq must match at least one character. For example,
+host pattern \(dq*.nghttp2.org\(dq matches against
+\(dqwww.nghttp2.org\(dq and \(dqgit.ngttp2.org\(dq, but does not
+match against \(dqnghttp2.org\(dq. The exact hosts match
+takes precedence over the wildcard hosts match.
+.sp
+If path part ends with \(dq*\(dq, it is treated as wildcard
+path. The wildcard path behaves differently from the
+normal path. For normal path, match is made around the
+boundary of path component separator,\(dq\fI/\fP\(dq. On the other
+hand, the wildcard path does not take into account the
+path component separator. All paths which include the
+wildcard path without last \(dq*\(dq as prefix, and are
+strictly longer than wildcard path without last \(dq*\(dq are
+matched. \(dq*\(dq must match at least one character. For
+example, the pattern \(dq\fI/foo*\fP\(dq matches \(dq\fI/foo/\fP\(dq and
+\(dq\fI/foobar\fP\(dq. But it does not match \(dq\fI/foo\fP\(dq, or \(dq\fI/fo\fP\(dq.
+.sp
+If <PATTERN> is omitted or empty string, \(dq\fI/\fP\(dq is used as
+pattern, which matches all request paths (catch\-all
+pattern). The catch\-all backend must be given.
+.sp
+When doing a match, nghttpx made some normalization to
+pattern, request host and path. For host part, they are
+converted to lower case. For path part, percent\-encoded
+unreserved characters defined in RFC 3986 are decoded,
+and any dot\-segments (\(dq..\(dq and \(dq.\(dq) are resolved and
+removed.
+.sp
+For example, \fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org/httpbin/\(aq
+matches the request host \(dqnghttp2.org\(dq and the request
+path \(dq\fI/httpbin/get\fP\(dq, but does not match the request host
+\(dqnghttp2.org\(dq and the request path \(dq\fI/index.html\fP\(dq.
+.sp
+The multiple <PATTERN>s can be specified, delimiting
+them by \(dq:\(dq. Specifying
+\fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org:www.nghttp2.org\(aq has the
+same effect to specify \fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org\(aq
+and \fI\%\-b\fP\(aq127.0.0.1,8080;www.nghttp2.org\(aq.
+.sp
+The backend addresses sharing same <PATTERN> are grouped
+together forming load balancing group.
+.sp
+Several parameters <PARAM> are accepted after <PATTERN>.
+The parameters are delimited by \(dq;\(dq. The available
+parameters are: \(dqproto=<PROTO>\(dq, \(dqtls\(dq,
+\(dqsni=<SNI_HOST>\(dq, \(dqfall=<N>\(dq, \(dqrise=<N>\(dq,
+\(dqaffinity=<METHOD>\(dq, \(dqdns\(dq, \(dqredirect\-if\-not\-tls\(dq,
+\(dqupgrade\-scheme\(dq, \(dqmruby=<PATH>\(dq,
+\(dqread\-timeout=<DURATION>\(dq, \(dqwrite\-timeout=<DURATION>\(dq,
+\(dqgroup=<GROUP>\(dq, \(dqgroup\-weight=<N>\(dq, \(dqweight=<N>\(dq, and
+\(dqdnf\(dq. The parameter consists of keyword, and
+optionally followed by \(dq=\(dq and value. For example, the
+parameter \(dqproto=h2\(dq consists of the keyword \(dqproto\(dq and
+value \(dqh2\(dq. The parameter \(dqtls\(dq consists of the keyword
+\(dqtls\(dq without value. Each parameter is described as
+follows.
+.sp
+The backend application protocol can be specified using
+optional \(dqproto\(dq parameter, and in the form of
+\(dqproto=<PROTO>\(dq. <PROTO> should be one of the following
+list without quotes: \(dqh2\(dq, \(dqhttp/1.1\(dq. The default
+value of <PROTO> is \(dqhttp/1.1\(dq. Note that usually \(dqh2\(dq
+refers to HTTP/2 over TLS. But in this option, it may
+mean HTTP/2 over cleartext TCP unless \(dqtls\(dq keyword is
+used (see below).
+.sp
+TLS can be enabled by specifying optional \(dqtls\(dq
+parameter. TLS is not enabled by default.
+.sp
+With \(dqsni=<SNI_HOST>\(dq parameter, it can override the TLS
+SNI field value with given <SNI_HOST>. This will
+default to the backend <HOST> name
+.sp
+The feature to detect whether backend is online or
+offline can be enabled using optional \(dqfall\(dq and \(dqrise\(dq
+parameters. Using \(dqfall=<N>\(dq parameter, if nghttpx
+cannot connect to a this backend <N> times in a row,
+this backend is assumed to be offline, and it is
+excluded from load balancing. If <N> is 0, this backend
+never be excluded from load balancing whatever times
+nghttpx cannot connect to it, and this is the default.
+There is also \(dqrise=<N>\(dq parameter. After backend was
+excluded from load balancing group, nghttpx periodically
+attempts to make a connection to the failed backend, and
+if the connection is made successfully <N> times in a
+row, the backend is assumed to be online, and it is now
+eligible for load balancing target. If <N> is 0, a
+backend is permanently offline, once it goes in that
+state, and this is the default behaviour.
+.sp
+The session affinity is enabled using
+\(dqaffinity=<METHOD>\(dq parameter. If \(dqip\(dq is given in
+<METHOD>, client IP based session affinity is enabled.
+If \(dqcookie\(dq is given in <METHOD>, cookie based session
+affinity is enabled. If \(dqnone\(dq is given in <METHOD>,
+session affinity is disabled, and this is the default.
+The session affinity is enabled per <PATTERN>. If at
+least one backend has \(dqaffinity\(dq parameter, and its
+<METHOD> is not \(dqnone\(dq, session affinity is enabled for
+all backend servers sharing the same <PATTERN>. It is
+advised to set \(dqaffinity\(dq parameter to all backend
+explicitly if session affinity is desired. The session
+affinity may break if one of the backend gets
+unreachable, or backend settings are reloaded or
+replaced by API.
+.sp
+If \(dqaffinity=cookie\(dq is used, the additional
+configuration is required.
+\(dqaffinity\-cookie\-name=<NAME>\(dq must be used to specify a
+name of cookie to use. Optionally,
+\(dqaffinity\-cookie\-path=<PATH>\(dq can be used to specify a
+path which cookie is applied. The optional
+\(dqaffinity\-cookie\-secure=<SECURE>\(dq controls the Secure
+attribute of a cookie. The default value is \(dqauto\(dq, and
+the Secure attribute is determined by a request scheme.
+If a request scheme is \(dqhttps\(dq, then Secure attribute is
+set. Otherwise, it is not set. If <SECURE> is \(dqyes\(dq,
+the Secure attribute is always set. If <SECURE> is
+\(dqno\(dq, the Secure attribute is always omitted.
+\(dqaffinity\-cookie\-stickiness=<STICKINESS>\(dq controls
+stickiness of this affinity. If <STICKINESS> is
+\(dqloose\(dq, removing or adding a backend server might break
+the affinity and the request might be forwarded to a
+different backend server. If <STICKINESS> is \(dqstrict\(dq,
+removing the designated backend server breaks affinity,
+but adding new backend server does not cause breakage.
+If the designated backend server becomes unavailable,
+new backend server is chosen as if the request does not
+have an affinity cookie. <STICKINESS> defaults to
+\(dqloose\(dq.
+.sp
+By default, name resolution of backend host name is done
+at start up, or reloading configuration. If \(dqdns\(dq
+parameter is given, name resolution takes place
+dynamically. This is useful if backend address changes
+frequently. If \(dqdns\(dq is given, name resolution of
+backend host name at start up, or reloading
+configuration is skipped.
+.sp
+If \(dqredirect\-if\-not\-tls\(dq parameter is used, the matched
+backend requires that frontend connection is TLS
+encrypted. If it isn\(aqt, nghttpx responds to the request
+with 308 status code, and https URI the client should
+use instead is included in Location header field. The
+port number in redirect URI is 443 by default, and can
+be changed using \fI\%\-\-redirect\-https\-port\fP option. If at
+least one backend has \(dqredirect\-if\-not\-tls\(dq parameter,
+this feature is enabled for all backend servers sharing
+the same <PATTERN>. It is advised to set
+\(dqredirect\-if\-no\-tls\(dq parameter to all backends
+explicitly if this feature is desired.
+.sp
+If \(dqupgrade\-scheme\(dq parameter is used along with \(dqtls\(dq
+parameter, HTTP/2 :scheme pseudo header field is changed
+to \(dqhttps\(dq from \(dqhttp\(dq when forwarding a request to this
+particular backend. This is a workaround for a backend
+server which requires \(dqhttps\(dq :scheme pseudo header
+field on TLS encrypted connection.
+.sp
+\(dqmruby=<PATH>\(dq parameter specifies a path to mruby
+script file which is invoked when this pattern is
+matched. All backends which share the same pattern must
+have the same mruby path.
+.sp
+\(dqread\-timeout=<DURATION>\(dq and \(dqwrite\-timeout=<DURATION>\(dq
+parameters specify the read and write timeout of the
+backend connection when this pattern is matched. All
+backends which share the same pattern must have the same
+timeouts. If these timeouts are entirely omitted for a
+pattern, \fI\%\-\-backend\-read\-timeout\fP and
+\fI\%\-\-backend\-write\-timeout\fP are used.
+.sp
+\(dqgroup=<GROUP>\(dq parameter specifies the name of group
+this backend address belongs to. By default, it belongs
+to the unnamed default group. The name of group is
+unique per pattern. \(dqgroup\-weight=<N>\(dq parameter
+specifies the weight of the group. The higher weight
+gets more frequently selected by the load balancing
+algorithm. <N> must be [1, 256] inclusive. The weight
+8 has 4 times more weight than 2. <N> must be the same
+for all addresses which share the same <GROUP>. If
+\(dqgroup\-weight\(dq is omitted in an address, but the other
+address which belongs to the same group specifies
+\(dqgroup\-weight\(dq, its weight is used. If no
+\(dqgroup\-weight\(dq is specified for all addresses, the
+weight of a group becomes 1. \(dqgroup\(dq and \(dqgroup\-weight\(dq
+are ignored if session affinity is enabled.
+.sp
+\(dqweight=<N>\(dq parameter specifies the weight of the
+backend address inside a group which this address
+belongs to. The higher weight gets more frequently
+selected by the load balancing algorithm. <N> must be
+[1, 256] inclusive. The weight 8 has 4 times more
+weight than weight 2. If this parameter is omitted,
+weight becomes 1. \(dqweight\(dq is ignored if session
+affinity is enabled.
+.sp
+If \(dqdnf\(dq parameter is specified, an incoming request is
+not forwarded to a backend and just consumed along with
+the request body (actually a backend server never be
+contacted). It is expected that the HTTP response is
+generated by mruby script (see \(dqmruby=<PATH>\(dq parameter
+above). \(dqdnf\(dq is an abbreviation of \(dqdo not forward\(dq.
+.sp
+Since \(dq;\(dq and \(dq:\(dq are used as delimiter, <PATTERN> must
+not contain these characters. In order to include \(dq:\(dq
+in <PATTERN>, one has to specify \(dq%3A\(dq (which is
+percent\-encoded from of \(dq:\(dq) instead. Since \(dq;\(dq has
+special meaning in shell, the option value must be
+quoted.
+.sp
+Default: \fB127.0.0.1,80\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-f, \-\-frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...]
+Set frontend host and port. If <HOST> is \(aq*\(aq, it
+assumes all addresses including both IPv4 and IPv6.
+UNIX domain socket can be specified by prefixing path
+name with \(dqunix:\(dq (e.g., unix:/var/run/nghttpx.sock).
+This option can be used multiple times to listen to
+multiple addresses.
+.sp
+This option can take 0 or more parameters, which are
+described below. Note that \(dqapi\(dq and \(dqhealthmon\(dq
+parameters are mutually exclusive.
+.sp
+Optionally, TLS can be disabled by specifying \(dqno\-tls\(dq
+parameter. TLS is enabled by default.
+.sp
+If \(dqsni\-fwd\(dq parameter is used, when performing a match
+to select a backend server, SNI host name received from
+the client is used instead of the request host. See
+\fI\%\-\-backend\fP option about the pattern match.
+.sp
+To make this frontend as API endpoint, specify \(dqapi\(dq
+parameter. This is disabled by default. It is
+important to limit the access to the API frontend.
+Otherwise, someone may change the backend server, and
+break your services, or expose confidential information
+to the outside the world.
+.sp
+To make this frontend as health monitor endpoint,
+specify \(dqhealthmon\(dq parameter. This is disabled by
+default. Any requests which come through this address
+are replied with 200 HTTP status, without no body.
+.sp
+To accept PROXY protocol version 1 and 2 on frontend
+connection, specify \(dqproxyproto\(dq parameter. This is
+disabled by default.
+.sp
+To receive HTTP/3 (QUIC) traffic, specify \(dqquic\(dq
+parameter. It makes nghttpx listen on UDP port rather
+than TCP port. UNIX domain socket, \(dqapi\(dq, and
+\(dqhealthmon\(dq parameters cannot be used with \(dqquic\(dq
+parameter.
+.sp
+Default: \fB*,3000\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backlog=<N>
+Set listen backlog size.
+.sp
+Default: \fB65536\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-address\-family=(auto|IPv4|IPv6)
+Specify address family of backend connections. If
+\(dqauto\(dq is given, both IPv4 and IPv6 are considered. If
+\(dqIPv4\(dq is given, only IPv4 address is considered. If
+\(dqIPv6\(dq is given, only IPv6 address is considered.
+.sp
+Default: \fBauto\fP
+.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 \-\-single\-thread
+Run everything in one thread inside the worker process.
+This feature is provided for better debugging
+experience, or for the platforms which lack thread
+support. If threading is disabled, this option is
+always enabled.
+.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\-connections\-per\-host=<N>
+Set maximum number of backend concurrent connections
+(and/or streams in case of HTTP/2) per origin host.
+This option is meaningful when \fI\%\-\-http2\-proxy\fP option is
+used. The origin host is determined by authority
+portion of request URI (or :authority header field for
+HTTP/2). To limit the number of connections per
+frontend for default mode, use
+\fI\%\-\-backend\-connections\-per\-frontend\fP\&.
+.sp
+Default: \fB8\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-connections\-per\-frontend=<N>
+Set maximum number of backend concurrent connections
+(and/or streams in case of HTTP/2) per frontend. This
+option is only used for default mode. 0 means
+unlimited. To limit the number of connections per host
+with \fI\%\-\-http2\-proxy\fP option, use
+\fI\%\-\-backend\-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 \-\-rlimit\-memlock=<N>
+Set maximum number of bytes of memory that may be locked
+into RAM. 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: \fB128K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-fastopen=<N>
+Enables \(dqTCP Fast Open\(dq for the listening socket and
+limits the maximum length for the queue of connections
+that have not yet completed the three\-way handshake. If
+value is 0 then fast open is disabled.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-kqueue
+Don\(aqt use kqueue. This option is only applicable for
+the platforms which have kqueue. For other platforms,
+this option will be simply ignored.
+.UNINDENT
+.SS Timeout
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-read\-timeout=<DURATION>
+Specify read timeout for HTTP/2 frontend connection.
+.sp
+Default: \fB3m\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-read\-timeout=<DURATION>
+Specify read timeout for HTTP/3 frontend connection.
+.sp
+Default: \fB3m\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-read\-timeout=<DURATION>
+Specify read timeout for HTTP/1.1 frontend connection.
+.sp
+Default: \fB1m\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 \-\-frontend\-keep\-alive\-timeout=<DURATION>
+Specify keep\-alive timeout for frontend HTTP/1
+connection.
+.sp
+Default: \fB1m\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-stream\-read\-timeout=<DURATION>
+Specify read timeout for HTTP/2 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 streams. 0 means no
+timeout.
+.sp
+Default: \fB1m\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-read\-timeout=<DURATION>
+Specify read timeout for backend connection.
+.sp
+Default: \fB1m\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\-connect\-timeout=<DURATION>
+Specify timeout before establishing TCP connection to
+backend.
+.sp
+Default: \fB30s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-keep\-alive\-timeout=<DURATION>
+Specify keep\-alive timeout for backend HTTP/1
+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: \fB30s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-setting\-timeout=<DURATION>
+Specify timeout before SETTINGS ACK is received from
+client.
+.sp
+Default: \fB10s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-settings\-timeout=<DURATION>
+Specify timeout before SETTINGS ACK is received from
+backend server.
+.sp
+Default: \fB10s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-max\-backoff=<DURATION>
+Specify maximum backoff interval. This is used when
+doing health check against offline backend (see \(dqfail\(dq
+parameter in \fI\%\-\-backend\fP option). It is also used to
+limit the maximum interval to temporarily disable
+backend when nghttpx failed to connect to it. These
+intervals are calculated using exponential backoff, and
+consecutive failed attempts increase the interval. This
+option caps its maximum value.
+.sp
+Default: \fB2m\fP
+.UNINDENT
+.SS SSL/TLS
+.INDENT 0.0
+.TP
+.B \-\-ciphers=<SUITE>
+Set allowed cipher list for frontend connection. The
+format of the string is described in OpenSSL ciphers(1).
+This option sets cipher suites for TLSv1.2 or earlier.
+Use \fI\%\-\-tls13\-ciphers\fP for TLSv1.3.
+.sp
+Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls13\-ciphers=<SUITE>
+Set allowed cipher list for frontend connection. The
+format of the string is described in OpenSSL ciphers(1).
+This option sets cipher suites for TLSv1.3. Use
+\fI\%\-\-ciphers\fP for TLSv1.2 or earlier.
+.sp
+Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client\-ciphers=<SUITE>
+Set allowed cipher list for backend connection. The
+format of the string is described in OpenSSL ciphers(1).
+This option sets cipher suites for TLSv1.2 or earlier.
+Use \fI\%\-\-tls13\-client\-ciphers\fP for TLSv1.3.
+.sp
+Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls13\-client\-ciphers=<SUITE>
+Set allowed cipher list for backend connection. The
+format of the string is described in OpenSSL ciphers(1).
+This option sets cipher suites for TLSv1.3. Use
+\fI\%\-\-tls13\-client\-ciphers\fP for TLSv1.2 or earlier.
+.sp
+Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ecdh\-curves=<LIST>
+Set supported curve list for frontend connections.
+<LIST> is a colon separated list of curve NID or names
+in the preference order. The supported curves depend on
+the linked OpenSSL library. This function requires
+OpenSSL >= 1.0.2.
+.sp
+Default: \fBX25519:P\-256:P\-384:P\-521\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-k, \-\-insecure
+Don\(aqt verify backend server\(aqs certificate if TLS is
+enabled for backend connections.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-cacert=<PATH>
+Set path to trusted CA certificate file. It is used in
+backend TLS connections to verify peer\(aqs certificate.
+It is also used to verify OCSP response from the script
+set by \fI\%\-\-fetch\-ocsp\-response\-file\fP\&. 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>[[;<PARAM>]...]
+Specify additional certificate and private key file.
+nghttpx will choose certificates based on the hostname
+indicated by client using TLS SNI extension. If nghttpx
+is built with OpenSSL >= 1.0.2, the shared elliptic
+curves (e.g., P\-256) between client and server are also
+taken into consideration. This allows nghttpx to send
+ECDSA certificate to modern clients, while sending RSA
+based certificate to older clients. This option can be
+used multiple times. To make OCSP stapling work,
+<CERTPATH> must be absolute path.
+.sp
+Additional parameter can be specified in <PARAM>. The
+available <PARAM> is \(dqsct\-dir=<DIR>\(dq.
+.sp
+\(dqsct\-dir=<DIR>\(dq specifies the path to directory which
+contains *.sct files for TLS
+signed_certificate_timestamp extension (RFC 6962). This
+feature requires OpenSSL >= 1.0.2. See also
+\fI\%\-\-tls\-sct\-dir\fP option.
+.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 \-\-alpn\-list=<LIST>
+Comma delimited list of ALPN protocol identifier sorted
+in the order of preference. That means most desirable
+protocol comes first. 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,h2\-16,h2\-14,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 \-\-verify\-client\-tolerate\-expired
+Accept expired client certificate. Operator should
+handle the expired client certificate by some means
+(e.g., mruby script). Otherwise, this option might
+cause a security risk.
+.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\-min\-proto\-version=<VER>
+Specify minimum SSL/TLS protocol. The name matching is
+done in case\-insensitive manner. The versions between
+\fI\%\-\-tls\-min\-proto\-version\fP and \fI\%\-\-tls\-max\-proto\-version\fP are
+enabled. If the protocol list advertised by client does
+not overlap this range, you will receive the error
+message \(dqunknown protocol\(dq. If a protocol version lower
+than TLSv1.2 is specified, make sure that the compatible
+ciphers are included in \fI\%\-\-ciphers\fP option. The default
+cipher list only includes ciphers compatible with
+TLSv1.2 or above. The available versions are:
+TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0
+.sp
+Default: \fBTLSv1.2\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-max\-proto\-version=<VER>
+Specify maximum SSL/TLS protocol. The name matching is
+done in case\-insensitive manner. The versions between
+\fI\%\-\-tls\-min\-proto\-version\fP and \fI\%\-\-tls\-max\-proto\-version\fP are
+enabled. If the protocol list advertised by client does
+not overlap this range, you will receive the error
+message \(dqunknown protocol\(dq. The available versions are:
+TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0
+.sp
+Default: \fBTLSv1.3\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-file=<PATH>
+Path to file that contains random data to construct TLS
+session ticket parameters. If aes\-128\-cbc is given in
+\fI\%\-\-tls\-ticket\-key\-cipher\fP, the file must contain exactly
+48 bytes. If aes\-256\-cbc is given in
+\fI\%\-\-tls\-ticket\-key\-cipher\fP, the file must contain exactly
+80 bytes. 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 every 1 hour internally and
+they are valid for 12 hours. This is recommended if
+ticket key sharing between nghttpx instances is not
+required.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached=<HOST>,<PORT>[;tls]
+Specify address of memcached server to get TLS ticket
+keys for session resumption. This enables shared TLS
+ticket key between multiple nghttpx instances. nghttpx
+does not set TLS ticket key to memcached. The external
+ticket key generator is required. nghttpx just gets TLS
+ticket keys from memcached, and use them, possibly
+replacing current set of keys. It is up to extern TLS
+ticket key generator to rotate keys frequently. See
+\(dqTLS SESSION TICKET RESUMPTION\(dq section in manual page
+to know the data format in memcached entry. Optionally,
+memcached connection can be encrypted with TLS by
+specifying \(dqtls\(dq parameter.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-address\-family=(auto|IPv4|IPv6)
+Specify address family of memcached connections to get
+TLS ticket keys. If \(dqauto\(dq is given, both IPv4 and IPv6
+are considered. If \(dqIPv4\(dq is given, only IPv4 address
+is considered. If \(dqIPv6\(dq is given, only IPv6 address is
+considered.
+.sp
+Default: \fBauto\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-interval=<DURATION>
+Set interval to get TLS ticket keys from memcached.
+.sp
+Default: \fB10m\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-max\-retry=<N>
+Set maximum number of consecutive retries before
+abandoning TLS ticket key retrieval. If this number is
+reached, the attempt is considered as failure, and
+\(dqfailure\(dq count is incremented by 1, which contributed
+to the value controlled
+\fI\%\-\-tls\-ticket\-key\-memcached\-max\-fail\fP option.
+.sp
+Default: \fB3\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-max\-fail=<N>
+Set maximum number of consecutive failure before
+disabling TLS ticket until next scheduled key retrieval.
+.sp
+Default: \fB2\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-cipher=<CIPHER>
+Specify cipher to encrypt TLS session ticket. Specify
+either aes\-128\-cbc or aes\-256\-cbc. By default,
+aes\-128\-cbc is used.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-cert\-file=<PATH>
+Path to client certificate for memcached connections to
+get TLS ticket keys.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-memcached\-private\-key\-file=<PATH>
+Path to client private key for memcached connections to
+get TLS ticket keys.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-fetch\-ocsp\-response\-file=<PATH>
+Path to fetch\-ocsp\-response script file. It should be
+absolute path.
+.sp
+Default: \fB/usr/local/share/nghttp2/fetch\-ocsp\-response\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ocsp\-update\-interval=<DURATION>
+Set interval to update OCSP response cache.
+.sp
+Default: \fB4h\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ocsp\-startup
+Start accepting connections after initial attempts to
+get OCSP responses finish. It does not matter some of
+the attempts fail. This feature is useful if OCSP
+responses must be available before accepting
+connections.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-verify\-ocsp
+nghttpx does not verify OCSP response.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-ocsp
+Disable OCSP stapling.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-session\-cache\-memcached=<HOST>,<PORT>[;tls]
+Specify address of memcached server to store session
+cache. This enables shared session cache between
+multiple nghttpx instances. Optionally, memcached
+connection can be encrypted with TLS by specifying \(dqtls\(dq
+parameter.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-session\-cache\-memcached\-address\-family=(auto|IPv4|IPv6)
+Specify address family of memcached connections to store
+session cache. If \(dqauto\(dq is given, both IPv4 and IPv6
+are considered. If \(dqIPv4\(dq is given, only IPv4 address
+is considered. If \(dqIPv6\(dq is given, only IPv6 address is
+considered.
+.sp
+Default: \fBauto\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-session\-cache\-memcached\-cert\-file=<PATH>
+Path to client certificate for memcached connections to
+store session cache.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-session\-cache\-memcached\-private\-key\-file=<PATH>
+Path to client private key for memcached connections to
+store session cache.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-dyn\-rec\-warmup\-threshold=<SIZE>
+Specify the threshold size for TLS dynamic record size
+behaviour. During a TLS session, after the threshold
+number of bytes have been written, the TLS record size
+will be increased to the maximum allowed (16K). The max
+record size will continue to be used on the active TLS
+session. After \fI\%\-\-tls\-dyn\-rec\-idle\-timeout\fP has elapsed,
+the record size is reduced to 1300 bytes. Specify 0 to
+always use the maximum record size, regardless of idle
+period. This behaviour applies to all TLS based
+frontends, and TLS HTTP/2 backends.
+.sp
+Default: \fB1M\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-dyn\-rec\-idle\-timeout=<DURATION>
+Specify TLS dynamic record size behaviour timeout. See
+\fI\%\-\-tls\-dyn\-rec\-warmup\-threshold\fP for more information.
+This behaviour applies to all TLS based frontends, and
+TLS HTTP/2 backends.
+.sp
+Default: \fB1s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-http2\-cipher\-block\-list
+Allow block listed cipher suite on frontend HTTP/2
+connection. See
+\fI\%https://tools.ietf.org/html/rfc7540#appendix\-A\fP for the
+complete HTTP/2 cipher suites block list.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client\-no\-http2\-cipher\-block\-list
+Allow block listed cipher suite on backend HTTP/2
+connection. See
+\fI\%https://tools.ietf.org/html/rfc7540#appendix\-A\fP for the
+complete HTTP/2 cipher suites block list.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-sct\-dir=<DIR>
+Specifies the directory where *.sct files exist. All
+*.sct files in <DIR> are read, and sent as
+extension_data of TLS signed_certificate_timestamp (RFC
+6962) to client. These *.sct files are for the
+certificate specified in positional command\-line
+argument <CERT>, or certificate option in configuration
+file. For additional certificates, use \fI\%\-\-subcert\fP
+option. This option requires OpenSSL >= 1.0.2.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-psk\-secrets=<PATH>
+Read list of PSK identity and secrets from <PATH>. This
+is used for frontend connection. The each line of input
+file is formatted as <identity>:<hex\-secret>, where
+<identity> is PSK identity, and <hex\-secret> is secret
+in hex. An empty line, and line which starts with \(aq#\(aq
+are skipped. The default enabled cipher list might not
+contain any PSK cipher suite. In that case, desired PSK
+cipher suites must be enabled using \fI\%\-\-ciphers\fP option.
+The desired PSK cipher suite may be block listed by
+HTTP/2. To use those cipher suites with HTTP/2,
+consider to use \fI\%\-\-no\-http2\-cipher\-block\-list\fP option.
+But be aware its implications.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client\-psk\-secrets=<PATH>
+Read PSK identity and secrets from <PATH>. This is used
+for backend connection. The each line of input file is
+formatted as <identity>:<hex\-secret>, where <identity>
+is PSK identity, and <hex\-secret> is secret in hex. An
+empty line, and line which starts with \(aq#\(aq are skipped.
+The first identity and secret pair encountered is used.
+The default enabled cipher list might not contain any
+PSK cipher suite. In that case, desired PSK cipher
+suites must be enabled using \fI\%\-\-client\-ciphers\fP option.
+The desired PSK cipher suite may be block listed by
+HTTP/2. To use those cipher suites with HTTP/2,
+consider to use \fI\%\-\-client\-no\-http2\-cipher\-block\-list\fP
+option. But be aware its implications.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-no\-postpone\-early\-data
+By default, except for QUIC connections, nghttpx
+postpones forwarding HTTP requests sent in early data,
+including those sent in partially in it, until TLS
+handshake finishes. If all backend server recognizes
+\(dqEarly\-Data\(dq header field, using this option makes
+nghttpx not postpone forwarding request and get full
+potential of 0\-RTT data.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-max\-early\-data=<SIZE>
+Sets the maximum amount of 0\-RTT data that server
+accepts.
+.sp
+Default: \fB16K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ktls
+Enable ktls. For server, ktls is enable if
+\fI\%\-\-tls\-session\-cache\-memcached\fP is not configured.
+.UNINDENT
+.SS HTTP/2
+.INDENT 0.0
+.TP
+.B \-c, \-\-frontend\-http2\-max\-concurrent\-streams=<N>
+Set the maximum number of the concurrent streams in one
+frontend HTTP/2 session.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-max\-concurrent\-streams=<N>
+Set the maximum number of the concurrent streams in one
+backend HTTP/2 session. This sets maximum number of
+concurrent opened pushed streams. The maximum number of
+concurrent requests are set by a remote server.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-window\-size=<SIZE>
+Sets the per\-stream initial window size of HTTP/2
+frontend connection.
+.sp
+Default: \fB65535\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-connection\-window\-size=<SIZE>
+Sets the per\-connection window size of HTTP/2 frontend
+connection.
+.sp
+Default: \fB65535\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-window\-size=<SIZE>
+Sets the initial window size of HTTP/2 backend
+connection.
+.sp
+Default: \fB65535\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-connection\-window\-size=<SIZE>
+Sets the per\-connection window size of HTTP/2 backend
+connection.
+.sp
+Default: \fB2147483647\fP
+.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 supported by
+default mode and HTTP/2 frontend via Link header field.
+It is also supported if both frontend and backend are
+HTTP/2 in default mode. In this case, server push from
+backend session is relayed to frontend, and server push
+via Link header field is also supported.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-optimize\-write\-buffer\-size
+(Experimental) Enable write buffer size optimization in
+frontend HTTP/2 TLS connection. This optimization aims
+to reduce write buffer size so that it only contains
+bytes which can send immediately. This makes server
+more responsive to prioritized HTTP/2 stream because the
+buffering of lower priority stream is reduced. This
+option is only effective on recent Linux platform.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-optimize\-window\-size
+(Experimental) Automatically tune connection level
+window size of frontend HTTP/2 TLS connection. If this
+feature is enabled, connection window size starts with
+the default window size, 65535 bytes. nghttpx
+automatically adjusts connection window size based on
+TCP receiving window size. The maximum window size is
+capped by the value specified by
+\fI\%\-\-frontend\-http2\-connection\-window\-size\fP\&. Since the
+stream is subject to stream level window size, it should
+be adjusted using \fI\%\-\-frontend\-http2\-window\-size\fP option as
+well. This option is only effective on recent Linux
+platform.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-encoder\-dynamic\-table\-size=<SIZE>
+Specify the maximum dynamic table size of HPACK encoder
+in the frontend HTTP/2 connection. The decoder (client)
+specifies the maximum dynamic table size it accepts.
+Then the negotiated dynamic table size is the minimum of
+this option value and the value which client specified.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-decoder\-dynamic\-table\-size=<SIZE>
+Specify the maximum dynamic table size of HPACK decoder
+in the frontend HTTP/2 connection.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-encoder\-dynamic\-table\-size=<SIZE>
+Specify the maximum dynamic table size of HPACK encoder
+in the backend HTTP/2 connection. The decoder (backend)
+specifies the maximum dynamic table size it accepts.
+Then the negotiated dynamic table size is the minimum of
+this option value and the value which backend specified.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-decoder\-dynamic\-table\-size=<SIZE>
+Specify the maximum dynamic table size of HPACK decoder
+in the backend HTTP/2 connection.
+.sp
+Default: \fB4K\fP
+.UNINDENT
+.SS Mode
+.INDENT 0.0
+.TP
+.B (default mode)
+Accept HTTP/2, and HTTP/1.1 over SSL/TLS. \(dqno\-tls\(dq
+parameter is used in \fI\%\-\-frontend\fP option, accept HTTP/2
+and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1
+connection can be upgraded to HTTP/2 through HTTP
+Upgrade.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-s, \-\-http2\-proxy
+Like default mode, but enable forward proxy. This is so
+called HTTP/2 proxy mode.
+.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.
+.IP \(bu 2
+$tls_cipher: cipher used for SSL/TLS connection.
+.IP \(bu 2
+$tls_client_fingerprint_sha256: SHA\-256 fingerprint of
+client certificate.
+.IP \(bu 2
+$tls_client_fingerprint_sha1: SHA\-1 fingerprint of
+client certificate.
+.IP \(bu 2
+$tls_client_subject_name: subject name in client
+certificate.
+.IP \(bu 2
+$tls_client_issuer_name: issuer name in client
+certificate.
+.IP \(bu 2
+$tls_client_serial: serial number in client
+certificate.
+.IP \(bu 2
+$tls_protocol: protocol for SSL/TLS connection.
+.IP \(bu 2
+$tls_session_id: session ID for SSL/TLS connection.
+.IP \(bu 2
+$tls_session_reused: \(dqr\(dq if SSL/TLS session was
+reused. Otherwise, \(dq.\(dq
+.IP \(bu 2
+$tls_sni: SNI server name for SSL/TLS connection.
+.IP \(bu 2
+$backend_host: backend host used to fulfill the
+request. \(dq\-\(dq if backend host is not available.
+.IP \(bu 2
+$backend_port: backend port used to fulfill the
+request. \(dq\-\(dq if backend host is not available.
+.IP \(bu 2
+$method: HTTP method
+.IP \(bu 2
+$path: Request path including query. For CONNECT
+request, authority is recorded.
+.IP \(bu 2
+$path_without_query: $path up to the first \(aq?\(aq
+character. For CONNECT request, authority is
+recorded.
+.IP \(bu 2
+$protocol_version: HTTP version (e.g., HTTP/1.1,
+HTTP/2)
+.UNINDENT
+.sp
+The variable can be enclosed by \(dq{\(dq and \(dq}\(dq for
+disambiguation (e.g., ${remote_addr}).
+.sp
+Default: \fB$remote_addr \- \- [$time_local] \(dq$request\(dq $status $body_bytes_sent \(dq$http_referer\(dq \(dq$http_user_agent\(dq\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-accesslog\-write\-early
+Write access log when response header fields are
+received from backend rather than when request
+transaction finishes.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-errorlog\-file=<PATH>
+Set path to write error log. To reopen file, send USR1
+signal to nghttpx. stderr will be redirected to the
+error log file unless \fI\%\-\-errorlog\-syslog\fP is used.
+.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\-add\-x\-forwarded\-proto
+Don\(aqt append additional X\-Forwarded\-Proto header field
+to the backend request. If inbound client sets
+X\-Forwarded\-Proto, and
+\fI\%\-\-no\-strip\-incoming\-x\-forwarded\-proto\fP option is used,
+they are passed to the backend.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-strip\-incoming\-x\-forwarded\-proto
+Don\(aqt strip X\-Forwarded\-Proto header field from inbound
+client requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-add\-forwarded=<LIST>
+Append RFC 7239 Forwarded header field with parameters
+specified in comma delimited list <LIST>. The supported
+parameters are \(dqby\(dq, \(dqfor\(dq, \(dqhost\(dq, and \(dqproto\(dq. By
+default, the value of \(dqby\(dq and \(dqfor\(dq parameters are
+obfuscated string. See \fI\%\-\-forwarded\-by\fP and
+\fI\%\-\-forwarded\-for\fP options respectively. Note that nghttpx
+does not translate non\-standard X\-Forwarded\-* header
+fields into Forwarded header field, and vice versa.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-strip\-incoming\-forwarded
+Strip Forwarded header field from inbound client
+requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-forwarded\-by=(obfuscated|ip|<VALUE>)
+Specify the parameter value sent out with \(dqby\(dq parameter
+of Forwarded header field. If \(dqobfuscated\(dq is given,
+the string is randomly generated at startup. If \(dqip\(dq is
+given, the interface address of the connection,
+including port number, is sent with \(dqby\(dq parameter. In
+case of UNIX domain socket, \(dqlocalhost\(dq is used instead
+of address and port. User can also specify the static
+obfuscated string. The limitation is that it must start
+with \(dq_\(dq, and only consists of character set
+[A\-Za\-z0\-9._\-], as described in RFC 7239.
+.sp
+Default: \fBobfuscated\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-forwarded\-for=(obfuscated|ip)
+Specify the parameter value sent out with \(dqfor\(dq
+parameter of Forwarded header field. If \(dqobfuscated\(dq is
+given, the string is randomly generated for each client
+connection. If \(dqip\(dq is given, the remote client address
+of the connection, without port number, is sent with
+\(dqfor\(dq parameter. In case of UNIX domain socket,
+\(dqlocalhost\(dq is used instead of address.
+.sp
+Default: \fBobfuscated\fP
+.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\-strip\-incoming\-early\-data
+Don\(aqt strip Early\-Data header field from inbound client
+requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-location\-rewrite
+Don\(aqt rewrite location header field in default mode.
+When \fI\%\-\-http2\-proxy\fP is used, location header field will
+not be altered regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-host\-rewrite
+Rewrite host and :authority header fields in default
+mode. When \fI\%\-\-http2\-proxy\fP is used, these headers will
+not be altered regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+Specify protocol ID, port, host and origin of
+alternative service. <HOST>, <ORIGIN> and <PARAMS> are
+optional. Empty <HOST> and <ORIGIN> are allowed and
+they are treated as nothing is specified. They are
+advertised in alt\-svc header field only in HTTP/1.1
+frontend. This option can be used multiple times to
+specify multiple alternative services.
+Example: \fI\%\-\-altsvc\fP=\(dqh2,443,,,ma=3600; persist=1\(dq
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-http2\-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+Just like \fI\%\-\-altsvc\fP option, but this altsvc is only sent
+in HTTP/2 frontend.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-add\-request\-header=<HEADER>
+Specify additional header field to add to request header
+set. The field name must be lowercase. 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\-request\-header\fP=\(dqfoo: bar\(dq
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-add\-response\-header=<HEADER>
+Specify additional header field to add to response
+header set. The field name must be lowercase. 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=\(dqfoo: bar\(dq
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-request\-header\-field\-buffer=<SIZE>
+Set maximum buffer size for incoming HTTP request header
+field list. This is the sum of header name and value in
+bytes. If trailer fields exist, they are counted
+towards this number.
+.sp
+Default: \fB64K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-max\-request\-header\-fields=<N>
+Set maximum number of incoming HTTP request header
+fields. If trailer fields exist, they are counted
+towards this number.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-response\-header\-field\-buffer=<SIZE>
+Set maximum buffer size for incoming HTTP response
+header field list. This is the sum of header name and
+value in bytes. If trailer fields exist, they are
+counted towards this number.
+.sp
+Default: \fB64K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-max\-response\-header\-fields=<N>
+Set maximum number of incoming HTTP response header
+fields. If trailer fields exist, they are counted
+towards this number.
+.sp
+Default: \fB500\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-error\-page=(<CODE>|*)=<PATH>
+Set file path to custom error page served when nghttpx
+originally generates HTTP error status code <CODE>.
+<CODE> must be greater than or equal to 400, and at most
+599. If \(dq*\(dq is used instead of <CODE>, it matches all
+HTTP status code. If error status code comes from
+backend server, the custom error pages are not used.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-server\-name=<NAME>
+Change server response header field value to <NAME>.
+.sp
+Default: \fBnghttpx\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-server\-rewrite
+Don\(aqt rewrite server header field in default mode. When
+\fI\%\-\-http2\-proxy\fP is used, these headers will not be altered
+regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-redirect\-https\-port=<PORT>
+Specify the port number which appears in Location header
+field when redirect to HTTPS URI is made due to
+\(dqredirect\-if\-not\-tls\(dq parameter in \fI\%\-\-backend\fP option.
+.sp
+Default: \fB443\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-require\-http\-scheme
+Always require http or https scheme in HTTP request. It
+also requires that https scheme must be used for an
+encrypted connection. Otherwise, http scheme must be
+used. This option is recommended for a server
+deployment which directly faces clients and the services
+it provides only require http or https scheme.
+.UNINDENT
+.SS API
+.INDENT 0.0
+.TP
+.B \-\-api\-max\-request\-body=<SIZE>
+Set the maximum size of request body for API request.
+.sp
+Default: \fB32M\fP
+.UNINDENT
+.SS DNS
+.INDENT 0.0
+.TP
+.B \-\-dns\-cache\-timeout=<DURATION>
+Set duration that cached DNS results remain valid. Note
+that nghttpx caches the unsuccessful results as well.
+.sp
+Default: \fB10s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-dns\-lookup\-timeout=<DURATION>
+Set timeout that DNS server is given to respond to the
+initial DNS query. For the 2nd and later queries,
+server is given time based on this timeout, and it is
+scaled linearly.
+.sp
+Default: \fB5s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-dns\-max\-try=<N>
+Set the number of DNS query before nghttpx gives up name
+lookup.
+.sp
+Default: \fB2\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-max\-requests=<N>
+The number of requests that single frontend connection
+can process. For HTTP/2, this is the number of streams
+in one HTTP/2 connection. For HTTP/1, this is the
+number of keep alive requests. This is hint to nghttpx,
+and it may allow additional few requests. The default
+value is unlimited.
+.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
+.INDENT 0.0
+.TP
+.B \-\-single\-process
+Run this program in a single process mode for debugging
+purpose. Without this option, nghttpx creates at least
+2 processes: main and worker processes. If this option
+is used, main and worker are unified into a single
+process. nghttpx still spawns additional process if
+neverbleed is used. In the single process mode, the
+signal handling feature is disabled.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-max\-worker\-processes=<N>
+The maximum number of worker processes. nghttpx spawns
+new worker process when it reloads its configuration.
+The previous worker process enters graceful termination
+period and will terminate when it finishes handling the
+existing connections. However, if reloading
+configurations happen very frequently, the worker
+processes might be piled up if they take a bit long time
+to finish the existing connections. With this option,
+if the number of worker processes exceeds the given
+value, the oldest worker process is terminated
+immediately. Specifying 0 means no limit and it is the
+default behaviour.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-process\-grace\-shutdown\-period=<DURATION>
+Maximum period for a worker process to terminate
+gracefully. When a worker process enters in graceful
+shutdown period (e.g., when nghttpx reloads its
+configuration) and it does not finish handling the
+existing connections in the given period of time, it is
+immediately terminated. Specifying 0 means no limit and
+it is the default behaviour.
+.UNINDENT
+.SS Scripting
+.INDENT 0.0
+.TP
+.B \-\-mruby\-file=<PATH>
+Set mruby script file
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-ignore\-per\-pattern\-mruby\-error
+Ignore mruby compile error for per\-pattern mruby script
+file. If error occurred, it is treated as if no mruby
+file were specified for the pattern.
+.UNINDENT
+.SS HTTP/3 and QUIC
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-idle\-timeout=<DURATION>
+Specify an idle timeout for QUIC connection.
+.sp
+Default: \fB30s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-debug\-log
+Output QUIC debug log to \fI/dev/stderr.\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-quic\-bpf\-program\-file=<PATH>
+Specify a path to eBPF program file reuseport_kern.o to
+direct an incoming QUIC UDP datagram to a correct
+socket.
+.sp
+Default: \fB/usr/local/lib/nghttp2/reuseport_kern.o\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-early\-data
+Enable early data on frontend QUIC connections. nghttpx
+sends \(dqEarly\-Data\(dq header field to a backend server if a
+request is received in early data and handshake has not
+finished. All backend servers should deal with possibly
+replayed requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-qlog\-dir=<DIR>
+Specify a directory where a qlog file is written for
+frontend QUIC connections. A qlog file is created per
+each QUIC connection. The file name is ISO8601 basic
+format, followed by \(dq\-\(dq, server Source Connection ID and
+\(dq.sqlog\(dq.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-require\-token
+Require an address validation token for a frontend QUIC
+connection. Server sends a token in Retry packet or
+NEW_TOKEN frame in the previous connection.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-congestion\-controller=<CC>
+Specify a congestion controller algorithm for a frontend
+QUIC connection. <CC> should be either \(dqcubic\(dq or
+\(dqbbr\(dq.
+.sp
+Default: \fBcubic\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-secret\-file=<PATH>
+Path to file that contains secure random data to be used
+as QUIC keying materials. It is used to derive keys for
+encrypting tokens and Connection IDs. It is not used to
+encrypt QUIC packets. Each line of this file must
+contain exactly 136 bytes hex\-encoded string (when
+decoded the byte string is 68 bytes long). The first 2
+bits of decoded byte string are used to identify the
+keying material. An empty line or a line which starts
+\(aq#\(aq is ignored. The file can contain more than one
+keying materials. Because the identifier is 2 bits, at
+most 4 keying materials are read and the remaining data
+is discarded. The first keying material in the file is
+primarily used for encryption and decryption for new
+connection. The other ones are used to decrypt data for
+the existing connections. Specifying multiple keying
+materials enables key rotation. Please note that key
+rotation does not occur automatically. User should
+update files or change options values and restart
+nghttpx gracefully. If opening or reading given file
+fails, all loaded keying materials 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, a keying material is generated
+internally on startup and reload.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-quic\-server\-id=<HEXSTRING>
+Specify server ID encoded in Connection ID to identify
+this particular server instance. Connection ID is
+encrypted and this part is not visible in public. It
+must be 4 bytes long and must be encoded in hex string
+(which is 8 bytes long). If this option is omitted, a
+random server ID is generated on startup and
+configuration reload.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-quic\-initial\-rtt=<DURATION>
+Specify the initial RTT of the frontend QUIC connection.
+.sp
+Default: \fB333ms\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-quic\-bpf
+Disable eBPF.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-window\-size=<SIZE>
+Sets the per\-stream initial window size of HTTP/3
+frontend connection.
+.sp
+Default: \fB256K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-connection\-window\-size=<SIZE>
+Sets the per\-connection window size of HTTP/3 frontend
+connection.
+.sp
+Default: \fB1M\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-max\-window\-size=<SIZE>
+Sets the maximum per\-stream window size of HTTP/3
+frontend connection. The window size is adjusted based
+on the receiving rate of stream data. The initial value
+is the value specified by \fI\%\-\-frontend\-http3\-window\-size\fP
+and the window size grows up to <SIZE> bytes.
+.sp
+Default: \fB6M\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-max\-connection\-window\-size=<SIZE>
+Sets the maximum per\-connection window size of HTTP/3
+frontend connection. The window size is adjusted based
+on the receiving rate of stream data. The initial value
+is the value specified by
+\fI\%\-\-frontend\-http3\-connection\-window\-size\fP and the window
+size grows up to <SIZE> bytes.
+.sp
+Default: \fB8M\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http3\-max\-concurrent\-streams=<N>
+Set the maximum number of the concurrent streams in one
+frontend HTTP/3 connection.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.SS Misc
+.INDENT 0.0
+.TP
+.B \-\-conf=<PATH>
+Load configuration from <PATH>. Please note that
+nghttpx always tries to read the default configuration
+file if \fI\%\-\-conf\fP is not given.
+.sp
+Default: \fB/etc/nghttpx/nghttpx.conf\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-include=<PATH>
+Load additional configurations from <PATH>. File <PATH>
+is read when configuration parser encountered this
+option. This option can be used multiple times, or even
+recursively.
+.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 h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). 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
+When specifying arguments including characters which have special
+meaning to a shell, we usually use quotes so that shell does not
+interpret them. When writing this configuration file, quotes for
+this purpose must not be used. For example, specify additional
+request header field, do this:
+.INDENT 7.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+add\-request\-header=foo: bar
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+instead of:
+.INDENT 7.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+add\-request\-header=\(dqfoo: bar\(dq
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.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 command\-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.
+.TP
+.B Error log
+Error log is written to stderr by default. It can be configured
+using \fI\%\-\-errorlog\-file\fP\&. The format of log message is as
+follows:
+.sp
+<datetime> <main\-pid> <current\-pid> <thread\-id> <level> (<filename>:<line>) <msg>
+.INDENT 7.0
+.TP
+.B <datetime>
+It is a combination of date and time when the log is written. It
+is in ISO 8601 format.
+.TP
+.B <main\-pid>
+It is a main process ID.
+.TP
+.B <current\-pid>
+It is a process ID which writes this log.
+.TP
+.B <thread\-id>
+It is a thread ID which writes this log. It would be unique
+within <current\-pid>.
+.TP
+.B <filename> and <line>
+They are source file name, and line number which produce this log.
+.TP
+.B <msg>
+It is a log message body.
+.UNINDENT
+.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 SIGHUP
+Reload configuration file given in \fI\%\-\-conf\fP\&.
+.TP
+.B SIGUSR1
+Reopen log files.
+.UNINDENT
+.sp
+SIGUSR2
+.INDENT 0.0
+.INDENT 3.5
+Fork and execute nghttpx. It will execute the binary in the same
+path with same command\-line arguments and environment variables. As
+of nghttpx version 1.20.0, the new main process sends SIGQUIT to the
+original main process when it is ready to serve requests. For the
+earlier versions of nghttpx, user has to send SIGQUIT to the
+original main process.
+.sp
+The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former
+is usually used to execute new binary, and the main process is newly
+spawned. On the other hand, the latter just reloads configuration
+file, and the same main process continues to exist.
+.UNINDENT
+.UNINDENT
+.sp
+\fBNOTE:\fP
+.INDENT 0.0
+.INDENT 3.5
+nghttpx consists of multiple processes: one process for processing
+these signals, and another one for processing requests. The former
+spawns the latter. The former is called main process, and the
+latter is called worker process. If neverbleed is enabled, the
+worker process spawns neverbleed daemon process which does RSA key
+processing. The above signal must be sent to the main process. If
+the other processes received one of them, it is ignored. This
+behaviour of these processes may change in the future release. In
+other words, in the future release, the processes other than main
+process may terminate upon the reception of these signals.
+Therefore these signals should not be sent to the processes other
+than main process.
+.UNINDENT
+.UNINDENT
+.SH SERVER PUSH
+.sp
+nghttpx supports HTTP/2 server push in default mode with Link header
+field. nghttpx looks for Link header field (\fI\%RFC 5988\fP) in response headers from
+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 restriction is applied for server push:
+.INDENT 0.0
+.IP 1. 3
+The associated stream must have method \(dqGET\(dq or \(dqPOST\(dq. The
+associated stream\(aqs status code must be 200.
+.UNINDENT
+.sp
+This limitation may be loosened in the future release.
+.sp
+nghttpx also supports server push if both frontend and backend are
+HTTP/2 in default mode. In this case, in addition to server push via
+Link header field, server push from backend is forwarded to frontend
+HTTP/2 session.
+.sp
+HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP is
+used.
+.SH UNIX DOMAIN SOCKET
+.sp
+nghttpx supports UNIX domain socket with a filename for both frontend
+and backend connections.
+.sp
+Please note that current nghttpx implementation does not delete a
+socket with a filename. And on start up, if nghttpx detects that the
+specified socket already exists in the file system, nghttpx first
+deletes it. However, if SIGUSR2 is used to execute new binary and
+both old and new configurations use same filename, new binary does not
+delete the socket and continues to use it.
+.SH OCSP STAPLING
+.sp
+OCSP query is done using external Python script
+\fBfetch\-ocsp\-response\fP, which has been originally developed in Perl
+as part of h2o project (\fI\%https://github.com/h2o/h2o\fP), and was
+translated into Python.
+.sp
+The script file is usually installed under
+\fB$(prefix)/share/nghttp2/\fP directory. The actual path to script can
+be customized using \fI\%\-\-fetch\-ocsp\-response\-file\fP option.
+.sp
+If OCSP query is failed, previous OCSP response, if any, is continued
+to be used.
+.sp
+\fI\%\-\-fetch\-ocsp\-response\-file\fP option provides wide range of
+possibility to manage OCSP response. It can take an arbitrary script
+or executable. The requirement is that it supports the command\-line
+interface of \fBfetch\-ocsp\-response\fP script, and it must return a
+valid DER encoded OCSP response on success. It must return exit code
+0 on success, and 75 for temporary error, and the other error code for
+generic failure. For large cluster of servers, it is not efficient
+for each server to perform OCSP query using \fBfetch\-ocsp\-response\fP\&.
+Instead, you can retrieve OCSP response in some way, and store it in a
+disk or a shared database. Then specify a program in
+\fI\%\-\-fetch\-ocsp\-response\-file\fP to fetch it from those stores.
+This could provide a way to share the OCSP response between fleet of
+servers, and also any OCSP query strategy can be applied which may be
+beyond the ability of nghttpx itself or \fBfetch\-ocsp\-response\fP
+script.
+.SH TLS SESSION RESUMPTION
+.sp
+nghttpx supports TLS session resumption through both session ID and
+session ticket.
+.SS SESSION ID RESUMPTION
+.sp
+By default, session ID is shared by all worker threads.
+.sp
+If \fI\%\-\-tls\-session\-cache\-memcached\fP is given, nghttpx will
+insert serialized session data to memcached with
+\fBnghttpx:tls\-session\-cache:\fP + lowercase hex string of session ID
+as a memcached entry key, with expiry time 12 hours. Session timeout
+is set to 12 hours.
+.sp
+By default, connections to memcached server are not encrypted. To
+enable encryption, use \fBtls\fP keyword in
+\fI\%\-\-tls\-session\-cache\-memcached\fP option.
+.SS TLS SESSION TICKET RESUMPTION
+.sp
+By default, session ticket is shared by all worker threads. The
+automatic key rotation is also enabled by default. Every an hour, new
+encryption key is generated, and previous encryption key becomes
+decryption only key. We set session timeout to 12 hours, and thus we
+keep at most 12 keys.
+.sp
+If \fI\%\-\-tls\-ticket\-key\-memcached\fP is given, encryption keys are
+retrieved from memcached. nghttpx just reads keys from memcached; one
+has to deploy key generator program to update keys frequently (e.g.,
+every 1 hour). The example key generator tlsticketupdate.go is
+available under contrib directory in nghttp2 archive. The memcached
+entry key is \fBnghttpx:tls\-ticket\-key\fP\&. The data format stored in
+memcached is the binary format described below:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
++\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+
+| VERSION (4) |LEN (2)|KEY(48 or 80) ...
++\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+
+ ^ |
+ | |
+ +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+
+ (LEN, KEY) pair can be repeated
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+All numbers in the above figure is bytes. All integer fields are
+network byte order.
+.sp
+First 4 bytes integer VERSION field, which must be 1. The 2 bytes
+integer LEN field gives the length of following KEY field, which
+contains key. If \fI\%\-\-tls\-ticket\-key\-cipher\fP=aes\-128\-cbc is
+used, LEN must be 48. If
+\fI\%\-\-tls\-ticket\-key\-cipher\fP=aes\-256\-cbc is used, LEN must be
+80. LEN and KEY pair can be repeated multiple times to store multiple
+keys. The key appeared first is used as encryption key. All the
+remaining keys are used as decryption only.
+.sp
+By default, connections to memcached server are not encrypted. To
+enable encryption, use \fBtls\fP keyword in
+\fI\%\-\-tls\-ticket\-key\-memcached\fP option.
+.sp
+If \fI\%\-\-tls\-ticket\-key\-file\fP is given, encryption key is read
+from the given file. In this case, nghttpx does not rotate key
+automatically. To rotate key, one has to restart nghttpx (see
+SIGNALS).
+.SH CERTIFICATE TRANSPARENCY
+.sp
+nghttpx supports TLS \fBsigned_certificate_timestamp\fP extension (\fI\%RFC
+6962\fP). The relevant options
+are \fI\%\-\-tls\-sct\-dir\fP and \fBsct\-dir\fP parameter in
+\fI\%\-\-subcert\fP\&. They takes a directory, and nghttpx reads all
+files whose extension is \fB\&.sct\fP under the directory. The \fB*.sct\fP
+files are encoded as \fBSignedCertificateTimestamp\fP struct described
+in \fI\%section 3.2 of RFC 69662\fP\&. This format is
+the same one used by \fI\%nginx\-ct\fP and \fI\%mod_ssl_ct\fP\&.
+\fI\%ct\-submit\fP can be
+used to submit certificates to log servers, and obtain the
+\fBSignedCertificateTimestamp\fP struct which can be used with nghttpx.
+.SH MRUBY SCRIPTING
+.sp
+\fBWARNING:\fP
+.INDENT 0.0
+.INDENT 3.5
+The current mruby extension API is experimental and not frozen. The
+API is subject to change in the future release.
+.UNINDENT
+.UNINDENT
+.sp
+\fBWARNING:\fP
+.INDENT 0.0
+.INDENT 3.5
+Almost all string value returned from method, or attribute is a
+fresh new mruby string, which involves memory allocation, and
+copies. Therefore, it is strongly recommended to store a return
+value in a local variable, and use it, instead of calling method or
+accessing attribute repeatedly.
+.UNINDENT
+.UNINDENT
+.sp
+nghttpx allows users to extend its capability using mruby scripts.
+nghttpx has 2 hook points to execute mruby script: request phase and
+response phase. The request phase hook is invoked after all request
+header fields are received from client. The response phase hook is
+invoked after all response header fields are received from backend
+server. These hooks allows users to modify header fields, or common
+HTTP variables, like authority or request path, and even return custom
+response without forwarding request to backend servers.
+.sp
+There are 2 levels of mruby script invocations: global and
+per\-pattern. The global mruby script is set by \fI\%\-\-mruby\-file\fP
+option and is called for all requests. The per\-pattern mruby script
+is set by \(dqmruby\(dq parameter in \fI\%\-b\fP option. It is invoked for
+a request which matches the particular pattern. The order of hook
+invocation is: global request phase hook, per\-pattern request phase
+hook, per\-pattern response phase hook, and finally global response
+phase hook. If a hook returns a response, any later hooks are not
+invoked. The global request hook is invoked before the pattern
+matching is made and changing request path may affect the pattern
+matching.
+.sp
+Please note that request and response hooks of per\-pattern mruby
+script for a single request might not come from the same script. This
+might happen after a request hook is executed, backend failed for some
+reason, and at the same time, backend configuration is replaced by API
+request, and then the request uses new configuration on retry. The
+response hook from new configuration, if it is specified, will be
+invoked.
+.sp
+The all mruby script will be evaluated once per thread on startup, and
+it must instantiate object and evaluate it as the return value (e.g.,
+\fBApp.new\fP). This object is called app object. If app object
+defines \fBon_req\fP method, it is called with \fI\%Nghttpx::Env\fP
+object on request hook. Similarly, if app object defines \fBon_resp\fP
+method, it is called with \fI\%Nghttpx::Env\fP object on response
+hook. For each method invocation, user can can access
+\fI\%Nghttpx::Request\fP and \fI\%Nghttpx::Response\fP objects
+via \fI\%Nghttpx::Env#req\fP and \fI\%Nghttpx::Env#resp\fP
+respectively.
+.INDENT 0.0
+.TP
+.B Nghttpx::REQUEST_PHASE
+Constant to represent request phase.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B Nghttpx::RESPONSE_PHASE
+Constant to represent response phase.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B class Nghttpx::Env
+Object to represent current request specific context.
+.INDENT 7.0
+.TP
+.B attribute [R] req
+Return \fI\%Request\fP object.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] resp
+Return \fI\%Response\fP object.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] ctx
+Return Ruby hash object. It persists until request finishes.
+So values set in request phase hook can be retrieved in
+response phase hook.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] phase
+Return the current phase.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] remote_addr
+Return IP address of a remote client. If connection is made
+via UNIX domain socket, this returns the string \(dqlocalhost\(dq.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] server_addr
+Return address of server that accepted the connection. This
+is a string which specified in \fI\%\-\-frontend\fP option,
+excluding port number, and not a resolved IP address. For
+UNIX domain socket, this is a path to UNIX domain socket.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] server_port
+Return port number of the server frontend which accepted the
+connection from client.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_used
+Return true if TLS is used on the connection.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_sni
+Return the TLS SNI value which client sent in this connection.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_fingerprint_sha256
+Return the SHA\-256 fingerprint of a client certificate.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_fingerprint_sha1
+Return the SHA\-1 fingerprint of a client certificate.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_issuer_name
+Return the issuer name of a client certificate.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_subject_name
+Return the subject name of a client certificate.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_serial
+Return the serial number of a client certificate.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_not_before
+Return the start date of a client certificate in seconds since
+the epoch.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_client_not_after
+Return the end date of a client certificate in seconds since
+the epoch.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_cipher
+Return a TLS cipher negotiated in this connection.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_protocol
+Return a TLS protocol version negotiated in this connection.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_session_id
+Return a session ID for this connection in hex string.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_session_reused
+Return true if, and only if a SSL/TLS session is reused.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] alpn
+Return ALPN identifier negotiated in this connection.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] tls_handshake_finished
+Return true if SSL/TLS handshake has finished. If it returns
+false in the request phase hook, the request is received in
+TLSv1.3 early data (0\-RTT) and might be vulnerable to the
+replay attack. nghttpx will send Early\-Data header field to
+backend servers to indicate this.
+.UNINDENT
+.UNINDENT
+.INDENT 0.0
+.TP
+.B class Nghttpx::Request
+Object to represent request from client. The modification to
+Request object is allowed only in request phase hook.
+.INDENT 7.0
+.TP
+.B attribute [R] http_version_major
+Return HTTP major version.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] http_version_minor
+Return HTTP minor version.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R/W] method
+HTTP method. On assignment, copy of given value is assigned.
+We don\(aqt accept arbitrary method name. We will document them
+later, but well known methods, like GET, PUT and POST, are all
+supported.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R/W] authority
+Authority (i.e., example.org), including optional port
+component . On assignment, copy of given value is assigned.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R/W] scheme
+Scheme (i.e., http, https). On assignment, copy of given
+value is assigned.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R/W] path
+Request path, including query component (i.e., /index.html).
+On assignment, copy of given value is assigned. The path does
+not include authority component of URI. This may include
+query component. nghttpx makes certain normalization for
+path. It decodes percent\-encoding for unreserved characters
+(see \fI\%https://tools.ietf.org/html/rfc3986#section\-2.3\fP), and
+resolves \(dq..\(dq and \(dq.\(dq. But it may leave characters which
+should be percent\-encoded as is. So be careful when comparing
+path against desired string.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] headers
+Return Ruby hash containing copy of request header fields.
+Changing values in returned hash does not change request
+header fields actually used in request processing. Use
+\fI\%Nghttpx::Request#add_header\fP or
+\fI\%Nghttpx::Request#set_header\fP to change request
+header fields.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B add_header(key, value)
+Add header entry associated with key. The value can be single
+string or array of string. It does not replace any existing
+values associated with key.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B set_header(key, value)
+Set header entry associated with key. The value can be single
+string or array of string. It replaces any existing values
+associated with key.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B clear_headers()
+Clear all existing request header fields.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B push(uri)
+Initiate to push resource identified by \fIuri\fP\&. Only HTTP/2
+protocol supports this feature. For the other protocols, this
+method is noop. \fIuri\fP can be absolute URI, absolute path or
+relative path to the current request. For absolute or
+relative path, scheme and authority are inherited from the
+current request. Currently, method is always GET. nghttpx
+will issue request to backend servers to fulfill this request.
+The request and response phase hooks will be called for pushed
+resource as well.
+.UNINDENT
+.UNINDENT
+.INDENT 0.0
+.TP
+.B class Nghttpx::Response
+Object to represent response from backend server.
+.INDENT 7.0
+.TP
+.B attribute [R] http_version_major
+Return HTTP major version.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] http_version_minor
+Return HTTP minor version.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R/W] status
+HTTP status code. It must be in the range [200, 999],
+inclusive. The non\-final status code is not supported in
+mruby scripting at the moment.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B attribute [R] headers
+Return Ruby hash containing copy of response header fields.
+Changing values in returned hash does not change response
+header fields actually used in response processing. Use
+\fI\%Nghttpx::Response#add_header\fP or
+\fI\%Nghttpx::Response#set_header\fP to change response
+header fields.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B add_header(key, value)
+Add header entry associated with key. The value can be single
+string or array of string. It does not replace any existing
+values associated with key.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B set_header(key, value)
+Set header entry associated with key. The value can be single
+string or array of string. It replaces any existing values
+associated with key.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B clear_headers()
+Clear all existing response header fields.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B return(body)
+Return custom response \fIbody\fP to a client. When this method
+is called in request phase hook, the request is not forwarded
+to the backend, and response phase hook for this request will
+not be invoked. When this method is called in response phase
+hook, response from backend server is canceled and discarded.
+The status code and response header fields should be set
+before using this method. To set status code, use
+\fI\%Nghttpx::Response#status\fP\&. If status code is not
+set, 200 is used. To set response header fields,
+\fI\%Nghttpx::Response#add_header\fP and
+\fI\%Nghttpx::Response#set_header\fP\&. When this method is
+invoked in response phase hook, the response headers are
+filled with the ones received from backend server. To send
+completely custom header fields, first call
+\fI\%Nghttpx::Response#clear_headers\fP to erase all
+existing header fields, and then add required header fields.
+It is an error to call this method twice for a given request.
+.UNINDENT
+.INDENT 7.0
+.TP
+.B send_info(status, headers)
+Send non\-final (informational) response to a client. \fIstatus\fP
+must be in the range [100, 199], inclusive. \fIheaders\fP is a
+hash containing response header fields. Its key must be a
+string, and the associated value must be either string or
+array of strings. Since this is not a final response, even if
+this method is invoked, request is still forwarded to a
+backend unless \fI\%Nghttpx::Response#return\fP is called.
+This method can be called multiple times. It cannot be called
+after \fI\%Nghttpx::Response#return\fP is called.
+.UNINDENT
+.UNINDENT
+.SS MRUBY EXAMPLES
+.sp
+Modify request path:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+class App
+ def on_req(env)
+ env.req.path = \(dq/apps#{env.req.path}\(dq
+ end
+end
+
+App.new
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+Don\(aqt forget to instantiate and evaluate object at the last line.
+.sp
+Restrict permission of viewing a content to a specific client
+addresses:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+class App
+ def on_req(env)
+ allowed_clients = [\(dq127.0.0.1\(dq, \(dq::1\(dq]
+
+ if env.req.path.start_with?(\(dq/log/\(dq) &&
+ !allowed_clients.include?(env.remote_addr) then
+ env.resp.status = 404
+ env.resp.return \(dqpermission denied\(dq
+ end
+ end
+end
+
+App.new
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.SH API ENDPOINTS
+.sp
+nghttpx exposes API endpoints to manipulate it via HTTP based API. By
+default, API endpoint is disabled. To enable it, add a dedicated
+frontend for API using \fI\%\-\-frontend\fP option with \(dqapi\(dq
+parameter. All requests which come from this frontend address, will
+be treated as API request.
+.sp
+The response is normally JSON dictionary, and at least includes the
+following keys:
+.INDENT 0.0
+.TP
+.B status
+The status of the request processing. The following values are
+defined:
+.INDENT 7.0
+.TP
+.B Success
+The request was successful.
+.TP
+.B Failure
+The request was failed. No change has been made.
+.UNINDENT
+.TP
+.B code
+HTTP status code
+.UNINDENT
+.sp
+Additionally, depending on the API endpoint, \fBdata\fP key may be
+present, and its value contains the API endpoint specific data.
+.sp
+We wrote \(dqnormally\(dq, since nghttpx may return ordinal HTML response in
+some cases where the error has occurred before reaching API endpoint
+(e.g., header field is too large).
+.sp
+The following section describes available API endpoints.
+.SS POST /api/v1beta1/backendconfig
+.sp
+This API replaces the current backend server settings with the
+requested ones. The request method should be POST, but PUT is also
+acceptable. The request body must be nghttpx configuration file
+format. For configuration file format, see \fI\%FILES\fP section. The
+line separator inside the request body must be single LF (0x0A).
+Currently, only \fI\%backend\fP option is parsed, the
+others are simply ignored. The semantics of this API is replace the
+current backend with the backend options in request body. Describe
+the desired set of backend severs, and nghttpx makes it happen. If
+there is no \fI\%backend\fP option is found in request
+body, the current set of backend is replaced with the \fI\%backend\fP option\(aqs default value, which is \fB127.0.0.1,80\fP\&.
+.sp
+The replacement is done instantly without breaking existing
+connections or requests. It also avoids any process creation as is
+the case with hot swapping with signals.
+.sp
+The one limitation is that only numeric IP address is allowed in
+\fI\%backend\fP in request body unless \(dqdns\(dq parameter
+is used while non numeric hostname is allowed in command\-line or
+configuration file is read using \fI\%\-\-conf\fP\&.
+.SS GET /api/v1beta1/configrevision
+.sp
+This API returns configuration revision of the current nghttpx. The
+configuration revision is opaque string, and it changes after each
+reloading by SIGHUP. With this API, an external application knows
+that whether nghttpx has finished reloading its configuration by
+comparing the configuration revisions between before and after
+reloading. It is recommended to disable persistent (keep\-alive)
+connection for this purpose in order to avoid to send a request using
+the reused connection which may bound to an old process.
+.sp
+This API returns response including \fBdata\fP key. Its value is JSON
+object, and it contains at least the following key:
+.INDENT 0.0
+.TP
+.B configRevision
+The configuration revision of the current nghttpx
+.UNINDENT
+.SH SEE ALSO
+.sp
+\fBnghttp(1)\fP, \fBnghttpd(1)\fP, \fBh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, 2016, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttpx.1.rst b/doc/nghttpx.1.rst
new file mode 100644
index 0000000..03109e4
--- /dev/null
+++ b/doc/nghttpx.1.rst
@@ -0,0 +1,2526 @@
+
+.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
+
+.. program:: nghttpx
+
+nghttpx(1)
+==========
+
+SYNOPSIS
+--------
+
+**nghttpx** [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+
+DESCRIPTION
+-----------
+
+A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.
+
+.. describe:: <PRIVATE_KEY>
+
+
+ Set path to server's private key. Required unless
+ "no-tls" parameter is used in :option:`--frontend` option.
+
+.. describe:: <CERT>
+
+ Set path to server's certificate. Required unless
+ "no-tls" parameter is used in :option:`--frontend` option. To
+ make OCSP stapling work, this must be an absolute path.
+
+
+OPTIONS
+-------
+
+The options are categorized into several groups.
+
+Connections
+~~~~~~~~~~~
+
+.. option:: -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...]
+
+
+ Set backend host and port. The multiple backend
+ addresses are accepted by repeating this option. UNIX
+ domain socket can be specified by prefixing path name
+ with "unix:" (e.g., unix:/var/run/backend.sock).
+
+ Optionally, if <PATTERN>s are given, the backend address
+ is only used if request matches the pattern. The
+ pattern matching is closely designed to ServeMux in
+ net/http package of Go programming language. <PATTERN>
+ consists of path, host + path or just host. The path
+ must start with "*/*". If it ends with "*/*", it matches
+ all request path in its subtree. To deal with the
+ request to the directory without trailing slash, the
+ path which ends with "*/*" also matches the request path
+ which only lacks trailing '*/*' (e.g., path "*/foo/*"
+ matches request path "*/foo*"). If it does not end with
+ "*/*", it performs exact match against the request path.
+ If host is given, it performs a match against the
+ request host. For a request received on the frontend
+ listener with "sni-fwd" parameter enabled, SNI host is
+ used instead of a request host. If host alone is given,
+ "*/*" is appended to it, so that it matches all request
+ paths under the host (e.g., specifying "nghttp2.org"
+ equals to "nghttp2.org/"). CONNECT method is treated
+ specially. It does not have path, and we don't allow
+ empty path. To workaround this, we assume that CONNECT
+ method has "*/*" as path.
+
+ Patterns with host take precedence over patterns with
+ just path. Then, longer patterns take precedence over
+ shorter ones.
+
+ Host can include "\*" in the left most position to
+ indicate wildcard match (only suffix match is done).
+ The "\*" must match at least one character. For example,
+ host pattern "\*.nghttp2.org" matches against
+ "www.nghttp2.org" and "git.ngttp2.org", but does not
+ match against "nghttp2.org". The exact hosts match
+ takes precedence over the wildcard hosts match.
+
+ If path part ends with "\*", it is treated as wildcard
+ path. The wildcard path behaves differently from the
+ normal path. For normal path, match is made around the
+ boundary of path component separator,"*/*". On the other
+ hand, the wildcard path does not take into account the
+ path component separator. All paths which include the
+ wildcard path without last "\*" as prefix, and are
+ strictly longer than wildcard path without last "\*" are
+ matched. "\*" must match at least one character. For
+ example, the pattern "*/foo\**" matches "*/foo/*" and
+ "*/foobar*". But it does not match "*/foo*", or "*/fo*".
+
+ If <PATTERN> is omitted or empty string, "*/*" is used as
+ pattern, which matches all request paths (catch-all
+ pattern). The catch-all backend must be given.
+
+ When doing a match, nghttpx made some normalization to
+ pattern, request host and path. For host part, they are
+ converted to lower case. For path part, percent-encoded
+ unreserved characters defined in RFC 3986 are decoded,
+ and any dot-segments (".." and ".") are resolved and
+ removed.
+
+ For example, :option:`-b`\'127.0.0.1,8080;nghttp2.org/httpbin/'
+ matches the request host "nghttp2.org" and the request
+ path "*/httpbin/get*", but does not match the request host
+ "nghttp2.org" and the request path "*/index.html*".
+
+ The multiple <PATTERN>s can be specified, delimiting
+ them by ":". Specifying
+ :option:`-b`\'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the
+ same effect to specify :option:`-b`\'127.0.0.1,8080;nghttp2.org'
+ and :option:`-b`\'127.0.0.1,8080;www.nghttp2.org'.
+
+ The backend addresses sharing same <PATTERN> are grouped
+ together forming load balancing group.
+
+ Several parameters <PARAM> are accepted after <PATTERN>.
+ The parameters are delimited by ";". The available
+ parameters are: "proto=<PROTO>", "tls",
+ "sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
+ "affinity=<METHOD>", "dns", "redirect-if-not-tls",
+ "upgrade-scheme", "mruby=<PATH>",
+ "read-timeout=<DURATION>", "write-timeout=<DURATION>",
+ "group=<GROUP>", "group-weight=<N>", "weight=<N>", and
+ "dnf". The parameter consists of keyword, and
+ optionally followed by "=" and value. For example, the
+ parameter "proto=h2" consists of the keyword "proto" and
+ value "h2". The parameter "tls" consists of the keyword
+ "tls" without value. Each parameter is described as
+ follows.
+
+ The backend application protocol can be specified using
+ optional "proto" parameter, and in the form of
+ "proto=<PROTO>". <PROTO> should be one of the following
+ list without quotes: "h2", "http/1.1". The default
+ value of <PROTO> is "http/1.1". Note that usually "h2"
+ refers to HTTP/2 over TLS. But in this option, it may
+ mean HTTP/2 over cleartext TCP unless "tls" keyword is
+ used (see below).
+
+ TLS can be enabled by specifying optional "tls"
+ parameter. TLS is not enabled by default.
+
+ With "sni=<SNI_HOST>" parameter, it can override the TLS
+ SNI field value with given <SNI_HOST>. This will
+ default to the backend <HOST> name
+
+ The feature to detect whether backend is online or
+ offline can be enabled using optional "fall" and "rise"
+ parameters. Using "fall=<N>" parameter, if nghttpx
+ cannot connect to a this backend <N> times in a row,
+ this backend is assumed to be offline, and it is
+ excluded from load balancing. If <N> is 0, this backend
+ never be excluded from load balancing whatever times
+ nghttpx cannot connect to it, and this is the default.
+ There is also "rise=<N>" parameter. After backend was
+ excluded from load balancing group, nghttpx periodically
+ attempts to make a connection to the failed backend, and
+ if the connection is made successfully <N> times in a
+ row, the backend is assumed to be online, and it is now
+ eligible for load balancing target. If <N> is 0, a
+ backend is permanently offline, once it goes in that
+ state, and this is the default behaviour.
+
+ The session affinity is enabled using
+ "affinity=<METHOD>" parameter. If "ip" is given in
+ <METHOD>, client IP based session affinity is enabled.
+ If "cookie" is given in <METHOD>, cookie based session
+ affinity is enabled. If "none" is given in <METHOD>,
+ session affinity is disabled, and this is the default.
+ The session affinity is enabled per <PATTERN>. If at
+ least one backend has "affinity" parameter, and its
+ <METHOD> is not "none", session affinity is enabled for
+ all backend servers sharing the same <PATTERN>. It is
+ advised to set "affinity" parameter to all backend
+ explicitly if session affinity is desired. The session
+ affinity may break if one of the backend gets
+ unreachable, or backend settings are reloaded or
+ replaced by API.
+
+ If "affinity=cookie" is used, the additional
+ configuration is required.
+ "affinity-cookie-name=<NAME>" must be used to specify a
+ name of cookie to use. Optionally,
+ "affinity-cookie-path=<PATH>" can be used to specify a
+ path which cookie is applied. The optional
+ "affinity-cookie-secure=<SECURE>" controls the Secure
+ attribute of a cookie. The default value is "auto", and
+ the Secure attribute is determined by a request scheme.
+ If a request scheme is "https", then Secure attribute is
+ set. Otherwise, it is not set. If <SECURE> is "yes",
+ the Secure attribute is always set. If <SECURE> is
+ "no", the Secure attribute is always omitted.
+ "affinity-cookie-stickiness=<STICKINESS>" controls
+ stickiness of this affinity. If <STICKINESS> is
+ "loose", removing or adding a backend server might break
+ the affinity and the request might be forwarded to a
+ different backend server. If <STICKINESS> is "strict",
+ removing the designated backend server breaks affinity,
+ but adding new backend server does not cause breakage.
+ If the designated backend server becomes unavailable,
+ new backend server is chosen as if the request does not
+ have an affinity cookie. <STICKINESS> defaults to
+ "loose".
+
+ By default, name resolution of backend host name is done
+ at start up, or reloading configuration. If "dns"
+ parameter is given, name resolution takes place
+ dynamically. This is useful if backend address changes
+ frequently. If "dns" is given, name resolution of
+ backend host name at start up, or reloading
+ configuration is skipped.
+
+ If "redirect-if-not-tls" parameter is used, the matched
+ backend requires that frontend connection is TLS
+ encrypted. If it isn't, nghttpx responds to the request
+ with 308 status code, and https URI the client should
+ use instead is included in Location header field. The
+ port number in redirect URI is 443 by default, and can
+ be changed using :option:`--redirect-https-port` option. If at
+ least one backend has "redirect-if-not-tls" parameter,
+ this feature is enabled for all backend servers sharing
+ the same <PATTERN>. It is advised to set
+ "redirect-if-no-tls" parameter to all backends
+ explicitly if this feature is desired.
+
+ If "upgrade-scheme" parameter is used along with "tls"
+ parameter, HTTP/2 :scheme pseudo header field is changed
+ to "https" from "http" when forwarding a request to this
+ particular backend. This is a workaround for a backend
+ server which requires "https" :scheme pseudo header
+ field on TLS encrypted connection.
+
+ "mruby=<PATH>" parameter specifies a path to mruby
+ script file which is invoked when this pattern is
+ matched. All backends which share the same pattern must
+ have the same mruby path.
+
+ "read-timeout=<DURATION>" and "write-timeout=<DURATION>"
+ parameters specify the read and write timeout of the
+ backend connection when this pattern is matched. All
+ backends which share the same pattern must have the same
+ timeouts. If these timeouts are entirely omitted for a
+ pattern, :option:`--backend-read-timeout` and
+ :option:`--backend-write-timeout` are used.
+
+ "group=<GROUP>" parameter specifies the name of group
+ this backend address belongs to. By default, it belongs
+ to the unnamed default group. The name of group is
+ unique per pattern. "group-weight=<N>" parameter
+ specifies the weight of the group. The higher weight
+ gets more frequently selected by the load balancing
+ algorithm. <N> must be [1, 256] inclusive. The weight
+ 8 has 4 times more weight than 2. <N> must be the same
+ for all addresses which share the same <GROUP>. If
+ "group-weight" is omitted in an address, but the other
+ address which belongs to the same group specifies
+ "group-weight", its weight is used. If no
+ "group-weight" is specified for all addresses, the
+ weight of a group becomes 1. "group" and "group-weight"
+ are ignored if session affinity is enabled.
+
+ "weight=<N>" parameter specifies the weight of the
+ backend address inside a group which this address
+ belongs to. The higher weight gets more frequently
+ selected by the load balancing algorithm. <N> must be
+ [1, 256] inclusive. The weight 8 has 4 times more
+ weight than weight 2. If this parameter is omitted,
+ weight becomes 1. "weight" is ignored if session
+ affinity is enabled.
+
+ If "dnf" parameter is specified, an incoming request is
+ not forwarded to a backend and just consumed along with
+ the request body (actually a backend server never be
+ contacted). It is expected that the HTTP response is
+ generated by mruby script (see "mruby=<PATH>" parameter
+ above). "dnf" is an abbreviation of "do not forward".
+
+ Since ";" and ":" are used as delimiter, <PATTERN> must
+ not contain these characters. In order to include ":"
+ in <PATTERN>, one has to specify "%3A" (which is
+ percent-encoded from of ":") instead. Since ";" has
+ special meaning in shell, the option value must be
+ quoted.
+
+
+ Default: ``127.0.0.1,80``
+
+.. option:: -f, --frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...]
+
+ Set frontend host and port. If <HOST> is '\*', it
+ assumes all addresses including both IPv4 and IPv6.
+ UNIX domain socket can be specified by prefixing path
+ name with "unix:" (e.g., unix:/var/run/nghttpx.sock).
+ This option can be used multiple times to listen to
+ multiple addresses.
+
+ This option can take 0 or more parameters, which are
+ described below. Note that "api" and "healthmon"
+ parameters are mutually exclusive.
+
+ Optionally, TLS can be disabled by specifying "no-tls"
+ parameter. TLS is enabled by default.
+
+ If "sni-fwd" parameter is used, when performing a match
+ to select a backend server, SNI host name received from
+ the client is used instead of the request host. See
+ :option:`--backend` option about the pattern match.
+
+ To make this frontend as API endpoint, specify "api"
+ parameter. This is disabled by default. It is
+ important to limit the access to the API frontend.
+ Otherwise, someone may change the backend server, and
+ break your services, or expose confidential information
+ to the outside the world.
+
+ To make this frontend as health monitor endpoint,
+ specify "healthmon" parameter. This is disabled by
+ default. Any requests which come through this address
+ are replied with 200 HTTP status, without no body.
+
+ To accept PROXY protocol version 1 and 2 on frontend
+ connection, specify "proxyproto" parameter. This is
+ disabled by default.
+
+ To receive HTTP/3 (QUIC) traffic, specify "quic"
+ parameter. It makes nghttpx listen on UDP port rather
+ than TCP port. UNIX domain socket, "api", and
+ "healthmon" parameters cannot be used with "quic"
+ parameter.
+
+
+ Default: ``*,3000``
+
+.. option:: --backlog=<N>
+
+ Set listen backlog size.
+
+ Default: ``65536``
+
+.. option:: --backend-address-family=(auto|IPv4|IPv6)
+
+ Specify address family of backend connections. If
+ "auto" is given, both IPv4 and IPv6 are considered. If
+ "IPv4" is given, only IPv4 address is considered. If
+ "IPv6" is given, only IPv6 address is considered.
+
+ Default: ``auto``
+
+.. 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:: --single-thread
+
+ Run everything in one thread inside the worker process.
+ This feature is provided for better debugging
+ experience, or for the platforms which lack thread
+ support. If threading is disabled, this option is
+ always enabled.
+
+.. 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-connections-per-host=<N>
+
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per origin host.
+ This option is meaningful when :option:`--http2-proxy` option is
+ used. The origin host is determined by authority
+ portion of request URI (or :authority header field for
+ HTTP/2). To limit the number of connections per
+ frontend for default mode, use
+ :option:`--backend-connections-per-frontend`\.
+
+ Default: ``8``
+
+.. option:: --backend-connections-per-frontend=<N>
+
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per frontend. This
+ option is only used for default mode. 0 means
+ unlimited. To limit the number of connections per host
+ with :option:`--http2-proxy` option, use
+ :option:`--backend-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:: --rlimit-memlock=<N>
+
+ Set maximum number of bytes of memory that may be locked
+ into RAM. 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: ``128K``
+
+.. option:: --fastopen=<N>
+
+ Enables "TCP Fast Open" for the listening socket and
+ limits the maximum length for the queue of connections
+ that have not yet completed the three-way handshake. If
+ value is 0 then fast open is disabled.
+
+ Default: ``0``
+
+.. option:: --no-kqueue
+
+ Don't use kqueue. This option is only applicable for
+ the platforms which have kqueue. For other platforms,
+ this option will be simply ignored.
+
+
+Timeout
+~~~~~~~
+
+.. option:: --frontend-http2-read-timeout=<DURATION>
+
+ Specify read timeout for HTTP/2 frontend connection.
+
+ Default: ``3m``
+
+.. option:: --frontend-http3-read-timeout=<DURATION>
+
+ Specify read timeout for HTTP/3 frontend connection.
+
+ Default: ``3m``
+
+.. option:: --frontend-read-timeout=<DURATION>
+
+ Specify read timeout for HTTP/1.1 frontend connection.
+
+ Default: ``1m``
+
+.. option:: --frontend-write-timeout=<DURATION>
+
+ Specify write timeout for all frontend connections.
+
+ Default: ``30s``
+
+.. option:: --frontend-keep-alive-timeout=<DURATION>
+
+ Specify keep-alive timeout for frontend HTTP/1
+ connection.
+
+ Default: ``1m``
+
+.. option:: --stream-read-timeout=<DURATION>
+
+ Specify read timeout for HTTP/2 streams. 0 means no
+ timeout.
+
+ Default: ``0``
+
+.. option:: --stream-write-timeout=<DURATION>
+
+ Specify write timeout for HTTP/2 streams. 0 means no
+ timeout.
+
+ Default: ``1m``
+
+.. option:: --backend-read-timeout=<DURATION>
+
+ Specify read timeout for backend connection.
+
+ Default: ``1m``
+
+.. option:: --backend-write-timeout=<DURATION>
+
+ Specify write timeout for backend connection.
+
+ Default: ``30s``
+
+.. option:: --backend-connect-timeout=<DURATION>
+
+ Specify timeout before establishing TCP connection to
+ backend.
+
+ Default: ``30s``
+
+.. option:: --backend-keep-alive-timeout=<DURATION>
+
+ Specify keep-alive timeout for backend HTTP/1
+ 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: ``30s``
+
+.. option:: --frontend-http2-setting-timeout=<DURATION>
+
+ Specify timeout before SETTINGS ACK is received from
+ client.
+
+ Default: ``10s``
+
+.. option:: --backend-http2-settings-timeout=<DURATION>
+
+ Specify timeout before SETTINGS ACK is received from
+ backend server.
+
+ Default: ``10s``
+
+.. option:: --backend-max-backoff=<DURATION>
+
+ Specify maximum backoff interval. This is used when
+ doing health check against offline backend (see "fail"
+ parameter in :option:`--backend` option). It is also used to
+ limit the maximum interval to temporarily disable
+ backend when nghttpx failed to connect to it. These
+ intervals are calculated using exponential backoff, and
+ consecutive failed attempts increase the interval. This
+ option caps its maximum value.
+
+ Default: ``2m``
+
+
+SSL/TLS
+~~~~~~~
+
+.. option:: --ciphers=<SUITE>
+
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use :option:`--tls13-ciphers` for TLSv1.3.
+
+ Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384``
+
+.. option:: --tls13-ciphers=<SUITE>
+
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ :option:`--ciphers` for TLSv1.2 or earlier.
+
+ Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256``
+
+.. option:: --client-ciphers=<SUITE>
+
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use :option:`--tls13-client-ciphers` for TLSv1.3.
+
+ Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384``
+
+.. option:: --tls13-client-ciphers=<SUITE>
+
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ :option:`--tls13-client-ciphers` for TLSv1.2 or earlier.
+
+ Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256``
+
+.. option:: --ecdh-curves=<LIST>
+
+ Set supported curve list for frontend connections.
+ <LIST> is a colon separated list of curve NID or names
+ in the preference order. The supported curves depend on
+ the linked OpenSSL library. This function requires
+ OpenSSL >= 1.0.2.
+
+ Default: ``X25519:P-256:P-384:P-521``
+
+.. option:: -k, --insecure
+
+ Don't verify backend server's certificate if TLS is
+ enabled for backend connections.
+
+.. option:: --cacert=<PATH>
+
+ Set path to trusted CA certificate file. It is used in
+ backend TLS connections to verify peer's certificate.
+ It is also used to verify OCSP response from the script
+ set by :option:`--fetch-ocsp-response-file`\. 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>[[;<PARAM>]...]
+
+ Specify additional certificate and private key file.
+ nghttpx will choose certificates based on the hostname
+ indicated by client using TLS SNI extension. If nghttpx
+ is built with OpenSSL >= 1.0.2, the shared elliptic
+ curves (e.g., P-256) between client and server are also
+ taken into consideration. This allows nghttpx to send
+ ECDSA certificate to modern clients, while sending RSA
+ based certificate to older clients. This option can be
+ used multiple times. To make OCSP stapling work,
+ <CERTPATH> must be absolute path.
+
+ Additional parameter can be specified in <PARAM>. The
+ available <PARAM> is "sct-dir=<DIR>".
+
+ "sct-dir=<DIR>" specifies the path to directory which
+ contains \*.sct files for TLS
+ signed_certificate_timestamp extension (RFC 6962). This
+ feature requires OpenSSL >= 1.0.2. See also
+ :option:`--tls-sct-dir` option.
+
+.. 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:: --alpn-list=<LIST>
+
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+
+ Default: ``h2,h2-16,h2-14,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:: --verify-client-tolerate-expired
+
+ Accept expired client certificate. Operator should
+ handle the expired client certificate by some means
+ (e.g., mruby script). Otherwise, this option might
+ cause a security risk.
+
+.. 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-min-proto-version=<VER>
+
+ Specify minimum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ :option:`--tls-min-proto-version` and :option:`\--tls-max-proto-version` are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". If a protocol version lower
+ than TLSv1.2 is specified, make sure that the compatible
+ ciphers are included in :option:`--ciphers` option. The default
+ cipher list only includes ciphers compatible with
+ TLSv1.2 or above. The available versions are:
+ TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0
+
+ Default: ``TLSv1.2``
+
+.. option:: --tls-max-proto-version=<VER>
+
+ Specify maximum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ :option:`--tls-min-proto-version` and :option:`\--tls-max-proto-version` are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". The available versions are:
+ TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0
+
+ Default: ``TLSv1.3``
+
+.. option:: --tls-ticket-key-file=<PATH>
+
+ Path to file that contains random data to construct TLS
+ session ticket parameters. If aes-128-cbc is given in
+ :option:`--tls-ticket-key-cipher`\, the file must contain exactly
+ 48 bytes. If aes-256-cbc is given in
+ :option:`--tls-ticket-key-cipher`\, the file must contain exactly
+ 80 bytes. 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 every 1 hour internally and
+ they are valid for 12 hours. This is recommended if
+ ticket key sharing between nghttpx instances is not
+ required.
+
+.. option:: --tls-ticket-key-memcached=<HOST>,<PORT>[;tls]
+
+ Specify address of memcached server to get TLS ticket
+ keys for session resumption. This enables shared TLS
+ ticket key between multiple nghttpx instances. nghttpx
+ does not set TLS ticket key to memcached. The external
+ ticket key generator is required. nghttpx just gets TLS
+ ticket keys from memcached, and use them, possibly
+ replacing current set of keys. It is up to extern TLS
+ ticket key generator to rotate keys frequently. See
+ "TLS SESSION TICKET RESUMPTION" section in manual page
+ to know the data format in memcached entry. Optionally,
+ memcached connection can be encrypted with TLS by
+ specifying "tls" parameter.
+
+.. option:: --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6)
+
+ Specify address family of memcached connections to get
+ TLS ticket keys. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+
+ Default: ``auto``
+
+.. option:: --tls-ticket-key-memcached-interval=<DURATION>
+
+ Set interval to get TLS ticket keys from memcached.
+
+ Default: ``10m``
+
+.. option:: --tls-ticket-key-memcached-max-retry=<N>
+
+ Set maximum number of consecutive retries before
+ abandoning TLS ticket key retrieval. If this number is
+ reached, the attempt is considered as failure, and
+ "failure" count is incremented by 1, which contributed
+ to the value controlled
+ :option:`--tls-ticket-key-memcached-max-fail` option.
+
+ Default: ``3``
+
+.. option:: --tls-ticket-key-memcached-max-fail=<N>
+
+ Set maximum number of consecutive failure before
+ disabling TLS ticket until next scheduled key retrieval.
+
+ Default: ``2``
+
+.. option:: --tls-ticket-key-cipher=<CIPHER>
+
+ Specify cipher to encrypt TLS session ticket. Specify
+ either aes-128-cbc or aes-256-cbc. By default,
+ aes-128-cbc is used.
+
+.. option:: --tls-ticket-key-memcached-cert-file=<PATH>
+
+ Path to client certificate for memcached connections to
+ get TLS ticket keys.
+
+.. option:: --tls-ticket-key-memcached-private-key-file=<PATH>
+
+ Path to client private key for memcached connections to
+ get TLS ticket keys.
+
+.. option:: --fetch-ocsp-response-file=<PATH>
+
+ Path to fetch-ocsp-response script file. It should be
+ absolute path.
+
+ Default: ``/usr/local/share/nghttp2/fetch-ocsp-response``
+
+.. option:: --ocsp-update-interval=<DURATION>
+
+ Set interval to update OCSP response cache.
+
+ Default: ``4h``
+
+.. option:: --ocsp-startup
+
+ Start accepting connections after initial attempts to
+ get OCSP responses finish. It does not matter some of
+ the attempts fail. This feature is useful if OCSP
+ responses must be available before accepting
+ connections.
+
+.. option:: --no-verify-ocsp
+
+ nghttpx does not verify OCSP response.
+
+.. option:: --no-ocsp
+
+ Disable OCSP stapling.
+
+.. option:: --tls-session-cache-memcached=<HOST>,<PORT>[;tls]
+
+ Specify address of memcached server to store session
+ cache. This enables shared session cache between
+ multiple nghttpx instances. Optionally, memcached
+ connection can be encrypted with TLS by specifying "tls"
+ parameter.
+
+.. option:: --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6)
+
+ Specify address family of memcached connections to store
+ session cache. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+
+ Default: ``auto``
+
+.. option:: --tls-session-cache-memcached-cert-file=<PATH>
+
+ Path to client certificate for memcached connections to
+ store session cache.
+
+.. option:: --tls-session-cache-memcached-private-key-file=<PATH>
+
+ Path to client private key for memcached connections to
+ store session cache.
+
+.. option:: --tls-dyn-rec-warmup-threshold=<SIZE>
+
+ Specify the threshold size for TLS dynamic record size
+ behaviour. During a TLS session, after the threshold
+ number of bytes have been written, the TLS record size
+ will be increased to the maximum allowed (16K). The max
+ record size will continue to be used on the active TLS
+ session. After :option:`--tls-dyn-rec-idle-timeout` has elapsed,
+ the record size is reduced to 1300 bytes. Specify 0 to
+ always use the maximum record size, regardless of idle
+ period. This behaviour applies to all TLS based
+ frontends, and TLS HTTP/2 backends.
+
+ Default: ``1M``
+
+.. option:: --tls-dyn-rec-idle-timeout=<DURATION>
+
+ Specify TLS dynamic record size behaviour timeout. See
+ :option:`--tls-dyn-rec-warmup-threshold` for more information.
+ This behaviour applies to all TLS based frontends, and
+ TLS HTTP/2 backends.
+
+ Default: ``1s``
+
+.. option:: --no-http2-cipher-block-list
+
+ Allow block listed cipher suite on frontend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+
+.. option:: --client-no-http2-cipher-block-list
+
+ Allow block listed cipher suite on backend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+
+.. option:: --tls-sct-dir=<DIR>
+
+ Specifies the directory where \*.sct files exist. All
+ \*.sct files in <DIR> are read, and sent as
+ extension_data of TLS signed_certificate_timestamp (RFC
+ 6962) to client. These \*.sct files are for the
+ certificate specified in positional command-line
+ argument <CERT>, or certificate option in configuration
+ file. For additional certificates, use :option:`--subcert`
+ option. This option requires OpenSSL >= 1.0.2.
+
+.. option:: --psk-secrets=<PATH>
+
+ Read list of PSK identity and secrets from <PATH>. This
+ is used for frontend connection. The each line of input
+ file is formatted as <identity>:<hex-secret>, where
+ <identity> is PSK identity, and <hex-secret> is secret
+ in hex. An empty line, and line which starts with '#'
+ are skipped. The default enabled cipher list might not
+ contain any PSK cipher suite. In that case, desired PSK
+ cipher suites must be enabled using :option:`--ciphers` option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use :option:`--no-http2-cipher-block-list` option.
+ But be aware its implications.
+
+.. option:: --client-psk-secrets=<PATH>
+
+ Read PSK identity and secrets from <PATH>. This is used
+ for backend connection. The each line of input file is
+ formatted as <identity>:<hex-secret>, where <identity>
+ is PSK identity, and <hex-secret> is secret in hex. An
+ empty line, and line which starts with '#' are skipped.
+ The first identity and secret pair encountered is used.
+ The default enabled cipher list might not contain any
+ PSK cipher suite. In that case, desired PSK cipher
+ suites must be enabled using :option:`--client-ciphers` option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use :option:`--client-no-http2-cipher-block-list`
+ option. But be aware its implications.
+
+.. option:: --tls-no-postpone-early-data
+
+ By default, except for QUIC connections, nghttpx
+ postpones forwarding HTTP requests sent in early data,
+ including those sent in partially in it, until TLS
+ handshake finishes. If all backend server recognizes
+ "Early-Data" header field, using this option makes
+ nghttpx not postpone forwarding request and get full
+ potential of 0-RTT data.
+
+.. option:: --tls-max-early-data=<SIZE>
+
+ Sets the maximum amount of 0-RTT data that server
+ accepts.
+
+ Default: ``16K``
+
+.. option:: --tls-ktls
+
+ Enable ktls. For server, ktls is enable if
+ :option:`--tls-session-cache-memcached` is not configured.
+
+
+HTTP/2
+~~~~~~
+
+.. option:: -c, --frontend-http2-max-concurrent-streams=<N>
+
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/2 session.
+
+ Default: ``100``
+
+.. option:: --backend-http2-max-concurrent-streams=<N>
+
+ Set the maximum number of the concurrent streams in one
+ backend HTTP/2 session. This sets maximum number of
+ concurrent opened pushed streams. The maximum number of
+ concurrent requests are set by a remote server.
+
+ Default: ``100``
+
+.. option:: --frontend-http2-window-size=<SIZE>
+
+ Sets the per-stream initial window size of HTTP/2
+ frontend connection.
+
+ Default: ``65535``
+
+.. option:: --frontend-http2-connection-window-size=<SIZE>
+
+ Sets the per-connection window size of HTTP/2 frontend
+ connection.
+
+ Default: ``65535``
+
+.. option:: --backend-http2-window-size=<SIZE>
+
+ Sets the initial window size of HTTP/2 backend
+ connection.
+
+ Default: ``65535``
+
+.. option:: --backend-http2-connection-window-size=<SIZE>
+
+ Sets the per-connection window size of HTTP/2 backend
+ connection.
+
+ Default: ``2147483647``
+
+.. 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 supported by
+ default mode and HTTP/2 frontend via Link header field.
+ It is also supported if both frontend and backend are
+ HTTP/2 in default mode. In this case, server push from
+ backend session is relayed to frontend, and server push
+ via Link header field is also supported.
+
+.. option:: --frontend-http2-optimize-write-buffer-size
+
+ (Experimental) Enable write buffer size optimization in
+ frontend HTTP/2 TLS connection. This optimization aims
+ to reduce write buffer size so that it only contains
+ bytes which can send immediately. This makes server
+ more responsive to prioritized HTTP/2 stream because the
+ buffering of lower priority stream is reduced. This
+ option is only effective on recent Linux platform.
+
+.. option:: --frontend-http2-optimize-window-size
+
+ (Experimental) Automatically tune connection level
+ window size of frontend HTTP/2 TLS connection. If this
+ feature is enabled, connection window size starts with
+ the default window size, 65535 bytes. nghttpx
+ automatically adjusts connection window size based on
+ TCP receiving window size. The maximum window size is
+ capped by the value specified by
+ :option:`--frontend-http2-connection-window-size`\. Since the
+ stream is subject to stream level window size, it should
+ be adjusted using :option:`--frontend-http2-window-size` option as
+ well. This option is only effective on recent Linux
+ platform.
+
+.. option:: --frontend-http2-encoder-dynamic-table-size=<SIZE>
+
+ Specify the maximum dynamic table size of HPACK encoder
+ in the frontend HTTP/2 connection. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+
+ Default: ``4K``
+
+.. option:: --frontend-http2-decoder-dynamic-table-size=<SIZE>
+
+ Specify the maximum dynamic table size of HPACK decoder
+ in the frontend HTTP/2 connection.
+
+ Default: ``4K``
+
+.. option:: --backend-http2-encoder-dynamic-table-size=<SIZE>
+
+ Specify the maximum dynamic table size of HPACK encoder
+ in the backend HTTP/2 connection. The decoder (backend)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which backend specified.
+
+ Default: ``4K``
+
+.. option:: --backend-http2-decoder-dynamic-table-size=<SIZE>
+
+ Specify the maximum dynamic table size of HPACK decoder
+ in the backend HTTP/2 connection.
+
+ Default: ``4K``
+
+
+Mode
+~~~~
+
+.. describe:: (default mode)
+
+
+ Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls"
+ parameter is used in :option:`--frontend` option, accept HTTP/2
+ and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1
+ connection can be upgraded to HTTP/2 through HTTP
+ Upgrade.
+
+.. option:: -s, --http2-proxy
+
+ Like default mode, but enable forward proxy. This is so
+ called HTTP/2 proxy mode.
+
+
+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.
+ * $tls_cipher: cipher used for SSL/TLS connection.
+ * $tls_client_fingerprint_sha256: SHA-256 fingerprint of
+ client certificate.
+ * $tls_client_fingerprint_sha1: SHA-1 fingerprint of
+ client certificate.
+ * $tls_client_subject_name: subject name in client
+ certificate.
+ * $tls_client_issuer_name: issuer name in client
+ certificate.
+ * $tls_client_serial: serial number in client
+ certificate.
+ * $tls_protocol: protocol for SSL/TLS connection.
+ * $tls_session_id: session ID for SSL/TLS connection.
+ * $tls_session_reused: "r" if SSL/TLS session was
+ reused. Otherwise, "."
+ * $tls_sni: SNI server name for SSL/TLS connection.
+ * $backend_host: backend host used to fulfill the
+ request. "-" if backend host is not available.
+ * $backend_port: backend port used to fulfill the
+ request. "-" if backend host is not available.
+ * $method: HTTP method
+ * $path: Request path including query. For CONNECT
+ request, authority is recorded.
+ * $path_without_query: $path up to the first '?'
+ character. For CONNECT request, authority is
+ recorded.
+ * $protocol_version: HTTP version (e.g., HTTP/1.1,
+ HTTP/2)
+
+ The variable can be enclosed by "{" and "}" for
+ disambiguation (e.g., ${remote_addr}).
+
+
+ Default: ``$remote_addr - - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"``
+
+.. option:: --accesslog-write-early
+
+ Write access log when response header fields are
+ received from backend rather than when request
+ transaction finishes.
+
+.. option:: --errorlog-file=<PATH>
+
+ Set path to write error log. To reopen file, send USR1
+ signal to nghttpx. stderr will be redirected to the
+ error log file unless :option:`--errorlog-syslog` is used.
+
+ 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-add-x-forwarded-proto
+
+ Don't append additional X-Forwarded-Proto header field
+ to the backend request. If inbound client sets
+ X-Forwarded-Proto, and
+ :option:`--no-strip-incoming-x-forwarded-proto` option is used,
+ they are passed to the backend.
+
+.. option:: --no-strip-incoming-x-forwarded-proto
+
+ Don't strip X-Forwarded-Proto header field from inbound
+ client requests.
+
+.. option:: --add-forwarded=<LIST>
+
+ Append RFC 7239 Forwarded header field with parameters
+ specified in comma delimited list <LIST>. The supported
+ parameters are "by", "for", "host", and "proto". By
+ default, the value of "by" and "for" parameters are
+ obfuscated string. See :option:`--forwarded-by` and
+ :option:`--forwarded-for` options respectively. Note that nghttpx
+ does not translate non-standard X-Forwarded-\* header
+ fields into Forwarded header field, and vice versa.
+
+.. option:: --strip-incoming-forwarded
+
+ Strip Forwarded header field from inbound client
+ requests.
+
+.. option:: --forwarded-by=(obfuscated|ip|<VALUE>)
+
+ Specify the parameter value sent out with "by" parameter
+ of Forwarded header field. If "obfuscated" is given,
+ the string is randomly generated at startup. If "ip" is
+ given, the interface address of the connection,
+ including port number, is sent with "by" parameter. In
+ case of UNIX domain socket, "localhost" is used instead
+ of address and port. User can also specify the static
+ obfuscated string. The limitation is that it must start
+ with "_", and only consists of character set
+ [A-Za-z0-9._-], as described in RFC 7239.
+
+ Default: ``obfuscated``
+
+.. option:: --forwarded-for=(obfuscated|ip)
+
+ Specify the parameter value sent out with "for"
+ parameter of Forwarded header field. If "obfuscated" is
+ given, the string is randomly generated for each client
+ connection. If "ip" is given, the remote client address
+ of the connection, without port number, is sent with
+ "for" parameter. In case of UNIX domain socket,
+ "localhost" is used instead of address.
+
+ Default: ``obfuscated``
+
+.. option:: --no-via
+
+ Don't append to Via header field. If Via header field
+ is received, it is left unaltered.
+
+.. option:: --no-strip-incoming-early-data
+
+ Don't strip Early-Data header field from inbound client
+ requests.
+
+.. option:: --no-location-rewrite
+
+ Don't rewrite location header field in default mode.
+ When :option:`--http2-proxy` is used, location header field will
+ not be altered regardless of this option.
+
+.. option:: --host-rewrite
+
+ Rewrite host and :authority header fields in default
+ mode. When :option:`--http2-proxy` is used, these headers will
+ not be altered regardless of this option.
+
+.. option:: --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+
+ Specify protocol ID, port, host and origin of
+ alternative service. <HOST>, <ORIGIN> and <PARAMS> are
+ optional. Empty <HOST> and <ORIGIN> are allowed and
+ they are treated as nothing is specified. They are
+ advertised in alt-svc header field only in HTTP/1.1
+ frontend. This option can be used multiple times to
+ specify multiple alternative services.
+ Example: :option:`--altsvc`\="h2,443,,,ma=3600; persist=1"
+
+.. option:: --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+
+ Just like :option:`--altsvc` option, but this altsvc is only sent
+ in HTTP/2 frontend.
+
+.. option:: --add-request-header=<HEADER>
+
+ Specify additional header field to add to request header
+ set. The field name must be lowercase. 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-request-header`\="foo: bar"
+
+.. option:: --add-response-header=<HEADER>
+
+ Specify additional header field to add to response
+ header set. The field name must be lowercase. 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"
+
+.. option:: --request-header-field-buffer=<SIZE>
+
+ Set maximum buffer size for incoming HTTP request header
+ field list. This is the sum of header name and value in
+ bytes. If trailer fields exist, they are counted
+ towards this number.
+
+ Default: ``64K``
+
+.. option:: --max-request-header-fields=<N>
+
+ Set maximum number of incoming HTTP request header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+
+ Default: ``100``
+
+.. option:: --response-header-field-buffer=<SIZE>
+
+ Set maximum buffer size for incoming HTTP response
+ header field list. This is the sum of header name and
+ value in bytes. If trailer fields exist, they are
+ counted towards this number.
+
+ Default: ``64K``
+
+.. option:: --max-response-header-fields=<N>
+
+ Set maximum number of incoming HTTP response header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+
+ Default: ``500``
+
+.. option:: --error-page=(<CODE>|*)=<PATH>
+
+ Set file path to custom error page served when nghttpx
+ originally generates HTTP error status code <CODE>.
+ <CODE> must be greater than or equal to 400, and at most
+ 599. If "\*" is used instead of <CODE>, it matches all
+ HTTP status code. If error status code comes from
+ backend server, the custom error pages are not used.
+
+.. option:: --server-name=<NAME>
+
+ Change server response header field value to <NAME>.
+
+ Default: ``nghttpx``
+
+.. option:: --no-server-rewrite
+
+ Don't rewrite server header field in default mode. When
+ :option:`--http2-proxy` is used, these headers will not be altered
+ regardless of this option.
+
+.. option:: --redirect-https-port=<PORT>
+
+ Specify the port number which appears in Location header
+ field when redirect to HTTPS URI is made due to
+ "redirect-if-not-tls" parameter in :option:`--backend` option.
+
+ Default: ``443``
+
+.. option:: --require-http-scheme
+
+ Always require http or https scheme in HTTP request. It
+ also requires that https scheme must be used for an
+ encrypted connection. Otherwise, http scheme must be
+ used. This option is recommended for a server
+ deployment which directly faces clients and the services
+ it provides only require http or https scheme.
+
+
+API
+~~~
+
+.. option:: --api-max-request-body=<SIZE>
+
+ Set the maximum size of request body for API request.
+
+ Default: ``32M``
+
+
+DNS
+~~~
+
+.. option:: --dns-cache-timeout=<DURATION>
+
+ Set duration that cached DNS results remain valid. Note
+ that nghttpx caches the unsuccessful results as well.
+
+ Default: ``10s``
+
+.. option:: --dns-lookup-timeout=<DURATION>
+
+ Set timeout that DNS server is given to respond to the
+ initial DNS query. For the 2nd and later queries,
+ server is given time based on this timeout, and it is
+ scaled linearly.
+
+ Default: ``5s``
+
+.. option:: --dns-max-try=<N>
+
+ Set the number of DNS query before nghttpx gives up name
+ lookup.
+
+ Default: ``2``
+
+.. option:: --frontend-max-requests=<N>
+
+ The number of requests that single frontend connection
+ can process. For HTTP/2, this is the number of streams
+ in one HTTP/2 connection. For HTTP/1, this is the
+ number of keep alive requests. This is hint to nghttpx,
+ and it may allow additional few requests. The default
+ value is unlimited.
+
+
+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.
+
+.. option:: --single-process
+
+ Run this program in a single process mode for debugging
+ purpose. Without this option, nghttpx creates at least
+ 2 processes: main and worker processes. If this option
+ is used, main and worker are unified into a single
+ process. nghttpx still spawns additional process if
+ neverbleed is used. In the single process mode, the
+ signal handling feature is disabled.
+
+.. option:: --max-worker-processes=<N>
+
+ The maximum number of worker processes. nghttpx spawns
+ new worker process when it reloads its configuration.
+ The previous worker process enters graceful termination
+ period and will terminate when it finishes handling the
+ existing connections. However, if reloading
+ configurations happen very frequently, the worker
+ processes might be piled up if they take a bit long time
+ to finish the existing connections. With this option,
+ if the number of worker processes exceeds the given
+ value, the oldest worker process is terminated
+ immediately. Specifying 0 means no limit and it is the
+ default behaviour.
+
+.. option:: --worker-process-grace-shutdown-period=<DURATION>
+
+ Maximum period for a worker process to terminate
+ gracefully. When a worker process enters in graceful
+ shutdown period (e.g., when nghttpx reloads its
+ configuration) and it does not finish handling the
+ existing connections in the given period of time, it is
+ immediately terminated. Specifying 0 means no limit and
+ it is the default behaviour.
+
+
+Scripting
+~~~~~~~~~
+
+.. option:: --mruby-file=<PATH>
+
+ Set mruby script file
+
+.. option:: --ignore-per-pattern-mruby-error
+
+ Ignore mruby compile error for per-pattern mruby script
+ file. If error occurred, it is treated as if no mruby
+ file were specified for the pattern.
+
+
+HTTP/3 and QUIC
+~~~~~~~~~~~~~~~
+
+.. option:: --frontend-quic-idle-timeout=<DURATION>
+
+ Specify an idle timeout for QUIC connection.
+
+ Default: ``30s``
+
+.. option:: --frontend-quic-debug-log
+
+ Output QUIC debug log to */dev/stderr.*
+
+.. option:: --quic-bpf-program-file=<PATH>
+
+ Specify a path to eBPF program file reuseport_kern.o to
+ direct an incoming QUIC UDP datagram to a correct
+ socket.
+
+ Default: ``/usr/local/lib/nghttp2/reuseport_kern.o``
+
+.. option:: --frontend-quic-early-data
+
+ Enable early data on frontend QUIC connections. nghttpx
+ sends "Early-Data" header field to a backend server if a
+ request is received in early data and handshake has not
+ finished. All backend servers should deal with possibly
+ replayed requests.
+
+.. option:: --frontend-quic-qlog-dir=<DIR>
+
+ Specify a directory where a qlog file is written for
+ frontend QUIC connections. A qlog file is created per
+ each QUIC connection. The file name is ISO8601 basic
+ format, followed by "-", server Source Connection ID and
+ ".sqlog".
+
+.. option:: --frontend-quic-require-token
+
+ Require an address validation token for a frontend QUIC
+ connection. Server sends a token in Retry packet or
+ NEW_TOKEN frame in the previous connection.
+
+.. option:: --frontend-quic-congestion-controller=<CC>
+
+ Specify a congestion controller algorithm for a frontend
+ QUIC connection. <CC> should be either "cubic" or
+ "bbr".
+
+ Default: ``cubic``
+
+.. option:: --frontend-quic-secret-file=<PATH>
+
+ Path to file that contains secure random data to be used
+ as QUIC keying materials. It is used to derive keys for
+ encrypting tokens and Connection IDs. It is not used to
+ encrypt QUIC packets. Each line of this file must
+ contain exactly 136 bytes hex-encoded string (when
+ decoded the byte string is 68 bytes long). The first 2
+ bits of decoded byte string are used to identify the
+ keying material. An empty line or a line which starts
+ '#' is ignored. The file can contain more than one
+ keying materials. Because the identifier is 2 bits, at
+ most 4 keying materials are read and the remaining data
+ is discarded. The first keying material in the file is
+ primarily used for encryption and decryption for new
+ connection. The other ones are used to decrypt data for
+ the existing connections. Specifying multiple keying
+ materials enables key rotation. Please note that key
+ rotation does not occur automatically. User should
+ update files or change options values and restart
+ nghttpx gracefully. If opening or reading given file
+ fails, all loaded keying materials 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, a keying material is generated
+ internally on startup and reload.
+
+.. option:: --quic-server-id=<HEXSTRING>
+
+ Specify server ID encoded in Connection ID to identify
+ this particular server instance. Connection ID is
+ encrypted and this part is not visible in public. It
+ must be 4 bytes long and must be encoded in hex string
+ (which is 8 bytes long). If this option is omitted, a
+ random server ID is generated on startup and
+ configuration reload.
+
+.. option:: --frontend-quic-initial-rtt=<DURATION>
+
+ Specify the initial RTT of the frontend QUIC connection.
+
+ Default: ``333ms``
+
+.. option:: --no-quic-bpf
+
+ Disable eBPF.
+
+.. option:: --frontend-http3-window-size=<SIZE>
+
+ Sets the per-stream initial window size of HTTP/3
+ frontend connection.
+
+ Default: ``256K``
+
+.. option:: --frontend-http3-connection-window-size=<SIZE>
+
+ Sets the per-connection window size of HTTP/3 frontend
+ connection.
+
+ Default: ``1M``
+
+.. option:: --frontend-http3-max-window-size=<SIZE>
+
+ Sets the maximum per-stream window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by :option:`--frontend-http3-window-size`
+ and the window size grows up to <SIZE> bytes.
+
+ Default: ``6M``
+
+.. option:: --frontend-http3-max-connection-window-size=<SIZE>
+
+ Sets the maximum per-connection window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by
+ :option:`--frontend-http3-connection-window-size` and the window
+ size grows up to <SIZE> bytes.
+
+ Default: ``8M``
+
+.. option:: --frontend-http3-max-concurrent-streams=<N>
+
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/3 connection.
+
+ Default: ``100``
+
+
+Misc
+~~~~
+
+.. option:: --conf=<PATH>
+
+ Load configuration from <PATH>. Please note that
+ nghttpx always tries to read the default configuration
+ file if :option:`--conf` is not given.
+
+ Default: ``/etc/nghttpx/nghttpx.conf``
+
+.. option:: --include=<PATH>
+
+ Load additional configurations from <PATH>. File <PATH>
+ is read when configuration parser encountered this
+ option. This option can be used multiple times, or even
+ recursively.
+
+.. 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 h, m, s or ms
+(hours, minutes, seconds and milliseconds, respectively). 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.
+
+ When specifying arguments including characters which have special
+ meaning to a shell, we usually use quotes so that shell does not
+ interpret them. When writing this configuration file, quotes for
+ this purpose must not be used. For example, specify additional
+ request header field, do this:
+
+ .. code-block:: text
+
+ add-request-header=foo: bar
+
+ instead of:
+
+ .. code-block:: text
+
+ add-request-header="foo: bar"
+
+ 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 command-line, use ``private-key-file`` and
+ ``certificate-file``.
+
+ :option:`--conf` option cannot be used in the configuration file and
+ will be ignored if specified.
+
+Error log
+ Error log is written to stderr by default. It can be configured
+ using :option:`--errorlog-file`. The format of log message is as
+ follows:
+
+ <datetime> <main-pid> <current-pid> <thread-id> <level> (<filename>:<line>) <msg>
+
+ <datetime>
+ It is a combination of date and time when the log is written. It
+ is in ISO 8601 format.
+
+ <main-pid>
+ It is a main process ID.
+
+ <current-pid>
+ It is a process ID which writes this log.
+
+ <thread-id>
+ It is a thread ID which writes this log. It would be unique
+ within <current-pid>.
+
+ <filename> and <line>
+ They are source file name, and line number which produce this log.
+
+ <msg>
+ It is a log message body.
+
+SIGNALS
+-------
+
+SIGQUIT
+ Shutdown gracefully. First accept pending connections and stop
+ accepting connection. After all connections are handled, nghttpx
+ exits.
+
+SIGHUP
+ Reload configuration file given in :option:`--conf`.
+
+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. As
+ of nghttpx version 1.20.0, the new main process sends SIGQUIT to the
+ original main process when it is ready to serve requests. For the
+ earlier versions of nghttpx, user has to send SIGQUIT to the
+ original main process.
+
+ The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former
+ is usually used to execute new binary, and the main process is newly
+ spawned. On the other hand, the latter just reloads configuration
+ file, and the same main process continues to exist.
+
+.. note::
+
+ nghttpx consists of multiple processes: one process for processing
+ these signals, and another one for processing requests. The former
+ spawns the latter. The former is called main process, and the
+ latter is called worker process. If neverbleed is enabled, the
+ worker process spawns neverbleed daemon process which does RSA key
+ processing. The above signal must be sent to the main process. If
+ the other processes received one of them, it is ignored. This
+ behaviour of these processes may change in the future release. In
+ other words, in the future release, the processes other than main
+ process may terminate upon the reception of these signals.
+ Therefore these signals should not be sent to the processes other
+ than main process.
+
+SERVER PUSH
+-----------
+
+nghttpx supports HTTP/2 server push in default mode with Link header
+field. nghttpx looks for Link header field (`RFC 5988
+<http://tools.ietf.org/html/rfc5988>`_) in response headers from
+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:: text
+
+ Link: </fonts/font.woff>; rel=preload
+ Link: </css/theme.css>; rel=preload
+
+Currently, the following restriction is applied for server push:
+
+1. The associated stream must have method "GET" or "POST". The
+ associated stream's status code must be 200.
+
+This limitation may be loosened in the future release.
+
+nghttpx also supports server push if both frontend and backend are
+HTTP/2 in default mode. In this case, in addition to server push via
+Link header field, server push from backend is forwarded to frontend
+HTTP/2 session.
+
+HTTP/2 server push will be disabled if :option:`--http2-proxy` is
+used.
+
+UNIX DOMAIN SOCKET
+------------------
+
+nghttpx supports UNIX domain socket with a filename for both frontend
+and backend connections.
+
+Please note that current nghttpx implementation does not delete a
+socket with a filename. And on start up, if nghttpx detects that the
+specified socket already exists in the file system, nghttpx first
+deletes it. However, if SIGUSR2 is used to execute new binary and
+both old and new configurations use same filename, new binary does not
+delete the socket and continues to use it.
+
+OCSP STAPLING
+-------------
+
+OCSP query is done using external Python script
+``fetch-ocsp-response``, which has been originally developed in Perl
+as part of h2o project (https://github.com/h2o/h2o), and was
+translated into Python.
+
+The script file is usually installed under
+``$(prefix)/share/nghttp2/`` directory. The actual path to script can
+be customized using :option:`--fetch-ocsp-response-file` option.
+
+If OCSP query is failed, previous OCSP response, if any, is continued
+to be used.
+
+:option:`--fetch-ocsp-response-file` option provides wide range of
+possibility to manage OCSP response. It can take an arbitrary script
+or executable. The requirement is that it supports the command-line
+interface of ``fetch-ocsp-response`` script, and it must return a
+valid DER encoded OCSP response on success. It must return exit code
+0 on success, and 75 for temporary error, and the other error code for
+generic failure. For large cluster of servers, it is not efficient
+for each server to perform OCSP query using ``fetch-ocsp-response``.
+Instead, you can retrieve OCSP response in some way, and store it in a
+disk or a shared database. Then specify a program in
+:option:`--fetch-ocsp-response-file` to fetch it from those stores.
+This could provide a way to share the OCSP response between fleet of
+servers, and also any OCSP query strategy can be applied which may be
+beyond the ability of nghttpx itself or ``fetch-ocsp-response``
+script.
+
+TLS SESSION RESUMPTION
+----------------------
+
+nghttpx supports TLS session resumption through both session ID and
+session ticket.
+
+SESSION ID RESUMPTION
+~~~~~~~~~~~~~~~~~~~~~
+
+By default, session ID is shared by all worker threads.
+
+If :option:`--tls-session-cache-memcached` is given, nghttpx will
+insert serialized session data to memcached with
+``nghttpx:tls-session-cache:`` + lowercase hex string of session ID
+as a memcached entry key, with expiry time 12 hours. Session timeout
+is set to 12 hours.
+
+By default, connections to memcached server are not encrypted. To
+enable encryption, use ``tls`` keyword in
+:option:`--tls-session-cache-memcached` option.
+
+TLS SESSION TICKET RESUMPTION
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, session ticket is shared by all worker threads. The
+automatic key rotation is also enabled by default. Every an hour, new
+encryption key is generated, and previous encryption key becomes
+decryption only key. We set session timeout to 12 hours, and thus we
+keep at most 12 keys.
+
+If :option:`--tls-ticket-key-memcached` is given, encryption keys are
+retrieved from memcached. nghttpx just reads keys from memcached; one
+has to deploy key generator program to update keys frequently (e.g.,
+every 1 hour). The example key generator tlsticketupdate.go is
+available under contrib directory in nghttp2 archive. The memcached
+entry key is ``nghttpx:tls-ticket-key``. The data format stored in
+memcached is the binary format described below:
+
+.. code-block:: text
+
+ +--------------+-------+----------------+
+ | VERSION (4) |LEN (2)|KEY(48 or 80) ...
+ +--------------+-------+----------------+
+ ^ |
+ | |
+ +------------------------+
+ (LEN, KEY) pair can be repeated
+
+All numbers in the above figure is bytes. All integer fields are
+network byte order.
+
+First 4 bytes integer VERSION field, which must be 1. The 2 bytes
+integer LEN field gives the length of following KEY field, which
+contains key. If :option:`--tls-ticket-key-cipher`\=aes-128-cbc is
+used, LEN must be 48. If
+:option:`--tls-ticket-key-cipher`\=aes-256-cbc is used, LEN must be
+80. LEN and KEY pair can be repeated multiple times to store multiple
+keys. The key appeared first is used as encryption key. All the
+remaining keys are used as decryption only.
+
+By default, connections to memcached server are not encrypted. To
+enable encryption, use ``tls`` keyword in
+:option:`--tls-ticket-key-memcached` option.
+
+If :option:`--tls-ticket-key-file` is given, encryption key is read
+from the given file. In this case, nghttpx does not rotate key
+automatically. To rotate key, one has to restart nghttpx (see
+SIGNALS).
+
+CERTIFICATE TRANSPARENCY
+------------------------
+
+nghttpx supports TLS ``signed_certificate_timestamp`` extension (`RFC
+6962 <https://tools.ietf.org/html/rfc6962>`_). The relevant options
+are :option:`--tls-sct-dir` and ``sct-dir`` parameter in
+:option:`--subcert`. They takes a directory, and nghttpx reads all
+files whose extension is ``.sct`` under the directory. The ``*.sct``
+files are encoded as ``SignedCertificateTimestamp`` struct described
+in `section 3.2 of RFC 69662
+<https://tools.ietf.org/html/rfc6962#section-3.2>`_. This format is
+the same one used by `nginx-ct
+<https://github.com/grahamedgecombe/nginx-ct>`_ and `mod_ssl_ct
+<https://httpd.apache.org/docs/trunk/mod/mod_ssl_ct.html>`_.
+`ct-submit <https://github.com/grahamedgecombe/ct-submit>`_ can be
+used to submit certificates to log servers, and obtain the
+``SignedCertificateTimestamp`` struct which can be used with nghttpx.
+
+MRUBY SCRIPTING
+---------------
+
+.. warning::
+
+ The current mruby extension API is experimental and not frozen. The
+ API is subject to change in the future release.
+
+.. warning::
+
+ Almost all string value returned from method, or attribute is a
+ fresh new mruby string, which involves memory allocation, and
+ copies. Therefore, it is strongly recommended to store a return
+ value in a local variable, and use it, instead of calling method or
+ accessing attribute repeatedly.
+
+nghttpx allows users to extend its capability using mruby scripts.
+nghttpx has 2 hook points to execute mruby script: request phase and
+response phase. The request phase hook is invoked after all request
+header fields are received from client. The response phase hook is
+invoked after all response header fields are received from backend
+server. These hooks allows users to modify header fields, or common
+HTTP variables, like authority or request path, and even return custom
+response without forwarding request to backend servers.
+
+There are 2 levels of mruby script invocations: global and
+per-pattern. The global mruby script is set by :option:`--mruby-file`
+option and is called for all requests. The per-pattern mruby script
+is set by "mruby" parameter in :option:`-b` option. It is invoked for
+a request which matches the particular pattern. The order of hook
+invocation is: global request phase hook, per-pattern request phase
+hook, per-pattern response phase hook, and finally global response
+phase hook. If a hook returns a response, any later hooks are not
+invoked. The global request hook is invoked before the pattern
+matching is made and changing request path may affect the pattern
+matching.
+
+Please note that request and response hooks of per-pattern mruby
+script for a single request might not come from the same script. This
+might happen after a request hook is executed, backend failed for some
+reason, and at the same time, backend configuration is replaced by API
+request, and then the request uses new configuration on retry. The
+response hook from new configuration, if it is specified, will be
+invoked.
+
+The all mruby script will be evaluated once per thread on startup, and
+it must instantiate object and evaluate it as the return value (e.g.,
+``App.new``). This object is called app object. If app object
+defines ``on_req`` method, it is called with :rb:class:`Nghttpx::Env`
+object on request hook. Similarly, if app object defines ``on_resp``
+method, it is called with :rb:class:`Nghttpx::Env` object on response
+hook. For each method invocation, user can can access
+:rb:class:`Nghttpx::Request` and :rb:class:`Nghttpx::Response` objects
+via :rb:attr:`Nghttpx::Env#req` and :rb:attr:`Nghttpx::Env#resp`
+respectively.
+
+.. rb:module:: Nghttpx
+
+.. rb:const:: REQUEST_PHASE
+
+ Constant to represent request phase.
+
+.. rb:const:: RESPONSE_PHASE
+
+ Constant to represent response phase.
+
+.. rb:class:: Env
+
+ Object to represent current request specific context.
+
+ .. rb:attr_reader:: req
+
+ Return :rb:class:`Request` object.
+
+ .. rb:attr_reader:: resp
+
+ Return :rb:class:`Response` object.
+
+ .. rb:attr_reader:: ctx
+
+ Return Ruby hash object. It persists until request finishes.
+ So values set in request phase hook can be retrieved in
+ response phase hook.
+
+ .. rb:attr_reader:: phase
+
+ Return the current phase.
+
+ .. rb:attr_reader:: remote_addr
+
+ Return IP address of a remote client. If connection is made
+ via UNIX domain socket, this returns the string "localhost".
+
+ .. rb:attr_reader:: server_addr
+
+ Return address of server that accepted the connection. This
+ is a string which specified in :option:`--frontend` option,
+ excluding port number, and not a resolved IP address. For
+ UNIX domain socket, this is a path to UNIX domain socket.
+
+ .. rb:attr_reader:: server_port
+
+ Return port number of the server frontend which accepted the
+ connection from client.
+
+ .. rb:attr_reader:: tls_used
+
+ Return true if TLS is used on the connection.
+
+ .. rb:attr_reader:: tls_sni
+
+ Return the TLS SNI value which client sent in this connection.
+
+ .. rb:attr_reader:: tls_client_fingerprint_sha256
+
+ Return the SHA-256 fingerprint of a client certificate.
+
+ .. rb:attr_reader:: tls_client_fingerprint_sha1
+
+ Return the SHA-1 fingerprint of a client certificate.
+
+ .. rb:attr_reader:: tls_client_issuer_name
+
+ Return the issuer name of a client certificate.
+
+ .. rb:attr_reader:: tls_client_subject_name
+
+ Return the subject name of a client certificate.
+
+ .. rb:attr_reader:: tls_client_serial
+
+ Return the serial number of a client certificate.
+
+ .. rb:attr_reader:: tls_client_not_before
+
+ Return the start date of a client certificate in seconds since
+ the epoch.
+
+ .. rb:attr_reader:: tls_client_not_after
+
+ Return the end date of a client certificate in seconds since
+ the epoch.
+
+ .. rb:attr_reader:: tls_cipher
+
+ Return a TLS cipher negotiated in this connection.
+
+ .. rb:attr_reader:: tls_protocol
+
+ Return a TLS protocol version negotiated in this connection.
+
+ .. rb:attr_reader:: tls_session_id
+
+ Return a session ID for this connection in hex string.
+
+ .. rb:attr_reader:: tls_session_reused
+
+ Return true if, and only if a SSL/TLS session is reused.
+
+ .. rb:attr_reader:: alpn
+
+ Return ALPN identifier negotiated in this connection.
+
+ .. rb:attr_reader:: tls_handshake_finished
+
+ Return true if SSL/TLS handshake has finished. If it returns
+ false in the request phase hook, the request is received in
+ TLSv1.3 early data (0-RTT) and might be vulnerable to the
+ replay attack. nghttpx will send Early-Data header field to
+ backend servers to indicate this.
+
+.. rb:class:: Request
+
+ Object to represent request from client. The modification to
+ Request object is allowed only in request phase hook.
+
+ .. rb:attr_reader:: http_version_major
+
+ Return HTTP major version.
+
+ .. rb:attr_reader:: http_version_minor
+
+ Return HTTP minor version.
+
+ .. rb:attr_accessor:: method
+
+ HTTP method. On assignment, copy of given value is assigned.
+ We don't accept arbitrary method name. We will document them
+ later, but well known methods, like GET, PUT and POST, are all
+ supported.
+
+ .. rb:attr_accessor:: authority
+
+ Authority (i.e., example.org), including optional port
+ component . On assignment, copy of given value is assigned.
+
+ .. rb:attr_accessor:: scheme
+
+ Scheme (i.e., http, https). On assignment, copy of given
+ value is assigned.
+
+ .. rb:attr_accessor:: path
+
+ Request path, including query component (i.e., /index.html).
+ On assignment, copy of given value is assigned. The path does
+ not include authority component of URI. This may include
+ query component. nghttpx makes certain normalization for
+ path. It decodes percent-encoding for unreserved characters
+ (see https://tools.ietf.org/html/rfc3986#section-2.3), and
+ resolves ".." and ".". But it may leave characters which
+ should be percent-encoded as is. So be careful when comparing
+ path against desired string.
+
+ .. rb:attr_reader:: headers
+
+ Return Ruby hash containing copy of request header fields.
+ Changing values in returned hash does not change request
+ header fields actually used in request processing. Use
+ :rb:meth:`Nghttpx::Request#add_header` or
+ :rb:meth:`Nghttpx::Request#set_header` to change request
+ header fields.
+
+ .. rb:method:: add_header(key, value)
+
+ Add header entry associated with key. The value can be single
+ string or array of string. It does not replace any existing
+ values associated with key.
+
+ .. rb:method:: set_header(key, value)
+
+ Set header entry associated with key. The value can be single
+ string or array of string. It replaces any existing values
+ associated with key.
+
+ .. rb:method:: clear_headers
+
+ Clear all existing request header fields.
+
+ .. rb:method:: push(uri)
+
+ Initiate to push resource identified by *uri*. Only HTTP/2
+ protocol supports this feature. For the other protocols, this
+ method is noop. *uri* can be absolute URI, absolute path or
+ relative path to the current request. For absolute or
+ relative path, scheme and authority are inherited from the
+ current request. Currently, method is always GET. nghttpx
+ will issue request to backend servers to fulfill this request.
+ The request and response phase hooks will be called for pushed
+ resource as well.
+
+.. rb:class:: Response
+
+ Object to represent response from backend server.
+
+ .. rb:attr_reader:: http_version_major
+
+ Return HTTP major version.
+
+ .. rb:attr_reader:: http_version_minor
+
+ Return HTTP minor version.
+
+ .. rb:attr_accessor:: status
+
+ HTTP status code. It must be in the range [200, 999],
+ inclusive. The non-final status code is not supported in
+ mruby scripting at the moment.
+
+ .. rb:attr_reader:: headers
+
+ Return Ruby hash containing copy of response header fields.
+ Changing values in returned hash does not change response
+ header fields actually used in response processing. Use
+ :rb:meth:`Nghttpx::Response#add_header` or
+ :rb:meth:`Nghttpx::Response#set_header` to change response
+ header fields.
+
+ .. rb:method:: add_header(key, value)
+
+ Add header entry associated with key. The value can be single
+ string or array of string. It does not replace any existing
+ values associated with key.
+
+ .. rb:method:: set_header(key, value)
+
+ Set header entry associated with key. The value can be single
+ string or array of string. It replaces any existing values
+ associated with key.
+
+ .. rb:method:: clear_headers
+
+ Clear all existing response header fields.
+
+ .. rb:method:: return(body)
+
+ Return custom response *body* to a client. When this method
+ is called in request phase hook, the request is not forwarded
+ to the backend, and response phase hook for this request will
+ not be invoked. When this method is called in response phase
+ hook, response from backend server is canceled and discarded.
+ The status code and response header fields should be set
+ before using this method. To set status code, use
+ :rb:attr:`Nghttpx::Response#status`. If status code is not
+ set, 200 is used. To set response header fields,
+ :rb:meth:`Nghttpx::Response#add_header` and
+ :rb:meth:`Nghttpx::Response#set_header`. When this method is
+ invoked in response phase hook, the response headers are
+ filled with the ones received from backend server. To send
+ completely custom header fields, first call
+ :rb:meth:`Nghttpx::Response#clear_headers` to erase all
+ existing header fields, and then add required header fields.
+ It is an error to call this method twice for a given request.
+
+ .. rb:method:: send_info(status, headers)
+
+ Send non-final (informational) response to a client. *status*
+ must be in the range [100, 199], inclusive. *headers* is a
+ hash containing response header fields. Its key must be a
+ string, and the associated value must be either string or
+ array of strings. Since this is not a final response, even if
+ this method is invoked, request is still forwarded to a
+ backend unless :rb:meth:`Nghttpx::Response#return` is called.
+ This method can be called multiple times. It cannot be called
+ after :rb:meth:`Nghttpx::Response#return` is called.
+
+MRUBY EXAMPLES
+~~~~~~~~~~~~~~
+
+Modify request path:
+
+.. code-block:: ruby
+
+ class App
+ def on_req(env)
+ env.req.path = "/apps#{env.req.path}"
+ end
+ end
+
+ App.new
+
+Don't forget to instantiate and evaluate object at the last line.
+
+Restrict permission of viewing a content to a specific client
+addresses:
+
+.. code-block:: ruby
+
+ class App
+ def on_req(env)
+ allowed_clients = ["127.0.0.1", "::1"]
+
+ if env.req.path.start_with?("/log/") &&
+ !allowed_clients.include?(env.remote_addr) then
+ env.resp.status = 404
+ env.resp.return "permission denied"
+ end
+ end
+ end
+
+ App.new
+
+API ENDPOINTS
+-------------
+
+nghttpx exposes API endpoints to manipulate it via HTTP based API. By
+default, API endpoint is disabled. To enable it, add a dedicated
+frontend for API using :option:`--frontend` option with "api"
+parameter. All requests which come from this frontend address, will
+be treated as API request.
+
+The response is normally JSON dictionary, and at least includes the
+following keys:
+
+status
+ The status of the request processing. The following values are
+ defined:
+
+ Success
+ The request was successful.
+
+ Failure
+ The request was failed. No change has been made.
+
+code
+ HTTP status code
+
+Additionally, depending on the API endpoint, ``data`` key may be
+present, and its value contains the API endpoint specific data.
+
+We wrote "normally", since nghttpx may return ordinal HTML response in
+some cases where the error has occurred before reaching API endpoint
+(e.g., header field is too large).
+
+The following section describes available API endpoints.
+
+POST /api/v1beta1/backendconfig
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This API replaces the current backend server settings with the
+requested ones. The request method should be POST, but PUT is also
+acceptable. The request body must be nghttpx configuration file
+format. For configuration file format, see `FILES`_ section. The
+line separator inside the request body must be single LF (0x0A).
+Currently, only :option:`backend <--backend>` option is parsed, the
+others are simply ignored. The semantics of this API is replace the
+current backend with the backend options in request body. Describe
+the desired set of backend severs, and nghttpx makes it happen. If
+there is no :option:`backend <--backend>` option is found in request
+body, the current set of backend is replaced with the :option:`backend
+<--backend>` option's default value, which is ``127.0.0.1,80``.
+
+The replacement is done instantly without breaking existing
+connections or requests. It also avoids any process creation as is
+the case with hot swapping with signals.
+
+The one limitation is that only numeric IP address is allowed in
+:option:`backend <--backend>` in request body unless "dns" parameter
+is used while non numeric hostname is allowed in command-line or
+configuration file is read using :option:`--conf`.
+
+GET /api/v1beta1/configrevision
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This API returns configuration revision of the current nghttpx. The
+configuration revision is opaque string, and it changes after each
+reloading by SIGHUP. With this API, an external application knows
+that whether nghttpx has finished reloading its configuration by
+comparing the configuration revisions between before and after
+reloading. It is recommended to disable persistent (keep-alive)
+connection for this purpose in order to avoid to send a request using
+the reused connection which may bound to an old process.
+
+This API returns response including ``data`` key. Its value is JSON
+object, and it contains at least the following key:
+
+configRevision
+ The configuration revision of the current nghttpx
+
+
+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
index 0000000..12ac718
--- /dev/null
+++ b/doc/nghttpx.h2r
@@ -0,0 +1,719 @@
+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.
+
+ When specifying arguments including characters which have special
+ meaning to a shell, we usually use quotes so that shell does not
+ interpret them. When writing this configuration file, quotes for
+ this purpose must not be used. For example, specify additional
+ request header field, do this:
+
+ .. code-block:: text
+
+ add-request-header=foo: bar
+
+ instead of:
+
+ .. code-block:: text
+
+ add-request-header="foo: bar"
+
+ 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 command-line, use ``private-key-file`` and
+ ``certificate-file``.
+
+ :option:`--conf` option cannot be used in the configuration file and
+ will be ignored if specified.
+
+Error log
+ Error log is written to stderr by default. It can be configured
+ using :option:`--errorlog-file`. The format of log message is as
+ follows:
+
+ <datetime> <main-pid> <current-pid> <thread-id> <level> (<filename>:<line>) <msg>
+
+ <datetime>
+ It is a combination of date and time when the log is written. It
+ is in ISO 8601 format.
+
+ <main-pid>
+ It is a main process ID.
+
+ <current-pid>
+ It is a process ID which writes this log.
+
+ <thread-id>
+ It is a thread ID which writes this log. It would be unique
+ within <current-pid>.
+
+ <filename> and <line>
+ They are source file name, and line number which produce this log.
+
+ <msg>
+ It is a log message body.
+
+SIGNALS
+-------
+
+SIGQUIT
+ Shutdown gracefully. First accept pending connections and stop
+ accepting connection. After all connections are handled, nghttpx
+ exits.
+
+SIGHUP
+ Reload configuration file given in :option:`--conf`.
+
+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. As
+ of nghttpx version 1.20.0, the new main process sends SIGQUIT to the
+ original main process when it is ready to serve requests. For the
+ earlier versions of nghttpx, user has to send SIGQUIT to the
+ original main process.
+
+ The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former
+ is usually used to execute new binary, and the main process is newly
+ spawned. On the other hand, the latter just reloads configuration
+ file, and the same main process continues to exist.
+
+.. note::
+
+ nghttpx consists of multiple processes: one process for processing
+ these signals, and another one for processing requests. The former
+ spawns the latter. The former is called main process, and the
+ latter is called worker process. If neverbleed is enabled, the
+ worker process spawns neverbleed daemon process which does RSA key
+ processing. The above signal must be sent to the main process. If
+ the other processes received one of them, it is ignored. This
+ behaviour of these processes may change in the future release. In
+ other words, in the future release, the processes other than main
+ process may terminate upon the reception of these signals.
+ Therefore these signals should not be sent to the processes other
+ than main process.
+
+SERVER PUSH
+-----------
+
+nghttpx supports HTTP/2 server push in default mode with Link header
+field. nghttpx looks for Link header field (`RFC 5988
+<http://tools.ietf.org/html/rfc5988>`_) in response headers from
+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:: text
+
+ Link: </fonts/font.woff>; rel=preload
+ Link: </css/theme.css>; rel=preload
+
+Currently, the following restriction is applied for server push:
+
+1. The associated stream must have method "GET" or "POST". The
+ associated stream's status code must be 200.
+
+This limitation may be loosened in the future release.
+
+nghttpx also supports server push if both frontend and backend are
+HTTP/2 in default mode. In this case, in addition to server push via
+Link header field, server push from backend is forwarded to frontend
+HTTP/2 session.
+
+HTTP/2 server push will be disabled if :option:`--http2-proxy` is
+used.
+
+UNIX DOMAIN SOCKET
+------------------
+
+nghttpx supports UNIX domain socket with a filename for both frontend
+and backend connections.
+
+Please note that current nghttpx implementation does not delete a
+socket with a filename. And on start up, if nghttpx detects that the
+specified socket already exists in the file system, nghttpx first
+deletes it. However, if SIGUSR2 is used to execute new binary and
+both old and new configurations use same filename, new binary does not
+delete the socket and continues to use it.
+
+OCSP STAPLING
+-------------
+
+OCSP query is done using external Python script
+``fetch-ocsp-response``, which has been originally developed in Perl
+as part of h2o project (https://github.com/h2o/h2o), and was
+translated into Python.
+
+The script file is usually installed under
+``$(prefix)/share/nghttp2/`` directory. The actual path to script can
+be customized using :option:`--fetch-ocsp-response-file` option.
+
+If OCSP query is failed, previous OCSP response, if any, is continued
+to be used.
+
+:option:`--fetch-ocsp-response-file` option provides wide range of
+possibility to manage OCSP response. It can take an arbitrary script
+or executable. The requirement is that it supports the command-line
+interface of ``fetch-ocsp-response`` script, and it must return a
+valid DER encoded OCSP response on success. It must return exit code
+0 on success, and 75 for temporary error, and the other error code for
+generic failure. For large cluster of servers, it is not efficient
+for each server to perform OCSP query using ``fetch-ocsp-response``.
+Instead, you can retrieve OCSP response in some way, and store it in a
+disk or a shared database. Then specify a program in
+:option:`--fetch-ocsp-response-file` to fetch it from those stores.
+This could provide a way to share the OCSP response between fleet of
+servers, and also any OCSP query strategy can be applied which may be
+beyond the ability of nghttpx itself or ``fetch-ocsp-response``
+script.
+
+TLS SESSION RESUMPTION
+----------------------
+
+nghttpx supports TLS session resumption through both session ID and
+session ticket.
+
+SESSION ID RESUMPTION
+~~~~~~~~~~~~~~~~~~~~~
+
+By default, session ID is shared by all worker threads.
+
+If :option:`--tls-session-cache-memcached` is given, nghttpx will
+insert serialized session data to memcached with
+``nghttpx:tls-session-cache:`` + lowercase hex string of session ID
+as a memcached entry key, with expiry time 12 hours. Session timeout
+is set to 12 hours.
+
+By default, connections to memcached server are not encrypted. To
+enable encryption, use ``tls`` keyword in
+:option:`--tls-session-cache-memcached` option.
+
+TLS SESSION TICKET RESUMPTION
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, session ticket is shared by all worker threads. The
+automatic key rotation is also enabled by default. Every an hour, new
+encryption key is generated, and previous encryption key becomes
+decryption only key. We set session timeout to 12 hours, and thus we
+keep at most 12 keys.
+
+If :option:`--tls-ticket-key-memcached` is given, encryption keys are
+retrieved from memcached. nghttpx just reads keys from memcached; one
+has to deploy key generator program to update keys frequently (e.g.,
+every 1 hour). The example key generator tlsticketupdate.go is
+available under contrib directory in nghttp2 archive. The memcached
+entry key is ``nghttpx:tls-ticket-key``. The data format stored in
+memcached is the binary format described below:
+
+.. code-block:: text
+
+ +--------------+-------+----------------+
+ | VERSION (4) |LEN (2)|KEY(48 or 80) ...
+ +--------------+-------+----------------+
+ ^ |
+ | |
+ +------------------------+
+ (LEN, KEY) pair can be repeated
+
+All numbers in the above figure is bytes. All integer fields are
+network byte order.
+
+First 4 bytes integer VERSION field, which must be 1. The 2 bytes
+integer LEN field gives the length of following KEY field, which
+contains key. If :option:`--tls-ticket-key-cipher`\=aes-128-cbc is
+used, LEN must be 48. If
+:option:`--tls-ticket-key-cipher`\=aes-256-cbc is used, LEN must be
+80. LEN and KEY pair can be repeated multiple times to store multiple
+keys. The key appeared first is used as encryption key. All the
+remaining keys are used as decryption only.
+
+By default, connections to memcached server are not encrypted. To
+enable encryption, use ``tls`` keyword in
+:option:`--tls-ticket-key-memcached` option.
+
+If :option:`--tls-ticket-key-file` is given, encryption key is read
+from the given file. In this case, nghttpx does not rotate key
+automatically. To rotate key, one has to restart nghttpx (see
+SIGNALS).
+
+CERTIFICATE TRANSPARENCY
+------------------------
+
+nghttpx supports TLS ``signed_certificate_timestamp`` extension (`RFC
+6962 <https://tools.ietf.org/html/rfc6962>`_). The relevant options
+are :option:`--tls-sct-dir` and ``sct-dir`` parameter in
+:option:`--subcert`. They takes a directory, and nghttpx reads all
+files whose extension is ``.sct`` under the directory. The ``*.sct``
+files are encoded as ``SignedCertificateTimestamp`` struct described
+in `section 3.2 of RFC 69662
+<https://tools.ietf.org/html/rfc6962#section-3.2>`_. This format is
+the same one used by `nginx-ct
+<https://github.com/grahamedgecombe/nginx-ct>`_ and `mod_ssl_ct
+<https://httpd.apache.org/docs/trunk/mod/mod_ssl_ct.html>`_.
+`ct-submit <https://github.com/grahamedgecombe/ct-submit>`_ can be
+used to submit certificates to log servers, and obtain the
+``SignedCertificateTimestamp`` struct which can be used with nghttpx.
+
+MRUBY SCRIPTING
+---------------
+
+.. warning::
+
+ The current mruby extension API is experimental and not frozen. The
+ API is subject to change in the future release.
+
+.. warning::
+
+ Almost all string value returned from method, or attribute is a
+ fresh new mruby string, which involves memory allocation, and
+ copies. Therefore, it is strongly recommended to store a return
+ value in a local variable, and use it, instead of calling method or
+ accessing attribute repeatedly.
+
+nghttpx allows users to extend its capability using mruby scripts.
+nghttpx has 2 hook points to execute mruby script: request phase and
+response phase. The request phase hook is invoked after all request
+header fields are received from client. The response phase hook is
+invoked after all response header fields are received from backend
+server. These hooks allows users to modify header fields, or common
+HTTP variables, like authority or request path, and even return custom
+response without forwarding request to backend servers.
+
+There are 2 levels of mruby script invocations: global and
+per-pattern. The global mruby script is set by :option:`--mruby-file`
+option and is called for all requests. The per-pattern mruby script
+is set by "mruby" parameter in :option:`-b` option. It is invoked for
+a request which matches the particular pattern. The order of hook
+invocation is: global request phase hook, per-pattern request phase
+hook, per-pattern response phase hook, and finally global response
+phase hook. If a hook returns a response, any later hooks are not
+invoked. The global request hook is invoked before the pattern
+matching is made and changing request path may affect the pattern
+matching.
+
+Please note that request and response hooks of per-pattern mruby
+script for a single request might not come from the same script. This
+might happen after a request hook is executed, backend failed for some
+reason, and at the same time, backend configuration is replaced by API
+request, and then the request uses new configuration on retry. The
+response hook from new configuration, if it is specified, will be
+invoked.
+
+The all mruby script will be evaluated once per thread on startup, and
+it must instantiate object and evaluate it as the return value (e.g.,
+``App.new``). This object is called app object. If app object
+defines ``on_req`` method, it is called with :rb:class:`Nghttpx::Env`
+object on request hook. Similarly, if app object defines ``on_resp``
+method, it is called with :rb:class:`Nghttpx::Env` object on response
+hook. For each method invocation, user can can access
+:rb:class:`Nghttpx::Request` and :rb:class:`Nghttpx::Response` objects
+via :rb:attr:`Nghttpx::Env#req` and :rb:attr:`Nghttpx::Env#resp`
+respectively.
+
+.. rb:module:: Nghttpx
+
+.. rb:const:: REQUEST_PHASE
+
+ Constant to represent request phase.
+
+.. rb:const:: RESPONSE_PHASE
+
+ Constant to represent response phase.
+
+.. rb:class:: Env
+
+ Object to represent current request specific context.
+
+ .. rb:attr_reader:: req
+
+ Return :rb:class:`Request` object.
+
+ .. rb:attr_reader:: resp
+
+ Return :rb:class:`Response` object.
+
+ .. rb:attr_reader:: ctx
+
+ Return Ruby hash object. It persists until request finishes.
+ So values set in request phase hook can be retrieved in
+ response phase hook.
+
+ .. rb:attr_reader:: phase
+
+ Return the current phase.
+
+ .. rb:attr_reader:: remote_addr
+
+ Return IP address of a remote client. If connection is made
+ via UNIX domain socket, this returns the string "localhost".
+
+ .. rb:attr_reader:: server_addr
+
+ Return address of server that accepted the connection. This
+ is a string which specified in :option:`--frontend` option,
+ excluding port number, and not a resolved IP address. For
+ UNIX domain socket, this is a path to UNIX domain socket.
+
+ .. rb:attr_reader:: server_port
+
+ Return port number of the server frontend which accepted the
+ connection from client.
+
+ .. rb:attr_reader:: tls_used
+
+ Return true if TLS is used on the connection.
+
+ .. rb:attr_reader:: tls_sni
+
+ Return the TLS SNI value which client sent in this connection.
+
+ .. rb:attr_reader:: tls_client_fingerprint_sha256
+
+ Return the SHA-256 fingerprint of a client certificate.
+
+ .. rb:attr_reader:: tls_client_fingerprint_sha1
+
+ Return the SHA-1 fingerprint of a client certificate.
+
+ .. rb:attr_reader:: tls_client_issuer_name
+
+ Return the issuer name of a client certificate.
+
+ .. rb:attr_reader:: tls_client_subject_name
+
+ Return the subject name of a client certificate.
+
+ .. rb:attr_reader:: tls_client_serial
+
+ Return the serial number of a client certificate.
+
+ .. rb:attr_reader:: tls_client_not_before
+
+ Return the start date of a client certificate in seconds since
+ the epoch.
+
+ .. rb:attr_reader:: tls_client_not_after
+
+ Return the end date of a client certificate in seconds since
+ the epoch.
+
+ .. rb:attr_reader:: tls_cipher
+
+ Return a TLS cipher negotiated in this connection.
+
+ .. rb:attr_reader:: tls_protocol
+
+ Return a TLS protocol version negotiated in this connection.
+
+ .. rb:attr_reader:: tls_session_id
+
+ Return a session ID for this connection in hex string.
+
+ .. rb:attr_reader:: tls_session_reused
+
+ Return true if, and only if a SSL/TLS session is reused.
+
+ .. rb:attr_reader:: alpn
+
+ Return ALPN identifier negotiated in this connection.
+
+ .. rb:attr_reader:: tls_handshake_finished
+
+ Return true if SSL/TLS handshake has finished. If it returns
+ false in the request phase hook, the request is received in
+ TLSv1.3 early data (0-RTT) and might be vulnerable to the
+ replay attack. nghttpx will send Early-Data header field to
+ backend servers to indicate this.
+
+.. rb:class:: Request
+
+ Object to represent request from client. The modification to
+ Request object is allowed only in request phase hook.
+
+ .. rb:attr_reader:: http_version_major
+
+ Return HTTP major version.
+
+ .. rb:attr_reader:: http_version_minor
+
+ Return HTTP minor version.
+
+ .. rb:attr_accessor:: method
+
+ HTTP method. On assignment, copy of given value is assigned.
+ We don't accept arbitrary method name. We will document them
+ later, but well known methods, like GET, PUT and POST, are all
+ supported.
+
+ .. rb:attr_accessor:: authority
+
+ Authority (i.e., example.org), including optional port
+ component . On assignment, copy of given value is assigned.
+
+ .. rb:attr_accessor:: scheme
+
+ Scheme (i.e., http, https). On assignment, copy of given
+ value is assigned.
+
+ .. rb:attr_accessor:: path
+
+ Request path, including query component (i.e., /index.html).
+ On assignment, copy of given value is assigned. The path does
+ not include authority component of URI. This may include
+ query component. nghttpx makes certain normalization for
+ path. It decodes percent-encoding for unreserved characters
+ (see https://tools.ietf.org/html/rfc3986#section-2.3), and
+ resolves ".." and ".". But it may leave characters which
+ should be percent-encoded as is. So be careful when comparing
+ path against desired string.
+
+ .. rb:attr_reader:: headers
+
+ Return Ruby hash containing copy of request header fields.
+ Changing values in returned hash does not change request
+ header fields actually used in request processing. Use
+ :rb:meth:`Nghttpx::Request#add_header` or
+ :rb:meth:`Nghttpx::Request#set_header` to change request
+ header fields.
+
+ .. rb:method:: add_header(key, value)
+
+ Add header entry associated with key. The value can be single
+ string or array of string. It does not replace any existing
+ values associated with key.
+
+ .. rb:method:: set_header(key, value)
+
+ Set header entry associated with key. The value can be single
+ string or array of string. It replaces any existing values
+ associated with key.
+
+ .. rb:method:: clear_headers
+
+ Clear all existing request header fields.
+
+ .. rb:method:: push(uri)
+
+ Initiate to push resource identified by *uri*. Only HTTP/2
+ protocol supports this feature. For the other protocols, this
+ method is noop. *uri* can be absolute URI, absolute path or
+ relative path to the current request. For absolute or
+ relative path, scheme and authority are inherited from the
+ current request. Currently, method is always GET. nghttpx
+ will issue request to backend servers to fulfill this request.
+ The request and response phase hooks will be called for pushed
+ resource as well.
+
+.. rb:class:: Response
+
+ Object to represent response from backend server.
+
+ .. rb:attr_reader:: http_version_major
+
+ Return HTTP major version.
+
+ .. rb:attr_reader:: http_version_minor
+
+ Return HTTP minor version.
+
+ .. rb:attr_accessor:: status
+
+ HTTP status code. It must be in the range [200, 999],
+ inclusive. The non-final status code is not supported in
+ mruby scripting at the moment.
+
+ .. rb:attr_reader:: headers
+
+ Return Ruby hash containing copy of response header fields.
+ Changing values in returned hash does not change response
+ header fields actually used in response processing. Use
+ :rb:meth:`Nghttpx::Response#add_header` or
+ :rb:meth:`Nghttpx::Response#set_header` to change response
+ header fields.
+
+ .. rb:method:: add_header(key, value)
+
+ Add header entry associated with key. The value can be single
+ string or array of string. It does not replace any existing
+ values associated with key.
+
+ .. rb:method:: set_header(key, value)
+
+ Set header entry associated with key. The value can be single
+ string or array of string. It replaces any existing values
+ associated with key.
+
+ .. rb:method:: clear_headers
+
+ Clear all existing response header fields.
+
+ .. rb:method:: return(body)
+
+ Return custom response *body* to a client. When this method
+ is called in request phase hook, the request is not forwarded
+ to the backend, and response phase hook for this request will
+ not be invoked. When this method is called in response phase
+ hook, response from backend server is canceled and discarded.
+ The status code and response header fields should be set
+ before using this method. To set status code, use
+ :rb:attr:`Nghttpx::Response#status`. If status code is not
+ set, 200 is used. To set response header fields,
+ :rb:meth:`Nghttpx::Response#add_header` and
+ :rb:meth:`Nghttpx::Response#set_header`. When this method is
+ invoked in response phase hook, the response headers are
+ filled with the ones received from backend server. To send
+ completely custom header fields, first call
+ :rb:meth:`Nghttpx::Response#clear_headers` to erase all
+ existing header fields, and then add required header fields.
+ It is an error to call this method twice for a given request.
+
+ .. rb:method:: send_info(status, headers)
+
+ Send non-final (informational) response to a client. *status*
+ must be in the range [100, 199], inclusive. *headers* is a
+ hash containing response header fields. Its key must be a
+ string, and the associated value must be either string or
+ array of strings. Since this is not a final response, even if
+ this method is invoked, request is still forwarded to a
+ backend unless :rb:meth:`Nghttpx::Response#return` is called.
+ This method can be called multiple times. It cannot be called
+ after :rb:meth:`Nghttpx::Response#return` is called.
+
+MRUBY EXAMPLES
+~~~~~~~~~~~~~~
+
+Modify request path:
+
+.. code-block:: ruby
+
+ class App
+ def on_req(env)
+ env.req.path = "/apps#{env.req.path}"
+ end
+ end
+
+ App.new
+
+Don't forget to instantiate and evaluate object at the last line.
+
+Restrict permission of viewing a content to a specific client
+addresses:
+
+.. code-block:: ruby
+
+ class App
+ def on_req(env)
+ allowed_clients = ["127.0.0.1", "::1"]
+
+ if env.req.path.start_with?("/log/") &&
+ !allowed_clients.include?(env.remote_addr) then
+ env.resp.status = 404
+ env.resp.return "permission denied"
+ end
+ end
+ end
+
+ App.new
+
+API ENDPOINTS
+-------------
+
+nghttpx exposes API endpoints to manipulate it via HTTP based API. By
+default, API endpoint is disabled. To enable it, add a dedicated
+frontend for API using :option:`--frontend` option with "api"
+parameter. All requests which come from this frontend address, will
+be treated as API request.
+
+The response is normally JSON dictionary, and at least includes the
+following keys:
+
+status
+ The status of the request processing. The following values are
+ defined:
+
+ Success
+ The request was successful.
+
+ Failure
+ The request was failed. No change has been made.
+
+code
+ HTTP status code
+
+Additionally, depending on the API endpoint, ``data`` key may be
+present, and its value contains the API endpoint specific data.
+
+We wrote "normally", since nghttpx may return ordinal HTML response in
+some cases where the error has occurred before reaching API endpoint
+(e.g., header field is too large).
+
+The following section describes available API endpoints.
+
+POST /api/v1beta1/backendconfig
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This API replaces the current backend server settings with the
+requested ones. The request method should be POST, but PUT is also
+acceptable. The request body must be nghttpx configuration file
+format. For configuration file format, see `FILES`_ section. The
+line separator inside the request body must be single LF (0x0A).
+Currently, only :option:`backend <--backend>` option is parsed, the
+others are simply ignored. The semantics of this API is replace the
+current backend with the backend options in request body. Describe
+the desired set of backend severs, and nghttpx makes it happen. If
+there is no :option:`backend <--backend>` option is found in request
+body, the current set of backend is replaced with the :option:`backend
+<--backend>` option's default value, which is ``127.0.0.1,80``.
+
+The replacement is done instantly without breaking existing
+connections or requests. It also avoids any process creation as is
+the case with hot swapping with signals.
+
+The one limitation is that only numeric IP address is allowed in
+:option:`backend <--backend>` in request body unless "dns" parameter
+is used while non numeric hostname is allowed in command-line or
+configuration file is read using :option:`--conf`.
+
+GET /api/v1beta1/configrevision
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This API returns configuration revision of the current nghttpx. The
+configuration revision is opaque string, and it changes after each
+reloading by SIGHUP. With this API, an external application knows
+that whether nghttpx has finished reloading its configuration by
+comparing the configuration revisions between before and after
+reloading. It is recommended to disable persistent (keep-alive)
+connection for this purpose in order to avoid to send a request using
+the reused connection which may bound to an old process.
+
+This API returns response including ``data`` key. Its value is JSON
+object, and it contains at least the following key:
+
+configRevision
+ The configuration revision of the current nghttpx
+
+
+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
index 0000000..dfa6b2d
--- /dev/null
+++ b/doc/package_README.rst.in
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/README.rst
diff --git a/doc/programmers-guide.rst b/doc/programmers-guide.rst
new file mode 100644
index 0000000..820cd20
--- /dev/null
+++ b/doc/programmers-guide.rst
@@ -0,0 +1,526 @@
+Programmers' Guide
+==================
+
+Architecture
+------------
+
+The most notable point in nghttp2 library architecture is it does not
+perform any I/O. nghttp2 only performs HTTP/2 protocol stuff based on
+input byte strings. It will call callback functions set by
+applications while processing input. The output of nghttp2 is just
+byte string. An application is responsible to send these output to
+the remote peer. The callback functions may be called while producing
+output.
+
+Not doing I/O makes embedding nghttp2 library in the existing code
+base very easy. Usually, the existing applications have its own I/O
+event loops. It is very hard to use nghttp2 in that situation if
+nghttp2 does its own I/O. It also makes light weight language wrapper
+for nghttp2 easy with the same reason. The down side is that an
+application author has to write more code to write complete
+application using nghttp2. This is especially true for simple "toy"
+application. For the real applications, however, this is not the
+case. This is because you probably want to support HTTP/1 which
+nghttp2 does not provide, and to do that, you will need to write your
+own HTTP/1 stack or use existing third-party library, and bind them
+together with nghttp2 and I/O event loop. In this point, not
+performing I/O in nghttp2 has more point than doing it.
+
+The primary object that an application uses is :type:`nghttp2_session`
+object, which is opaque struct and its details are hidden in order to
+ensure the upgrading its internal architecture without breaking the
+backward compatibility. An application can set callbacks to
+:type:`nghttp2_session` object through the dedicated object and
+functions, and it also interacts with it via many API function calls.
+
+An application can create as many :type:`nghttp2_session` object as it
+wants. But single :type:`nghttp2_session` object must be used by a
+single thread at the same time. This is not so hard to enforce since
+most event-based architecture applications use is single thread per
+core, and handling one connection I/O is done by single thread.
+
+To feed input to :type:`nghttp2_session` object, one can use
+`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` functions.
+They behave similarly, and the difference is that
+`nghttp2_session_recv()` will use :type:`nghttp2_read_callback` to get
+input. On the other hand, `nghttp2_session_mem_recv()` will take
+input as its parameter. If in doubt, use `nghttp2_session_mem_recv()`
+since it is simpler, and could be faster since it avoids calling
+callback function.
+
+To get output from :type:`nghttp2_session` object, one can use
+`nghttp2_session_send()` or `nghttp2_session_mem_send()`. The
+difference between them is that the former uses
+:type:`nghttp2_send_callback` to pass output to an application. On
+the other hand, the latter returns the output to the caller. If in
+doubt, use `nghttp2_session_mem_send()` since it is simpler. But
+`nghttp2_session_send()` might be easier to use if the output buffer
+an application has is fixed sized.
+
+In general, an application should call `nghttp2_session_mem_send()`
+when it gets input from underlying connection. Since there is great
+chance to get something pushed into transmission queue while the call
+of `nghttp2_session_mem_send()`, it is recommended to call
+`nghttp2_session_mem_recv()` after `nghttp2_session_mem_send()`.
+
+There is a question when we are safe to close HTTP/2 session without
+waiting for the closure of underlying connection. We offer 2 API
+calls for this: `nghttp2_session_want_read()` and
+`nghttp2_session_want_write()`. If they both return 0, application
+can destroy :type:`nghttp2_session`, and then close the underlying
+connection. But make sure that the buffered output has been
+transmitted to the peer before closing the connection when
+`nghttp2_session_mem_send()` is used, since
+`nghttp2_session_want_write()` does not take into account the
+transmission of the buffered data outside of :type:`nghttp2_session`.
+
+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.
+
+`nghttp2_session_send()` and `nghttp2_session_mem_send()` send first
+24 bytes of client magic string (MAGIC)
+(:macro:`NGHTTP2_CLIENT_MAGIC`) on client configuration. The
+applications are responsible to send SETTINGS frame as part of
+connection preface using `nghttp2_submit_settings()`. Similarly,
+`nghttp2_session_recv()` and `nghttp2_session_mem_recv()` consume
+MAGIC on server configuration unless
+`nghttp2_option_set_no_recv_client_magic()` is used with nonzero
+option value.
+
+.. _http-messaging:
+
+HTTP Messaging
+--------------
+
+By default, nghttp2 library checks HTTP messaging rules described in
+`HTTP/2 specification, section 8
+<https://tools.ietf.org/html/rfc7540#section-8>`_. Everything
+described in that section is not validated however. We briefly
+describe what the library does in this area. In the following
+description, without loss of generality we omit CONTINUATION frame
+since they must follow HEADERS frame and are processed atomically. In
+other words, they are just one big HEADERS frame. To disable these
+validations, use `nghttp2_option_set_no_http_messaging()`. Please
+note that disabling this feature does not change the fundamental
+client and server model of HTTP. That is, even if the validation is
+disabled, only client can send requests.
+
+For HTTP request, including those carried by PUSH_PROMISE, HTTP
+message starts with one HEADERS frame containing request headers. It
+is followed by zero or more DATA frames containing request body, which
+is followed by zero or one HEADERS containing trailer headers. The
+request headers must include ":scheme", ":method" and ":path" pseudo
+header fields unless ":method" is not "CONNECT". ":authority" is
+optional, but nghttp2 requires either ":authority" or "Host" header
+field must be present. If ":method" is "CONNECT", the request headers
+must include ":method" and ":authority" and must omit ":scheme" and
+":path".
+
+For HTTP response, HTTP message starts with zero or more HEADERS
+frames containing non-final response (status code 1xx). They are
+followed by one HEADERS frame containing final response headers
+(non-1xx). It is followed by zero or more DATA frames containing
+response body, which is followed by zero or one HEADERS containing
+trailer headers. The non-final and final response headers must
+contain ":status" pseudo header field containing 3 digits only.
+
+All request and response headers must include exactly one valid value
+for each pseudo header field. Additionally nghttp2 requires all
+request headers must not include more than one "Host" header field.
+
+HTTP/2 prohibits connection-specific header fields. The following
+header fields must not appear: "Connection", "Keep-Alive",
+"Proxy-Connection", "Transfer-Encoding" and "Upgrade". Additionally,
+"TE" header field must not include any value other than "trailers".
+
+Each header field name and value must obey the field-name and
+field-value production rules described in `RFC 7230, section
+3.2. <https://tools.ietf.org/html/rfc7230#section-3.2>`_.
+Additionally, all field name must be lower cased. The invalid header
+fields are treated as stream error, and that stream is reset. If
+application wants to treat these headers in their own way, use
+`nghttp2_on_invalid_header_callback
+<https://nghttp2.org/documentation/types.html#c.nghttp2_on_invalid_header_callback>`_.
+
+For "http" or "https" URIs, ":path" pseudo header fields must start
+with "/". The only exception is OPTIONS request, in that case, "*" is
+allowed in ":path" pseudo header field to represent system-wide
+OPTIONS request.
+
+With the above validations, nghttp2 library guarantees that header
+field name passed to `nghttp2_on_header_callback()` is not empty.
+Also required pseudo headers are all present and not empty.
+
+nghttp2 enforces "Content-Length" validation as well. All request or
+response headers must not contain more than one "Content-Length"
+header field. If "Content-Length" header field is present, it must be
+parsed as 64 bit signed integer. The sum of data length in the
+following DATA frames must match with the number in "Content-Length"
+header field if it is present (this does not include padding bytes).
+
+RFC 7230 says that server must not send "Content-Length" in any
+response with 1xx, and 204 status code. It also says that
+"Content-Length" is not allowed in any response with 200 status code
+to a CONNECT request. nghttp2 enforces them as well.
+
+Any deviation results in stream error of type PROTOCOL_ERROR. If
+error is found in PUSH_PROMISE frame, stream error is raised against
+promised stream.
+
+The order of transmission of the HTTP/2 frames
+----------------------------------------------
+
+This section describes the internals of libnghttp2 about the
+scheduling of transmission of HTTP/2 frames. This is pretty much
+internal stuff, so the details could change in the future versions of
+the library.
+
+libnghttp2 categorizes HTTP/2 frames into 4 categories: urgent,
+regular, syn_stream, and data in the order of higher priority.
+
+The urgent category includes PING and SETTINGS. They are sent with
+highest priority. The order inside the category is FIFO.
+
+The regular category includes frames other than PING, SETTINGS, DATA,
+and HEADERS which does not create stream (which counts toward
+concurrent stream limit). The order inside the category is FIFO.
+
+The syn_stream category includes HEADERS frame which creates stream,
+that counts toward the concurrent stream limit.
+
+The data category includes DATA frame, and the scheduling among DATA
+frames are determined by HTTP/2 dependency tree.
+
+If the application wants to send frames in the specific order, and the
+default transmission order does not fit, it has to schedule frames by
+itself using the callbacks (e.g.,
+:type:`nghttp2_on_frame_send_callback`).
+
+RST_STREAM has special side effect when it is submitted by
+`nghttp2_submit_rst_stream()`. It cancels all pending HEADERS and
+DATA frames whose stream ID matches the one in the RST_STREAM frame.
+This may cause unexpected behaviour for the application in some cases.
+For example, suppose that application wants to send RST_STREAM after
+sending response HEADERS and DATA. Because of the reason we mentioned
+above, the following code does not work:
+
+.. code-block:: c
+
+ nghttp2_submit_response(...)
+ nghttp2_submit_rst_stream(...)
+
+RST_STREAM cancels HEADERS (and DATA), and just RST_STREAM is sent.
+The correct way is use :type:`nghttp2_on_frame_send_callback`, and
+after HEADERS and DATA frames are sent, issue
+`nghttp2_submit_rst_stream()`. FYI,
+:type:`nghttp2_on_frame_not_send_callback` tells you why frames are
+not sent.
+
+Implement user defined HTTP/2 non-critical extensions
+-----------------------------------------------------
+
+As of nghttp2 v1.8.0, we have added HTTP/2 non-critical extension
+framework, which lets application send and receive user defined custom
+HTTP/2 non-critical extension frames. nghttp2 also offers built-in
+functionality to send and receive official HTTP/2 extension frames
+(e.g., ALTSVC frame). For these built-in handler, refer to the next
+section.
+
+To send extension frame, use `nghttp2_submit_extension()`, and
+implement :type:`nghttp2_pack_extension_callback`. The callback
+implements how to encode data into wire format. The callback must be
+set to :type:`nghttp2_session_callbacks` using
+`nghttp2_session_callbacks_set_pack_extension_callback()`.
+
+For example, we will illustrate how to send `ALTSVC
+<https://tools.ietf.org/html/rfc7838>`_ frame.
+
+.. code-block:: c
+
+ typedef struct {
+ const char *origin;
+ const char *field;
+ } alt_svc;
+
+ ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf,
+ size_t len, const nghttp2_frame *frame,
+ void *user_data) {
+ const alt_svc *altsvc = (const alt_svc *)frame->ext.payload;
+ size_t originlen = strlen(altsvc->origin);
+ size_t fieldlen = strlen(altsvc->field);
+
+ uint8_t *p;
+
+ if (len < 2 + originlen + fieldlen || originlen > 0xffff) {
+ return NGHTTP2_ERR_CANCEL;
+ }
+
+ p = buf;
+ *p++ = originlen >> 8;
+ *p++ = originlen & 0xff;
+ memcpy(p, altsvc->origin, originlen);
+ p += originlen;
+ memcpy(p, altsvc->field, fieldlen);
+ p += fieldlen;
+
+ return p - buf;
+ }
+
+This implements :type:`nghttp2_pack_extension_callback`. We have to
+set this callback to :type:`nghttp2_session_callbacks`:
+
+.. code-block:: c
+
+ nghttp2_session_callbacks_set_pack_extension_callback(
+ callbacks, pack_extension_callback);
+
+To send ALTSVC frame, call `nghttp2_submit_extension()`:
+
+.. code-block:: c
+
+ static const alt_svc altsvc = {"example.com", "h2=\":8000\""};
+
+ nghttp2_submit_extension(session, 0xa, NGHTTP2_FLAG_NONE, 0,
+ (void *)&altsvc);
+
+Notice that ALTSVC is use frame type ``0xa``.
+
+To receive extension frames, implement 2 callbacks:
+:type:`nghttp2_unpack_extension_callback` and
+:type:`nghttp2_on_extension_chunk_recv_callback`.
+:type:`nghttp2_unpack_extension_callback` implements the way how to
+decode wire format. :type:`nghttp2_on_extension_chunk_recv_callback`
+implements how to buffer the incoming extension payload. These
+callbacks must be set using
+`nghttp2_session_callbacks_set_unpack_extension_callback()` and
+`nghttp2_session_callbacks_set_on_extension_chunk_recv_callback()`
+respectively. The application also must tell the library which
+extension frame type it is willing to receive using
+`nghttp2_option_set_user_recv_extension_type()`. Note that the
+application has to create :type:`nghttp2_option` object for that
+purpose, and initialize session with it.
+
+We use ALTSVC again to illustrate how to receive extension frames. We
+use different ``alt_svc`` struct than the previous one.
+
+First implement 2 callbacks. We store incoming ALTSVC payload to
+global variable ``altsvc_buffer``. Don't do this in production code
+since this is not thread safe:
+
+.. code-block:: c
+
+ typedef struct {
+ const uint8_t *origin;
+ size_t originlen;
+ const uint8_t *field;
+ size_t fieldlen;
+ } alt_svc;
+
+ /* buffers incoming ALTSVC payload */
+ uint8_t altsvc_buffer[4096];
+ /* The length of byte written to altsvc_buffer */
+ size_t altsvc_bufferlen = 0;
+
+ int on_extension_chunk_recv_callback(nghttp2_session *session,
+ const nghttp2_frame_hd *hd,
+ const uint8_t *data, size_t len,
+ void *user_data) {
+ if (sizeof(altsvc_buffer) < altsvc_bufferlen + len) {
+ altsvc_bufferlen = 0;
+ return NGHTTP2_ERR_CANCEL;
+ }
+
+ memcpy(altsvc_buffer + altsvc_bufferlen, data, len);
+ altsvc_bufferlen += len;
+
+ return 0;
+ }
+
+ int unpack_extension_callback(nghttp2_session *session, void **payload,
+ const nghttp2_frame_hd *hd, void *user_data) {
+ uint8_t *origin, *field;
+ size_t originlen, fieldlen;
+ uint8_t *p, *end;
+ alt_svc *altsvc;
+
+ if (altsvc_bufferlen < 2) {
+ altsvc_bufferlen = 0;
+ return NGHTTP2_ERR_CANCEL;
+ }
+
+ p = altsvc_buffer;
+ end = altsvc_buffer + altsvc_bufferlen;
+
+ originlen = ((*p) << 8) + *(p + 1);
+ p += 2;
+
+ if (p + originlen > end) {
+ altsvc_bufferlen = 0;
+ return NGHTTP2_ERR_CANCEL;
+ }
+
+ origin = p;
+ field = p + originlen;
+ fieldlen = end - field;
+
+ altsvc = (alt_svc *)malloc(sizeof(alt_svc));
+ altsvc->origin = origin;
+ altsvc->originlen = originlen;
+ altsvc->field = field;
+ altsvc->fieldlen = fieldlen;
+
+ *payload = altsvc;
+
+ altsvc_bufferlen = 0;
+
+ return 0;
+ }
+
+Set these callbacks to :type:`nghttp2_session_callbacks`:
+
+.. code-block:: c
+
+ nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
+ callbacks, on_extension_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_unpack_extension_callback(
+ callbacks, unpack_extension_callback);
+
+
+In ``unpack_extension_callback`` above, we set unpacked ``alt_svc``
+object to ``*payload``. nghttp2 library then, calls
+:type:`nghttp2_on_frame_recv_callback`, and ``*payload`` will be
+available as ``frame->ext.payload``:
+
+.. code-block:: c
+
+ int on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+
+ switch (frame->hd.type) {
+ ...
+ case 0xa: {
+ alt_svc *altsvc = (alt_svc *)frame->ext.payload;
+ fprintf(stderr, "ALTSVC frame received\n");
+ fprintf(stderr, " origin: %.*s\n", (int)altsvc->originlen, altsvc->origin);
+ fprintf(stderr, " field : %.*s\n", (int)altsvc->fieldlen, altsvc->field);
+ free(altsvc);
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+Finally, application should set the extension frame types it is
+willing to receive:
+
+.. code-block:: c
+
+ nghttp2_option_set_user_recv_extension_type(option, 0xa);
+
+The :type:`nghttp2_option` must be set to :type:`nghttp2_session` on
+its creation:
+
+.. code-block:: c
+
+ nghttp2_session_client_new2(&session, callbacks, user_data, option);
+
+How to use built-in HTTP/2 extension frame handlers
+---------------------------------------------------
+
+In the previous section, we talked about the user defined HTTP/2
+extension frames. In this section, we talk about HTTP/2 extension
+frame support built into nghttp2 library.
+
+As of this writing, nghttp2 supports ALTSVC extension frame. To send
+ALTSVC frame, use `nghttp2_submit_altsvc()` function.
+
+To receive ALTSVC frame through built-in functionality, application
+has to use `nghttp2_option_set_builtin_recv_extension_type()` to
+indicate the willingness of receiving ALTSVC frame:
+
+.. code-block:: c
+
+ nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
+
+This is very similar to the case when we used to receive user defined
+frames.
+
+If the same frame type is set using
+`nghttp2_option_set_builtin_recv_extension_type()` and
+`nghttp2_option_set_user_recv_extension_type()`, the latter takes
+precedence. Application can implement its own frame handler rather
+than using built-in handler.
+
+The :type:`nghttp2_option` must be set to :type:`nghttp2_session` on
+its creation, like so:
+
+.. code-block:: c
+
+ nghttp2_session_client_new2(&session, callbacks, user_data, option);
+
+When ALTSVC is received, :type:`nghttp2_on_frame_recv_callback` will
+be called as usual.
+
+Stream priorities
+-----------------
+
+By default, the stream prioritization scheme described in :rfc:`7540`
+is used. This scheme has been formally deprecated by :rfc:`9113`. In
+order to disable it, send
+:enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` of
+value of 1 via `nghttp2_submit_settings()`. This settings ID is
+defined by :rfc:`9218`. The sender of this settings value disables
+RFC 7540 priorities, and instead it enables RFC 9218 Extensible
+Prioritization Scheme. This new prioritization scheme has 2 methods
+to convey the stream priorities to a remote endpoint: Priority header
+field and PRIORITY_UPDATE frame. nghttp2 supports both methods. In
+order to receive and process PRIORITY_UPDATE frame, server has to call
+``nghttp2_option_set_builtin_recv_extension_type(option,
+NGHTTP2_PRIORITY_UPDATE)`` (see the above section), and pass the
+option to `nghttp2_session_server_new2()` or
+`nghttp2_session_server_new3()` to create a server session. Client
+can send Priority header field via `nghttp2_submit_request()`. It can
+also send PRIORITY_UPDATE frame via
+`nghttp2_submit_priority_update()`. Server processes Priority header
+field in a request header field and updates the stream priority unless
+HTTP messaging rule enforcement is disabled (see
+`nghttp2_option_set_no_http_messaging()`).
+
+For the purpose of smooth migration from RFC 7540 priorities, client
+is advised to send
+:enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` of
+value of 1. Until it receives the first server SETTINGS frame, it can
+send both RFC 7540 and RFC 9128 priority signals. If client receives
+SETTINGS_NO_RFC7540_PRIORITIES of value of 0, or it is omitted ,
+client stops sending PRIORITY_UPDATE frame. Priority header field
+will be sent in anyway since it is an end-to-end signal. If
+SETTINGS_NO_RFC7540_PRIORITIES of value of 1 is received, client stops
+sending RFC 7540 priority signals. This is the advice described in
+:rfc:`9218#section-2.1.1`.
+
+Server has an optional mechanism to fallback to RFC 7540 priorities.
+By default, if server sends SETTINGS_NO_RFC7540_PRIORITIES of value of
+1, it completely disables RFC 7540 priorities and no fallback. By
+setting nonzero value to
+`nghttp2_option_set_server_fallback_rfc7540_priorities()`, server
+falls back to RFC 7540 priorities if it sends
+SETTINGS_NO_RFC7540_PRIORITIES value of value of 1, and client omits
+SETTINGS_NO_RFC7540_PRIORITIES in its SETTINGS frame.
diff --git a/doc/security.rst b/doc/security.rst
new file mode 100644
index 0000000..00b0c9c
--- /dev/null
+++ b/doc/security.rst
@@ -0,0 +1 @@
+.. include:: ../doc/sources/security.rst
diff --git a/doc/sources/building-android-binary.rst b/doc/sources/building-android-binary.rst
new file mode 100644
index 0000000..339ac44
--- /dev/null
+++ b/doc/sources/building-android-binary.rst
@@ -0,0 +1,127 @@
+Building Android binary
+=======================
+
+In this article, we briefly describe how to build Android binary using
+`Android NDK <https://developer.android.com/ndk>`_ 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`` script to make the build easier. To make
+the script work, NDK directory must be set to ``NDK`` environment
+variable. NDK directory is the directory where NDK is unpacked:
+
+.. code-block:: text
+
+ $ unzip android-ndk-$NDK_VERSION-linux.zip
+ $ cd android-ndk-$NDK_VERSION
+ $ export NDK=$PWD
+
+The dependent libraries, such as OpenSSL, libev, and c-ares should be
+built with the same NDK toolchain and installed under
+``$NDK/usr/local``. We recommend to build these libraries as static
+library to make the deployment easier. libxml2 support is currently
+disabled.
+
+Although zlib comes with Android NDK, it seems not to be a part of
+public API, so we have to built it for our own. That also provides us
+proper .pc file as a bonus.
+
+Before running ``android-config``, ``NDK`` environment variable must
+be set to point to the correct path.
+
+You need to set ``NGHTTP2`` environment variable to the absolute path
+to the source directory of nghttp2.
+
+To configure OpenSSL, use the following script:
+
+.. code-block:: sh
+
+ #!/bin/sh
+
+ . $NGHTTP2/android-env
+
+ export ANDROID_NDK_HOME=$NDK
+ export PATH=$TOOLCHAIN/bin:$PATH
+
+ ./Configure no-shared --prefix=$PREFIX android-arm64
+
+And run the following script to build and install without
+documentation:
+
+.. code-block:: sh
+
+ #!/bin/sh
+
+ . $NGHTTP2/android-env
+
+ export PATH=$TOOLCHAIN/bin:$PATH
+
+ make install_sw
+
+To configure libev, use the following script:
+
+.. code-block:: sh
+
+ #!/bin/sh
+
+ . $NGHTTP2/android-env
+
+ ./configure \
+ --host=$TARGET \
+ --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 c-ares, use the following script:
+
+.. code-block:: sh
+
+ #!/bin/sh -e
+
+ . $NGHTTP2/android-env
+
+ ./configure \
+ --host=$TARGET \
+ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+ --prefix=$PREFIX \
+ --disable-shared
+
+And run ``make install`` to build and install.
+
+To configure zlib, use the following script:
+
+.. code-block:: sh
+
+ #!/bin/sh -e
+
+ . $NGHTTP2/android-env
+
+ export HOST=$TARGET
+
+ ./configure \
+ --prefix=$PREFIX \
+ --libdir=$PREFIX/lib \
+ --includedir=$PREFIX/include \
+ --static
+
+And run ``make install`` to build and install.
+
+After prerequisite libraries are prepared, run ``android-config`` and
+then ``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:
+
+.. code-block:: text
+
+ $ $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip src/nghttpx
diff --git a/doc/sources/contribute.rst b/doc/sources/contribute.rst
new file mode 100644
index 0000000..1e39f2b
--- /dev/null
+++ b/doc/sources/contribute.rst
@@ -0,0 +1,56 @@
+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-15.
+
+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-14 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://github.com/llvm/llvm-project/blob/main/clang/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
index 0000000..879a0eb
--- /dev/null
+++ b/doc/sources/h2load-howto.rst
@@ -0,0 +1,142 @@
+.. program:: h2load
+
+h2load - HTTP/2 benchmarking tool - HOW-TO
+==========================================
+
+:doc:`h2load.1` is benchmarking tool for HTTP/2 and HTTP/1.1. It
+supports SSL/TLS and clear text for all supported protocols.
+
+Compiling from source
+---------------------
+
+h2load is compiled alongside nghttp2 and requires that the
+``--enable-app`` flag is passed to ``./configure`` and `required
+dependencies <https://github.com/nghttp2/nghttp2#requirements>`_ are
+available during compilation. For details on compiling, see `nghttp2:
+Building from Git
+<https://github.com/nghttp2/nghttp2#building-from-git>`_.
+
+Basic Usage
+-----------
+
+In order to set benchmark settings, specify following 3 options.
+
+:option:`-n`
+ The number of total requests. Default: 1
+
+:option:`-c`
+ The number of concurrent clients. Default: 1
+
+:option:`-m`
+ The max concurrent streams to issue per client. Default: 1
+
+For SSL/TLS connection, the protocol will be negotiated via ALPN. You
+can set specific protocols in :option:`--alpn-list` option. For
+cleartext connection, the default protocol is HTTP/2. To change the
+protocol in cleartext connection, use :option:`--no-tls-proto` option.
+For convenience, :option:`--h1` option forces HTTP/1.1 for both
+cleartext and SSL/TLS connections.
+
+Here is a command-line to perform benchmark to URI \https://localhost
+using total 100000 requests, 100 concurrent clients and 10 max
+concurrent streams:
+
+.. code-block:: text
+
+ $ h2load -n100000 -c100 -m10 https://localhost
+
+The benchmarking result looks like this:
+
+.. code-block:: text
+
+ finished in 7.08s, 141164.80 req/s, 555.33MB/s
+ requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
+ status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
+ traffic: 4125025824 bytes total, 11023424 bytes headers (space savings 93.07%), 4096000000 bytes data
+ min max mean sd +/- sd
+ time for request: 15.31ms 146.85ms 69.78ms 9.26ms 92.43%
+ time for connect: 1.08ms 25.04ms 10.71ms 9.80ms 64.00%
+ time to 1st byte: 25.36ms 184.96ms 79.11ms 53.97ms 78.00%
+ req/s (client) : 1412.04 1447.84 1426.52 10.57 63.00%
+
+See the h2load manual page :ref:`h2load-1-output` section for the
+explanation of the above numbers.
+
+Timing-based load-testing
+-------------------------
+
+As of v1.26.0, h2load supports timing-based load-testing. This method
+performs load-testing in terms of a given duration instead of a
+pre-defined number of requests. The new option :option:`--duration`
+specifies how long the load-testing takes. For example,
+``--duration=10`` makes h2load perform load-testing against a server
+for 10 seconds. You can also specify a “warming-up” period with
+:option:`--warm-up-time`. If :option:`--duration` is used,
+:option:`-n` option is ignored.
+
+The following command performs load-testing for 10 seconds after 5
+seconds warming up period:
+
+.. code-block:: text
+
+ $ h2load -c100 -m100 --duration=10 --warm-up-time=5 https://localhost
+
+Flow Control
+------------
+
+HTTP/2 has flow control and it may affect benchmarking results. By
+default, h2load uses large enough flow control window, which
+effectively disables flow control. To adjust receiver flow control
+window size, there are following options:
+
+:option:`-w`
+ Sets the stream level initial window size to
+ (2**<N>)-1.
+
+:option:`-W`
+ Sets the connection level initial window size to
+ (2**<N>)-1.
+
+Multi-Threading
+---------------
+
+Sometimes benchmarking client itself becomes a bottleneck. To remedy
+this situation, use :option:`-t` option to specify the number of native
+thread to use.
+
+:option:`-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 :option:`-p` option.
+
+Multiple URIs
+-------------
+
+If multiple URIs are specified, they are used in round robin manner.
+
+.. note::
+
+ Please note that h2load uses scheme, host and port in the first URI
+ and ignores those parts in the rest of the URIs.
+
+UNIX domain socket
+------------------
+
+To request against UNIX domain socket, use :option:`--base-uri`, and
+specify ``unix:`` followed by the path to UNIX domain socket. For
+example, if UNIX domain socket is ``/tmp/nghttpx.sock``, use
+``--base-uri=unix:/tmp/nghttpx.sock``. h2load uses scheme, host and
+port in the first URI in command-line or input file.
+
+HTTP/3
+------
+
+h2load supports HTTP/3 if it is built with HTTP/3 enabled. HTTP/3
+support is experimental.
+
+In order to send HTTP/3 request, specify ``h3`` to
+:option:`--alpn-list`.
diff --git a/doc/sources/index.rst b/doc/sources/index.rst
new file mode 100644
index 0000000..b03c348
--- /dev/null
+++ b/doc/sources/index.rst
@@ -0,0 +1,50 @@
+.. nghttp2 documentation main 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 implementation of Hypertext Transfer Protocol version 2.
+
+The project is hosted at `github.com/nghttp2/nghttp2
+<https://github.com/nghttp2/nghttp2>`_.
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ package_README
+ contribute
+ security
+ building-android-binary
+ tutorial-client
+ tutorial-server
+ tutorial-hpack
+ nghttp.1
+ nghttpd.1
+ nghttpx.1
+ h2load.1
+ nghttpx-howto
+ h2load-howto
+ programmers-guide
+ apiref
+ nghttp2.h
+ nghttp2ver.h
+ Source <https://github.com/nghttp2/nghttp2>
+ Issues <https://github.com/nghttp2/nghttp2/issues>
+ nghttp2.org <https://nghttp2.org/>
+
+Released Versions
+=================
+
+https://github.com/nghttp2/nghttp2/releases
+
+Resources
+---------
+
+* HTTP/2 https://tools.ietf.org/html/rfc7540
+* HPACK https://tools.ietf.org/html/rfc7541
+* HTTP Alternative Services https://tools.ietf.org/html/rfc7838
diff --git a/doc/sources/nghttpx-howto.rst b/doc/sources/nghttpx-howto.rst
new file mode 100644
index 0000000..50412f7
--- /dev/null
+++ b/doc/sources/nghttpx-howto.rst
@@ -0,0 +1,642 @@
+.. program:: nghttpx
+
+nghttpx - HTTP/2 proxy - HOW-TO
+===============================
+
+:doc:`nghttpx.1` is a proxy translating protocols between HTTP/2 and
+other protocols (e.g., HTTP/1). 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 :option:`--http2-proxy`, it operates in
+default mode. In this mode, it works as reverse proxy (gateway) for
+HTTP/3, HTTP/2 and HTTP/1 clients to backend servers. This is also
+known as "HTTP/2 router".
+
+By default, 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 frontend
+protocol selection will be done via ALPN.
+
+To turn off encryption on frontend connection, use ``no-tls`` keyword
+in :option:`--frontend` option. HTTP/2 and HTTP/1 are available on
+the frontend, and an 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.
+
+In order to receive HTTP/3 traffic, use ``quic`` parameter in
+:option:`--frontend` option (.e.g, ``--frontend='*,443;quic'``)
+
+nghttpx can listen on multiple frontend addresses. This is achieved
+by using multiple :option:`--frontend` options. For each frontend
+address, TLS can be enabled or disabled.
+
+By default, backend connections are not encrypted. To enable TLS
+encryption on backend connections, use ``tls`` keyword in
+:option:`--backend` option. Using patterns and ``proto`` keyword in
+:option:`--backend` option, backend application protocol can be
+specified per host/request path pattern. It means that you can use
+both HTTP/2 and HTTP/1 in backend connections at the same time. Note
+that default backend protocol is HTTP/1.1. To use HTTP/2 in backend,
+you have to specify ``h2`` in ``proto`` keyword in :option:`--backend`
+explicitly.
+
+The backend is supposed to be a Web server. For example, to make
+nghttpx listen to encrypted HTTP/2 requests at port 8443, and a
+backend Web server is configured to listen to HTTP requests at port
+8080 on the same host, run nghttpx command-line like this:
+
+.. code-block:: text
+
+ $ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
+
+Then an HTTP/2 enabled client can access the nghttpx server using HTTP/2. For
+example, you can send a GET request using nghttp:
+
+.. code-block:: text
+
+ $ nghttp -nv https://localhost:8443/
+
+HTTP/2 proxy mode
+-----------------
+
+If nghttpx is invoked with :option:`--http2-proxy` (or its shorthand
+:option:`-s`) option, it operates in HTTP/2 proxy mode. The supported
+protocols in frontend and backend connections are the same as in `default
+mode`_. The difference is that this mode acts like a forward proxy and
+assumes the backend is an HTTP proxy server (e.g., Squid, Apache Traffic
+Server). HTTP/1 requests must include an absolute URI in request line.
+
+By default, the frontend connection is encrypted. So this mode is
+also called secure proxy.
+
+To turn off encryption on the frontend connection, use ``no-tls`` keyword
+in :option:`--frontend` option.
+
+The backend must be an HTTP proxy server. nghttpx supports multiple
+backend server addresses. It translates incoming requests to HTTP
+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.
+
+The backend connection is not encrypted by default. To enable
+encryption, use ``tls`` keyword in :option:`--backend` option. The
+default backend protocol is HTTP/1.1. To use HTTP/2 in backend
+connection, use :option:`--backend` option, and specify ``h2`` in
+``proto`` keyword explicitly.
+
+For example, to make nghttpx listen to encrypted HTTP/2 requests at
+port 8443, and a backend HTTP proxy server is configured to listen to
+HTTP/1 requests at port 8080 on the same host, run nghttpx command-line
+like this:
+
+.. code-block:: text
+
+ $ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
+
+At the time of this writing, Firefox 41 and Chromium v46 can use
+nghttpx as HTTP/2 proxy.
+
+To make Firefox or Chromium use nghttpx as HTTP/2 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 and
+Chromium require valid certificate for secure proxy.
+
+For Firefox, 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:
+
+.. code-block:: text
+
+ file:///path/to/proxy.pac
+
+For Chromium, use following command-line:
+
+.. code-block:: text
+
+ $ 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:
+
+.. code-block:: text
+
+ CONFIG proxy.config.reverse_proxy.enabled INT 0
+ CONFIG proxy.config.url_remap.remap_required INT 0
+
+Consult Traffic server `documentation
+<http://trafficserver.readthedocs.org/en/latest/admin-guide/configuration/transparent-forward-proxying.en.html>`_
+to know how to configure traffic server as forward proxy and its
+security implications.
+
+ALPN support
+------------
+
+ALPN support requires OpenSSL >= 1.0.2.
+
+Disable frontend SSL/TLS
+------------------------
+
+The frontend connections are encrypted with SSL/TLS by default. To
+turn off SSL/TLS, use ``no-tls`` keyword in :option:`--frontend`
+option. If this option is used, the private key and certificate are
+not required to run nghttpx.
+
+Enable backend SSL/TLS
+----------------------
+
+The backend connections are not encrypted by default. To enable
+SSL/TLS encryption, use ``tls`` keyword in :option:`--backend` option.
+
+Enable SSL/TLS on memcached connection
+--------------------------------------
+
+By default, memcached connection is not encrypted. To enable
+encryption, use ``tls`` keyword in
+:option:`--tls-ticket-key-memcached` for TLS ticket key, and
+:option:`--tls-session-cache-memcached` for TLS session cache.
+
+Specifying additional server certificates
+-----------------------------------------
+
+nghttpx accepts additional server private key and certificate pairs
+using :option:`--subcert` option. It can be used multiple times.
+
+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 :option:`--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 :option:`--insecure` 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 writing
+individually.
+
+To perform rate limit for reading, use :option:`--read-rate` and
+:option:`--read-burst` options. For writing, use
+:option:`--write-rate` and :option:`--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:
+
+* In the default mode (:option:`--http2-proxy` is not used)
+* :option:`--no-location-rewrite` is not used
+* URI in location header field is an absolute 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 is replaced with the ones used in
+frontend, and authority is replaced with which appears in
+``:authority``, or ``host`` request header field. ``:authority``
+header field has precedence over ``host``.
+
+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.
+
+As of nghttpx version 1.20.0, that is all you have to do. The new
+main process sends QUIT signal to the original process, when it is
+ready to serve requests, to shut it down gracefully.
+
+For earlier versions of nghttpx, you have to do one more thing. 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.
+
+If you want to just reload configuration file without executing new
+binary, send SIGHUP to nghttpx main process.
+
+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 :option:`--accesslog-file` and
+:option:`--errorlog-file` options.
+
+Multiple frontend addresses
+---------------------------
+
+nghttpx can listen on multiple frontend addresses. To specify them,
+just use :option:`--frontend` (or its shorthand :option:`-f`) option
+repeatedly. TLS can be enabled or disabled per frontend address
+basis. For example, to listen on port 443 with TLS enabled, and on
+port 80 without TLS:
+
+.. code-block:: text
+
+ frontend=*,443
+ frontend=*,80;no-tls
+
+
+Multiple backend addresses
+--------------------------
+
+nghttpx supports multiple backend addresses. To specify them, just
+use :option:`--backend` (or its shorthand :option:`-b`) option
+repeatedly. For example, to use ``192.168.0.10:8080`` and
+``192.168.0.11:8080``, use command-line like this:
+``-b192.168.0.10,8080 -b192.168.0.11,8080``. In configuration file,
+this looks like:
+
+.. code-block:: text
+
+ backend=192.168.0.10,8080
+ backend=192.168.0.11,8008
+
+nghttpx can route request to different backend according to request
+host and path. For example, to route request destined to host
+``doc.example.com`` to backend server ``docserv:3000``, you can write
+like so:
+
+.. code-block:: text
+
+ backend=docserv,3000;doc.example.com/
+
+When you write this option in command-line, you should enclose
+argument with single or double quotes, since the character ``;`` has a
+special meaning in shell.
+
+To route, request to request path ``/foo`` to backend server
+``[::1]:8080``, you can write like so:
+
+.. code-block:: text
+
+ backend=::1,8080;/foo
+
+If the last character of path pattern is ``/``, all request paths
+which start with that pattern match:
+
+.. code-block:: text
+
+ backend=::1,8080;/bar/
+
+The request path ``/bar/buzz`` matches the ``/bar/``.
+
+You can use ``*`` at the end of the path pattern to make it wildcard
+pattern. ``*`` must match at least one character:
+
+.. code-block:: text
+
+ backend=::1,8080;/sample*
+
+The request path ``/sample1/foo`` matches the ``/sample*`` pattern.
+
+Of course, you can specify both host and request path at the same
+time:
+
+.. code-block:: text
+
+ backend=192.168.0.10,8080;example.com/foo
+
+We can use ``*`` in the left most position of host to achieve wildcard
+suffix match. If ``*`` is the left most character, then the remaining
+string should match the request host suffix. ``*`` must match at
+least one character. For example, ``*.example.com`` matches
+``www.example.com`` and ``dev.example.com``, and does not match
+``example.com`` and ``nghttp2.org``. The exact match (without ``*``)
+always takes precedence over wildcard match.
+
+One important thing you have to remember is that we have to specify
+default routing pattern for so called "catch all" pattern. To write
+"catch all" pattern, just specify backend server address, without
+pattern.
+
+Usually, host is the value of ``Host`` header field. In HTTP/2, the
+value of ``:authority`` pseudo header field is used.
+
+When you write multiple backend addresses sharing the same routing
+pattern, they are used as load balancing. For example, to use 2
+servers ``serv1:3000`` and ``serv2:3000`` for request host
+``example.com`` and path ``/myservice``, you can write like so:
+
+.. code-block:: text
+
+ backend=serv1,3000;example.com/myservice
+ backend=serv2,3000;example.com/myservice
+
+You can also specify backend application protocol in
+:option:`--backend` option using ``proto`` keyword after pattern.
+Utilizing this allows ngttpx to route certain request to HTTP/2, other
+requests to HTTP/1. For example, to route requests to ``/ws/`` in
+backend HTTP/1.1 connection, and use backend HTTP/2 for other
+requests, do this:
+
+.. code-block:: text
+
+ backend=serv1,3000;/;proto=h2
+ backend=serv1,3000;/ws/;proto=http/1.1
+
+The default backend protocol is HTTP/1.1.
+
+TLS can be enabled per pattern basis:
+
+.. code-block:: text
+
+ backend=serv1,8443;/;proto=h2;tls
+ backend=serv2,8080;/ws/;proto=http/1.1
+
+In the above case, connection to serv1 will be encrypted by TLS. On
+the other hand, connection to serv2 will not be encrypted by TLS.
+
+Dynamic hostname lookup
+-----------------------
+
+By default, nghttpx performs backend hostname lookup at start up, or
+configuration reload, and keeps using them in its entire session. To
+make nghttpx perform hostname lookup dynamically, use ``dns``
+parameter in :option:`--backend` option, like so:
+
+.. code-block:: text
+
+ backend=foo.example.com,80;;dns
+
+nghttpx will cache resolved addresses for certain period of time. To
+change this cache period, use :option:`--dns-cache-timeout`.
+
+Enable PROXY protocol
+---------------------
+
+PROXY protocol can be enabled per frontend. In order to enable PROXY
+protocol, use ``proxyproto`` parameter in :option:`--frontend` option,
+like so:
+
+.. code-block:: text
+
+ frontend=*,443;proxyproto
+
+nghttpx supports both PROXY protocol v1 and v2. AF_UNIX in PROXY
+protocol version 2 is ignored.
+
+Session affinity
+----------------
+
+Two kinds of session affinity are available: client IP, and HTTP
+Cookie.
+
+To enable client IP based affinity, specify ``affinity=ip`` parameter
+in :option:`--backend` option. If PROXY protocol is enabled, then an
+address obtained from PROXY protocol is taken into consideration.
+
+To enable HTTP Cookie based affinity, specify ``affinity=cookie``
+parameter, and specify a name of cookie in ``affinity-cookie-name``
+parameter. Optionally, a Path attribute can be specified in
+``affinity-cookie-path`` parameter:
+
+.. code-block:: text
+
+ backend=127.0.0.1,3000;;affinity=cookie;affinity-cookie-name=nghttpxlb;affinity-cookie-path=/
+
+Secure attribute of cookie is set if client connection is protected by
+TLS. ``affinity-cookie-stickiness`` specifies the stickiness of this
+affinity. If ``loose`` is given, which is the default, removing or
+adding a backend server might break affinity. While ``strict`` is
+given, removing the designated backend server breaks affinity, but
+adding new backend server does not cause breakage.
+
+PSK cipher suites
+-----------------
+
+nghttpx supports pre-shared key (PSK) cipher suites for both frontend
+and backend TLS connections. For frontend connection, use
+:option:`--psk-secrets` option to specify a file which contains PSK
+identity and secrets. The format of the file is
+``<identity>:<hex-secret>``, where ``<identity>`` is PSK identity, and
+``<hex-secret>`` is PSK secret in hex, like so:
+
+.. code-block:: text
+
+ client1:9567800e065e078085c241d54a01c6c3f24b3bab71a606600f4c6ad2c134f3b9
+ client2:b1376c3f8f6dcf7c886c5bdcceecd1e6f1d708622b6ddd21bda26ebd0c0bca99
+
+nghttpx server accepts any of the identity and secret pairs in the
+file. The default cipher suite list does not contain PSK cipher
+suites. In order to use PSK, PSK cipher suite must be enabled by
+using :option:`--ciphers` option. The desired PSK cipher suite may be
+listed in `HTTP/2 cipher block list
+<https://tools.ietf.org/html/rfc7540#appendix-A>`_. In order to use
+such PSK cipher suite with HTTP/2, disable HTTP/2 cipher block list by
+using :option:`--no-http2-cipher-block-list` option. But you should
+understand its implications.
+
+At the time of writing, even if only PSK cipher suites are specified
+in :option:`--ciphers` option, certificate and private key are still
+required.
+
+For backend connection, use :option:`--client-psk-secrets` option to
+specify a file which contains single PSK identity and secret. The
+format is the same as the file used by :option:`--psk-secrets`
+described above, but only first identity and secret pair is solely
+used, like so:
+
+.. code-block:: text
+
+ client2:b1376c3f8f6dcf7c886c5bdcceecd1e6f1d708622b6ddd21bda26ebd0c0bca99
+
+The default cipher suite list does not contain PSK cipher suites. In
+order to use PSK, PSK cipher suite must be enabled by using
+:option:`--client-ciphers` option. The desired PSK cipher suite may
+be listed in `HTTP/2 cipher block list
+<https://tools.ietf.org/html/rfc7540#appendix-A>`_. In order to use
+such PSK cipher suite with HTTP/2, disable HTTP/2 cipher block list by
+using :option:`--client-no-http2-cipher-block-list` option. But you
+should understand its implications.
+
+TLSv1.3
+-------
+
+As of nghttpx v1.34.0, if it is built with OpenSSL 1.1.1 or later, it
+supports TLSv1.3. 0-RTT data is supported, but by default its
+processing is postponed until TLS handshake completes to mitigate
+replay attack. This costs extra round trip and reduces effectiveness
+of 0-RTT data. :option:`--tls-no-postpone-early-data` makes nghttpx
+not wait for handshake to complete before forwarding request included
+in 0-RTT to get full potential of 0-RTT data. In this case, nghttpx
+adds ``Early-Data: 1`` header field when forwarding a request to a
+backend server. All backend servers should recognize this header
+field and understand that there is a risk for replay attack. See `RFC
+8470 <https://tools.ietf.org/html/rfc8470>`_ for ``Early-Data`` header
+field.
+
+nghttpx disables anti replay protection provided by OpenSSL. The anti
+replay protection of OpenSSL requires that a resumed request must hit
+the same server which generates the session ticket. Therefore it
+might not work nicely in a deployment where there are multiple nghttpx
+instances sharing ticket encryption keys via memcached.
+
+Because TLSv1.3 completely changes the semantics of cipher suite
+naming scheme and structure, nghttpx provides the new option
+:option:`--tls13-ciphers` and :option:`--tls13-client-ciphers` to
+change preferred cipher list for TLSv1.3.
+
+WebSockets over HTTP/2
+----------------------
+
+nghttpx supports `RFC 8441 <https://tools.ietf.org/html/rfc8441>`_
+Bootstrapping WebSockets with HTTP/2 for both frontend and backend
+connections. This feature is enabled by default and no configuration
+is required.
+
+WebSockets over HTTP/3 is also supported.
+
+HTTP/3
+------
+
+nghttpx supports HTTP/3 if it is built with HTTP/3 support enabled.
+HTTP/3 support is experimental.
+
+In order to listen UDP port to receive HTTP/3 traffic,
+:option:`--frontend` option must have ``quic`` parameter:
+
+.. code-block:: text
+
+ frontend=*,443;quic
+
+The above example makes nghttpx receive HTTP/3 traffic on UDP
+port 443.
+
+nghttpx does not support HTTP/3 on backend connection.
+
+Hot swapping (SIGUSR2) or configuration reload (SIGHUP) require eBPF
+program. Without eBPF, old worker processes keep getting HTTP/3
+traffic and do not work as intended. The QUIC keying material to
+encrypt Connection ID must be set with
+:option:`--frontend-quic-secret-file` and must provide the existing
+keys in order to keep the existing connections alive during reload.
+
+The construction of Connection ID closely follows Block Cipher CID
+Algorithm described in `QUIC-LB draft
+<https://datatracker.ietf.org/doc/html/draft-ietf-quic-load-balancers>`_.
+A Connection ID that nghttpx generates is always 20 bytes long. It
+uses first 2 bits as a configuration ID. The remaining bits in the
+first byte are reserved and random. The next 4 bytes are server ID.
+The next 4 bytes are used to route UDP datagram to a correct
+``SO_REUSEPORT`` socket. The remaining bytes are randomly generated.
+The server ID and the next 12 bytes are encrypted with AES-ECB. The
+key is derived from the keying materials stored in a file specified by
+:option:`--frontend-quic-secret-file`. The first 2 bits of keying
+material in the file is used as a configuration ID. The remaining
+bits and following 3 bytes are reserved and unused. The next 32 bytes
+are used as an initial secret. The remaining 32 bytes are used as a
+salt. The encryption key is generated by `HKDF
+<https://datatracker.ietf.org/doc/html/rfc5869>`_ with SHA256 and
+these keying materials and ``connection id encryption key`` as info.
+
+In order announce that HTTP/3 endpoint is available, you should
+specify alt-svc header field. For example, the following options send
+alt-svc header field in HTTP/1.1 and HTTP/2 response:
+
+.. code-block:: text
+
+ altsvc=h3,443,,,ma=3600
+ http2-altsvc=h3,443,,,ma=3600
+
+Migration from nghttpx v1.18.x or earlier
+-----------------------------------------
+
+As of nghttpx v1.19.0, :option:`--ciphers` option only changes cipher
+list for frontend TLS connection. In order to change cipher list for
+backend connection, use :option:`--client-ciphers` option.
+
+Similarly, :option:`--no-http2-cipher-block-list` option only disables
+HTTP/2 cipher block list for frontend connection. In order to disable
+HTTP/2 cipher block list for backend connection, use
+:option:`--client-no-http2-cipher-block-list` option.
+
+``--accept-proxy-protocol`` option was deprecated. Instead, use
+``proxyproto`` parameter in :option:`--frontend` option to enable
+PROXY protocol support per frontend.
+
+Migration from nghttpx v1.8.0 or earlier
+----------------------------------------
+
+As of nghttpx 1.9.0, ``--frontend-no-tls`` and ``--backend-no-tls``
+have been removed.
+
+To disable encryption on frontend connection, use ``no-tls`` keyword
+in :option:`--frontend` potion:
+
+.. code-block:: text
+
+ frontend=*,3000;no-tls
+
+The TLS encryption is now disabled on backend connection in all modes
+by default. To enable encryption on backend connection, use ``tls``
+keyword in :option:`--backend` option:
+
+.. code-block:: text
+
+ backend=127.0.0.1,8080;tls
+
+As of nghttpx 1.9.0, ``--http2-bridge``, ``--client`` and
+``--client-proxy`` options have been removed. These functionality can
+be used using combinations of options.
+
+Use following option instead of ``--http2-bridge``:
+
+.. code-block:: text
+
+ backend=<ADDR>,<PORT>;;proto=h2;tls
+
+Use following options instead of ``--client``:
+
+.. code-block:: text
+
+ frontend=<ADDR>,<PORT>;no-tls
+ backend=<ADDR>,<PORT>;;proto=h2;tls
+
+Use following options instead of ``--client-proxy``:
+
+.. code-block:: text
+
+ http2-proxy=yes
+ frontend=<ADDR>,<PORT>;no-tls
+ backend=<ADDR>,<PORT>;;proto=h2;tls
+
+We also removed ``--backend-http2-connections-per-worker`` option. It
+was present because previously the number of backend h2 connection was
+statically configured, and defaulted to 1. Now the number of backend
+h2 connection is increased on demand. We know the maximum number of
+concurrent streams per connection. When we push as many request as
+the maximum concurrency to the one connection, we create another new
+connection so that we can distribute load and avoid delay the request
+processing. This is done automatically without any configuration.
diff --git a/doc/sources/security.rst b/doc/sources/security.rst
new file mode 100644
index 0000000..5a8fcd0
--- /dev/null
+++ b/doc/sources/security.rst
@@ -0,0 +1,33 @@
+Security Process
+================
+
+If you find a vulnerability in our software, please send the email to
+"tatsuhiro.t at gmail dot com" about its details instead of submitting
+issues on github issue page. It is a standard practice not to
+disclose vulnerability information publicly until a fixed version is
+released, or mitigation is worked out. In the future, we may setup a
+dedicated mail address for this purpose.
+
+If we identify that the reported issue is really a vulnerability, we
+open a new security advisory draft using `GitHub security feature
+<https://github.com/nghttp2/nghttp2/security>`_ and discuss the
+mitigation and bug fixes there. The fixes are committed to the
+private repository.
+
+We write the security advisory and get CVE number from GitHub
+privately. We also discuss the disclosure date to the public.
+
+We make a new release with the fix at the same time when the
+vulnerability is disclosed to public.
+
+At least 7 days before the public disclosure date, we open a new issue
+on `nghttp2 issue tracker
+<https://github.com/nghttp2/nghttp2/issues>`_ which notifies that the
+upcoming release will have a security fix. The ``SECURITY`` label is
+attached to this kind of issue. The issue is not opened if a
+vulnerability is already disclosed, and it is publicly known that
+nghttp2 is affected by that.
+
+Before few hours of new release, we merge the fixes to the master
+branch (and/or a release branch if necessary) and make a new release.
+Security advisory is disclosed on GitHub.
diff --git a/doc/sources/tutorial-client.rst b/doc/sources/tutorial-client.rst
new file mode 100644
index 0000000..95a6230
--- /dev/null
+++ b/doc/sources/tutorial-client.rst
@@ -0,0 +1,464 @@
+Tutorial: HTTP/2 client
+=========================
+
+In this tutorial, we are going to write a very primitive HTTP/2
+client. The complete source code, `libevent-client.c`_, is attached at
+the end of this page. It also resides in the examples directory in
+the archive or repository.
+
+This simple client takes a single HTTPS URI and retrieves the resource
+at the URI. The synopsis is:
+
+.. code-block:: text
+
+ $ 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.
+
+The client starts with some libevent and OpenSSL setup in the
+``main()`` and ``run()`` functions. This setup isn't specific to
+nghttp2, but one thing you should look at is setup of ALPN. Client
+tells application protocols that it supports to server via ALPN::
+
+ 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_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+
+ return ssl_ctx;
+ }
+
+Here we see ``SSL_CTX_get_alpn_protos()`` function call. We instructs
+OpenSSL to notify the server that we support h2, ALPN identifier for
+HTTP/2.
+
+The example client defines a couple of structs:
+
+We define and use a ``http2_session_data`` structure to store 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 one URI, it uses only one stream. We
+store the single stream's data in a ``http2_stream_data`` structure
+and the ``stream_data`` points to it. The ``http2_stream_data``
+structure is defined as follows::
+
+ typedef struct {
+ /* The NULL-terminated URI string to retrieve. */
+ const char *uri;
+ /* Parsed result of the |uri| */
+ struct http_parser_url *u;
+ /* The authority 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 create and initialize these structures in
+``create_http2_session_data()`` and ``create_http2_stream_data()``
+respectively.
+
+``initiate_connection()`` is called to start the connection to the
+remote server. It's defined as::
+
+ 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_enable(bev, EV_READ | EV_WRITE);
+ 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;
+ }
+
+``initiate_connection()`` creates a bufferevent for the connection and
+sets up three callbacks: ``readcb``, ``writecb``, and ``eventcb``.
+
+The ``eventcb()`` is invoked by the libevent event loop when an event
+(e.g. connection has been established, timeout, etc.) occurs 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;
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+
+ fprintf(stderr, "Connected\n");
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "h2 is not negotiated\n");
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ 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);
+ }
+
+Here we validate that HTTP/2 is negotiated, and if not, drop
+connection.
+
+For ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and ``BEV_EVENT_TIMEOUT``
+events, we just simply tear down the connection.
+
+The ``BEV_EVENT_CONNECTED`` event is invoked when the SSL/TLS
+handshake has completed successfully. After this we're ready to begin
+communicating via HTTP/2.
+
+The ``initialize_nghttp2_session()`` function initializes the nghttp2
+session object and several callbacks::
+
+ 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 a client, we use `nghttp2_session_client_new()`
+to initialize the nghttp2 session object. The callbacks setup are
+explained later.
+
+The `delete_http2_session_data()` function destroys ``session_data``
+and frees its bufferevent, so the underlying connection is closed. It
+also calls `nghttp2_session_del()` to delete the nghttp2 session
+object.
+
+A HTTP/2 connection begins by sending the client connection preface,
+which is a 24 byte magic byte string (:macro:`NGHTTP2_CLIENT_MAGIC`),
+followed by a SETTINGS frame. The 24 byte magic string is sent
+automatically by nghttp2. We send the SETTINGS frame 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;
+
+ /* client 24 bytes magic string will be sent by nghttp2 library */
+ 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 as 100. This is not
+needed for this tiny example program, it just demonstrates use of the
+SETTINGS frame. To queue the SETTINGS frame for transmission, we call
+`nghttp2_submit_settings()`. Note that `nghttp2_submit_settings()`
+only queues the frame for transmission, and doesn't actually send it.
+All ``nghttp2_submit_*()`` family functions have this property. To
+actually send the frame, `nghttp2_session_send()` has to be called,
+which is described (and called) later.
+
+After the transmission of the client connection header, we enqueue the
+HTTP request in the ``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 the HTTP request header fields in ``hdrs``, which is an array
+of :type:`nghttp2_nv`. There are four header fields to be sent:
+``:method``, ``:scheme``, ``:authority``, and ``:path``. To queue the
+HTTP request, we call `nghttp2_submit_request()`. The ``stream_data``
+is passed via the *stream_user_data* parameter, which is helpfully
+later passed back to callback functions.
+
+`nghttp2_submit_request()` returns the newly assigned stream ID for
+the request.
+
+The next bufferevent callback is ``readcb()``, which is invoked when
+data is available to read from 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, (size_t)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 the nghttp2
+session object using the `nghttp2_session_mem_recv()` function.
+`nghttp2_session_mem_recv()` processes the received data and may
+invoke nghttp2 callbacks and queue frames for transmission. Since
+there may be pending frames for transmission, we call immediately
+``session_send()`` to send them. ``session_send()`` 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 pending frames into
+wire format and calls the ``send_callback()`` function to send them.
+``send_callback()`` has type :type:`nghttp2_send_callback` and is
+defined as::
+
+ 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 (ssize_t)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 the ``write()`` system
+call, we'd soon receive an ``EAGAIN`` or ``EWOULDBLOCK`` error, since
+sockets have a limited send buffer. If that happens, it's possible to
+return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the nghttp2 library
+to stop sending further data. When writing to a bufferevent, you
+should regulate the amount of data written, to avoid possible huge
+memory consumption. In this example client however we don't implement
+a limit. 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 has 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()`,
+so there is no data to write in this function. All we have to do is
+check if the connection should be dropped or not. The nghttp2 session
+object keeps track of reception and transmission of GOAWAY frames and
+other error conditions. Using this information, the nghttp2 session
+object can state whether the connection should be dropped or not.
+More specifically, when both `nghttp2_session_want_read()` and
+`nghttp2_session_want_write()` return 0, the connection is no-longer
+required and can be closed. Since we're using bufferevent and its
+deferred callback option, the bufferevent output buffer may still
+contain pending data when the ``writecb()`` is called. To handle this
+situation, we also check whether the output buffer is empty or not. If
+all of these conditions are met, then we drop the connection.
+
+Now let's look at the remaining nghttp2 callbacks setup in the
+``initialize_nghttp2_setup()`` function.
+
+A server responds to the request by first sending a HEADERS frame.
+The HEADERS frame consists of response header name/value pairs, and
+the ``on_header_callback()`` is called for each 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;
+ }
+
+In this tutorial, we just print the name/value pairs on stderr.
+
+After the HEADERS frame has been fully received (and thus all response
+header name/value pairs have been received), the
+``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;
+ }
+
+``on_frame_recv_callback()`` is called for other frame types too.
+
+In this tutorial, we are just interested in the HTTP response HEADERS
+frame. We check the frame type and its category (it should be
+:macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). We also
+check its stream ID.
+
+Next, zero or more DATA frames can be received. 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 HTTP response body. After checking the
+stream ID, we just write the received data to stdout. Note 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 receiving
+resource we wanted (or the stream was reset by RST_STREAM from the
+remote peer), we call `nghttp2_session_terminate_session()` to
+commence 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
index 0000000..36e82d9
--- /dev/null
+++ b/doc/sources/tutorial-hpack.rst
@@ -0,0 +1,126 @@
+Tutorial: HPACK API
+===================
+
+In this tutorial, we describe basic use of nghttp2's HPACK API. We
+briefly describe the APIs for deflating and inflating header fields.
+The full example of using these APIs, `deflate.c`_, is attached at the
+end of this page. It also resides in the examples directory in the
+archive or repository.
+
+Deflating (encoding) headers
+----------------------------
+
+First we need to initialize a :type:`nghttp2_hd_deflater` object using
+the `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 a :type:`nghttp2_hd_deflater` object,
+initializes it, and assigns its pointer to ``*deflater_ptr``. The
+*deflate_hd_table_bufsize_max* is the upper bound of header table size
+the deflater will use. This will limit the memory usage by the
+deflater object for the dynamic header table. If in doubt, just
+specify 4096 here, which is the default upper bound of dynamic header
+table buffer size.
+
+To encode header fields, use the `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()` described above. The encoded byte string is
+written to the buffer *buf*, which has length *buflen*. The *nva* is
+a pointer to an array of headers fields, each of type
+:type:`nghttp2_nv`. *nvlen* is the number of header fields which
+*nva* contains.
+
+It is important to initialize and assign all members of
+:type:`nghttp2_nv`. For security sensitive header fields (such as
+cookies), set the :macro:`NGHTTP2_NV_FLAG_NO_INDEX` flag in
+:member:`nghttp2_nv.flags`. Setting this flag prevents recovery of
+sensitive header fields by compression based attacks: This is achieved
+by not inserting the header field into the dynamic header table.
+
+`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 the encoded result length, use `nghttp2_hd_deflate_bound()`::
+
+ size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+ const nghttp2_nv *nva, size_t nvlen);
+
+Pass this function the same parameters (*deflater*, *nva*, and
+*nvlen*) which will be passed to `nghttp2_hd_deflate_hd()`.
+
+Subsequent calls to `nghttp2_hd_deflate_hd()` will use the current
+encoder state and perform differential encoding, which yields HPAC's
+fundamental compression gain.
+
+If `nghttp2_hd_deflate_hd()` fails, the failure is fatal and any
+further calls with the same deflater object will fail. Thus it's very
+important to use `nghttp2_hd_deflate_bound()` to determine the
+required size of the output buffer.
+
+To delete a :type:`nghttp2_hd_deflater` object, use the
+`nghttp2_hd_deflate_del()` function.
+
+Inflating (decoding) headers
+----------------------------
+
+A :type:`nghttp2_hd_inflater` object is used 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_hd2()`::
+
+ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
+ nghttp2_nv *nv_out, int *inflate_flags,
+ const uint8_t *in, size_t inlen,
+ int in_final);
+
+`nghttp2_hd_inflate_hd2()` reads a stream of bytes and outputs a
+single header field at a time. Multiple calls are normally required to
+read a full stream of bytes and output all of the header fields.
+
+The *inflater* is the inflater object initialized above. The *nv_out*
+is a pointer to a :type:`nghttp2_nv` into which one header field may
+be stored. The *in* is a pointer to input data, and *inlen* is its
+length. The caller is not required to specify the whole deflated
+header data via *in* at once: Instead it can call this function
+multiple times as additional data bytes become available. 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 an output parameter; on success the function
+sets it to a bitset of flags. It will be described later.
+
+This function returns when each header field is inflated. When this
+happens, the function sets the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag
+in *inflate_flags*, and a header field is stored in *nv_out*. The
+return value indicates the number of bytes read from *in* processed so
+far, which may be less than *inlen*. The caller should call the
+function repeatedly until all bytes are processed. Processed bytes
+should be removed from *in*, and *inlen* should be adjusted
+appropriately.
+
+If *in_final* is nonzero and all given data was processed, the
+function sets the :macro:`NGHTTP2_HD_INFLATE_FINAL` flag in
+*inflate_flags*. When you see this flag set, call the
+`nghttp2_hd_inflate_end_headers()` function.
+
+If *in_final* is zero and the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag is
+not set, it indicates that all given data was processed. The caller
+is required to pass additional data.
+
+Example usage of `nghttp2_hd_inflate_hd2()` is shown in the
+`inflate_header_block()` function in `deflate.c`_.
+
+Finally, to delete a :type:`nghttp2_hd_inflater` object, use
+`nghttp2_hd_inflate_del()`.
diff --git a/doc/sources/tutorial-server.rst b/doc/sources/tutorial-server.rst
new file mode 100644
index 0000000..41680bd
--- /dev/null
+++ b/doc/sources/tutorial-server.rst
@@ -0,0 +1,577 @@
+Tutorial: HTTP/2 server
+=========================
+
+In this tutorial, we are going to write a 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. The source also resides in the examples directory in the
+archive or repository.
+
+This simple server takes 3 arguments: The port number to listen on,
+the path to your SSL/TLS private key file, and the path to your
+certificate file. The synopsis is:
+
+.. code-block:: text
+
+ $ 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.
+
+The server starts with some libevent and OpenSSL setup in the
+``main()`` and ``run()`` functions. This setup isn't specific to
+nghttp2, but one thing you should look at is setup of ALPN callback.
+The ALPN callback is used by the server to select application
+protocols offered by client. In ALPN, client sends the list of
+supported application protocols, and server selects one of them. We
+provide the callback for it::
+
+ static int alpn_select_proto_cb(SSL *ssl _U_, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg _U_) {
+ int rv;
+
+ rv = nghttp2_select_alpn(out, outlen, in, inlen);
+
+ if (rv != 1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ 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());
+
+ ...
+
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);
+
+ return ssl_ctx;
+ }
+
+In ``alpn_select_proto_cb()``, we use `nghttp2_select_alpn()` to
+select application protocol. The `nghttp2_select_alpn()` returns 1
+only if it selected h2 (ALPN identifier for HTTP/2), and out
+parameters were assigned accordingly.
+
+Next, let's take a look at the main structures used by the example
+application:
+
+We use the ``app_context`` 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. To manage them, we
+use 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``.
+
+libevent's bufferevent structure is used to perform network I/O, with
+the pointer to the bufferevent stored in the ``http2_session_data``
+structure. 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 the bufferevent in
+``http2_session_data``.
+
+We first create a listener object to accept incoming connections.
+libevent's ``struct evconnlistener`` is used 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, (int)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 connection's
+bufferevent is initialized at the same 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.) occurs 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) {
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+
+ fprintf(stderr, "%s connected\n", session_data->client_addr);
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr);
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ initialize_nghttp2_session(session_data);
+
+ if (send_server_connection_header(session_data) != 0 ||
+ session_send(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);
+ }
+
+Here we validate that HTTP/2 is negotiated, and if not, drop
+connection.
+
+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 its associated bufferevent member.
+As a result, the underlying connection is closed.
+
+The
+``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake has
+completed successfully. After this we are ready to begin communicating
+via HTTP/2.
+
+The ``initialize_nghttp2_session()`` function initializes the nghttp2
+session object and several callbacks::
+
+ 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_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_new(&session_data->session, callbacks, session_data);
+
+ nghttp2_session_callbacks_del(callbacks);
+ }
+
+Since we are creating a server, we use `nghttp2_session_server_new()`
+to initialize the nghttp2 session object. We also setup 5 callbacks
+for the nghttp2 session, these are explained later.
+
+The server now begins by sending the server connection preface, which
+always consists of a SETTINGS frame.
+``send_server_connection_header()`` configures and submits it::
+
+ 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;
+ }
+
+In the example SETTINGS frame we've set
+SETTINGS_MAX_CONCURRENT_STREAMS to 100. `nghttp2_submit_settings()`
+is used to queue the frame for transmission, but note it only queues
+the frame for transmission, and doesn't 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, (size_t)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 both invoke the previously setup callbacks and also queue
+outgoing frames. To send any pending outgoing frames, we immediately
+call ``session_send()``.
+
+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 the ``send_callback()``, which is 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 (ssize_t)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 the ``write()`` system
+call in the ``send_callback()``, we'd soon receive an ``EAGAIN`` or
+``EWOULDBLOCK`` error since sockets have a limited send buffer. If
+that happens, it's possible to return :macro:`NGHTTP2_ERR_WOULDBLOCK`
+to signal the nghttp2 library to stop sending further data. But here,
+when writing to the bufferevent, we have to regulate the amount data
+to 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`.
+
+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 can state whether the
+connection should be dropped or not. More specifically, if both
+`nghttp2_session_want_read()` and `nghttp2_session_want_write()`
+return 0, the connection is no-longer required and can be closed.
+Since we are using bufferevent and its deferred callback option, the
+bufferevent output buffer may still contain pending data when the
+``writecb()`` is called. To handle this, we check whether the output
+buffer is empty or not. If all of 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 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()`. The
+``http2_stream_data`` object can later be easily retrieved from the
+stream, 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 the ``: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()`` 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 using ``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 file content is performed by the ``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;
+ }
+
+nghttp2 uses the :type:`nghttp2_data_provider` structure to send the
+entity body to the remote peer. The ``source`` member of this
+structure is a union, which can be either a void pointer or an int
+(which is intended to be used as file descriptor). In this example
+server, we use it as a 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 occurs 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, the :macro:`NGHTTP2_DATA_FLAG_EOF` flag is set to signal nghttp2
+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;
+ }
+
+Lastly, we destroy the ``http2_stream_data`` object in this function,
+since the stream is about to close and we no longer need the object.
diff --git a/doc/tutorial-client.rst.in b/doc/tutorial-client.rst.in
new file mode 100644
index 0000000..4f7fcfc
--- /dev/null
+++ b/doc/tutorial-client.rst.in
@@ -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
index 0000000..832dedf
--- /dev/null
+++ b/doc/tutorial-hpack.rst.in
@@ -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
index 0000000..1972266
--- /dev/null
+++ b/doc/tutorial-server.rst.in
@@ -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/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..25a7261
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,78 @@
+FROM debian:12 as build
+
+RUN apt-get update && \
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ git clang make binutils autoconf automake autotools-dev libtool \
+ pkg-config \
+ zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \
+ libelf-dev
+
+RUN git clone --depth 1 -b OpenSSL_1_1_1w+quic https://github.com/quictls/openssl && \
+ cd openssl && \
+ ./config --openssldir=/etc/ssl && \
+ make -j$(nproc) && \
+ make install_sw && \
+ cd .. && \
+ rm -rf openssl
+
+RUN git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3 && \
+ cd nghttp3 && \
+ autoreconf -i && \
+ ./configure --enable-lib-only && \
+ make -j$(nproc) && \
+ make install-strip && \
+ cd .. && \
+ rm -rf nghttp3
+
+RUN git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/ngtcp2 && \
+ cd ngtcp2 && \
+ autoreconf -i && \
+ ./configure --enable-lib-only \
+ LIBTOOL_LDFLAGS="-static-libtool-libs" \
+ OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" \
+ PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \
+ make -j$(nproc) && \
+ make install-strip && \
+ cd .. && \
+ rm -rf ngtcp2
+
+RUN git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf && \
+ cd libbpf && \
+ PREFIX=/usr/local make -C src install && \
+ cd .. && \
+ rm -rf libbpf
+
+RUN git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \
+ cd nghttp2 && \
+ git submodule update --init && \
+ autoreconf -i && \
+ ./configure --disable-examples --disable-hpack-tools \
+ --with-mruby --with-neverbleed \
+ --enable-http3 --with-libbpf \
+ CC=clang CXX=clang++ \
+ LIBTOOL_LDFLAGS="-static-libtool-libs" \
+ OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \
+ LIBEV_LIBS="-l:libev.a" \
+ JEMALLOC_LIBS="-l:libjemalloc.a" \
+ LIBCARES_LIBS="-l:libcares.a" \
+ ZLIB_LIBS="-l:libz.a" \
+ LIBBPF_LIBS="-L/usr/local/lib64 -l:libbpf.a -l:libelf.a" \
+ LDFLAGS="-static-libgcc -static-libstdc++" \
+ PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \
+ make -j$(nproc) install-strip && \
+ cd .. && \
+ rm -rf nghttp2
+
+FROM gcr.io/distroless/base-debian12
+
+COPY --from=build \
+ /usr/local/share/nghttp2/ \
+ /usr/local/share/nghttp2/
+COPY --from=build \
+ /usr/local/bin/h2load \
+ /usr/local/bin/nghttpx \
+ /usr/local/bin/nghttp \
+ /usr/local/bin/nghttpd \
+ /usr/local/bin/
+COPY --from=build /usr/local/lib/nghttp2/reuseport_kern.o \
+ /usr/local/lib/nghttp2/
diff --git a/docker/README.rst b/docker/README.rst
new file mode 100644
index 0000000..e08af23
--- /dev/null
+++ b/docker/README.rst
@@ -0,0 +1,25 @@
+Dockerfile
+==========
+
+Dockerfile creates the applications bundled with nghttp2.
+These applications are:
+
+- nghttp
+- nghttpd
+- nghttpx
+- h2load
+
+HTTP/3 and eBPF features are enabled.
+
+In order to run nghttpx with HTTP/3 endpoint, you need to run the
+image with the escalated privilege. Here is the example command-line
+to run nghttpx to listen to HTTP/3 on port 443, assuming that the
+current directory contains a private key and a certificate in
+server.key and server.crt respectively:
+
+.. code-block:: text
+
+ $ docker run --rm -it -v /path/to/certs:/shared --net=host --privileged \
+ nghttp2 nghttpx \
+ /shared/server.key /shared/server.crt \
+ -f'*,443;quic'
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..16335a5
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,5 @@
+client
+libevent-client
+libevent-server
+deflate
+tiny-nghttpd
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..7a57bbf
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,37 @@
+if(ENABLE_EXAMPLES)
+ file(GLOB c_sources *.c)
+ set_source_files_properties(${c_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}")
+ file(GLOB cxx_sources *.cc)
+ set_source_files_properties(${cxx_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}")
+
+ include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include"
+
+ ${LIBEVENT_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ )
+
+ link_libraries(
+ nghttp2
+ ${LIBEVENT_OPENSSL_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${APP_LIBRARIES}
+ )
+
+ add_executable(client client.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(libevent-client libevent-client.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(libevent-server libevent-server.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(deflate deflate.c $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+endif()
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 0000000..7b682ed
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,54 @@
+# 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 = CMakeLists.txt
+
+if ENABLE_EXAMPLES
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib/includes \
+ -I$(top_builddir)/lib/includes \
+ -I$(top_srcdir)/third-party \
+ @LIBEVENT_OPENSSL_CFLAGS@ \
+ @OPENSSL_CFLAGS@ \
+ @DEFS@
+AM_LDFLAGS = @LIBTOOL_LDFLAGS@
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+ $(top_builddir)/third-party/liburl-parser.la \
+ @LIBEVENT_OPENSSL_LIBS@ \
+ @OPENSSL_LIBS@ \
+ @APPLDFLAGS@
+
+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
+
+endif # ENABLE_EXAMPLES
diff --git a/examples/client.c b/examples/client.c
new file mode 100644
index 0000000..ce8d1d0
--- /dev/null
+++ b/examples/client.c
@@ -0,0 +1,699 @@
+/*
+ * 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 <inttypes.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif /* HAVE_FCNTL_H */
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif /* HAVE_NETDB_H */
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.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.
+ */
+NGHTTP2_NORETURN
+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.
+ */
+NGHTTP2_NORETURN
+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.
+ */
+NGHTTP2_NORETURN
+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, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ struct Connection *connection;
+ int rv;
+ (void)session;
+ (void)flags;
+
+ 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, uint8_t *buf,
+ size_t length, int flags, void *user_data) {
+ struct Connection *connection;
+ int rv;
+ (void)session;
+ (void)flags;
+
+ 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) {
+ size_t i;
+ (void)user_data;
+
+ 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, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, 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) {
+ size_t i;
+ (void)user_data;
+
+ 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, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, 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, void *user_data) {
+ struct Request *req;
+ (void)error_code;
+ (void)user_data;
+
+ 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;
+}
+
+/*
+ * 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,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ struct Request *req;
+ (void)flags;
+ (void)user_data;
+
+ 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);
+}
+
+/*
+ * 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);
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+}
+
+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(TLS_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;
+
+ /* 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);
+ }
+
+ rv = nghttp2_submit_settings(connection.session, NGHTTP2_FLAG_NONE, NULL, 0);
+
+ if (rv != 0) {
+ diec("nghttp2_submit_settings", rv);
+ }
+
+ /* 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 = (uint16_t)port;
+ }
+ }
+ res->hostportlen = (size_t)(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);
+
+ 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
index 0000000..df1cb92
--- /dev/null
+++ b/examples/deflate.c
@@ -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(void) {
+ 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, 1, nva[i].namelen, stdout);
+ printf(": ");
+ fwrite(nva[i].value, 1, nva[i].valuelen, 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 = (size_t)rv;
+
+ printf("\nDeflate (%zu byte(s), ratio %.02f):\n\n", outlen,
+ sum == 0 ? 0 : (double)outlen / (double)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 = (size_t)rv;
+
+ in += proclen;
+ inlen -= proclen;
+
+ if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ fwrite(nv.name, 1, nv.namelen, stderr);
+ fprintf(stderr, ": ");
+ fwrite(nv.value, 1, nv.valuelen, 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
index 0000000..0e1c14a
--- /dev/null
+++ b/examples/libevent-client.c
@@ -0,0 +1,595 @@
+/*
+ * 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 __sgi
+# include <string.h>
+# define errx(exitcode, format, args...) \
+ { \
+ warnx(format, ##args); \
+ exit(exitcode); \
+ }
+# define warnx(format, args...) fprintf(stderr, format "\n", ##args)
+char *strndup(const char *s, size_t size);
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#ifndef __sgi
+# include <err.h>
+#endif
+#include <signal.h>
+#include <string.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 "url-parser/url_parser.h"
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+typedef struct {
+ /* The NULL-terminated URI string to retrieve. */
+ const char *uri;
+ /* Parsed result of the |uri| */
+ struct http_parser_url *u;
+ /* The authority 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 +=
+ (size_t)snprintf(stream_data->authority + u->field_data[UF_HOST].len,
+ extra, ":%u", u->port);
+ }
+
+ /* If we don't have path in URI, we use "/" as path. */
+ stream_data->pathlen = 1;
+ 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 += (size_t)(u->field_data[UF_QUERY].len + 1);
+ }
+
+ 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);
+ } else {
+ stream_data->path[0] = '/';
+ }
+ if (u->field_set & (1 << UF_QUERY)) {
+ stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] =
+ '?';
+ memcpy(stream_data->path + stream_data->pathlen -
+ u->field_data[UF_QUERY].len,
+ &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
+ }
+
+ 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, 1, namelen, f);
+ fprintf(f, ": ");
+ fwrite(value, 1, valuelen, 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, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ struct bufferevent *bev = session_data->bev;
+ (void)session;
+ (void)flags;
+
+ bufferevent_write(bev, data, length);
+ return (ssize_t)length;
+}
+
+/* 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, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ 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,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+
+ 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,
+ const nghttp2_frame *frame, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+
+ 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, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ if (session_data->stream_data->stream_id == stream_id) {
+ fwrite(data, 1, len, 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=%u\n", stream_id,
+ error_code);
+ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return 0;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(void) {
+ SSL_CTX *ssl_ctx;
+ ssl_ctx = SSL_CTX_new(TLS_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_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+
+ 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;
+
+ /* client 24 bytes magic string will be sent by nghttp2 library */
+ 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, (size_t)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, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ (void)bev;
+
+ 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;
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+
+ fprintf(stderr, "Connected\n");
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ if (alpn == NULL) {
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+ }
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "h2 is not negotiated\n");
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ 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_enable(bev, EV_READ | EV_WRITE);
+ 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);
+
+ run(argv[1]);
+ return 0;
+}
diff --git a/examples/libevent-server.c b/examples/libevent-server.c
new file mode 100644
index 0000000..fa45d37
--- /dev/null
+++ b/examples/libevent-server.c
@@ -0,0 +1,787 @@
+/*
+ * 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 __sgi
+# define errx(exitcode, format, args...) \
+ { \
+ warnx(format, ##args); \
+ exit(exitcode); \
+ }
+# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno))
+# define warnx(format, args...) fprintf(stderr, format "\n", ##args)
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif /* HAVE_NETDB_H */
+#include <signal.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif /* HAVE_FCNTL_H */
+#include <ctype.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+#include <netinet/tcp.h>
+#ifndef __sgi
+# include <err.h>
+#endif
+#include <string.h>
+#include <errno.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 int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ int rv;
+ (void)ssl;
+ (void)arg;
+
+ rv = nghttp2_select_alpn(out, outlen, in, inlen);
+
+ if (rv != 1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ 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;
+
+ ssl_ctx = SSL_CTX_new(TLS_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);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
+ errx(1, "SSL_CTX_set1_curves_list failed: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+ {
+ EC_KEY *ecdh;
+ 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);
+ }
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+
+ 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);
+ }
+
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_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,
+ http2_stream_data *stream_data) {
+ (void)session_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);
+ bufferevent_enable(session_data->bev, EV_READ | EV_WRITE);
+ rv = getnameinfo(addr, (socklen_t)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, (size_t)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, const uint8_t *data,
+ size_t length, int flags, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ struct bufferevent *bev = session_data->bev;
+ (void)session;
+ (void)flags;
+
+ /* 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 (ssize_t)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 (uint8_t)(c - '0');
+ }
+ if ('A' <= c && c <= 'F') {
+ return (uint8_t)(c - 'A' + 10);
+ }
+ if ('a' <= c && c <= 'f') {
+ return (uint8_t)(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++] = (char)value[i++];
+ continue;
+ }
+ res[j++] =
+ (char)((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, int32_t stream_id,
+ uint8_t *buf, size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *user_data) {
+ int fd = source->fd;
+ ssize_t r;
+ (void)session;
+ (void)stream_id;
+ (void)user_data;
+
+ 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;
+}
+
+static 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, void *user_data) {
+ http2_stream_data *stream_data;
+ const char PATH[] = ":path";
+ (void)flags;
+ (void)user_data;
+
+ 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, void *user_data) {
+ http2_session_data *session_data = (http2_session_data *)user_data;
+ http2_stream_data *stream_data;
+ (void)error_code;
+
+ 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_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_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_new(&session_data->session, callbacks, session_data);
+
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+/* 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, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ (void)bev;
+
+ 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, short events, void *ptr) {
+ http2_session_data *session_data = (http2_session_data *)ptr;
+ if (events & BEV_EVENT_CONNECTED) {
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+ SSL *ssl;
+ (void)bev;
+
+ fprintf(stderr, "%s connected\n", session_data->client_addr);
+
+ ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+ SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+
+ if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+ fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr);
+ delete_http2_session_data(session_data);
+ return;
+ }
+
+ initialize_nghttp2_session(session_data);
+
+ if (send_server_connection_header(session_data) != 0 ||
+ session_send(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, int fd,
+ struct sockaddr *addr, int addrlen, void *arg) {
+ app_context *app_ctx = (app_context *)arg;
+ http2_session_data *session_data;
+ (void)listener;
+
+ 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, "Could not resolve server address");
+ }
+ 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, (int)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);
+
+ run(argv[1], argv[2], argv[3]);
+ return 0;
+}
diff --git a/fedora/spdylay.spec b/fedora/spdylay.spec
new file mode 100644
index 0000000..967d908
--- /dev/null
+++ b/fedora/spdylay.spec
@@ -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/fuzz/README.rst b/fuzz/README.rst
new file mode 100644
index 0000000..54ae832
--- /dev/null
+++ b/fuzz/README.rst
@@ -0,0 +1,33 @@
+Fuzzer
+======
+
+This directory contains fuzzer target mainly written to integrate
+nghttp2 into `oss-fuzz <https://github.com/google/oss-fuzz>`_.
+
+fuzz_target.cc contains an entry point of fuzzer. corpus directory
+contains initial data for fuzzer.
+
+The file name of initial data under corpus is the lower-cased hex
+string of SHA-256 hash of its own content.
+
+corpus/h2spec contains input data which was recorded when we ran
+`h2spec <https://github.com/summerwind/h2spec>`_ against nghttpd.
+
+corpus/nghttp contains input data which was recorded when we ran
+nghttp against nghttpd with some varying command line options of
+nghttp.
+
+
+To build fuzz_target.cc, make sure that libnghttp2 is built with
+following compiler/linker flags:
+
+.. code-block:: text
+
+ CPPFLAGS="-fsanitize-coverage=edge -fsanitize=address"
+ LDFLAGS="-fsanitize-coverage=edge -fsanitize=address"
+
+Then, fuzz_target.cc can be built using the following command:
+
+.. code-block:: text
+
+ $ clang++ -fsanitize-coverage=edge -fsanitize=address -I../lib/includes -std=c++11 fuzz_target.cc ../lib/.libs/libnghttp2.a /usr/lib/llvm-3.9/lib/libFuzzer.a -o nghttp2_fuzzer
diff --git a/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 b/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8
new file mode 100644
index 0000000..f2bc466
--- /dev/null
+++ b/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8
Binary files differ
diff --git a/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a b/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a
new file mode 100644
index 0000000..629ce58
--- /dev/null
+++ b/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a
Binary files differ
diff --git a/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 b/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66
new file mode 100644
index 0000000..61c6383
--- /dev/null
+++ b/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66
Binary files differ
diff --git a/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a b/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a
new file mode 100644
index 0000000..f8651f1
--- /dev/null
+++ b/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a
Binary files differ
diff --git a/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd b/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd
new file mode 100644
index 0000000..15c71aa
--- /dev/null
+++ b/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd
Binary files differ
diff --git a/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f b/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f
new file mode 100644
index 0000000..31a5ddc
--- /dev/null
+++ b/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f
@@ -0,0 +1,2 @@
+INVALID CONNECTION PREFACE
+
diff --git a/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 b/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32
new file mode 100644
index 0000000..cf66978
--- /dev/null
+++ b/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32
Binary files differ
diff --git a/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 b/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39
new file mode 100644
index 0000000..981e66a
--- /dev/null
+++ b/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39
Binary files differ
diff --git a/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 b/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9
new file mode 100644
index 0000000..fb65c24
--- /dev/null
+++ b/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9
Binary files differ
diff --git a/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 b/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5
new file mode 100644
index 0000000..62d5887
--- /dev/null
+++ b/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5
Binary files differ
diff --git a/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a b/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a
new file mode 100644
index 0000000..e528508
--- /dev/null
+++ b/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a
Binary files differ
diff --git a/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc b/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc
new file mode 100644
index 0000000..4cd627b
--- /dev/null
+++ b/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc
Binary files differ
diff --git a/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 b/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800
new file mode 100644
index 0000000..d5c77de
--- /dev/null
+++ b/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800
Binary files differ
diff --git a/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 b/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300
new file mode 100644
index 0000000..e8ac9ee
--- /dev/null
+++ b/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300
Binary files differ
diff --git a/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 b/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0
new file mode 100644
index 0000000..da1612a
--- /dev/null
+++ b/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0
Binary files differ
diff --git a/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab b/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab
new file mode 100644
index 0000000..ab72d09
--- /dev/null
+++ b/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab
Binary files differ
diff --git a/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 b/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8
new file mode 100644
index 0000000..a9123c4
--- /dev/null
+++ b/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8
Binary files differ
diff --git a/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 b/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76
new file mode 100644
index 0000000..b6b54dc
--- /dev/null
+++ b/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76
Binary files differ
diff --git a/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 b/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248
new file mode 100644
index 0000000..9338c99
--- /dev/null
+++ b/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248
Binary files differ
diff --git a/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 b/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6
new file mode 100644
index 0000000..aa67cbc
--- /dev/null
+++ b/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6
Binary files differ
diff --git a/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 b/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678
new file mode 100644
index 0000000..ad14518
--- /dev/null
+++ b/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678
Binary files differ
diff --git a/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df b/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df
new file mode 100644
index 0000000..e36ad42
--- /dev/null
+++ b/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df
Binary files differ
diff --git a/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f b/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f
new file mode 100644
index 0000000..a0c5dc3
--- /dev/null
+++ b/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f
Binary files differ
diff --git a/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 b/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132
new file mode 100644
index 0000000..9176566
--- /dev/null
+++ b/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132
Binary files differ
diff --git a/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce b/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce
new file mode 100644
index 0000000..1eb839a
--- /dev/null
+++ b/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce
Binary files differ
diff --git a/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 b/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0
new file mode 100644
index 0000000..5e6fd53
--- /dev/null
+++ b/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0
Binary files differ
diff --git a/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 b/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91
new file mode 100644
index 0000000..e8e09a8
--- /dev/null
+++ b/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91
Binary files differ
diff --git a/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 b/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5
new file mode 100644
index 0000000..f631036
--- /dev/null
+++ b/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5
Binary files differ
diff --git a/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 b/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933
new file mode 100644
index 0000000..c99b1d8
--- /dev/null
+++ b/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933
Binary files differ
diff --git a/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a b/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a
new file mode 100644
index 0000000..d168ca6
--- /dev/null
+++ b/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a
Binary files differ
diff --git a/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c b/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c
new file mode 100644
index 0000000..cf176eb
--- /dev/null
+++ b/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c
Binary files differ
diff --git a/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d b/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d
new file mode 100644
index 0000000..07a4f67
--- /dev/null
+++ b/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d
Binary files differ
diff --git a/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f b/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f
new file mode 100644
index 0000000..73f52d1
--- /dev/null
+++ b/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f
Binary files differ
diff --git a/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca b/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca
new file mode 100644
index 0000000..a27ca46
--- /dev/null
+++ b/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca
Binary files differ
diff --git a/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c b/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c
new file mode 100644
index 0000000..f5a2f2f
--- /dev/null
+++ b/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c
Binary files differ
diff --git a/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf b/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf
new file mode 100644
index 0000000..bdb70ef
--- /dev/null
+++ b/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf
Binary files differ
diff --git a/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 b/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941
new file mode 100644
index 0000000..b5a05b6
--- /dev/null
+++ b/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941
Binary files differ
diff --git a/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a b/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a
new file mode 100644
index 0000000..fa18eff
--- /dev/null
+++ b/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a
Binary files differ
diff --git a/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 b/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801
new file mode 100644
index 0000000..b8e0b33
--- /dev/null
+++ b/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801
Binary files differ
diff --git a/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 b/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60
new file mode 100644
index 0000000..2be33f5
--- /dev/null
+++ b/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60
Binary files differ
diff --git a/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 b/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82
new file mode 100644
index 0000000..a7d696a
--- /dev/null
+++ b/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82
Binary files differ
diff --git a/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab b/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab
new file mode 100644
index 0000000..2f00755
--- /dev/null
+++ b/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab
Binary files differ
diff --git a/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 b/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193
new file mode 100644
index 0000000..d212c23
--- /dev/null
+++ b/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193
Binary files differ
diff --git a/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 b/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16
new file mode 100644
index 0000000..43a8423
--- /dev/null
+++ b/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16
Binary files differ
diff --git a/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 b/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937
new file mode 100644
index 0000000..43cf972
--- /dev/null
+++ b/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937
Binary files differ
diff --git a/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 b/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13
new file mode 100644
index 0000000..e41d637
--- /dev/null
+++ b/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13
Binary files differ
diff --git a/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a b/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a
new file mode 100644
index 0000000..cc92edc
--- /dev/null
+++ b/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a
Binary files differ
diff --git a/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 b/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6
new file mode 100644
index 0000000..e1597d9
--- /dev/null
+++ b/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6
Binary files differ
diff --git a/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e b/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e
new file mode 100644
index 0000000..11175d8
--- /dev/null
+++ b/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e
Binary files differ
diff --git a/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa b/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa
new file mode 100644
index 0000000..4d233a0
--- /dev/null
+++ b/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa
Binary files differ
diff --git a/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a b/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a
new file mode 100644
index 0000000..71ab2d8
--- /dev/null
+++ b/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a
Binary files differ
diff --git a/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 b/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63
new file mode 100644
index 0000000..979e96a
--- /dev/null
+++ b/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63
Binary files differ
diff --git a/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 b/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88
new file mode 100644
index 0000000..5b47f4f
--- /dev/null
+++ b/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88
Binary files differ
diff --git a/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 b/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8
new file mode 100644
index 0000000..1ebf432
--- /dev/null
+++ b/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8
Binary files differ
diff --git a/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 b/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23
new file mode 100644
index 0000000..f2e3ff2
--- /dev/null
+++ b/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23
Binary files differ
diff --git a/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e b/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e
new file mode 100644
index 0000000..22137b3
--- /dev/null
+++ b/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e
Binary files differ
diff --git a/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c b/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c
new file mode 100644
index 0000000..570e98a
--- /dev/null
+++ b/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c
Binary files differ
diff --git a/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d b/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d
new file mode 100644
index 0000000..c0f50b7
--- /dev/null
+++ b/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d
Binary files differ
diff --git a/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 b/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33
new file mode 100644
index 0000000..5a4c52a
--- /dev/null
+++ b/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33
Binary files differ
diff --git a/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 b/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604
new file mode 100644
index 0000000..a7fa65d
--- /dev/null
+++ b/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604
Binary files differ
diff --git a/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 b/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885
new file mode 100644
index 0000000..c89a9a7
--- /dev/null
+++ b/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885
Binary files differ
diff --git a/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e b/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e
new file mode 100644
index 0000000..e42f5f4
--- /dev/null
+++ b/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e
Binary files differ
diff --git a/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea b/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea
new file mode 100644
index 0000000..3200027
--- /dev/null
+++ b/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea
Binary files differ
diff --git a/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f b/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f
new file mode 100644
index 0000000..28709d4
--- /dev/null
+++ b/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f
Binary files differ
diff --git a/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 b/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2
new file mode 100644
index 0000000..46316c1
--- /dev/null
+++ b/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2
Binary files differ
diff --git a/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 b/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0
new file mode 100644
index 0000000..68a831a
--- /dev/null
+++ b/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0
Binary files differ
diff --git a/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a b/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a
new file mode 100644
index 0000000..582b979
--- /dev/null
+++ b/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a
Binary files differ
diff --git a/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 b/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707
new file mode 100644
index 0000000..ea39871
--- /dev/null
+++ b/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707
Binary files differ
diff --git a/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 b/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18
new file mode 100644
index 0000000..38e5774
--- /dev/null
+++ b/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18
Binary files differ
diff --git a/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 b/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45
new file mode 100644
index 0000000..7a8ad5d
--- /dev/null
+++ b/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45
Binary files differ
diff --git a/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada b/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada
new file mode 100644
index 0000000..063bdab
--- /dev/null
+++ b/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada
Binary files differ
diff --git a/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 b/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5
new file mode 100644
index 0000000..7eed615
--- /dev/null
+++ b/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5
Binary files differ
diff --git a/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a b/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a
new file mode 100644
index 0000000..aa862db
--- /dev/null
+++ b/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a
Binary files differ
diff --git a/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 b/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0
new file mode 100644
index 0000000..1c2503e
--- /dev/null
+++ b/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0
Binary files differ
diff --git a/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 b/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357
new file mode 100644
index 0000000..4f6a2eb
--- /dev/null
+++ b/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357
Binary files differ
diff --git a/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 b/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453
new file mode 100644
index 0000000..0c10194
--- /dev/null
+++ b/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453
Binary files differ
diff --git a/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb b/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb
new file mode 100644
index 0000000..b92e520
--- /dev/null
+++ b/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb
Binary files differ
diff --git a/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef b/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef
new file mode 100644
index 0000000..cd87803
--- /dev/null
+++ b/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef
Binary files differ
diff --git a/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 b/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43
new file mode 100644
index 0000000..63ca53a
--- /dev/null
+++ b/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43
Binary files differ
diff --git a/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 b/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838
new file mode 100644
index 0000000..08dd2bd
--- /dev/null
+++ b/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838
Binary files differ
diff --git a/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 b/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702
new file mode 100644
index 0000000..51c931c
--- /dev/null
+++ b/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702
Binary files differ
diff --git a/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 b/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7
new file mode 100644
index 0000000..3513471
--- /dev/null
+++ b/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7
Binary files differ
diff --git a/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 b/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88
new file mode 100644
index 0000000..3f897bd
--- /dev/null
+++ b/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88
Binary files differ
diff --git a/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 b/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07
new file mode 100644
index 0000000..a8b06a9
--- /dev/null
+++ b/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07
Binary files differ
diff --git a/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 b/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1
new file mode 100644
index 0000000..dfa9766
--- /dev/null
+++ b/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1
Binary files differ
diff --git a/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 b/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225
new file mode 100644
index 0000000..1b8b19e
--- /dev/null
+++ b/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225
Binary files differ
diff --git a/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 b/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1
new file mode 100644
index 0000000..e8d7b2d
--- /dev/null
+++ b/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1
Binary files differ
diff --git a/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f b/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f
new file mode 100644
index 0000000..1b4ae59
--- /dev/null
+++ b/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f
Binary files differ
diff --git a/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 b/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8
new file mode 100644
index 0000000..7d81f74
--- /dev/null
+++ b/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8
Binary files differ
diff --git a/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 b/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76
new file mode 100644
index 0000000..eae40c4
--- /dev/null
+++ b/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76
Binary files differ
diff --git a/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b b/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b
new file mode 100644
index 0000000..875250c
--- /dev/null
+++ b/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b
Binary files differ
diff --git a/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 b/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5
new file mode 100644
index 0000000..4ca7ddb
--- /dev/null
+++ b/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5
Binary files differ
diff --git a/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb b/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb
new file mode 100644
index 0000000..8c90cf3
--- /dev/null
+++ b/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb
Binary files differ
diff --git a/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 b/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7
new file mode 100644
index 0000000..3aff723
--- /dev/null
+++ b/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7
Binary files differ
diff --git a/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 b/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7
new file mode 100644
index 0000000..20ed145
--- /dev/null
+++ b/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7
Binary files differ
diff --git a/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a b/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a
new file mode 100644
index 0000000..adf60df
--- /dev/null
+++ b/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a
Binary files differ
diff --git a/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 b/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9
new file mode 100644
index 0000000..28b6df1
--- /dev/null
+++ b/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9
Binary files differ
diff --git a/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 b/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423
new file mode 100644
index 0000000..6c1c4bc
--- /dev/null
+++ b/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423
Binary files differ
diff --git a/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d b/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d
new file mode 100644
index 0000000..e290f86
--- /dev/null
+++ b/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d
Binary files differ
diff --git a/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e b/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e
new file mode 100644
index 0000000..4b92a83
--- /dev/null
+++ b/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e
Binary files differ
diff --git a/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 b/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9
new file mode 100644
index 0000000..5759f66
--- /dev/null
+++ b/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9
Binary files differ
diff --git a/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc b/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc
new file mode 100644
index 0000000..abfa58f
--- /dev/null
+++ b/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc
Binary files differ
diff --git a/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad b/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad
new file mode 100644
index 0000000..e3ef3dc
--- /dev/null
+++ b/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad
Binary files differ
diff --git a/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b b/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b
new file mode 100644
index 0000000..da91c8d
--- /dev/null
+++ b/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b
Binary files differ
diff --git a/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 b/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11
new file mode 100644
index 0000000..a418c41
--- /dev/null
+++ b/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11
Binary files differ
diff --git a/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 b/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7
new file mode 100644
index 0000000..40659b5
--- /dev/null
+++ b/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7
Binary files differ
diff --git a/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 b/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0
new file mode 100644
index 0000000..98b11ed
--- /dev/null
+++ b/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0
Binary files differ
diff --git a/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 b/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3
new file mode 100644
index 0000000..a88435f
--- /dev/null
+++ b/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3
Binary files differ
diff --git a/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 b/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6
new file mode 100644
index 0000000..783b6a3
--- /dev/null
+++ b/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6
Binary files differ
diff --git a/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 b/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105
new file mode 100644
index 0000000..3ec3fbb
--- /dev/null
+++ b/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105
Binary files differ
diff --git a/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d b/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d
new file mode 100644
index 0000000..348471d
--- /dev/null
+++ b/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d
Binary files differ
diff --git a/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf b/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf
new file mode 100644
index 0000000..004ea71
--- /dev/null
+++ b/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf
Binary files differ
diff --git a/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a b/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a
new file mode 100644
index 0000000..11e84b4
--- /dev/null
+++ b/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a
Binary files differ
diff --git a/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 b/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65
new file mode 100644
index 0000000..cf972d1
--- /dev/null
+++ b/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65
Binary files differ
diff --git a/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 b/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11
new file mode 100644
index 0000000..d894be7
--- /dev/null
+++ b/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11
Binary files differ
diff --git a/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 b/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120
new file mode 100644
index 0000000..303b68a
--- /dev/null
+++ b/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120
Binary files differ
diff --git a/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 b/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7
new file mode 100644
index 0000000..b19f4f6
--- /dev/null
+++ b/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7
Binary files differ
diff --git a/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df b/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df
new file mode 100644
index 0000000..e5c39cc
--- /dev/null
+++ b/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df
Binary files differ
diff --git a/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 b/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629
new file mode 100644
index 0000000..81e30d7
--- /dev/null
+++ b/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629
Binary files differ
diff --git a/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 b/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14
new file mode 100644
index 0000000..0af0a7d
--- /dev/null
+++ b/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14
Binary files differ
diff --git a/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 b/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433
new file mode 100644
index 0000000..27dec29
--- /dev/null
+++ b/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433
Binary files differ
diff --git a/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 b/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73
new file mode 100644
index 0000000..d528c3f
--- /dev/null
+++ b/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73
Binary files differ
diff --git a/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee b/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee
new file mode 100644
index 0000000..ac58d53
--- /dev/null
+++ b/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee
Binary files differ
diff --git a/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 b/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76
new file mode 100644
index 0000000..317ead9
--- /dev/null
+++ b/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76
Binary files differ
diff --git a/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a b/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a
new file mode 100644
index 0000000..ff4af29
--- /dev/null
+++ b/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a
Binary files differ
diff --git a/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 b/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877
new file mode 100644
index 0000000..2019289
--- /dev/null
+++ b/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877
Binary files differ
diff --git a/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc b/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc
new file mode 100644
index 0000000..1514db5
--- /dev/null
+++ b/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc
Binary files differ
diff --git a/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 b/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03
new file mode 100644
index 0000000..45db5f0
--- /dev/null
+++ b/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03
Binary files differ
diff --git a/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e b/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e
new file mode 100644
index 0000000..76f3f05
--- /dev/null
+++ b/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e
Binary files differ
diff --git a/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 b/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98
new file mode 100644
index 0000000..f7cf1d7
--- /dev/null
+++ b/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98
Binary files differ
diff --git a/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 b/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116
new file mode 100644
index 0000000..14891ed
--- /dev/null
+++ b/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116
Binary files differ
diff --git a/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 b/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6
new file mode 100644
index 0000000..95f5a2e
--- /dev/null
+++ b/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6
Binary files differ
diff --git a/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 b/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5
new file mode 100644
index 0000000..8b31bfd
--- /dev/null
+++ b/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5
Binary files differ
diff --git a/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb b/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb
new file mode 100644
index 0000000..a2142c6
--- /dev/null
+++ b/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb
Binary files differ
diff --git a/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 b/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911
new file mode 100644
index 0000000..6f9f9ac
--- /dev/null
+++ b/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911
Binary files differ
diff --git a/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa b/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa
new file mode 100644
index 0000000..b3e2459
--- /dev/null
+++ b/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa
Binary files differ
diff --git a/fuzz/fuzz_frames.cc b/fuzz/fuzz_frames.cc
new file mode 100644
index 0000000..2a20c42
--- /dev/null
+++ b/fuzz/fuzz_frames.cc
@@ -0,0 +1,160 @@
+#include <string>
+#include <fuzzer/FuzzedDataProvider.h>
+
+extern "C" {
+#include <string.h>
+#include "nghttp2_hd.h"
+#include "nghttp2_frame.h"
+
+#include "nghttp2_test_helper.h"
+
+#define HEADERS_LENGTH 7
+
+static nghttp2_nv fuzz_make_nv(std::string s1, std::string s2) {
+ nghttp2_nv nv;
+ uint8_t *n = (uint8_t *)malloc(s1.size());
+ memcpy(n, s1.c_str(), s1.size());
+
+ uint8_t *v = (uint8_t *)malloc(s2.size());
+ memcpy(v, s2.c_str(), s2.size());
+
+ nv.name = n;
+ nv.value = v;
+ nv.namelen = s1.size();
+ nv.valuelen = s2.size();
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ return nv;
+}
+
+static void fuzz_free_nv(nghttp2_nv *nv) {
+ free(nv->name);
+ free(nv->value);
+}
+
+void check_frame_pack_headers(FuzzedDataProvider *data_provider) {
+ nghttp2_hd_deflater deflater;
+ nghttp2_hd_inflater inflater;
+ nghttp2_headers frame, oframe;
+ nghttp2_bufs bufs;
+ nghttp2_nv *nva;
+ nghttp2_priority_spec pri_spec;
+ size_t nvlen;
+ nva_out out;
+ size_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);
+
+ /* Create a set of headers seeded with data from the fuzzer */
+ nva = (nghttp2_nv *)mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL);
+ for (int i = 0; i < HEADERS_LENGTH; i++) {
+ nva[i] = fuzz_make_nv(data_provider->ConsumeRandomLengthString(30),
+ data_provider->ConsumeRandomLengthString(300));
+ }
+
+ 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);
+
+ /* Perform a set of operations with the fuzz data */
+ rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater);
+ if (rv == 0) {
+ unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem);
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+ }
+
+ nghttp2_nv *nva2 = NULL;
+ rv = nghttp2_nv_array_copy(&nva2, nva, nvlen, mem);
+ if (rv == 0) {
+ nghttp2_nv_array_del(nva2, mem);
+ }
+
+ /* Cleanup */
+ for (int i = 0; i < HEADERS_LENGTH; i++) {
+ fuzz_free_nv(&nva[i]);
+ }
+
+ nghttp2_bufs_free(&bufs);
+ nghttp2_frame_headers_free(&frame, mem);
+ nghttp2_hd_inflate_free(&inflater);
+ nghttp2_hd_deflate_free(&deflater);
+}
+
+void check_frame_push_promise(FuzzedDataProvider *data_provider) {
+ nghttp2_hd_deflater deflater;
+ nghttp2_hd_inflater inflater;
+ nghttp2_push_promise frame, oframe;
+ nghttp2_bufs bufs;
+ nghttp2_nv *nva;
+ nghttp2_priority_spec pri_spec;
+ size_t nvlen;
+ nva_out out;
+ size_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);
+
+ /* Create a set of headers seeded with data from the fuzzer */
+ nva = (nghttp2_nv *)mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL);
+ for (int i = 0; i < HEADERS_LENGTH; i++) {
+ nva[i] = fuzz_make_nv(data_provider->ConsumeRandomLengthString(30),
+ data_provider->ConsumeRandomLengthString(300));
+ }
+ nvlen = HEADERS_LENGTH;
+ nghttp2_priority_spec_default_init(&pri_spec);
+
+ /* Perform a set of operations with the fuzz data */
+ 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);
+ if (rv == 0) {
+ unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+ }
+
+ nghttp2_nv *nva2 = NULL;
+ rv = nghttp2_nv_array_copy(&nva2, nva, nvlen, mem);
+ if (rv == 0) {
+ nghttp2_nv_array_del(nva2, mem);
+ }
+
+ /* Cleanup */
+ for (int i = 0; i < HEADERS_LENGTH; i++) {
+ fuzz_free_nv(&nva[i]);
+ }
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_bufs_free(&bufs);
+
+ nghttp2_frame_push_promise_free(&frame, mem);
+ nghttp2_hd_inflate_free(&inflater);
+ nghttp2_hd_deflate_free(&deflater);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ FuzzedDataProvider data_provider(data, size);
+
+ check_frame_pack_headers(&data_provider);
+ check_frame_push_promise(&data_provider);
+ return 0;
+}
+
+} // extern C
diff --git a/fuzz/fuzz_target.cc b/fuzz/fuzz_target.cc
new file mode 100644
index 0000000..4adc5ed
--- /dev/null
+++ b/fuzz/fuzz_target.cc
@@ -0,0 +1,79 @@
+#include <nghttp2/nghttp2.h>
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+void send_pending(nghttp2_session *session) {
+ for (;;) {
+ const uint8_t *data;
+ auto n = nghttp2_session_mem_send(session, &data);
+ if (n == 0) {
+ return;
+ }
+ }
+}
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+ nghttp2_session_callbacks_set_before_frame_send_callback(
+ callbacks, before_frame_send_callback);
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_server_new(&session, callbacks, nullptr);
+ nghttp2_session_callbacks_del(callbacks);
+
+ nghttp2_settings_entry iv{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+ send_pending(session);
+ nghttp2_session_mem_recv(session, data, size);
+ send_pending(session);
+
+ nghttp2_session_del(session);
+
+ return 0;
+}
diff --git a/fuzz/fuzz_target_fdp.cc b/fuzz/fuzz_target_fdp.cc
new file mode 100644
index 0000000..f94b964
--- /dev/null
+++ b/fuzz/fuzz_target_fdp.cc
@@ -0,0 +1,99 @@
+#include <string>
+#include <vector>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <nghttp2/nghttp2.h>
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ return 0;
+}
+} // namespace
+
+namespace {
+void send_pending(nghttp2_session *session) {
+ for (;;) {
+ const uint8_t *data;
+ auto n = nghttp2_session_mem_send(session, &data);
+ if (n == 0) {
+ return;
+ }
+ }
+}
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks *callbacks;
+
+ nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+ on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+ nghttp2_session_callbacks_set_before_frame_send_callback(
+ callbacks, before_frame_send_callback);
+ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_server_new(&session, callbacks, nullptr);
+ nghttp2_session_callbacks_del(callbacks);
+
+ FuzzedDataProvider data_provider(data, size);
+
+ /* Initialise a random iv */
+ nghttp2_settings_entry *iv;
+ int size_of_iv = data_provider.ConsumeIntegralInRange(1, 10);
+ iv = (nghttp2_settings_entry *)malloc(sizeof(nghttp2_settings_entry) *
+ size_of_iv);
+ for (int i = 0; i < size_of_iv; i++) {
+ iv[i].settings_id = data_provider.ConsumeIntegralInRange(0, 1000);
+ iv[i].value = data_provider.ConsumeIntegralInRange(0, 1000);
+ }
+
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, size_of_iv);
+ send_pending(session);
+
+ std::vector<uint8_t> d = data_provider.ConsumeRemainingBytes<uint8_t>();
+ nghttp2_session_mem_recv(session, d.data(), d.size());
+
+ send_pending(session);
+
+ nghttp2_session_del(session);
+
+ free(iv);
+
+ return 0;
+}
diff --git a/genauthoritychartbl.py b/genauthoritychartbl.py
new file mode 100755
index 0000000..1b10028
--- /dev/null
+++ b/genauthoritychartbl.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+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') 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/gendowncasetbl.py b/gendowncasetbl.py
new file mode 100755
index 0000000..04aeda7
--- /dev/null
+++ b/gendowncasetbl.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+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
index 0000000..2ac3c37
--- /dev/null
+++ b/genheaderfunc.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+from gentokenlookup import gentokenlookup
+
+HEADERS = [
+ ':authority',
+ ':method',
+ ':path',
+ ':scheme',
+ ':status',
+ ':host', # for spdy
+ ':protocol',
+ 'expect',
+ 'host',
+ 'if-modified-since',
+ "te",
+ "cookie",
+ "http2-settings",
+ "server",
+ "via",
+ "forwarded",
+ "x-forwarded-for",
+ "x-forwarded-proto",
+ "alt-svc",
+ "content-length",
+ "location",
+ "trailer",
+ "link",
+ "accept-encoding",
+ "accept-language",
+ "cache-control",
+ "user-agent",
+ "date",
+ "content-type",
+ "early-data",
+ "sec-websocket-accept",
+ "sec-websocket-key",
+ "priority",
+ # disallowed h1 headers
+ 'connection',
+ 'keep-alive',
+ 'proxy-connection',
+ 'transfer-encoding',
+ 'upgrade'
+]
+
+if __name__ == '__main__':
+ gentokenlookup(HEADERS, 'HD_')
diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py
new file mode 100755
index 0000000..d15d0b5
--- /dev/null
+++ b/genlibtokenlookup.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+
+HEADERS = [
+ (':authority', 0),
+ (':method', 1),
+ (':method', 2),
+ (':path', 3),
+ (':path', 4),
+ (':scheme', 5),
+ (':scheme', 6),
+ (':status', 7),
+ (':status', 8),
+ (':status', 9),
+ (':status', 10),
+ (':status', 11),
+ (':status', 12),
+ (':status', 13),
+ ('accept-charset', 14),
+ ('accept-encoding', 15),
+ ('accept-language', 16),
+ ('accept-ranges', 17),
+ ('accept', 18),
+ ('access-control-allow-origin', 19),
+ ('age', 20),
+ ('allow', 21),
+ ('authorization', 22),
+ ('cache-control', 23),
+ ('content-disposition', 24),
+ ('content-encoding', 25),
+ ('content-language', 26),
+ ('content-length', 27),
+ ('content-location', 28),
+ ('content-range', 29),
+ ('content-type', 30),
+ ('cookie', 31),
+ ('date', 32),
+ ('etag', 33),
+ ('expect', 34),
+ ('expires', 35),
+ ('from', 36),
+ ('host', 37),
+ ('if-match', 38),
+ ('if-modified-since', 39),
+ ('if-none-match', 40),
+ ('if-range', 41),
+ ('if-unmodified-since', 42),
+ ('last-modified', 43),
+ ('link', 44),
+ ('location', 45),
+ ('max-forwards', 46),
+ ('proxy-authenticate', 47),
+ ('proxy-authorization', 48),
+ ('range', 49),
+ ('referer', 50),
+ ('refresh', 51),
+ ('retry-after', 52),
+ ('server', 53),
+ ('set-cookie', 54),
+ ('strict-transport-security', 55),
+ ('transfer-encoding', 56),
+ ('user-agent', 57),
+ ('vary', 58),
+ ('via', 59),
+ ('www-authenticate', 60),
+ ('te', None),
+ ('connection', None),
+ ('keep-alive',None),
+ ('proxy-connection', None),
+ ('upgrade', None),
+ (':protocol', None),
+ ('priority', None),
+]
+
+def to_enum_hd(k):
+ res = 'NGHTTP2_TOKEN_'
+ 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] = []
+ if k not in ent[c]:
+ ent[c].append(k)
+
+ return res
+
+def gen_enum():
+ name = ''
+ print('typedef enum {')
+ for k, token in HEADERS:
+ if token is None:
+ print(' {},'.format(to_enum_hd(k)))
+ else:
+ if name != k:
+ name = k
+ print(' {} = {},'.format(to_enum_hd(k), token))
+ print('} nghttp2_token;')
+
+def gen_index_header():
+ print('''\
+static int32_t 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[{}]) {{'''.format(size - 1))
+ for c in sorted(ents.keys()):
+ headers = sorted(ents[c])
+ print('''\
+ case '{}':'''.format(c))
+ for k in headers:
+ print('''\
+ if (memeq("{}", 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/genmethodchartbl.py b/genmethodchartbl.py
new file mode 100755
index 0000000..a0c3a37
--- /dev/null
+++ b/genmethodchartbl.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+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') 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/genmethodfunc.py b/genmethodfunc.py
new file mode 100755
index 0000000..436a77a
--- /dev/null
+++ b/genmethodfunc.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+from io import StringIO
+
+from gentokenlookup import gentokenlookup
+
+# copied from llhttp.h, and stripped trailing spaces and backslashes.
+SRC = '''
+ XX(0, DELETE, DELETE)
+ XX(1, GET, GET)
+ XX(2, HEAD, HEAD)
+ XX(3, POST, POST)
+ XX(4, PUT, PUT)
+ XX(5, CONNECT, CONNECT)
+ XX(6, OPTIONS, OPTIONS)
+ XX(7, TRACE, TRACE)
+ 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)
+ XX(16, BIND, BIND)
+ XX(17, REBIND, REBIND)
+ XX(18, UNBIND, UNBIND)
+ XX(19, ACL, ACL)
+ XX(20, REPORT, REPORT)
+ XX(21, MKACTIVITY, MKACTIVITY)
+ XX(22, CHECKOUT, CHECKOUT)
+ XX(23, MERGE, MERGE)
+ XX(24, MSEARCH, M-SEARCH)
+ XX(25, NOTIFY, NOTIFY)
+ XX(26, SUBSCRIBE, SUBSCRIBE)
+ XX(27, UNSUBSCRIBE, UNSUBSCRIBE)
+ XX(28, PATCH, PATCH)
+ XX(29, PURGE, PURGE)
+ XX(30, MKCALENDAR, MKCALENDAR)
+ XX(31, LINK, LINK)
+ XX(32, UNLINK, UNLINK)
+ XX(33, SOURCE, SOURCE)
+'''
+
+if __name__ == '__main__':
+ methods = []
+ for line in StringIO(SRC):
+ line = line.strip()
+ if not line.startswith('XX'):
+ continue
+ _, m, _ = line.split(',', 2)
+ methods.append(m.strip())
+ gentokenlookup(methods, 'HTTP_')
diff --git a/gennghttpxfun.py b/gennghttpxfun.py
new file mode 100755
index 0000000..2977407
--- /dev/null
+++ b/gennghttpxfun.py
@@ -0,0 +1,242 @@
+#!/usr/bin/env python3
+
+from gentokenlookup import gentokenlookup
+
+OPTIONS = [
+ "private-key-file",
+ "private-key-passwd-file",
+ "certificate-file",
+ "dh-param-file",
+ "subcert",
+ "backend",
+ "frontend",
+ "workers",
+ "http2-max-concurrent-streams",
+ "log-level",
+ "daemon",
+ "http2-proxy",
+ "http2-bridge",
+ "client-proxy",
+ "add-x-forwarded-for",
+ "strip-incoming-x-forwarded-for",
+ "no-via",
+ "frontend-http2-read-timeout",
+ "frontend-read-timeout",
+ "frontend-write-timeout",
+ "backend-read-timeout",
+ "backend-write-timeout",
+ "stream-read-timeout",
+ "stream-write-timeout",
+ "accesslog-file",
+ "accesslog-syslog",
+ "accesslog-format",
+ "errorlog-file",
+ "errorlog-syslog",
+ "backend-keep-alive-timeout",
+ "frontend-http2-window-bits",
+ "backend-http2-window-bits",
+ "frontend-http2-connection-window-bits",
+ "backend-http2-connection-window-bits",
+ "frontend-no-tls",
+ "backend-no-tls",
+ "backend-tls-sni-field",
+ "pid-file",
+ "user",
+ "syslog-facility",
+ "backlog",
+ "ciphers",
+ "client",
+ "insecure",
+ "cacert",
+ "backend-ipv4",
+ "backend-ipv6",
+ "backend-http-proxy-uri",
+ "read-rate",
+ "read-burst",
+ "write-rate",
+ "write-burst",
+ "worker-read-rate",
+ "worker-read-burst",
+ "worker-write-rate",
+ "worker-write-burst",
+ "npn-list",
+ "tls-proto-list",
+ "verify-client",
+ "verify-client-cacert",
+ "client-private-key-file",
+ "client-cert-file",
+ "frontend-http2-dump-request-header",
+ "frontend-http2-dump-response-header",
+ "http2-no-cookie-crumbling",
+ "frontend-frame-debug",
+ "padding",
+ "altsvc",
+ "add-request-header",
+ "add-response-header",
+ "worker-frontend-connections",
+ "no-location-rewrite",
+ "no-host-rewrite",
+ "backend-http1-connections-per-host",
+ "backend-http1-connections-per-frontend",
+ "listener-disable-timeout",
+ "tls-ticket-key-file",
+ "rlimit-nofile",
+ "backend-request-buffer",
+ "backend-response-buffer",
+ "no-server-push",
+ "backend-http2-connections-per-worker",
+ "fetch-ocsp-response-file",
+ "ocsp-update-interval",
+ "no-ocsp",
+ "include",
+ "tls-ticket-key-cipher",
+ "host-rewrite",
+ "tls-session-cache-memcached",
+ "tls-session-cache-memcached-tls",
+ "tls-ticket-key-memcached",
+ "tls-ticket-key-memcached-interval",
+ "tls-ticket-key-memcached-max-retry",
+ "tls-ticket-key-memcached-max-fail",
+ "mruby-file",
+ "accept-proxy-protocol",
+ "conf",
+ "fastopen",
+ "tls-dyn-rec-warmup-threshold",
+ "tls-dyn-rec-idle-timeout",
+ "add-forwarded",
+ "strip-incoming-forwarded",
+ "forwarded-by",
+ "forwarded-for",
+ "response-header-field-buffer",
+ "max-response-header-fields",
+ "request-header-field-buffer",
+ "max-request-header-fields",
+ "header-field-buffer",
+ "max-header-fields",
+ "no-http2-cipher-block-list",
+ "no-http2-cipher-black-list",
+ "backend-http1-tls",
+ "tls-session-cache-memcached-cert-file",
+ "tls-session-cache-memcached-private-key-file",
+ "tls-session-cache-memcached-address-family",
+ "tls-ticket-key-memcached-tls",
+ "tls-ticket-key-memcached-cert-file",
+ "tls-ticket-key-memcached-private-key-file",
+ "tls-ticket-key-memcached-address-family",
+ "backend-address-family",
+ "frontend-http2-max-concurrent-streams",
+ "backend-http2-max-concurrent-streams",
+ "backend-connections-per-frontend",
+ "backend-tls",
+ "backend-connections-per-host",
+ "error-page",
+ "no-kqueue",
+ "frontend-http2-settings-timeout",
+ "backend-http2-settings-timeout",
+ "api-max-request-body",
+ "backend-max-backoff",
+ "server-name",
+ "no-server-rewrite",
+ "frontend-http2-optimize-write-buffer-size",
+ "frontend-http2-optimize-window-size",
+ "frontend-http2-window-size",
+ "frontend-http2-connection-window-size",
+ "backend-http2-window-size",
+ "backend-http2-connection-window-size",
+ "frontend-http2-encoder-dynamic-table-size",
+ "frontend-http2-decoder-dynamic-table-size",
+ "backend-http2-encoder-dynamic-table-size",
+ "backend-http2-decoder-dynamic-table-size",
+ "ecdh-curves",
+ "tls-sct-dir",
+ "backend-connect-timeout",
+ "dns-cache-timeout",
+ "dns-lookup-timeout",
+ "dns-max-try",
+ "frontend-keep-alive-timeout",
+ "psk-secrets",
+ "client-psk-secrets",
+ "client-no-http2-cipher-block-list",
+ "client-no-http2-cipher-black-list",
+ "client-ciphers",
+ "accesslog-write-early",
+ "tls-min-proto-version",
+ "tls-max-proto-version",
+ "redirect-https-port",
+ "frontend-max-requests",
+ "single-thread",
+ "single-process",
+ "no-add-x-forwarded-proto",
+ "no-strip-incoming-x-forwarded-proto",
+ "ocsp-startup",
+ "no-verify-ocsp",
+ "verify-client-tolerate-expired",
+ "ignore-per-pattern-mruby-error",
+ "tls-no-postpone-early-data",
+ "tls-max-early-data",
+ "tls13-ciphers",
+ "tls13-client-ciphers",
+ "no-strip-incoming-early-data",
+ "quic-bpf-program-file",
+ "no-quic-bpf",
+ "http2-altsvc",
+ "frontend-http3-read-timeout",
+ "frontend-quic-idle-timeout",
+ "frontend-quic-debug-log",
+ "frontend-http3-window-size",
+ "frontend-http3-connection-window-size",
+ "frontend-http3-max-window-size",
+ "frontend-http3-max-connection-window-size",
+ "frontend-http3-max-concurrent-streams",
+ "frontend-quic-early-data",
+ "frontend-quic-qlog-dir",
+ "frontend-quic-require-token",
+ "frontend-quic-congestion-controller",
+ "quic-server-id",
+ "frontend-quic-secret-file",
+ "rlimit-memlock",
+ "max-worker-processes",
+ "worker-process-grace-shutdown-period",
+ "frontend-quic-initial-rtt",
+ "require-http-scheme",
+ "tls-ktls",
+ "alpn-list",
+]
+
+LOGVARS = [
+ "remote_addr",
+ "time_local",
+ "time_iso8601",
+ "request",
+ "status",
+ "body_bytes_sent",
+ "remote_port",
+ "server_port",
+ "request_time",
+ "pid",
+ "alpn",
+ "ssl_cipher",
+ "ssl_protocol",
+ "ssl_session_id",
+ "ssl_session_reused",
+ "tls_cipher",
+ "tls_protocol",
+ "tls_session_id",
+ "tls_session_reused",
+ "tls_sni",
+ "tls_client_fingerprint_sha256",
+ "tls_client_fingerprint_sha1",
+ "tls_client_subject_name",
+ "tls_client_issuer_name",
+ "tls_client_serial",
+ "backend_host",
+ "backend_port",
+ "method",
+ "path",
+ "path_without_query",
+ "protocol_version",
+]
+
+if __name__ == '__main__':
+ gentokenlookup(OPTIONS, 'SHRPX_OPTID_', value_type='char', comp_fun='util::strieq_l')
+ gentokenlookup(LOGVARS, 'LogFragmentType::', value_type='char', comp_fun='util::strieq_l', return_type='LogFragmentType', fail_value='LogFragmentType::NONE')
diff --git a/gennmchartbl.py b/gennmchartbl.py
new file mode 100755
index 0000000..b75b9a6
--- /dev/null
+++ b/gennmchartbl.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+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/genpathchartbl.py b/genpathchartbl.py
new file mode 100755
index 0000000..cd8c714
--- /dev/null
+++ b/genpathchartbl.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+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 (0x21 <= i and i < 0x7f):
+ sys.stdout.write('1 /* {} */, '.format(chr(i)))
+ elif 0x80 <= i:
+ sys.stdout.write('1 /* {} */, '.format(hex(i)))
+ else:
+ sys.stdout.write('0 /* {} */, '.format(name(i)))
+ if (i + 1)%4 == 0:
+ sys.stdout.write('\n')
diff --git a/gentokenlookup.py b/gentokenlookup.py
new file mode 100644
index 0000000..8277858
--- /dev/null
+++ b/gentokenlookup.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+def to_enum_hd(k, prefix):
+ res = prefix
+ 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(tokens, prefix):
+ print('''\
+enum {''')
+ for k in sorted(tokens):
+ print('''\
+ {},'''.format(to_enum_hd(k, prefix)))
+ print('''\
+ {}MAXIDX,
+}};'''.format(prefix))
+
+def gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value):
+ print('''\
+{} lookup_token(const {} *name, size_t namelen) {{
+ switch (namelen) {{'''.format(return_type, value_type))
+ b = build_header(tokens)
+ for size in sorted(b.keys()):
+ ents = b[size]
+ print('''\
+ case {}:'''.format(size))
+ print('''\
+ switch (name[{}]) {{'''.format(size - 1))
+ for c in sorted(ents.keys()):
+ headers = sorted(ents[c])
+ print('''\
+ case '{}':'''.format(c))
+ for k in headers:
+ print('''\
+ if ({}("{}", name, {})) {{
+ return {};
+ }}'''.format(comp_fun, k[:-1], size - 1, to_enum_hd(k, prefix)))
+ print('''\
+ break;''')
+ print('''\
+ }
+ break;''')
+ print('''\
+ }}
+ return {};
+}}'''.format(fail_value))
+
+def gentokenlookup(tokens, prefix, value_type='uint8_t', comp_fun='util::streq_l', return_type='int', fail_value='-1'):
+ gen_enum(tokens, prefix)
+ print()
+ gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value)
diff --git a/genvchartbl.py b/genvchartbl.py
new file mode 100755
index 0000000..a4ff8d1
--- /dev/null
+++ b/genvchartbl.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+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)))
+ 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
index 0000000..6a0db27
--- /dev/null
+++ b/git-clang-format
@@ -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/go.mod b/go.mod
new file mode 100644
index 0000000..4469aec
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,24 @@
+module github.com/nghttp2/nghttp2
+
+go 1.21.1
+
+require (
+ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
+ github.com/quic-go/quic-go v0.41.0
+ github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8
+ golang.org/x/net v0.20.0
+)
+
+require (
+ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+ github.com/onsi/ginkgo/v2 v2.9.5 // indirect
+ github.com/quic-go/qpack v0.4.0 // indirect
+ go.uber.org/mock v0.3.0 // indirect
+ golang.org/x/crypto v0.18.0 // indirect
+ golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
+ golang.org/x/mod v0.11.0 // indirect
+ golang.org/x/sys v0.16.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/tools v0.9.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..bb79e47
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,57 @@
+github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
+github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
+github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
+github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
+github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
+github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8 h1:zKJxuRe+a0O34V81GAZWOrotuU6mveT30QLjJ7OPMMg=
+github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8/go.mod h1:gTqc3Q4boc+cKRlSFywTYdX9t6VGRcsThlNIWwaL3Dc=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
+golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
+golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/help2rst.py b/help2rst.py
new file mode 100755
index 0000000..245f669
--- /dev/null
+++ b/help2rst.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# script to produce rst file from program's help output.
+
+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('''
+.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
+
+.. program:: {cmdname}
+
+{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
+ in_footer = 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 == '--':
+ in_footer = True
+ continue
+
+ if in_footer:
+ print(line.strip())
+ continue
+
+ 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 *, but don't escape * if it is used as bullet list.
+ m = re.match(r'^\s*\*\s+', text)
+ if m:
+ text = text[:len(m.group(0))] + re.sub(r'\*', r'\*', text[len(m.group(0)):])
+ else:
+ text = re.sub(r'\*', r'\*', text)
+ # markup option reference
+ text = re.sub(r'(^|\s)(-[a-zA-Z]|--[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/.gitignore b/integration-tests/.gitignore
new file mode 100644
index 0000000..f40c109
--- /dev/null
+++ b/integration-tests/.gitignore
@@ -0,0 +1,3 @@
+# generated files
+config.go
+setenv
diff --git a/integration-tests/CMakeLists.txt b/integration-tests/CMakeLists.txt
new file mode 100644
index 0000000..cc92e9f
--- /dev/null
+++ b/integration-tests/CMakeLists.txt
@@ -0,0 +1,45 @@
+set(GO_FILES
+ nghttpx_http1_test.go
+ nghttpx_http2_test.go
+ server_tester.go
+ server_tester_http3.go
+)
+
+# XXX unused
+set(EXTRA_DIST
+ ${GO_FILES}
+ server.key
+ server.crt
+ alt-server.key
+ alt-server.crt
+ setenv
+ req-set-header.rb
+ resp-set-header.rb
+ req-return.rb
+ resp-return.rb
+)
+
+# 'go test' requires both config.go and the test files in the same directory.
+# For out-of-tree builds, config.go is normally not placed next to the source
+# files, so copy the tests to the build directory as a workaround.
+set(GO_BUILD_FILES)
+if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
+ foreach(gofile IN LISTS GO_FILES)
+ set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${gofile}")
+ add_custom_command(OUTPUT "${outfile}"
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}" "${outfile}"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}"
+ )
+ list(APPEND GO_BUILD_FILES "${outfile}")
+ endforeach()
+endif()
+
+if(ENABLE_HTTP3)
+ set(GO_TEST_TAGS quic)
+endif()
+
+add_custom_target(it
+ COMMAND sh setenv go test -v --tags=${GO_TEST_TAGS}
+ DEPENDS ${GO_BUILD_FILES}
+)
diff --git a/integration-tests/Makefile.am b/integration-tests/Makefile.am
new file mode 100644
index 0000000..21166ed
--- /dev/null
+++ b/integration-tests/Makefile.am
@@ -0,0 +1,52 @@
+# 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.
+
+GO_FILES = \
+ nghttpx_http1_test.go \
+ nghttpx_http2_test.go \
+ nghttpx_http3_test.go \
+ server_tester.go \
+ server_tester_http3.go
+
+EXTRA_DIST = \
+ CMakeLists.txt \
+ $(GO_FILES) \
+ server.key \
+ server.crt \
+ alt-server.key \
+ alt-server.crt \
+ setenv \
+ req-set-header.rb \
+ resp-set-header.rb \
+ req-return.rb \
+ resp-return.rb
+
+GO_TEST_TAGS =
+
+if ENABLE_HTTP3
+GO_TEST_TAGS += quic
+endif # ENABLE_HTTP3
+
+it:
+ for i in $(GO_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done
+ sh setenv go test -v --tags=${GO_TEST_TAGS}
diff --git a/integration-tests/alt-server.crt b/integration-tests/alt-server.crt
new file mode 100644
index 0000000..f003eb1
--- /dev/null
+++ b/integration-tests/alt-server.crt
@@ -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
index 0000000..a977663
--- /dev/null
+++ b/integration-tests/alt-server.key
@@ -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
index 0000000..3d79297
--- /dev/null
+++ b/integration-tests/config.go.in
@@ -0,0 +1,6 @@
+package nghttp2
+
+const (
+ buildDir = "@top_builddir@"
+ sourceDir = "@top_srcdir@"
+)
diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go
new file mode 100644
index 0000000..740396d
--- /dev/null
+++ b/integration-tests/nghttpx_http1_test.go
@@ -0,0 +1,1631 @@
+package nghttp2
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "regexp"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/net/http2/hpack"
+ "golang.org/x/net/websocket"
+)
+
+// TestH1H1PlainGET tests whether simple HTTP/1 GET request works.
+func TestH1H1PlainGET(t *testing.T) {
+ st := newServerTester(t, options{})
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1PlainGET",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with
+// Connection: close request header field works.
+func TestH1H1PlainGETClose(t *testing.T) {
+ st := newServerTester(t, options{})
+ 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)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1InvalidMethod tests that server rejects invalid method with
+// 501 status code
+func TestH1H1InvalidMethod(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1InvalidMethod",
+ method: "get",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotImplemented; 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1MultipleRequestCL\r\nContent-Length: 0\r\nContent-Length: 0\r\n\r\n",
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; 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(t, options{})
+// 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)
+// }
+// }
+
+// TestH1H1AffinityCookie tests that affinity cookie is sent back in
+// cleartext http.
+func TestH1H1AffinityCookie(t *testing.T) {
+ opts := options{
+ args: []string{"--affinity-cookie"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1AffinityCookie",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar`
+ validCookie := regexp.MustCompile(pattern)
+ if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
+ t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
+ }
+}
+
+// TestH1H1AffinityCookieTLS tests that affinity cookie is sent back
+// in https.
+func TestH1H1AffinityCookieTLS(t *testing.T) {
+ opts := options{
+ args: []string{"--alpn-h1", "--affinity-cookie"},
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1AffinityCookieTLS",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure`
+ validCookie := regexp.MustCompile(pattern)
+ if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
+ t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
+ }
+}
+
+// TestH1H1GracefulShutdown tests graceful shutdown.
+func TestH1H1GracefulShutdown(t *testing.T) {
+ st := newServerTester(t, options{})
+ 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, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+
+ if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil {
+ t.Fatalf("Error st.cmd.Process.Signal() = %v", err)
+ }
+
+ time.Sleep(150 * time.Millisecond)
+
+ res, err = st.http1(requestParam{
+ name: "TestH1H1GracefulShutdown-2",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; 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
+ b := make([]byte, 256)
+ if _, err := st.conn.Read(b); 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) {
+ opts := options{
+ args: []string{"--host-rewrite"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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)
+ }
+}
+
+// TestH1H1BadHost tests that server rejects request including bad
+// characters in host header field.
+func TestH1H1BadHost(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H1HBadHost\r\nHost: foo\"bar\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1BadAuthority tests that server rejects request including
+// bad characters in authority component of requset URI.
+func TestH1H1BadAuthority(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "GET http://foo\"bar/ HTTP/1.1\r\nTest-Case: TestH1H1HBadAuthority\r\nHost: foobar\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1BadScheme tests that server rejects request including
+// bad characters in scheme component of requset URI.
+func TestH1H1BadScheme(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "GET http*://example.com/ HTTP/1.1\r\nTest-Case: TestH1H1HBadScheme\r\nHost: example.com\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request
+// without Host header field
+func TestH1H1HTTP10(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusOK; 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusOK; 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)
+ }
+}
+
+// TestH1H1RequestTrailer tests request trailer part is forwarded to
+// backend.
+func TestH1H1RequestTrailer(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ buf := make([]byte, 4096)
+ for {
+ _, err := r.Body.Read(buf)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("r.Body.Read() = %v", err)
+ }
+ }
+ if got, want := r.Trailer.Get("foo"), "bar"; got != want {
+ t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1RequestTrailer",
+ body: []byte("1"),
+ trailer: []hpack.HeaderField{
+ pair("foo", "bar"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HeaderFieldBufferPath tests that request with request path
+// larger than configured buffer size is rejected.
+func TestH1H1HeaderFieldBufferPath(t *testing.T) {
+ // The value 100 is chosen so that sum of header fields bytes
+ // does not exceed it. We use > 100 bytes URI to exceed this
+ // limit.
+ opts := options{
+ args: []string{"--request-header-field-buffer=100"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("execution path should not be here")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1HeaderFieldBufferPath",
+ path: "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HeaderFieldBuffer tests that request with header fields
+// larger than configured buffer size is rejected.
+func TestH1H1HeaderFieldBuffer(t *testing.T) {
+ opts := options{
+ args: []string{"--request-header-field-buffer=10"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("execution path should not be here")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1HeaderFieldBuffer",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HeaderFields tests that request with header fields more
+// than configured number is rejected.
+func TestH1H1HeaderFields(t *testing.T) {
+ opts := options{
+ args: []string{"--max-request-header-fields=1"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("execution path should not be here")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1HeaderFields",
+ header: []hpack.HeaderField{
+ // Add extra header field to ensure that
+ // header field limit exceeds
+ pair("Connection", "close"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1Websocket tests that HTTP Upgrade to WebSocket works.
+func TestH1H1Websocket(t *testing.T) {
+ opts := options{
+ handler: websocket.Handler(func(ws *websocket.Conn) {
+ if _, err := io.Copy(ws, ws); err != nil {
+ t.Fatalf("Error io.Copy() = %v", err)
+ }
+ }).ServeHTTP,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ content := []byte("hello world")
+ res := st.websocket(requestParam{
+ name: "TestH1H1Websocket",
+ body: content,
+ })
+ if got, want := res.body, content; !bytes.Equal(got, want) {
+ t.Errorf("echo: %q; want %q", got, want)
+ }
+}
+
+// TestH1H1ReqPhaseSetHeader tests mruby request phase hook
+// modifies request header fields.
+func TestH1H1ReqPhaseSetHeader(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-set-header.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
+ t.Errorf("User-Agent = %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1ReqPhaseSetHeader",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1ReqPhaseReturn tests mruby request phase hook returns
+// custom response.
+func TestH1H1ReqPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1ReqPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from req"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1ReqPhaseReturnCONNECTMethod tests that mruby request phase
+// hook resets llhttp HPE_PAUSED_UPGRADE.
+func TestH1H1ReqPhaseReturnCONNECTMethod(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusNotFound; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+
+ hdCheck := func() {
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+
+ for _, tt := range hdtests {
+ if got, want := resp.Header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if _, err := io.ReadAll(resp.Body); err != nil {
+ t.Fatalf("Error io.ReadAll() = %v", err)
+ }
+ }
+
+ hdCheck()
+
+ if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusNotFound; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+
+ hdCheck()
+
+ if _, err := io.ReadAll(resp.Body); err != nil {
+ t.Fatalf("Error io.ReadAll() = %v", err)
+ }
+}
+
+// TestH1H1RespPhaseSetHeader tests mruby response phase hook modifies
+// response header fields.
+func TestH1H1RespPhaseSetHeader(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/resp-set-header.rb"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1RespPhaseSetHeader",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ if got, want := res.header.Get("alpha"), "bravo"; got != want {
+ t.Errorf("alpha = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1RespPhaseReturn tests mruby response phase hook returns
+// custom response.
+func TestH1H1RespPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/resp-return.rb"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1RespPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "21"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from resp"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HTTPSRedirect tests that the request to the backend which
+// requires TLS is redirected to https URI.
+func TestH1H1HTTPSRedirect(t *testing.T) {
+ opts := options{
+ args: []string{"--redirect-if-not-tls"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1HTTPSRedirect",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusPermanentRedirect; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+ if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
+ t.Errorf("location: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1HTTPSRedirectPort tests that the request to the backend
+// which requires TLS is redirected to https URI with given port.
+func TestH1H1HTTPSRedirectPort(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--redirect-if-not-tls",
+ "--redirect-https-port=8443",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ path: "/foo?bar",
+ name: "TestH1H1HTTPSRedirectPort",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusPermanentRedirect; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+ if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
+ t.Errorf("location: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1POSTRequests tests that server can handle 2 requests with
+// request body.
+func TestH1H1POSTRequests(t *testing.T) {
+ st := newServerTester(t, options{})
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1POSTRequestsNo1",
+ body: make([]byte, 1),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ res, err = st.http1(requestParam{
+ name: "TestH1H1POSTRequestsNo2",
+ body: make([]byte, 65536),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1CONNECTMethodFailure tests that CONNECT method failure
+// resets llhttp HPE_PAUSED_UPGRADE.
+func TestH1H1CONNECTMethodFailure(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get("required-header") == "" {
+ w.WriteHeader(http.StatusNotFound)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusNotFound; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+
+ if _, err := io.ReadAll(resp.Body); err != nil {
+ t.Fatalf("Error io.ReadAll() = %v", err)
+ }
+
+ if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\r\nrequired-header: foo\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusOK; got != want {
+ t.Errorf("status: %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) {
+// opts := options{
+// args: []string{"--http2-bridge"},
+// }
+// st := newServerTester(t, opts)
+// 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusOK; 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusOK; 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ 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 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: 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")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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 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) {
+ opts := options{
+ args: []string{"--http2-bridge", "--no-via"},
+ handler: 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")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+}
+
+// TestH1H2ReqPhaseReturn tests mruby request phase hook returns
+// custom response.
+func TestH1H2ReqPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/req-return.rb",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H2ReqPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from req"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH1H2RespPhaseReturn tests mruby response phase hook returns
+// custom response.
+func TestH1H2RespPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/resp-return.rb",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H2RespPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "21"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from resp"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH1H2TE tests that "te: trailers" header is forwarded to HTTP/2
+// backend server by stripping other encodings.
+func TestH1H2TE(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("te"), "trailers"; got != want {
+ t.Errorf("te: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H2TE",
+ header: []hpack.HeaderField{
+ pair("te", "foo,trailers,bar"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1APIBackendconfig exercise backendconfig API endpoint routine
+// for successful case.
+func TestH1APIBackendconfig(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1APIBackendconfig",
+ path: "/api/v1beta1/backendconfig",
+ method: "PUT",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH1APIBackendconfigQuery exercise backendconfig API endpoint
+// routine with query.
+func TestH1APIBackendconfigQuery(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1APIBackendconfigQuery",
+ path: "/api/v1beta1/backendconfig?foo=bar",
+ method: "PUT",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH1APIBackendconfigBadMethod exercise backendconfig API endpoint
+// routine with bad method.
+func TestH1APIBackendconfigBadMethod(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1APIBackendconfigBadMethod",
+ path: "/api/v1beta1/backendconfig",
+ method: "GET",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusMethodNotAllowed; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Failure"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 405; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH1APIConfigrevision tests configrevision API.
+func TestH1APIConfigrevision(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1APIConfigrevision",
+ path: "/api/v1beta1/configrevision",
+ method: "GET",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want = %v", got, want)
+ }
+
+ var apiResp APIResponse
+ d := json.NewDecoder(bytes.NewBuffer(res.body))
+ d.UseNumber()
+ err = d.Decode(&apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshalling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Data["configRevision"], json.Number("0"); got != want {
+ t.Errorf(`apiResp.Data["configRevision"]: %v %t; want %v`, got, got, want)
+ }
+}
+
+// TestH1APINotFound exercise backendconfig API endpoint routine when
+// API endpoint is not found.
+func TestH1APINotFound(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1APINotFound",
+ path: "/api/notfound",
+ method: "GET",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Failure"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 404; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH1Healthmon tests health monitor endpoint.
+func TestH1Healthmon(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3011;healthmon;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3011,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1Healthmon",
+ path: "/alpha/bravo",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH1ResponseBeforeRequestEnd tests the situation where response
+// ends before request body finishes.
+func TestH1ResponseBeforeRequestEnd(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, fmt.Sprintf("POST / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1ResponseBeforeRequestEnd\r\nContent-Length: 1000000\r\n\r\n",
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusNotFound; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1ChunkedEndsPrematurely tests that an HTTP/1.1 request fails
+// if the backend chunked encoded response ends prematurely.
+func TestH1H1ChunkedEndsPrematurely(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ _, err := st.http1(requestParam{
+ name: "TestH1H1ChunkedEndsPrematurely",
+ })
+ if err == nil {
+ t.Fatal("st.http1() should fail")
+ }
+}
+
+// TestH1H1RequestMalformedTransferEncoding tests that server rejects
+// request which contains malformed transfer-encoding.
+func TestH1H1RequestMalformedTransferEncoding(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1RequestMalformedTransferEncoding\r\nTransfer-Encoding: ,chunked\r\n\r\n",
+ 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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1ResponseMalformedTransferEncoding tests a request fails if
+// its response contains malformed transfer-encoding.
+func TestH1H1ResponseMalformedTransferEncoding(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: ,chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH1H1ResponseMalformedTransferEncoding",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+ if got, want := res.status, http.StatusBadGateway; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH1H1ResponseUnknownTransferEncoding tests a request succeeds if
+// its response contains unknown transfer-encoding.
+func TestH1H1ResponseUnknownTransferEncoding(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: foo\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1ResponseUnknownTransferEncoding\r\n\r\n",
+ st.authority)); err != nil {
+ t.Fatalf("Error: io.WriteString() = %v", err)
+ }
+
+ r := bufio.NewReader(st.conn)
+
+ resp := make([]byte, 4096)
+
+ resplen, err := r.Read(resp)
+ if err != nil {
+ t.Fatalf("Error: r.Read() = %v", err)
+ }
+
+ resp = resp[:resplen]
+
+ const expect = "HTTP/1.1 200 OK\r\nTransfer-Encoding: foo\r\nConnection: close\r\nServer: nghttpx\r\nVia: 1.1 nghttpx\r\n\r\n"
+
+ if got, want := string(resp), expect; got != want {
+ t.Errorf("resp = %v, want %v", got, want)
+ }
+}
+
+// TestH1H1RequestHTTP10TransferEncoding tests that server rejects
+// HTTP/1.0 request which contains transfer-encoding.
+func TestH1H1RequestHTTP10TransferEncoding(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1RequestHTTP10TransferEncoding\r\nTransfer-Encoding: chunked\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)
+ }
+
+ defer resp.Body.Close()
+
+ if got, want := resp.StatusCode, http.StatusBadRequest; got != want {
+ t.Errorf("status: %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
index 0000000..5324a18
--- /dev/null
+++ b/integration-tests/nghttpx_http2_test.go
@@ -0,0 +1,3740 @@
+package nghttp2
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "regexp"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/hpack"
+)
+
+// TestH2H1PlainGET tests whether simple HTTP/2 GET request works.
+func TestH2H1PlainGET(t *testing.T) {
+ st := newServerTester(t, options{})
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1PlainGET",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddXfp tests that server appends :scheme to the existing
+// x-forwarded-proto header field.
+func TestH2H1AddXfp(t *testing.T) {
+ opts := options{
+ args: []string{"--no-strip-incoming-x-forwarded-proto"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "foo, http"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1NoAddXfp tests that server does not append :scheme to the
+// existing x-forwarded-proto header field.
+func TestH2H1NoAddXfp(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--no-add-x-forwarded-proto",
+ "--no-strip-incoming-x-forwarded-proto",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "foo"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1NoAddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1StripXfp tests that server strips incoming
+// x-forwarded-proto header field.
+func TestH2H1StripXfp(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "http"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1StripXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1StripNoAddXfp tests that server strips incoming
+// x-forwarded-proto header field, and does not add another.
+func TestH2H1StripNoAddXfp(t *testing.T) {
+ opts := options{
+ args: []string{"--no-add-x-forwarded-proto"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, found := r.Header["X-Forwarded-Proto"]; found {
+ t.Errorf("X-Forwarded-Proto = %q; want nothing", got)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1StripNoAddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddXff tests that server generates X-Forwarded-For header
+// field when forwarding request to backend.
+func TestH2H1AddXff(t *testing.T) {
+ opts := options{
+ args: []string{"--add-x-forwarded-for"},
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddXff",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddXff2 tests that server appends X-Forwarded-For header
+// field to existing one when forwarding request to backend.
+func TestH2H1AddXff2(t *testing.T) {
+ opts := options{
+ args: []string{"--add-x-forwarded-for"},
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddXff2",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for
+// option.
+func TestH2H1StripXff(t *testing.T) {
+ opts := options{
+ args: []string{"--strip-incoming-x-forwarded-for"},
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1StripXff",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and
+// --add-x-forwarded-for options.
+func TestH2H1StripAddXff(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--strip-incoming-x-forwarded-for",
+ "--add-x-forwarded-for",
+ },
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1StripAddXff",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedObfuscated tests that server generates
+// Forwarded header field with obfuscated "by" and "for" parameters.
+func TestH2H1AddForwardedObfuscated(t *testing.T) {
+ opts := options{
+ args: []string{"--add-forwarded=by,for,host,proto"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ pattern := fmt.Sprintf(`by=_[^;]+;for=_[^;]+;host="127\.0\.0\.1:%v";proto=http`, serverPort)
+ validFwd := regexp.MustCompile(pattern)
+ got := r.Header.Get("Forwarded")
+
+ if !validFwd.MatchString(got) {
+ t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedObfuscated",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedByIP tests that server generates Forwarded header
+// field with IP address in "by" parameter.
+func TestH2H1AddForwardedByIP(t *testing.T) {
+ opts := options{
+ args: []string{"--add-forwarded=by,for", "--forwarded-by=ip"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ pattern := fmt.Sprintf(`by="127\.0\.0\.1:%v";for=_[^;]+`, serverPort)
+ validFwd := regexp.MustCompile(pattern)
+ if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
+ t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedByIP",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedForIP tests that server generates Forwarded header
+// field with IP address in "for" parameters.
+func TestH2H1AddForwardedForIP(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--add-forwarded=by,for,host,proto",
+ "--forwarded-by=_alpha",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ want := fmt.Sprintf(`by=_alpha;for=127.0.0.1;host="127.0.0.1:%v";proto=http`, serverPort)
+ if got := r.Header.Get("Forwarded"); got != want {
+ t.Errorf("Forwarded = %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedForIP",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedMerge tests that server generates Forwarded
+// header field with IP address in "by" and "for" parameters. The
+// generated values must be appended to the existing value.
+func TestH2H1AddForwardedMerge(t *testing.T) {
+ opts := options{
+ args: []string{"--add-forwarded=proto"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Forwarded"), `host=foo, proto=http`; got != want {
+ t.Errorf("Forwarded = %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedMerge",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedStrip tests that server generates Forwarded
+// header field with IP address in "by" and "for" parameters. The
+// generated values must not include the existing value.
+func TestH2H1AddForwardedStrip(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--strip-incoming-forwarded",
+ "--add-forwarded=proto",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Forwarded"), `proto=http`; got != want {
+ t.Errorf("Forwarded = %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedStrip",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1StripForwarded tests that server strips incoming Forwarded
+// header field.
+func TestH2H1StripForwarded(t *testing.T) {
+ opts := options{
+ args: []string{"--strip-incoming-forwarded"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, found := r.Header["Forwarded"]; found {
+ t.Errorf("Forwarded = %v; want nothing", got)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1StripForwarded",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AddForwardedStatic tests that server generates Forwarded
+// header field with the given static obfuscated string for "by"
+// parameter.
+func TestH2H1AddForwardedStatic(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--add-forwarded=by,for",
+ "--forwarded-by=_alpha",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ pattern := `by=_alpha;for=_[^;]+`
+ validFwd := regexp.MustCompile(pattern)
+ if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
+ t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AddForwardedStatic",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1GenerateVia tests that server generates Via header field to and
+// from backend server.
+func TestH2H1GenerateVia(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Via"), "2 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Via"), "foo, 2 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+ w.Header().Add("Via", "bar")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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) {
+ opts := options{
+ args: []string{"--no-via"},
+ handler: 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")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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) {
+ opts := options{
+ args: []string{"--host-rewrite"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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(t, options{})
+ 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ // we set content-length: 1024, but only send 3 bytes.
+ w.Header().Add("Content-Length", "1024")
+ if _, err := w.Write([]byte("foo")); err != nil {
+ t.Fatalf("Error w.Write() = %v", err)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1BadResponseCL",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ want := http2.ErrCodeInternal
+ 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) {
+ opts := options{
+ handler: 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")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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) {
+ opts := options{
+ handler: 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 := io.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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ChunkedRequestBody",
+ method: "POST",
+ body: []byte("foo"),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1MultipleRequestCL tests that server rejects request with
+// multiple Content-Length request header fields.
+func TestH2H1MultipleRequestCL(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+ if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
+ t.Errorf("res.errCode: %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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+ if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
+ t.Errorf("res.errCode: %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(t, options{})
+// 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)
+// }
+// }
+
+// TestH2H1InvalidMethod tests that server rejects invalid method with
+// 501.
+func TestH2H1InvalidMethod(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1InvalidMethod",
+ method: "get",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusNotImplemented; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1BadAuthority tests that server rejects request including
+// bad characters in :authority header field.
+func TestH2H1BadAuthority(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1BadAuthority",
+ authority: `foo\bar`,
+ })
+ 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", got, want)
+ }
+}
+
+// TestH2H1BadScheme tests that server rejects request including
+// bad characters in :scheme header field.
+func TestH2H1BadScheme(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1BadScheme",
+ scheme: "http*",
+ })
+ 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", 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) {
+ opts := options{
+ handler: 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)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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(t, options{})
+ 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, http.StatusOK; 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) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Error("server should not forward bad request")
+ },
+ }
+ st := newServerTester(t, opts)
+ 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) {
+ opts := options{
+ args: []string{"--subcert=" + testDir + "/alt-server.key:" + testDir + "/alt-server.crt"},
+ tls: true,
+ tlsConfig: &tls.Config{
+ ServerName: "alt-domain",
+ },
+ }
+ st := newServerTester(t, opts)
+ 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)
+ }
+}
+
+// TestH2H1TLSXfp tests nghttpx sends x-forwarded-proto header field
+// with http value since :scheme is http, even if the frontend
+// connection is encrypted.
+func TestH2H1TLSXfp(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("x-forwarded-proto"), "http"; got != want {
+ t.Errorf("x-forwarded-proto: want %v; got %v", want, got)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1TLSXfp",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ServerPush tests server push using Link header field from
+// backend server.
+func TestH2H1ServerPush(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ // only resources marked as rel=preload are pushed
+ if !strings.HasPrefix(r.URL.Path, "/css/") {
+ w.Header().Add("Link", "</css/main.css>; rel=preload, </foo>, </css/theme.css>; rel=preload")
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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, http.StatusOK; got != want {
+ t.Errorf("mainCSS.status: %v; want %v", got, want)
+ }
+ themeCSS := res.pushResponse[1]
+ if got, want := themeCSS.status, http.StatusOK; got != want {
+ t.Errorf("themeCSS.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RequestTrailer tests request trailer part is forwarded to
+// backend.
+func TestH2H1RequestTrailer(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ buf := make([]byte, 4096)
+ for {
+ _, err := r.Body.Read(buf)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("r.Body.Read() = %v", err)
+ }
+ }
+ if got, want := r.Trailer.Get("foo"), "bar"; got != want {
+ t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RequestTrailer",
+ body: []byte("1"),
+ trailer: []hpack.HeaderField{
+ pair("foo", "bar"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1HeaderFieldBuffer tests that request with header fields
+// larger than configured buffer size is rejected.
+func TestH2H1HeaderFieldBuffer(t *testing.T) {
+ opts := options{
+ args: []string{"--request-header-field-buffer=10"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("execution path should not be here")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1HeaderFieldBuffer",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1HeaderFields tests that request with header fields more
+// than configured number is rejected.
+func TestH2H1HeaderFields(t *testing.T) {
+ opts := options{
+ args: []string{"--max-request-header-fields=1"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("execution path should not be here")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1HeaderFields",
+ // we have at least 4 pseudo-header fields sent, and
+ // that ensures that buffer limit exceeds.
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ReqPhaseSetHeader tests mruby request phase hook
+// modifies request header fields.
+func TestH2H1ReqPhaseSetHeader(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-set-header.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
+ t.Errorf("User-Agent = %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ReqPhaseSetHeader",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ReqPhaseReturn tests mruby request phase hook returns
+// custom response.
+func TestH2H1ReqPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ReqPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from req"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RespPhaseSetHeader tests mruby response phase hook modifies
+// response header fields.
+func TestH2H1RespPhaseSetHeader(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/resp-set-header.rb"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RespPhaseSetHeader",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ if got, want := res.header.Get("alpha"), "bravo"; got != want {
+ t.Errorf("alpha = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RespPhaseReturn tests mruby response phase hook returns
+// custom response.
+func TestH2H1RespPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/resp-return.rb"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RespPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "21"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from resp"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1Upgrade tests HTTP Upgrade to HTTP/2
+func TestH2H1Upgrade(t *testing.T) {
+ st := newServerTester(t, options{})
+ defer st.Close()
+
+ res, err := st.http1(requestParam{
+ name: "TestH2H1Upgrade",
+ header: []hpack.HeaderField{
+ pair("Connection", "Upgrade, HTTP2-Settings"),
+ pair("Upgrade", "h2c"),
+ pair("HTTP2-Settings", "AAMAAABkAAQAAP__"),
+ },
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http1() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusSwitchingProtocols; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ res, err = st.http2(requestParam{
+ httpUpgrade: true,
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1ForwardedForObfuscated tests that Forwarded
+// header field includes obfuscated address even if PROXY protocol
+// version 1 containing TCP4 entry is accepted.
+func TestH2H1ProxyProtocolV1ForwardedForObfuscated(t *testing.T) {
+ pattern := `^for=_[^;]+$`
+ validFwd := regexp.MustCompile(pattern)
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=obfuscated",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
+ t.Errorf("Forwarded: %v; want pattern %v", got, pattern)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1ForwardedForObfuscated",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1TCP4 tests PROXY protocol version 1
+// containing TCP4 entry is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV1TCP4(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TCP4",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1TCP6 tests PROXY protocol version 1
+// containing TCP6 entry is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV1TCP6(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), `for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"`; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ::1 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TCP6",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1TCP4TLS tests PROXY protocol version 1 over
+// TLS containing TCP4 entry is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV1TCP4TLS(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ tcpData: []byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n"),
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TCP4TLS",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1TCP6TLS tests PROXY protocol version 1 over
+// TLS containing TCP6 entry is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV1TCP6TLS(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), `for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"`; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ tcpData: []byte("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ::1 12345 8080\r\n"),
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TCP6TLS",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1Unknown tests PROXY protocol version 1
+// containing UNKNOWN entry is accepted.
+func TestH2H1ProxyProtocolV1Unknown(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, notWant := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got == notWant {
+ t.Errorf("X-Forwarded-For: %v; want something else", got)
+ }
+ if got, notWant := r.Header.Get("Forwarded"), "for=192.168.0.2"; got == notWant {
+ t.Errorf("Forwarded: %v; want something else", got)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY UNKNOWN 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1Unknown",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1JustUnknown tests PROXY protocol version 1
+// containing only "PROXY UNKNOWN" is accepted.
+func TestH2H1ProxyProtocolV1JustUnknown(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY UNKNOWN\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1JustUnknown",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV1TooLongLine tests PROXY protocol version 1
+// line longer than 107 bytes must be rejected
+func TestH2H1ProxyProtocolV1TooLongLine(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY UNKNOWN ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 655350\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TooLongLine",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1BadLineEnd tests that PROXY protocol version
+// 1 line ending without \r\n should be rejected.
+func TestH2H1ProxyProtocolV1BadLineEnd(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080\r \n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1BadLineEnd",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1NoEnd tests that PROXY protocol version 1
+// line containing no \r\n should be rejected.
+func TestH2H1ProxyProtocolV1NoEnd(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1NoEnd",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1EmbeddedNULL tests that PROXY protocol
+// version 1 line containing NULL character should be rejected.
+func TestH2H1ProxyProtocolV1EmbeddedNULL(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ b := []byte("PROXY TCP6 ::1*foo ::1 12345 8080\r\n")
+ b[14] = 0
+ if _, err := st.conn.Write(b); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1EmbeddedNULL",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1MissingSrcPort tests that PROXY protocol
+// version 1 line without src port should be rejected.
+func TestH2H1ProxyProtocolV1MissingSrcPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1MissingSrcPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1MissingDstPort tests that PROXY protocol
+// version 1 line without dst port should be rejected.
+func TestH2H1ProxyProtocolV1MissingDstPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 \r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1MissingDstPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidSrcPort tests that PROXY protocol
+// containing invalid src port should be rejected.
+func TestH2H1ProxyProtocolV1InvalidSrcPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123x 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidSrcPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidDstPort tests that PROXY protocol
+// containing invalid dst port should be rejected.
+func TestH2H1ProxyProtocolV1InvalidDstPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123456 80x\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidDstPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1LeadingZeroPort tests that PROXY protocol
+// version 1 line with non zero port with leading zero should be
+// rejected.
+func TestH2H1ProxyProtocolV1LeadingZeroPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 03000 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1LeadingZeroPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1TooLargeSrcPort tests that PROXY protocol
+// containing too large src port should be rejected.
+func TestH2H1ProxyProtocolV1TooLargeSrcPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 65536 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TooLargeSrcPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1TooLargeDstPort tests that PROXY protocol
+// containing too large dst port should be rejected.
+func TestH2H1ProxyProtocolV1TooLargeDstPort(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 65536\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1TooLargeDstPort",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidSrcAddr tests that PROXY protocol
+// containing invalid src addr should be rejected.
+func TestH2H1ProxyProtocolV1InvalidSrcAddr(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 192.168.0.1 ::1 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidSrcAddr",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidDstAddr tests that PROXY protocol
+// containing invalid dst addr should be rejected.
+func TestH2H1ProxyProtocolV1InvalidDstAddr(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 192.168.0.1 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidDstAddr",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidProtoFamily tests that PROXY protocol
+// containing invalid protocol family should be rejected.
+func TestH2H1ProxyProtocolV1InvalidProtoFamily(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PROXY UNIX ::1 ::1 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidProtoFamily",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV1InvalidID tests that PROXY protocol
+// containing invalid PROXY protocol version 1 ID should be rejected.
+func TestH2H1ProxyProtocolV1InvalidID(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ if _, err := st.conn.Write([]byte("PR0XY TCP6 ::1 ::1 12345 8080\r\n")); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV1InvalidID",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV2TCP4 tests PROXY protocol version 2
+// containing AF_INET family is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV2TCP4(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.2").To4(),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.100").To4(),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2TCP4",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2TCP6 tests PROXY protocol version 2
+// containing AF_INET6 family is accepted and X-Forwarded-For contains
+// advertised src address.
+func TestH2H1ProxyProtocolV2TCP6(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "2001:db8:85a3::8a2e:370:7334"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), `for="[2001:db8:85a3::8a2e:370:7334]"`; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("::1"),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2TCP6",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2TCP4TLS tests PROXY protocol version 2 over
+// TLS containing AF_INET family is accepted and X-Forwarded-For
+// contains advertised src address.
+func TestH2H1ProxyProtocolV2TCP4TLS(t *testing.T) {
+ var v2Hdr bytes.Buffer
+ if err := writeProxyProtocolV2(&v2Hdr, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.2").To4(),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.100").To4(),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ tcpData: v2Hdr.Bytes(),
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2TCP4TLS",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2TCP6TLS tests PROXY protocol version 2 over
+// TLS containing AF_INET6 family is accepted and X-Forwarded-For
+// contains advertised src address.
+func TestH2H1ProxyProtocolV2TCP6TLS(t *testing.T) {
+ var v2Hdr bytes.Buffer
+ if err := writeProxyProtocolV2(&v2Hdr, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("::1"),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "2001:db8:85a3::8a2e:370:7334"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), `for="[2001:db8:85a3::8a2e:370:7334]"`; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ tcpData: v2Hdr.Bytes(),
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2TCP6TLS",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2Local tests PROXY protocol version 2
+// containing cmd == Local is ignored.
+func TestH2H1ProxyProtocolV2Local(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: proxyProtocolV2CommandLocal,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.2").To4(),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.100").To4(),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2Local",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2UnknownCmd tests PROXY protocol version 2
+// containing unknown cmd should be rejected.
+func TestH2H1ProxyProtocolV2UnknownCmd(t *testing.T) {
+ opts := options{
+ args: []string{"--accept-proxy-protocol"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: 0xf,
+ sourceAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.2").To4(),
+ Port: 12345,
+ },
+ destinationAddress: &net.TCPAddr{
+ IP: net.ParseIP("192.168.0.100").To4(),
+ Port: 8080,
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ _, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2UnknownCmd",
+ })
+
+ if err == nil {
+ t.Fatalf("connection was not terminated")
+ }
+}
+
+// TestH2H1ProxyProtocolV2Unix tests PROXY protocol version 2
+// containing AF_UNIX family is ignored.
+func TestH2H1ProxyProtocolV2Unix(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ sourceAddress: &net.UnixAddr{
+ Name: "/foo",
+ Net: "unix",
+ },
+ destinationAddress: &net.UnixAddr{
+ Name: "/bar",
+ Net: "unix",
+ },
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2Unix",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ProxyProtocolV2Unspec tests PROXY protocol version 2
+// containing AF_UNSPEC family is ignored.
+func TestH2H1ProxyProtocolV2Unspec(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--accept-proxy-protocol",
+ "--add-x-forwarded-for",
+ "--add-forwarded=for",
+ "--forwarded-for=ip",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want {
+ t.Errorf("X-Forwarded-For: %v; want %v", got, want)
+ }
+ if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want {
+ t.Errorf("Forwarded: %v; want %v", got, want)
+ }
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ var b bytes.Buffer
+ if err := writeProxyProtocolV2(&b, proxyProtocolV2{
+ command: proxyProtocolV2CommandProxy,
+ additionalData: []byte("foobar"),
+ }); err != nil {
+ t.Fatalf("Error writeProxyProtocolV2() = %v", err)
+ }
+
+ if _, err := st.conn.Write(b.Bytes()); err != nil {
+ t.Fatalf("Error st.conn.Write() = %v", err)
+ }
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ProxyProtocolV2Unspec",
+ })
+
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ExternalDNS tests that DNS resolution using external DNS
+// with HTTP/1 backend works.
+func TestH2H1ExternalDNS(t *testing.T) {
+ opts := options{
+ args: []string{"--external-dns"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ExternalDNS",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1DNS tests that DNS resolution without external DNS with
+// HTTP/1 backend works.
+func TestH2H1DNS(t *testing.T) {
+ opts := options{
+ args: []string{"--dns"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1DNS",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1HTTPSRedirect tests that the request to the backend which
+// requires TLS is redirected to https URI.
+func TestH2H1HTTPSRedirect(t *testing.T) {
+ opts := options{
+ args: []string{"--redirect-if-not-tls"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1HTTPSRedirect",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusPermanentRedirect; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+ if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
+ t.Errorf("location: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1HTTPSRedirectPort tests that the request to the backend
+// which requires TLS is redirected to https URI with given port.
+func TestH2H1HTTPSRedirectPort(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--redirect-if-not-tls",
+ "--redirect-https-port=8443",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ path: "/foo?bar",
+ name: "TestH2H1HTTPSRedirectPort",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusPermanentRedirect; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+ if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
+ t.Errorf("location: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1Code204 tests that 204 response without content-length, and
+// transfer-encoding is valid.
+func TestH2H1Code204(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNoContent)
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1Code204",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNoContent; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1Code204CL0 tests that 204 response with content-length: 0
+// is allowed.
+func TestH2H1Code204CL0(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 204\r\nContent-Length: 0\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1Code204CL0",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNoContent; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ if got, found := res.header["Content-Length"]; found {
+ t.Errorf("Content-Length = %v, want nothing", got)
+ }
+}
+
+// TestH2H1Code204CLNonzero tests that 204 response with nonzero
+// content-length is not allowed.
+func TestH2H1Code204CLNonzero(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 204\r\nContent-Length: 1\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1Code204CLNonzero",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadGateway; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1Code204TE tests that 204 response with transfer-encoding is
+// not allowed.
+func TestH2H1Code204TE(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 204\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1Code204TE",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadGateway; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1AffinityCookie tests that affinity cookie is sent back in
+// cleartext http.
+func TestH2H1AffinityCookie(t *testing.T) {
+ opts := options{
+ args: []string{"--affinity-cookie"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AffinityCookie",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar`
+ validCookie := regexp.MustCompile(pattern)
+ if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
+ t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
+ }
+}
+
+// TestH2H1AffinityCookieTLS tests that affinity cookie is sent back
+// in https.
+func TestH2H1AffinityCookieTLS(t *testing.T) {
+ opts := options{
+ args: []string{"--affinity-cookie"},
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1AffinityCookieTLS",
+ scheme: "https",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure`
+ validCookie := regexp.MustCompile(pattern)
+ if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
+ t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
+ }
+}
+
+// TestH2H1GracefulShutdown tests graceful shutdown.
+func TestH2H1GracefulShutdown(t *testing.T) {
+ st := newServerTester(t, options{})
+ 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
+ if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil {
+ t.Fatalf("Error st.cmd.Process.Signal() = %v", err)
+ }
+
+ time.Sleep(150 * time.Millisecond)
+
+ // 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++
+ 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("content-length", "1")
+ w.Header().Add("content-length", "1")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2MultipleResponseCL",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.errCode, http2.ErrCodeInternal; got != want {
+ t.Errorf("res.errCode: %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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("content-length", "")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2InvalidResponseCL",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.errCode, http2.ErrCodeInternal; got != want {
+ t.Errorf("res.errCode: %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) {
+// opts := options{
+// args: []string{"--http2-bridge"},
+// }
+// st := newServerTester(t, opts)
+// 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) {
+ opts := options{
+ args: []string{"--http2-bridge", "--host-rewrite"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("request-host", r.Host)
+ },
+ }
+ st := newServerTester(t, opts)
+ 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, http.StatusOK; 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)
+ }
+}
+
+// TestH2H2TLSXfp tests nghttpx sends x-forwarded-proto header field
+// with http value since :scheme is http, even if the frontend
+// connection is encrypted.
+func TestH2H2TLSXfp(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("x-forwarded-proto"), "http"; got != want {
+ t.Errorf("x-forwarded-proto: want %v; got %v", want, got)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2TLSXfp",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddXfp tests that server appends :scheme to the existing
+// x-forwarded-proto header field.
+func TestH2H2AddXfp(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--no-strip-incoming-x-forwarded-proto",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "foo, http"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2NoAddXfp tests that server does not append :scheme to the
+// existing x-forwarded-proto header field.
+func TestH2H2NoAddXfp(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--no-add-x-forwarded-proto",
+ "--no-strip-incoming-x-forwarded-proto",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "foo"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2NoAddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2StripXfp tests that server strips incoming
+// x-forwarded-proto header field.
+func TestH2H2StripXfp(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ xfp := r.Header.Get("X-Forwarded-Proto")
+ if got, want := xfp, "http"; got != want {
+ t.Errorf("X-Forwarded-Proto = %q; want %q", got, want)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2StripXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2StripNoAddXfp tests that server strips incoming
+// x-forwarded-proto header field, and does not add another.
+func TestH2H2StripNoAddXfp(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--no-add-x-forwarded-proto"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, found := r.Header["X-Forwarded-Proto"]; found {
+ t.Errorf("X-Forwarded-Proto = %q; want nothing", got)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2StripNoAddXfp",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-proto", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddXff tests that server generates X-Forwarded-For header
+// field when forwarding request to backend.
+func TestH2H2AddXff(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--add-x-forwarded-for"},
+ handler: 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)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddXff",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddXff2 tests that server appends X-Forwarded-For header
+// field to existing one when forwarding request to backend.
+func TestH2H2AddXff2(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--add-x-forwarded-for"},
+ handler: 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)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddXff2",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2StripXff tests that --strip-incoming-x-forwarded-for
+// option.
+func TestH2H2StripXff(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--strip-incoming-x-forwarded-for",
+ },
+ handler: 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)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2StripXff",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2StripAddXff tests that --strip-incoming-x-forwarded-for and
+// --add-x-forwarded-for options.
+func TestH2H2StripAddXff(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--strip-incoming-x-forwarded-for",
+ "--add-x-forwarded-for",
+ },
+ handler: 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)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2StripAddXff",
+ header: []hpack.HeaderField{
+ pair("x-forwarded-for", "host"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddForwarded tests that server generates Forwarded header
+// field using static obfuscated "by" parameter.
+func TestH2H2AddForwarded(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--add-forwarded=by,for,host,proto",
+ "--forwarded-by=_alpha",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ pattern := fmt.Sprintf(`by=_alpha;for=_[^;]+;host="127\.0\.0\.1:%v";proto=https`, serverPort)
+ validFwd := regexp.MustCompile(pattern)
+ if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
+ t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddForwarded",
+ scheme: "https",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddForwardedMerge tests that server generates Forwarded
+// header field using static obfuscated "by" parameter, and
+// existing Forwarded header field.
+func TestH2H2AddForwardedMerge(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--add-forwarded=by,host,proto",
+ "--forwarded-by=_alpha",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ want := fmt.Sprintf(`host=foo, by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
+ if got := r.Header.Get("Forwarded"); got != want {
+ t.Errorf("Forwarded = %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddForwardedMerge",
+ scheme: "https",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H2AddForwardedStrip tests that server generates Forwarded
+// header field using static obfuscated "by" parameter, and
+// existing Forwarded header field stripped.
+func TestH2H2AddForwardedStrip(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--strip-incoming-forwarded",
+ "--add-forwarded=by,host,proto",
+ "--forwarded-by=_alpha",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ want := fmt.Sprintf(`by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
+ if got := r.Header.Get("Forwarded"); got != want {
+ t.Errorf("Forwarded = %v; want %v", got, want)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2AddForwardedStrip",
+ scheme: "https",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H2StripForwarded tests that server strips incoming Forwarded
+// header field.
+func TestH2H2StripForwarded(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--strip-incoming-forwarded"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, found := r.Header["Forwarded"]; found {
+ t.Errorf("Forwarded = %v; want nothing", got)
+ }
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2StripForwarded",
+ scheme: "https",
+ header: []hpack.HeaderField{
+ pair("forwarded", "host=foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H2ReqPhaseReturn tests mruby request phase hook returns
+// custom response.
+func TestH2H2ReqPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/req-return.rb",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2ReqPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from req"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2RespPhaseReturn tests mruby response phase hook returns
+// custom response.
+func TestH2H2RespPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/resp-return.rb",
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2RespPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "21"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from resp"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2ExternalDNS tests that DNS resolution using external DNS
+// with HTTP/2 backend works.
+func TestH2H2ExternalDNS(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--external-dns"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2ExternalDNS",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2DNS tests that DNS resolution without external DNS with
+// HTTP/2 backend works.
+func TestH2H2DNS(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge", "--dns"},
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2DNS",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H2Code204 tests that 204 response without content-length, and
+// transfer-encoding is valid.
+func TestH2H2Code204(t *testing.T) {
+ opts := options{
+ args: []string{"--http2-bridge"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNoContent)
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H2Code204",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNoContent; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2APIBackendconfig exercise backendconfig API endpoint routine
+// for successful case.
+func TestH2APIBackendconfig(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2APIBackendconfig",
+ path: "/api/v1beta1/backendconfig",
+ method: "PUT",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH2APIBackendconfigQuery exercise backendconfig API endpoint
+// routine with query.
+func TestH2APIBackendconfigQuery(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2APIBackendconfigQuery",
+ path: "/api/v1beta1/backendconfig?foo=bar",
+ method: "PUT",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH2APIBackendconfigBadMethod exercise backendconfig API endpoint
+// routine with bad method.
+func TestH2APIBackendconfigBadMethod(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2APIBackendconfigBadMethod",
+ path: "/api/v1beta1/backendconfig",
+ method: "GET",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusMethodNotAllowed; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Failure"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 405; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH2APIConfigrevision tests configrevision API.
+func TestH2APIConfigrevision(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2APIConfigrevision",
+ path: "/api/v1beta1/configrevision",
+ method: "GET",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want = %v", got, want)
+ }
+
+ var apiResp APIResponse
+ d := json.NewDecoder(bytes.NewBuffer(res.body))
+ d.UseNumber()
+ err = d.Decode(&apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshalling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Success"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 200; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Data["configRevision"], json.Number("0"); got != want {
+ t.Errorf(`apiResp.Data["configRevision"]: %v %t; want %v`, got, got, want)
+ }
+}
+
+// TestH2APINotFound exercise backendconfig API endpoint routine when
+// API endpoint is not found.
+func TestH2APINotFound(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3010;api;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3010,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2APINotFound",
+ path: "/api/notfound",
+ method: "GET",
+ body: []byte(`# comment
+backend=127.0.0.1,3011
+
+`),
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+
+ var apiResp APIResponse
+ err = json.Unmarshal(res.body, &apiResp)
+ if err != nil {
+ t.Fatalf("Error unmarshaling API response: %v", err)
+ }
+ if got, want := apiResp.Status, "Failure"; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+ if got, want := apiResp.Code, 404; got != want {
+ t.Errorf("apiResp.Status: %v; want %v", got, want)
+ }
+}
+
+// TestH2Healthmon tests health monitor endpoint.
+func TestH2Healthmon(t *testing.T) {
+ opts := options{
+ args: []string{"-f127.0.0.1,3011;healthmon;no-tls"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ connectPort: 3011,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2Healthmon",
+ path: "/alpha/bravo",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2ResponseBeforeRequestEnd tests the situation where response
+// ends before request body finishes.
+func TestH2ResponseBeforeRequestEnd(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("request should not be forwarded")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2ResponseBeforeRequestEnd",
+ noEndStream: true,
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH2H1ChunkedEndsPrematurely tests that a stream is reset if the
+// backend chunked encoded response ends prematurely.
+func TestH2H1ChunkedEndsPrematurely(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1ChunkedEndsPrematurely",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.errCode, http2.ErrCodeInternal; got != want {
+ t.Errorf("res.errCode = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption verifies that https
+// scheme in non-encrypted connection is treated as error.
+func TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption(t *testing.T) {
+ opts := options{
+ args: []string{"--require-http-scheme"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption",
+ scheme: "https",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadRequest; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RequireHTTPSchemeHTTPWithEncryption verifies that http
+// scheme in encrypted connection is treated as error.
+func TestH2H1RequireHTTPSchemeHTTPWithEncryption(t *testing.T) {
+ opts := options{
+ args: []string{"--require-http-scheme"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RequireHTTPSchemeHTTPWithEncryption",
+ scheme: "http",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadRequest; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption verifies
+// that unknown scheme in non-encrypted connection is treated as
+// error.
+func TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption(t *testing.T) {
+ opts := options{
+ args: []string{"--require-http-scheme"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption",
+ scheme: "unknown",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadRequest; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption verifies that
+// unknown scheme in encrypted connection is treated as error.
+func TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption(t *testing.T) {
+ opts := options{
+ args: []string{"--require-http-scheme"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Errorf("server should not forward this request")
+ },
+ tls: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http2(requestParam{
+ name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption",
+ scheme: "unknown",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http2() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusBadRequest; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
diff --git a/integration-tests/nghttpx_http3_test.go b/integration-tests/nghttpx_http3_test.go
new file mode 100644
index 0000000..9ea85d7
--- /dev/null
+++ b/integration-tests/nghttpx_http3_test.go
@@ -0,0 +1,393 @@
+//go:build quic
+
+package nghttp2
+
+import (
+ "bytes"
+ "crypto/rand"
+ "io"
+ "net/http"
+ "regexp"
+ "testing"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+// TestH3H1PlainGET tests whether simple HTTP/3 GET request works.
+func TestH3H1PlainGET(t *testing.T) {
+ st := newServerTester(t, options{
+ quic: true,
+ })
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1PlainGET",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH3H1RequestBody tests HTTP/3 request with body works.
+func TestH3H1RequestBody(t *testing.T) {
+ body := make([]byte, 3333)
+ _, err := rand.Read(body)
+ if err != nil {
+ t.Fatalf("Unable to create request body: %v", err)
+ }
+
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ buf := make([]byte, 4096)
+ buflen := 0
+ p := buf
+
+ for {
+ if len(p) == 0 {
+ t.Fatal("Request body is too large")
+ }
+
+ n, err := r.Body.Read(p)
+
+ p = p[n:]
+ buflen += n
+
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+
+ t.Fatalf("r.Body.Read() = %v", err)
+ }
+ }
+
+ buf = buf[:buflen]
+
+ if got, want := buf, body; !bytes.Equal(got, want) {
+ t.Fatalf("buf = %v; want %v", got, want)
+ }
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1RequestBody",
+ body: body,
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH3H1GenerateVia tests that server generates Via header field to
+// and from backend server.
+func TestH3H1GenerateVia(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Via"), "3 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1GenerateVia",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+ if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+}
+
+// TestH3H1AppendVia tests that server adds value to existing Via
+// header field to and from backend server.
+func TestH3H1AppendVia(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ if got, want := r.Header.Get("Via"), "foo, 3 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+ w.Header().Add("Via", "bar")
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1AppendVia",
+ header: []hpack.HeaderField{
+ pair("via", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+ if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+}
+
+// TestH3H1NoVia tests that server does not add value to existing Via
+// header field to and from backend server.
+func TestH3H1NoVia(t *testing.T) {
+ opts := options{
+ args: []string{"--no-via"},
+ handler: 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")
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1NoVia",
+ header: []hpack.HeaderField{
+ pair("via", "foo"),
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+ if got, want := res.header.Get("Via"), "bar"; got != want {
+ t.Errorf("Via: %v; want %v", got, want)
+ }
+}
+
+// TestH3H1BadResponseCL tests that server returns error when
+// content-length response header field value does not match its
+// response body size.
+func TestH3H1BadResponseCL(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ // we set content-length: 1024, but only send 3 bytes.
+ w.Header().Add("Content-Length", "1024")
+ if _, err := w.Write([]byte("foo")); err != nil {
+ t.Fatalf("Error w.Write() = %v", err)
+ }
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ _, err := st.http3(requestParam{
+ name: "TestH3H1BadResponseCL",
+ })
+ if err == nil {
+ t.Fatal("st.http3() should fail")
+ }
+}
+
+// TestH3H1HTTPSRedirect tests that HTTPS redirect should not happen
+// with HTTP/3.
+func TestH3H1HTTPSRedirect(t *testing.T) {
+ opts := options{
+ args: []string{"--redirect-if-not-tls"},
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1HTTPSRedirect",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+}
+
+// TestH3H1AffinityCookieTLS tests that affinity cookie is sent back
+// in https.
+func TestH3H1AffinityCookieTLS(t *testing.T) {
+ opts := options{
+ args: []string{"--affinity-cookie"},
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H1AffinityCookieTLS",
+ scheme: "https",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusOK; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure`
+ validCookie := regexp.MustCompile(pattern)
+ if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
+ t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
+ }
+}
+
+// TestH3H2ReqPhaseReturn tests mruby request phase hook returns
+// custom response.
+func TestH3H2ReqPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/req-return.rb",
+ },
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("request should not be forwarded")
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H2ReqPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "20"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from req"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH3H2RespPhaseReturn tests mruby response phase hook returns
+// custom response.
+func TestH3H2RespPhaseReturn(t *testing.T) {
+ opts := options{
+ args: []string{
+ "--http2-bridge",
+ "--mruby-file=" + testDir + "/resp-return.rb",
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3H2RespPhaseReturn",
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("status = %v; want %v", got, want)
+ }
+
+ hdtests := []struct {
+ k, v string
+ }{
+ {"content-length", "21"},
+ {"from", "mruby"},
+ }
+ for _, tt := range hdtests {
+ if got, want := res.header.Get(tt.k), tt.v; got != want {
+ t.Errorf("%v = %v; want %v", tt.k, got, want)
+ }
+ }
+
+ if got, want := string(res.body), "Hello World from resp"; got != want {
+ t.Errorf("body = %v; want %v", got, want)
+ }
+}
+
+// TestH3ResponseBeforeRequestEnd tests the situation where response
+// ends before request body finishes.
+func TestH3ResponseBeforeRequestEnd(t *testing.T) {
+ opts := options{
+ args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("request should not be forwarded")
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ res, err := st.http3(requestParam{
+ name: "TestH3ResponseBeforeRequestEnd",
+ noEndStream: true,
+ })
+ if err != nil {
+ t.Fatalf("Error st.http3() = %v", err)
+ }
+ if got, want := res.status, http.StatusNotFound; got != want {
+ t.Errorf("res.status: %v; want %v", got, want)
+ }
+}
+
+// TestH3H1ChunkedEndsPrematurely tests that a stream is reset if the
+// backend chunked encoded response ends prematurely.
+func TestH3H1ChunkedEndsPrematurely(t *testing.T) {
+ opts := options{
+ handler: func(w http.ResponseWriter, r *http.Request) {
+ hj, ok := w.(http.Hijacker)
+ if !ok {
+ http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
+ return
+ }
+ conn, bufrw, err := hj.Hijack()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer conn.Close()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ bufrw.Flush()
+ },
+ quic: true,
+ }
+ st := newServerTester(t, opts)
+ defer st.Close()
+
+ _, err := st.http3(requestParam{
+ name: "TestH3H1ChunkedEndsPrematurely",
+ })
+ if err == nil {
+ t.Fatal("st.http3() should fail")
+ }
+}
diff --git a/integration-tests/req-return.rb b/integration-tests/req-return.rb
new file mode 100644
index 0000000..51d315e
--- /dev/null
+++ b/integration-tests/req-return.rb
@@ -0,0 +1,12 @@
+class App
+ def on_req(env)
+ resp = env.resp
+
+ resp.clear_headers
+ resp.status = 404
+ resp.add_header "from", "mruby"
+ resp.return "Hello World from req"
+ end
+end
+
+App.new
diff --git a/integration-tests/req-set-header.rb b/integration-tests/req-set-header.rb
new file mode 100644
index 0000000..986f128
--- /dev/null
+++ b/integration-tests/req-set-header.rb
@@ -0,0 +1,7 @@
+class App
+ def on_req(env)
+ env.req.set_header "User-Agent", "mruby"
+ end
+end
+
+App.new
diff --git a/integration-tests/resp-return.rb b/integration-tests/resp-return.rb
new file mode 100644
index 0000000..fbbd775
--- /dev/null
+++ b/integration-tests/resp-return.rb
@@ -0,0 +1,12 @@
+class App
+ def on_resp(env)
+ resp = env.resp
+
+ resp.clear_headers
+ resp.status = 404
+ resp.add_header "from", "mruby"
+ resp.return "Hello World from resp"
+ end
+end
+
+App.new
diff --git a/integration-tests/resp-set-header.rb b/integration-tests/resp-set-header.rb
new file mode 100644
index 0000000..228837a
--- /dev/null
+++ b/integration-tests/resp-set-header.rb
@@ -0,0 +1,7 @@
+class App
+ def on_resp(env)
+ env.resp.set_header "Alpha", "bravo"
+ end
+end
+
+App.new
diff --git a/integration-tests/server.crt b/integration-tests/server.crt
new file mode 100644
index 0000000..c50fdaa
--- /dev/null
+++ b/integration-tests/server.crt
@@ -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
index 0000000..0fc02bb
--- /dev/null
+++ b/integration-tests/server.key
@@ -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
index 0000000..a1bccad
--- /dev/null
+++ b/integration-tests/server_tester.go
@@ -0,0 +1,819 @@
+package nghttp2
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "crypto/tls"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "os/exec"
+ "sort"
+ "strconv"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/tatsuhiro-t/go-nghttp2"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/hpack"
+ "golang.org/x/net/websocket"
+)
+
+const (
+ serverBin = buildDir + "/src/nghttpx"
+ serverPort = 3009
+ testDir = sourceDir + "/integration-tests"
+ logDir = buildDir + "/integration-tests"
+)
+
+func pair(name, value string) hpack.HeaderField {
+ return hpack.HeaderField{
+ Name: name,
+ Value: value,
+ }
+}
+
+type serverTester struct {
+ 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
+ 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
+ errCh chan error
+}
+
+type options struct {
+ // args is the additional arguments to nghttpx.
+ args []string
+ // handler is the handler to handle the request. It defaults
+ // to noopHandler.
+ handler http.HandlerFunc
+ // connectPort is the server side port where client connection
+ // is made. It defaults to serverPort.
+ connectPort int
+ // tls, if set to true, sets up TLS frontend connection.
+ tls bool
+ // tlsConfig is the client side TLS configuration that is used
+ // when tls is true.
+ tlsConfig *tls.Config
+ // tcpData is additional data that are written to connection
+ // before TLS handshake starts. This field is ignored if tls
+ // is false.
+ tcpData []byte
+ // quic, if set to true, sets up QUIC frontend connection.
+ // quic implies tls = true.
+ quic bool
+}
+
+// newServerTester creates test context.
+func newServerTester(t *testing.T, opts options) *serverTester {
+ if opts.quic {
+ opts.tls = true
+ }
+
+ if opts.handler == nil {
+ opts.handler = noopHandler
+ }
+ if opts.connectPort == 0 {
+ opts.connectPort = serverPort
+ }
+
+ ts := httptest.NewUnstartedServer(opts.handler)
+
+ var args []string
+ var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS, affinityCookie, alpnH1 bool
+
+ for _, k := range opts.args {
+ switch k {
+ case "--http2-bridge":
+ backendTLS = true
+ case "--dns":
+ dns = true
+ case "--external-dns":
+ dns = true
+ externalDNS = true
+ case "--accept-proxy-protocol":
+ acceptProxyProtocol = true
+ case "--redirect-if-not-tls":
+ redirectIfNotTLS = true
+ case "--affinity-cookie":
+ affinityCookie = true
+ case "--alpn-h1":
+ alpnH1 = true
+ default:
+ args = append(args, k)
+ }
+ }
+ 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")
+ ts.StartTLS()
+ args = append(args, "-k")
+ } else {
+ ts.Start()
+ }
+ scheme := "http"
+ if opts.tls {
+ scheme = "https"
+ args = append(args, testDir+"/server.key", testDir+"/server.crt")
+ }
+
+ 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"
+ if !externalDNS {
+ b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1))
+ } else {
+ sep := strings.LastIndex(backendURL.Host, ":")
+ if sep == -1 {
+ t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host)
+ }
+ // We use awesome service nip.io.
+ b += fmt.Sprintf("%v.nip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:])
+ }
+
+ if backendTLS {
+ b += ";proto=h2;tls"
+ }
+ if dns {
+ b += ";dns"
+ }
+
+ if redirectIfNotTLS {
+ b += ";redirect-if-not-tls"
+ }
+
+ if affinityCookie {
+ b += ";affinity=cookie;affinity-cookie-name=affinity;affinity-cookie-path=/foo/bar"
+ }
+
+ noTLS := ";no-tls"
+ if opts.tls {
+ noTLS = ""
+ }
+
+ var proxyProto string
+ if acceptProxyProtocol {
+ proxyProto = ";proxyproto"
+ }
+
+ args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b,
+ "--errorlog-file="+logDir+"/log.txt", "-LINFO")
+
+ if opts.quic {
+ args = append(args,
+ fmt.Sprintf("-f127.0.0.1,%v;quic", serverPort),
+ "--no-quic-bpf")
+ }
+
+ authority := fmt.Sprintf("127.0.0.1:%v", opts.connectPort)
+
+ 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),
+ errCh: make(chan error),
+ }
+
+ st.cmd.Stdout = os.Stdout
+ st.cmd.Stderr = os.Stderr
+
+ if err := st.cmd.Start(); err != nil {
+ st.t.Fatalf("Error starting %v: %v", serverBin, err)
+ }
+
+ retry := 0
+ for {
+ time.Sleep(50 * time.Millisecond)
+
+ conn, err := net.Dial("tcp", authority)
+ if err == nil && opts.tls {
+ if len(opts.tcpData) > 0 {
+ if _, err := conn.Write(opts.tcpData); err != nil {
+ st.Close()
+ st.t.Fatal("Error writing TCP data")
+ }
+ }
+
+ var tlsConfig *tls.Config
+ if opts.tlsConfig == nil {
+ tlsConfig = new(tls.Config)
+ } else {
+ tlsConfig = opts.tlsConfig.Clone()
+ }
+ tlsConfig.InsecureSkipVerify = true
+ if alpnH1 {
+ tlsConfig.NextProtos = []string{"http/1.1"}
+ } else {
+ tlsConfig.NextProtos = []string{"h2"}
+ }
+ tlsConn := tls.Client(conn, tlsConfig)
+ err = tlsConn.Handshake()
+ if err == nil {
+ conn = tlsConn
+ }
+ }
+ if err != nil {
+ retry++
+ if retry >= 100 {
+ st.Close()
+ st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid")
+ }
+ continue
+ }
+ st.conn = conn
+ break
+ }
+
+ st.fr = http2.NewFramer(st.conn, st.conn)
+ 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 {
+ done := make(chan struct{})
+ go func() {
+ if err := st.cmd.Wait(); err != nil {
+ st.t.Errorf("Error st.cmd.Wait() = %v", err)
+ }
+ close(done)
+ }()
+
+ if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil {
+ st.t.Errorf("Error st.cmd.Process.Signal() = %v", err)
+ }
+
+ select {
+ case <-done:
+ case <-time.After(10 * time.Second):
+ if err := st.cmd.Process.Kill(); err != nil {
+ st.t.Errorf("Error st.cmd.Process.Kill() = %v", err)
+ }
+ <-done
+ }
+ }
+ 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")
+ }
+}
+
+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
+ trailer []hpack.HeaderField // trailer part
+ httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade
+ noEndStream bool // true if END_STREAM should not be sent
+}
+
+// wrapper for request body to set trailer part
+type chunkedBodyReader struct {
+ trailer []hpack.HeaderField
+ trailerWritten bool
+ body io.Reader
+ req *http.Request
+}
+
+func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) {
+ // document says that we have to set http.Request.Trailer
+ // after request was sent and before body returns EOF.
+ if !cbr.trailerWritten {
+ cbr.trailerWritten = true
+ for _, h := range cbr.trailer {
+ cbr.req.Trailer.Set(h.Name, h.Value)
+ }
+ }
+ return cbr.body.Read(p)
+}
+
+func (st *serverTester) websocket(rp requestParam) *serverResponse {
+ urlstring := st.url + "/echo"
+
+ config, err := websocket.NewConfig(urlstring, st.url)
+ if err != nil {
+ st.t.Fatalf("websocket.NewConfig(%q, %q) returned error: %v", urlstring, st.url, err)
+ }
+
+ config.Header.Add("Test-Case", rp.name)
+ for _, h := range rp.header {
+ config.Header.Add(h.Name, h.Value)
+ }
+
+ ws, err := websocket.NewClient(config, st.conn)
+ if err != nil {
+ st.t.Fatalf("Error creating websocket client: %v", err)
+ }
+
+ if _, err := ws.Write(rp.body); err != nil {
+ st.t.Fatalf("ws.Write() returned error: %v", err)
+ }
+
+ msg := make([]byte, 1024)
+ var n int
+ if n, err = ws.Read(msg); err != nil {
+ st.t.Fatalf("ws.Read() returned error: %v", err)
+ }
+
+ res := &serverResponse{
+ body: msg[:n],
+ }
+
+ return res
+}
+
+func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
+ method := "GET"
+ if rp.method != "" {
+ method = rp.method
+ }
+
+ var body io.Reader
+ var cbr *chunkedBodyReader
+ if rp.body != nil {
+ body = bytes.NewBuffer(rp.body)
+ if len(rp.trailer) != 0 {
+ cbr = &chunkedBodyReader{
+ trailer: rp.trailer,
+ body: body,
+ }
+ body = cbr
+ }
+ }
+
+ reqURL := st.url
+
+ if rp.path != "" {
+ u, err := url.Parse(st.url)
+ if err != nil {
+ st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err)
+ }
+ u.Path = ""
+ u.RawQuery = ""
+ reqURL = u.String() + rp.path
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ req, err := http.NewRequestWithContext(ctx, method, reqURL, 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 cbr != nil {
+ cbr.req = req
+ // this makes request use chunked encoding
+ req.ContentLength = -1
+ req.Trailer = make(http.Header)
+ for _, h := range cbr.trailer {
+ req.Trailer.Set(h.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 := io.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) 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
+
+ if !rp.httpUpgrade {
+ 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 && len(rp.trailer) == 0 && !rp.noEndStream,
+ 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, len(rp.trailer) == 0 && !rp.noEndStream, rp.body); err != nil {
+ return nil, err
+ }
+ }
+
+ if len(rp.trailer) != 0 {
+ st.headerBlkBuf.Reset()
+ for _, h := range rp.trailer {
+ _ = st.enc.WriteField(h)
+ }
+ err := st.fr.WriteHeaders(http2.HeadersFrameParam{
+ StreamID: id,
+ EndStream: true,
+ EndHeaders: true,
+ BlockFragment: st.headerBlkBuf.Bytes(),
+ })
+ if 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: %w", 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
+ connClose bool // Connection: close is included in response header in HTTP/1 test
+ reqHeader http.Header // http request header, currently only stores 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) {
+ if _, err := io.ReadAll(r.Body); err != nil {
+ http.Error(w, fmt.Sprintf("Error io.ReadAll() = %v", err), http.StatusInternalServerError)
+ }
+}
+
+type APIResponse struct {
+ Status string `json:"status,omitempty"`
+ Code int `json:"code,omitempty"`
+ Data map[string]interface{} `json:"data,omitempty"`
+}
+
+type proxyProtocolV2 struct {
+ command proxyProtocolV2Command
+ sourceAddress net.Addr
+ destinationAddress net.Addr
+ additionalData []byte
+}
+
+type proxyProtocolV2Command int
+
+const (
+ proxyProtocolV2CommandLocal proxyProtocolV2Command = 0x0
+ proxyProtocolV2CommandProxy proxyProtocolV2Command = 0x1
+)
+
+type proxyProtocolV2Family int
+
+const (
+ proxyProtocolV2FamilyUnspec proxyProtocolV2Family = 0x0
+ proxyProtocolV2FamilyInet proxyProtocolV2Family = 0x1
+ proxyProtocolV2FamilyInet6 proxyProtocolV2Family = 0x2
+ proxyProtocolV2FamilyUnix proxyProtocolV2Family = 0x3
+)
+
+type proxyProtocolV2Protocol int
+
+const (
+ proxyProtocolV2ProtocolUnspec proxyProtocolV2Protocol = 0x0
+ proxyProtocolV2ProtocolStream proxyProtocolV2Protocol = 0x1
+ proxyProtocolV2ProtocolDgram proxyProtocolV2Protocol = 0x2
+)
+
+func writeProxyProtocolV2(w io.Writer, hdr proxyProtocolV2) error {
+ if _, err := w.Write([]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}); err != nil {
+ return err
+ }
+ if _, err := w.Write([]byte{byte(0x20 | hdr.command)}); err != nil {
+ return err
+ }
+
+ switch srcAddr := hdr.sourceAddress.(type) {
+ case *net.TCPAddr:
+ dstAddr := hdr.destinationAddress.(*net.TCPAddr)
+ if len(srcAddr.IP) != len(dstAddr.IP) {
+ panic("len(srcAddr.IP) != len(dstAddr.IP)")
+ }
+ var fam byte
+ if len(srcAddr.IP) == 4 {
+ fam = byte(proxyProtocolV2FamilyInet << 4)
+ } else {
+ fam = byte(proxyProtocolV2FamilyInet6 << 4)
+ }
+ fam |= byte(proxyProtocolV2ProtocolStream)
+ if _, err := w.Write([]byte{fam}); err != nil {
+ return err
+ }
+ length := uint16(len(srcAddr.IP)*2 + 4 + len(hdr.additionalData))
+ if err := binary.Write(w, binary.BigEndian, length); err != nil {
+ return err
+ }
+ if _, err := w.Write(srcAddr.IP); err != nil {
+ return err
+ }
+ if _, err := w.Write(dstAddr.IP); err != nil {
+ return err
+ }
+ if err := binary.Write(w, binary.BigEndian, uint16(srcAddr.Port)); err != nil {
+ return err
+ }
+ if err := binary.Write(w, binary.BigEndian, uint16(dstAddr.Port)); err != nil {
+ return err
+ }
+ case *net.UnixAddr:
+ dstAddr := hdr.destinationAddress.(*net.UnixAddr)
+ if len(srcAddr.Name) > 108 {
+ panic("too long Unix source address")
+ }
+ if len(dstAddr.Name) > 108 {
+ panic("too long Unix destination address")
+ }
+ fam := byte(proxyProtocolV2FamilyUnix << 4)
+ switch srcAddr.Net {
+ case "unix":
+ fam |= byte(proxyProtocolV2ProtocolStream)
+ case "unixdgram":
+ fam |= byte(proxyProtocolV2ProtocolDgram)
+ default:
+ fam |= byte(proxyProtocolV2ProtocolUnspec)
+ }
+ if _, err := w.Write([]byte{fam}); err != nil {
+ return err
+ }
+ length := uint16(216 + len(hdr.additionalData))
+ if err := binary.Write(w, binary.BigEndian, length); err != nil {
+ return err
+ }
+ zeros := make([]byte, 108)
+ if _, err := w.Write([]byte(srcAddr.Name)); err != nil {
+ return err
+ }
+ if _, err := w.Write(zeros[:108-len(srcAddr.Name)]); err != nil {
+ return err
+ }
+ if _, err := w.Write([]byte(dstAddr.Name)); err != nil {
+ return err
+ }
+ if _, err := w.Write(zeros[:108-len(dstAddr.Name)]); err != nil {
+ return err
+ }
+ default:
+ fam := byte(proxyProtocolV2FamilyUnspec<<4) | byte(proxyProtocolV2ProtocolUnspec)
+ if _, err := w.Write([]byte{fam}); err != nil {
+ return err
+ }
+ length := uint16(len(hdr.additionalData))
+ if err := binary.Write(w, binary.BigEndian, length); err != nil {
+ return err
+ }
+ }
+
+ if _, err := w.Write(hdr.additionalData); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/integration-tests/server_tester_http3.go b/integration-tests/server_tester_http3.go
new file mode 100644
index 0000000..7bbc4e7
--- /dev/null
+++ b/integration-tests/server_tester_http3.go
@@ -0,0 +1,90 @@
+//go:build quic
+
+package nghttp2
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "io"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/quic-go/quic-go/http3"
+)
+
+func (st *serverTester) http3(rp requestParam) (*serverResponse, error) {
+ rt := &http3.RoundTripper{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+
+ defer rt.Close()
+
+ c := &http.Client{
+ Transport: rt,
+ }
+
+ method := "GET"
+ if rp.method != "" {
+ method = rp.method
+ }
+
+ var body io.Reader
+
+ if rp.body != nil {
+ body = bytes.NewBuffer(rp.body)
+ }
+
+ reqURL := st.url
+
+ if rp.path != "" {
+ u, err := url.Parse(st.url)
+ if err != nil {
+ st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err)
+ }
+ u.Path = ""
+ u.RawQuery = ""
+ reqURL = u.String() + rp.path
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ req, err := http.NewRequestWithContext(ctx, method, reqURL, 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)
+
+ // TODO http3 package does not support trailer at the time of
+ // this writing.
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ defer resp.Body.Close()
+
+ respBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ res := &serverResponse{
+ status: resp.StatusCode,
+ header: resp.Header,
+ body: respBody,
+ connClose: resp.Close,
+ }
+
+ return res, nil
+}
diff --git a/integration-tests/setenv.in b/integration-tests/setenv.in
new file mode 100644
index 0000000..7177200
--- /dev/null
+++ b/integration-tests/setenv.in
@@ -0,0 +1,13 @@
+#!/bin/sh -e
+
+libdir="@abs_top_builddir@/lib"
+if [ -d "$libdir/.libs" ]; then
+ libdir="$libdir/.libs"
+fi
+
+export CGO_CFLAGS="-I@abs_top_srcdir@/lib/includes -I@abs_top_builddir@/lib/includes @CFLAGS@"
+export CGO_CPPFLAGS="@CPPFLAGS@"
+export CGO_LDFLAGS="-L$libdir @LDFLAGS@"
+export LD_LIBRARY_PATH="$libdir"
+export GODEBUG=cgocheck=0
+"$@"
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..f64f46b
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,3 @@
+# generated files
+includes/nghttp2/nghttp2ver.h
+libnghttp2.pc
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644
index 0000000..4180748
--- /dev/null
+++ b/lib/CMakeLists.txt
@@ -0,0 +1,80 @@
+add_subdirectory(includes)
+
+include_directories(
+ "${CMAKE_CURRENT_SOURCE_DIR}/includes"
+ "${CMAKE_CURRENT_BINARY_DIR}/includes"
+)
+
+add_definitions(-DBUILDING_NGHTTP2)
+
+set(NGHTTP2_SOURCES
+ 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_alpn.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
+ nghttp2_http.c
+ nghttp2_rcbuf.c
+ nghttp2_extpri.c
+ nghttp2_ratelim.c
+ nghttp2_time.c
+ nghttp2_debug.c
+ sfparse.c
+)
+
+set(NGHTTP2_RES "")
+
+if(WIN32)
+ configure_file(
+ version.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set(NGHTTP2_RES ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+ add_library(nghttp2 SHARED ${NGHTTP2_SOURCES} ${NGHTTP2_RES})
+ set_target_properties(nghttp2 PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}"
+ VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
+ C_VISIBILITY_PRESET hidden
+ )
+ target_include_directories(nghttp2 INTERFACE
+ "${CMAKE_CURRENT_BINARY_DIR}/includes"
+ "${CMAKE_CURRENT_SOURCE_DIR}/includes"
+ )
+
+ install(TARGETS nghttp2
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(HAVE_CUNIT OR ENABLE_STATIC_LIB)
+ # Static library (for unittests because of symbol visibility)
+ add_library(nghttp2_static STATIC ${NGHTTP2_SOURCES})
+ set_target_properties(nghttp2_static PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}"
+ VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
+ ARCHIVE_OUTPUT_NAME nghttp2${STATIC_LIB_SUFFIX}
+ )
+ target_compile_definitions(nghttp2_static PUBLIC "-DNGHTTP2_STATICLIB")
+ if(ENABLE_STATIC_LIB)
+ install(TARGETS nghttp2_static
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+ endif()
+endif()
+
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp2.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..1168c1e
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,81 @@
+# 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 CMakeLists.txt version.rc.in
+
+AM_CFLAGS = $(WARNCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGHTTP2 \
+ @DEFS@
+AM_LDFLAGS = @LIBTOOL_LDFLAGS@
+
+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_alpn.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 \
+ nghttp2_http.c \
+ nghttp2_rcbuf.c \
+ nghttp2_extpri.c \
+ nghttp2_ratelim.c \
+ nghttp2_time.c \
+ nghttp2_debug.c \
+ sfparse.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_alpn.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 \
+ nghttp2_http.h \
+ nghttp2_rcbuf.h \
+ nghttp2_extpri.h \
+ nghttp2_ratelim.h \
+ nghttp2_time.h \
+ nghttp2_debug.h \
+ sfparse.h
+
+libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
+libnghttp2_la_LDFLAGS = $(AM_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
index 0000000..752389e
--- /dev/null
+++ b/lib/Makefile.msvc
@@ -0,0 +1,254 @@
+#
+# GNU Makefile for nghttp2 / MSVC.
+#
+# By G. Vanem <gvanem@yahoo.no> 2013
+# Updated 3/2015 by Remo Eichenberger @remoe
+# The MIT License apply.
+#
+
+THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))
+
+_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV//g' -e 's/], //g')
+_VERSION := $(subst ., ,$(_VERSION))
+VER_MAJOR := $(word 1,$(_VERSION))
+VER_MINOR := $(word 2,$(_VERSION))
+VER_MICRO := $(word 3,$(_VERSION))
+VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO)
+VERSION_NUM := (($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO))
+
+GENERATED := 'Generated by $(realpath Makefile.MSVC)'
+
+OBJ_DIR := MSVC_obj
+#SUFFIX :=-vc90-mt-x86
+
+#
+# Where to copy nghttp2.dll + lib + headers to.
+# Note: 'make install' is not in default targets. Do it explicitly.
+#
+TARGET_DIR ?= ../_VC_ROOT
+VC_ROOT := $(abspath $(TARGET_DIR))
+INSTALL_BIN := $(VC_ROOT)/bin
+INSTALL_LIB := $(VC_ROOT)/lib
+INSTALL_HDR := $(VC_ROOT)/include
+DLL_R := $(OBJ_DIR)/nghttp2$(SUFFIX).dll
+DLL_D := $(OBJ_DIR)/nghttp2d$(SUFFIX).dll
+LIB_R := $(OBJ_DIR)/nghttp2-static.lib
+LIB_D := $(OBJ_DIR)/nghttp2d-static.lib
+IMP_R := $(OBJ_DIR)/nghttp2.lib
+IMP_D := $(OBJ_DIR)/nghttp2d.lib
+
+#
+# Build for DEBUG-model and RELEASE at the same time.
+#
+TARGETS := $(LIB_R) $(DLL_R) $(IMP_R) \
+ $(LIB_D) $(DLL_D) $(IMP_D)
+
+EXT_LIBS =
+
+NGHTTP2_PDB_R := $(OBJ_DIR)/nghttp2.pdb
+NGHTTP2_PDB_D := $(OBJ_DIR)/nghttp2d.pdb
+
+CC = cl
+LD := link
+AR := lib
+#CC := icl
+#LD := xilink
+#AR := xilib
+RC := rc
+CFLAGS := -I./includes -Dssize_t=long
+
+CFLAGS_R := -nologo -MD -W3 -Z7 -DBUILDING_NGHTTP2
+CFLAGS_D := -nologo -MDd -W3 -Z7 -DBUILDING_NGHTTP2 \
+ -Ot -D_DEBUG -GF -RTCs -RTCu # -RTCc -GS
+
+LDFLAGS := -nologo -MAP -debug -incremental:no -opt:ref,icf -MANIFEST # -verbose
+
+
+NGHTTP2_SRC := 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_alpn.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 \
+ nghttp2_http.c \
+ nghttp2_rcbuf.c
+
+NGHTTP2_OBJ_R := $(addprefix $(OBJ_DIR)/r_, $(notdir $(NGHTTP2_SRC:.c=.obj)))
+NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj)))
+
+.PHONY: all intro test_ver install copy_headers_and_libs \
+ install_nghttp2_pyd_0 install_nghttp2_pyd_1 \
+ build_nghttp2_pyd_0 build_nghttp2_pyd_1 \
+ clean_nghttp2_pyd_0 clean_nghttp2_pyd_1
+
+
+all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS)
+ @echo 'Welcome to NgHTTP2 (release + debug).'
+ @echo 'Do a "make -f Makefile.MSVC install" at own risk!'
+
+intro:
+ @echo 'Building NgHTTP (MSVC) ver. "$(VERSION)".'
+
+test_ver:
+ @echo '$$(VERSION): "$(VERSION)".'
+ @echo '$$(_VERSION): "$(_VERSION)".'
+ @echo '$$(VER_MAJOR): "$(VER_MAJOR)".'
+ @echo '$$(VER_MINOR): "$(VER_MINOR)".'
+ @echo '$$(VER_MICRO): "$(VER_MICRO)".'
+
+$(OBJ_DIR):
+ - mkdir $(OBJ_DIR)
+
+install: includes/nghttp2/nghttp2.h includes/nghttp2/nghttp2ver.h \
+ $(TARGETS) \
+ copy_headers_and_libs
+
+#
+# This MUST be done before using the 'install_nghttp2_pyd_1' rule.
+#
+copy_headers_and_libs:
+ - mkdir -p $(INSTALL_HDR)/nghttp2 $(INSTALL_BIN) $(INSTALL_LIB)
+ cp --update $(addprefix includes/nghttp2/, nghttp2.h nghttp2ver.h) $(INSTALL_HDR)/nghttp2
+ cp --update $(DLL_R) $(DLL_D) $(NGHTTP2_PDB_R) $(NGHTTP2_PDB_D) $(INSTALL_BIN)
+ cp --update $(IMP_R) $(IMP_D) $(LIB_R) $(LIB_D) $(INSTALL_LIB)
+ @echo
+
+$(LIB_R): $(NGHTTP2_OBJ_R)
+ $(AR) -nologo -out:$@ $^
+ @echo
+
+$(LIB_D): $(NGHTTP2_OBJ_D)
+ $(AR) -nologo -out:$@ $^
+ @echo
+
+
+$(IMP_R): $(DLL_R)
+
+$(DLL_R): $(NGHTTP2_OBJ_R) $(OBJ_DIR)/r_nghttp2.res
+ $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_R) $(NGHTTP2_OBJ_R) -PDB:$(NGHTTP2_PDB_R) $(OBJ_DIR)/r_nghttp2.res $(EXT_LIBS)
+ mt -nologo -manifest $@.manifest -outputresource:$@\;2
+ @echo
+
+$(IMP_D): $(DLL_D)
+
+$(DLL_D): $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res
+ $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_D) $(NGHTTP2_OBJ_D) -PDB:$(NGHTTP2_PDB_D) $(OBJ_DIR)/d_nghttp2.res $(EXT_LIBS)
+ mt -nologo -manifest $@.manifest -outputresource:$@\;2
+ @echo
+
+
+WIN_OBJDIR:=$(shell cygpath -w $(abspath $(OBJ_DIR)))
+WIN_OBJDIR:=$(subst \,/,$(WIN_OBJDIR))
+
+$(OBJ_DIR)/r_%.obj: %.c $(THIS_MAKEFILE)
+ $(CC) $(CFLAGS_R) $(CFLAGS) -Fo$@ -c $<
+ @echo
+
+$(OBJ_DIR)/d_%.obj: %.c $(THIS_MAKEFILE)
+ $(CC) $(CFLAGS_D) $(CFLAGS) -Fo$@ -c $<
+ @echo
+
+$(OBJ_DIR)/r_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE)
+ $(RC) -D_RELEASE -Fo $@ $<
+ @echo
+
+$(OBJ_DIR)/d_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE)
+ $(RC) -D_DEBUG -Fo $@ $<
+ @echo
+
+includes/nghttp2/nghttp2ver.h: includes/nghttp2/nghttp2ver.h.in $(THIS_MAKEFILE)
+ sed < includes/nghttp2/nghttp2ver.h.in \
+ -e 's/@PACKAGE_VERSION@/$(VERSION)/g' \
+ -e 's/@PACKAGE_VERSION_NUM@/$(VERSION_NUM)/g' > $@
+ touch --reference=includes/nghttp2/nghttp2ver.h.in $@
+
+
+define RES_FILE
+ #include <winver.h>
+
+ VS_VERSION_INFO VERSIONINFO
+ FILEVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0
+ PRODUCTVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0
+ FILEFLAGSMASK 0x3fL
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+ #ifdef _DEBUG
+ #define VER_STR "$(VERSION).0 (MSVC debug)"
+ #define DBG "d"
+ FILEFLAGS 0x1L
+ #else
+ #define VER_STR "$(VERSION).0 (MSVC release)"
+ #define DBG ""
+ FILEFLAGS 0x0L
+ #endif
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "http://tatsuhiro-t.github.io/nghttp2/"
+ VALUE "FileDescription", "nghttp2; HTTP/2 C library"
+ VALUE "FileVersion", VER_STR
+ VALUE "InternalName", "nghttp2" DBG
+ VALUE "LegalCopyright", "The MIT License"
+ VALUE "LegalTrademarks", ""
+ VALUE "OriginalFilename", "nghttp2" DBG ".dll"
+ VALUE "ProductName", "NGHTTP2."
+ VALUE "ProductVersion", VER_STR
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+ END
+endef
+
+export RES_FILE
+
+$(OBJ_DIR)/nghttp2.rc: Makefile.MSVC
+ @echo 'Generating $@...'
+ @echo ' /* $(GENERATED). DO NOT EDIT.' > $@
+ @echo ' */' >> $@
+ @echo "$$RES_FILE" >> $@
+
+clean:
+ rm -f $(OBJ_DIR)/* includes/nghttp2/nghttp2ver.h
+ @echo
+
+vclean realclean: clean
+ - rm -rf $(OBJ_DIR)
+ - rm -f .depend.MSVC
+
+#
+# Use gcc to generated the dependencies. No MSVC specific args please!
+#
+REPLACE_R = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/r_\1.obj: /'
+REPLACE_D = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/d_\1.obj: /'
+
+depend: includes/nghttp2/nghttp2ver.h
+ @echo '# $(GENERATED). DO NOT EDIT.' > .depend.MSVC
+ gcc -MM $(CFLAGS) $(NGHTTP2_SRC) >> .depend.tmp
+ @echo '#' >> .depend.MSVC
+ @echo '# Release lib objects:' >> .depend.MSVC
+ sed -e $(REPLACE_R) .depend.tmp >> .depend.MSVC
+ @echo '#' >> .depend.MSVC
+ @echo '# Debug lib objects:' >> .depend.MSVC
+ sed -e $(REPLACE_D) .depend.tmp >> .depend.MSVC
+ rm -f .depend.tmp
+
+-include .depend.MSVC
diff --git a/lib/includes/CMakeLists.txt b/lib/includes/CMakeLists.txt
new file mode 100644
index 0000000..17de2ec
--- /dev/null
+++ b/lib/includes/CMakeLists.txt
@@ -0,0 +1,4 @@
+install(FILES
+ nghttp2/nghttp2.h
+ "${CMAKE_CURRENT_BINARY_DIR}/nghttp2/nghttp2ver.h"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/nghttp2")
diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am
new file mode 100644
index 0000000..c07cb4d
--- /dev/null
+++ b/lib/includes/Makefile.am
@@ -0,0 +1,26 @@
+# 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 = CMakeLists.txt
+
+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
index 0000000..7910db2
--- /dev/null
+++ b/lib/includes/nghttp2/nghttp2.h
@@ -0,0 +1,5941 @@
+/*
+ * 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
+
+/* Define WIN32 when build target is Win32 API (borrowed from
+ libcurl) */
+#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
+# define WIN32
+#endif
+
+/* Compatibility for non-Clang compilers */
+#ifndef __has_declspec_attribute
+# define __has_declspec_attribute(x) 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+/* MSVC < 2013 does not have inttypes.h because it is not C99
+ compliant. See compiler macros and version number in
+ https://sourceforge.net/p/predef/wiki/Compilers/ */
+# include <stdint.h>
+#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */
+# include <inttypes.h>
+#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */
+#include <sys/types.h>
+#include <stdarg.h>
+
+#include <nghttp2/nghttp2ver.h>
+
+#ifdef NGHTTP2_STATICLIB
+# define NGHTTP2_EXTERN
+#elif defined(WIN32) || (__has_declspec_attribute(dllexport) && \
+ __has_declspec_attribute(dllimport))
+# ifdef BUILDING_NGHTTP2
+# define NGHTTP2_EXTERN __declspec(dllexport)
+# else /* !BUILDING_NGHTTP2 */
+# define NGHTTP2_EXTERN __declspec(dllimport)
+# endif /* !BUILDING_NGHTTP2 */
+#else /* !defined(WIN32) */
+# ifdef BUILDING_NGHTTP2
+# define NGHTTP2_EXTERN __attribute__((visibility("default")))
+# else /* !BUILDING_NGHTTP2 */
+# define NGHTTP2_EXTERN
+# endif /* !BUILDING_NGHTTP2 */
+#endif /* !defined(WIN32) */
+
+/**
+ * @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"
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_PROTO_VERSION_ID`.
+ */
+#define NGHTTP2_PROTO_VERSION_ID_LEN 2
+
+/**
+ * @macro
+ *
+ * The serialized 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 "\x2h2"
+
+/**
+ * @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"
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_CLEARTEXT_PROTO_VERSION_ID`.
+ */
+#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN 3
+
+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 magic string, which is the first 24 bytes byte string of
+ * client connection preface.
+ */
+#define NGHTTP2_CLIENT_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_CLIENT_MAGIC`.
+ */
+#define NGHTTP2_CLIENT_MAGIC_LEN 24
+
+/**
+ * @macro
+ *
+ * The default max number of settings per SETTINGS frame
+ */
+#define NGHTTP2_DEFAULT_MAX_SETTINGS 32
+
+/**
+ * @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`,
+ * :type:`nghttp2_recv_callback` and
+ * :type:`nghttp2_send_data_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 or HEADERS frame for a given stream has been already
+ * submitted and has not been fully processed yet. Application
+ * should wait for the transmission of the previously submitted
+ * frame before submitting another.
+ */
+ 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,
+ /**
+ * Invalid HTTP header field was received and stream is going to be
+ * closed.
+ */
+ NGHTTP2_ERR_HTTP_HEADER = -531,
+ /**
+ * Violation in HTTP messaging rule.
+ */
+ NGHTTP2_ERR_HTTP_MESSAGING = -532,
+ /**
+ * Stream was refused.
+ */
+ NGHTTP2_ERR_REFUSED_STREAM = -533,
+ /**
+ * Unexpected internal error, but recovered.
+ */
+ NGHTTP2_ERR_INTERNAL = -534,
+ /**
+ * Indicates that a processing was canceled.
+ */
+ NGHTTP2_ERR_CANCEL = -535,
+ /**
+ * When a local endpoint expects to receive SETTINGS frame, it
+ * receives an other type of frame.
+ */
+ NGHTTP2_ERR_SETTINGS_EXPECTED = -536,
+ /**
+ * When a local endpoint receives too many settings entries
+ * in a single SETTINGS frame.
+ */
+ NGHTTP2_ERR_TOO_MANY_SETTINGS = -537,
+ /**
+ * The errors < :enum:`nghttp2_error.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 client magic (see :macro:`NGHTTP2_CLIENT_MAGIC`) was
+ * received and further processing is not possible.
+ */
+ NGHTTP2_ERR_BAD_CLIENT_MAGIC = -903,
+ /**
+ * Possible flooding by peer was detected in this HTTP/2 session.
+ * Flooding is measured by how many PING and SETTINGS frames with
+ * ACK flag set are queued for transmission. These frames are
+ * response for the peer initiated frames, and peer can cause memory
+ * exhaustion on server side to send these frames forever and does
+ * not read network.
+ */
+ NGHTTP2_ERR_FLOODED = -904
+} nghttp2_error;
+
+/**
+ * @struct
+ *
+ * The object representing single contiguous buffer.
+ */
+typedef struct {
+ /**
+ * The pointer to the buffer.
+ */
+ uint8_t *base;
+ /**
+ * The length of the buffer.
+ */
+ size_t len;
+} nghttp2_vec;
+
+struct nghttp2_rcbuf;
+
+/**
+ * @struct
+ *
+ * The object representing reference counted buffer. The details of
+ * this structure are intentionally hidden from the public API.
+ */
+typedef struct nghttp2_rcbuf nghttp2_rcbuf;
+
+/**
+ * @function
+ *
+ * Increments the reference count of |rcbuf| by 1.
+ */
+NGHTTP2_EXTERN void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf);
+
+/**
+ * @function
+ *
+ * Decrements the reference count of |rcbuf| by 1. If the reference
+ * count becomes zero, the object pointed by |rcbuf| will be freed.
+ * In this case, application must not use |rcbuf| again.
+ */
+NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf);
+
+/**
+ * @function
+ *
+ * Returns the underlying buffer managed by |rcbuf|.
+ */
+NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the underlying buffer is statically allocated,
+ * and 0 otherwise. This can be useful for language bindings that wish
+ * to avoid creating duplicate strings for these buffers.
+ */
+NGHTTP2_EXTERN int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf);
+
+/**
+ * @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 ("Literal
+ * Header Field never Indexed" representation must be used in HPACK
+ * encoding). Other implementation calls this bit as "sensitive".
+ */
+ NGHTTP2_NV_FLAG_NO_INDEX = 0x01,
+ /**
+ * This flag is set solely by application. If this flag is set, the
+ * library does not make a copy of header field name. This could
+ * improve performance.
+ */
+ NGHTTP2_NV_FLAG_NO_COPY_NAME = 0x02,
+ /**
+ * This flag is set solely by application. If this flag is set, the
+ * library does not make a copy of header field value. This could
+ * improve performance.
+ */
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE = 0x04
+} nghttp2_nv_flag;
+
+/**
+ * @struct
+ *
+ * The name/value pair, which mainly used to represent header fields.
+ */
+typedef struct {
+ /**
+ * The |name| byte string. If this struct is presented from library
+ * (e.g., :type:`nghttp2_on_frame_recv_callback`), |name| is
+ * guaranteed to be NULL-terminated. For some callbacks
+ * (:type:`nghttp2_before_frame_send_callback`,
+ * :type:`nghttp2_on_frame_send_callback`, and
+ * :type:`nghttp2_on_frame_not_send_callback`), it may not be
+ * NULL-terminated if header field is passed from application with
+ * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`).
+ * When application is constructing this struct, |name| is not
+ * required to be NULL-terminated.
+ */
+ uint8_t *name;
+ /**
+ * The |value| byte string. If this struct is presented from
+ * library (e.g., :type:`nghttp2_on_frame_recv_callback`), |value|
+ * is guaranteed to be NULL-terminated. For some callbacks
+ * (:type:`nghttp2_before_frame_send_callback`,
+ * :type:`nghttp2_on_frame_send_callback`, and
+ * :type:`nghttp2_on_frame_not_send_callback`), it may not be
+ * NULL-terminated if header field is passed from application with
+ * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE`).
+ * When application is constructing this struct, |value| is not
+ * required to be NULL-terminated.
+ */
+ uint8_t *value;
+ /**
+ * The length of the |name|, excluding terminating NULL.
+ */
+ size_t namelen;
+ /**
+ * The length of the |value|, excluding terminating NULL.
+ */
+ 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. This frame type won't be passed to any
+ * callbacks because the library processes this frame type and its
+ * preceding HEADERS/PUSH_PROMISE as a single frame.
+ */
+ NGHTTP2_CONTINUATION = 0x09,
+ /**
+ * The ALTSVC frame, which is defined in `RFC 7383
+ * <https://tools.ietf.org/html/rfc7838#section-4>`_.
+ */
+ NGHTTP2_ALTSVC = 0x0a,
+ /**
+ * The ORIGIN frame, which is defined by `RFC 8336
+ * <https://tools.ietf.org/html/rfc8336>`_.
+ */
+ NGHTTP2_ORIGIN = 0x0c,
+ /**
+ * The PRIORITY_UPDATE frame, which is defined by :rfc:`9218`.
+ */
+ NGHTTP2_PRIORITY_UPDATE = 0x10
+} nghttp2_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,
+ /**
+ * SETTINGS_ENABLE_CONNECT_PROTOCOL
+ * (`RFC 8441 <https://tools.ietf.org/html/rfc8441>`_)
+ */
+ NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08,
+ /**
+ * SETTINGS_NO_RFC7540_PRIORITIES (:rfc:`9218`)
+ */
+ NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09
+} nghttp2_settings_id;
+/* Note: If we add SETTINGS, update the capacity of
+ NGHTTP2_INBOUND_NUM_IV as well */
+
+/**
+ * @macro
+ *
+ * .. warning::
+ *
+ * Deprecated. The initial max concurrent streams is 0xffffffffu.
+ *
+ * Default maximum number of incoming concurrent streams. Use
+ * `nghttp2_submit_settings()` with
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS`
+ * to change the maximum number of incoming concurrent streams.
+ *
+ * .. note::
+ *
+ * The maximum number of outgoing concurrent streams is 100 by
+ * default.
+ */
+#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,
+ /**
+ * Indicates that END_STREAM flag must not be set even if
+ * NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send
+ * trailer fields with `nghttp2_submit_request()` or
+ * `nghttp2_submit_response()`.
+ */
+ NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02,
+ /**
+ * Indicates that application will send complete DATA frame in
+ * :type:`nghttp2_send_data_callback`.
+ */
+ NGHTTP2_DATA_FLAG_NO_COPY = 0x04
+} 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.NGHTTP2_DATA_FLAG_EOF` flag
+ * in |*data_flags|.
+ *
+ * Sometime it is desirable to avoid copying data into |buf| and let
+ * application to send data directly. To achieve this, set
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to
+ * |*data_flags| (and possibly other flags, just like when we do
+ * copy), and return the number of bytes to send without copying data
+ * into |buf|. The library, seeing
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke
+ * :type:`nghttp2_send_data_callback`. The application must send
+ * complete DATA frame in that callback.
+ *
+ * If this callback is set by `nghttp2_submit_request()`,
+ * `nghttp2_submit_response()` or `nghttp2_submit_headers()` and
+ * `nghttp2_submit_data()` with flag parameter
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to
+ * |*data_flags|, DATA frame will have END_STREAM flag set. Usually,
+ * this is expected behaviour and all are fine. One exception is send
+ * trailer fields. You cannot send trailer fields after sending frame
+ * with END_STREAM set. To avoid this problem, one can set
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along
+ * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the
+ * library not to set END_STREAM in DATA frame. Then application can
+ * use `nghttp2_submit_trailer()` to send trailer fields.
+ * `nghttp2_submit_trailer()` can be called inside this callback.
+ *
+ * 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_error.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()`.
+ *
+ * By default, |length| is limited to 16KiB at maximum. If peer
+ * allows larger frames, application can enlarge transmission buffer
+ * size. See :type:`nghttp2_data_source_read_length_callback` for
+ * more details.
+ *
+ * If the application just wants to return from
+ * `nghttp2_session_send()` or `nghttp2_session_mem_send()` without
+ * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`.
+ *
+ * In case of error, there are 2 choices. Returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will
+ * close the stream by issuing RST_STREAM with
+ * :enum:`nghttp2_error_code.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_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ * Returning :enum:`nghttp2_error.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., trailer fields) 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.
+ *
+ * Currently, no extension is supported. This is a place holder for
+ * the future extensions.
+ */
+ void *payload;
+} nghttp2_extension;
+
+/**
+ * @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_error.NGHTTP2_ERR_WOULDBLOCK`. For
+ * other errors, it must return
+ * :enum:`nghttp2_error.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()`.
+ *
+ * .. note::
+ *
+ * The |length| may be very small. If that is the case, and
+ * application disables Nagle algorithm (``TCP_NODELAY``), then just
+ * writing |data| to the network stack leads to very small packet,
+ * and it is very inefficient. An application should be responsible
+ * to buffer up small chunks of data as necessary to avoid this
+ * situation.
+ */
+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
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in
+ * :type:`nghttp2_data_source_read_callback` to send complete DATA
+ * frame.
+ *
+ * The |frame| is a DATA frame to send. The |framehd| is the
+ * serialized frame header (9 bytes). The |length| is the length of
+ * application data to send (this does not include padding). The
+ * |source| is the same pointer passed to
+ * :type:`nghttp2_data_source_read_callback`.
+ *
+ * The application first must send frame header |framehd| of length 9
+ * bytes. If ``frame->data.padlen > 0``, send 1 byte of value
+ * ``frame->data.padlen - 1``. Then send exactly |length| bytes of
+ * application data. Finally, if ``frame->data.padlen > 1``, send
+ * ``frame->data.padlen - 1`` bytes of zero as padding.
+ *
+ * The application has to send complete DATA frame in this callback.
+ * If all data were written successfully, return 0.
+ *
+ * If it cannot send any data at all, just return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`; the library will call
+ * this callback with the same parameters later (It is recommended to
+ * send complete DATA frame at once in this function to deal with
+ * error; if partial frame data has already sent, it is impossible to
+ * send another data in that state, and all we can do is tear down
+ * connection). When data is fully processed, but application wants
+ * to make `nghttp2_session_mem_send()` or `nghttp2_session_send()`
+ * return immediately without processing next frames, return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. If application decided to
+ * reset this stream, return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, then
+ * the library will send RST_STREAM with INTERNAL_ERROR as error code.
+ * The application can also return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which will
+ * result in connection closure. Returning any other value is treated
+ * as :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned.
+ */
+typedef int (*nghttp2_send_data_callback)(nghttp2_session *session,
+ nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source,
+ 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_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF
+ * before it reads any single byte, it must return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must
+ * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ * Returning 0 is treated as
+ * :enum:`nghttp2_error.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()` and
+ * `nghttp2_session_mem_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`.
+ *
+ * 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_error.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()` and
+ * `nghttp2_session_mem_recv()` when an invalid non-DATA frame is
+ * received. The error is indicated by the |lib_error_code|, which is
+ * one of the values defined in :type:`nghttp2_error`. 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_mem_recv()` functions
+ * immediately return
+ * :enum:`nghttp2_error.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, int lib_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_error.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_error.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.
+ * It can also return :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` to
+ * cancel the transmission of the given frame.
+ *
+ * If there is a fatal error while executing this callback, the
+ * implementation should return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which makes
+ * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * If the other value is returned, it is treated as if
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned.
+ * But the implementation should not rely on this since the library
+ * may define new return value to extend its capability.
+ *
+ * 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_send()` and `nghttp2_session_mem_send()` functions
+ * immediately return
+ * :enum:`nghttp2_error.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_send()` and `nghttp2_session_mem_send()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * `nghttp2_session_get_stream_user_data()` can be used to get
+ * associated data.
+ *
+ * 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()`, `nghttp2_session_mem_recv()`,
+ * `nghttp2_session_send()`, and `nghttp2_session_mem_send()`
+ * functions immediately return
+ * :enum:`nghttp2_error.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.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 server applications probably create an object to store
+ * information about new stream if ``frame->hd.type ==
+ * NGHTTP2_HEADERS`` and ``frame->headers.cat ==
+ * NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side,
+ * ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST``
+ * containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing
+ * trailer fields and never get PUSH_PROMISE in this callback.
+ *
+ * For the client applications, ``frame->hd.type`` is either
+ * ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of
+ * ``NGHTTP2_HEADERS``, ``frame->headers.cat ==
+ * NGHTTP2_HCAT_RESPONSE`` means that it is the first response
+ * headers, but it may be non-final response which is indicated by 1xx
+ * status code. In this case, there may be zero or more HEADERS frame
+ * with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` which has
+ * non-final response code and finally client gets exactly one HEADERS
+ * frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS``
+ * containing final response headers (non-1xx status code). The
+ * trailer fields also has ``frame->headers.cat ==
+ * NGHTTP2_HCAT_HEADERS`` which does not contain any status code.
+ *
+ * Returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will
+ * close the stream (promised stream if frame is PUSH_PROMISE) by
+ * issuing RST_STREAM with
+ * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case,
+ * :type:`nghttp2_on_header_callback` and
+ * :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_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ * Again, use ``frame->push_promise.promised_stream_id`` as stream_id
+ * parameter in `nghttp2_submit_rst_stream()` if frame is
+ * PUSH_PROMISE.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * It can return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` to
+ * reset the stream (promised stream if frame is PUSH_PROMISE). For
+ * critical errors, it must return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other
+ * value is returned, it is treated as if
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. If
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned,
+ * `nghttp2_session_mem_recv()` function will immediately return
+ * :enum:`nghttp2_error.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.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. More specifically, "Literal Header
+ * Field never Indexed" representation must be used in HPACK encoding.
+ *
+ * When this callback is invoked, ``frame->hd.type`` is either
+ * :enum:`nghttp2_frame_type.NGHTTP2_HEADERS` or
+ * :enum:`nghttp2_frame_type.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.
+ *
+ * Both |name| and |value| are guaranteed to be NULL-terminated. The
+ * |namelen| and |valuelen| do not include terminal NULL. If
+ * `nghttp2_option_set_no_http_messaging()` is used with nonzero
+ * value, NULL character may be included in |name| or |value| before
+ * terminating NULL.
+ *
+ * Please note that unless `nghttp2_option_set_no_http_messaging()` is
+ * used, nghttp2 library does perform validation against the |name|
+ * and the |value| using `nghttp2_check_header_name()` and
+ * `nghttp2_check_header_value()`. In addition to this, nghttp2
+ * performs validation based on HTTP Messaging rule, which is briefly
+ * explained in :ref:`http-messaging` section.
+ *
+ * If the application uses `nghttp2_session_mem_recv()`, it can return
+ * :enum:`nghttp2_error.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_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will
+ * close the stream (promised stream if frame is PUSH_PROMISE) by
+ * issuing RST_STREAM with
+ * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case,
+ * :type:`nghttp2_on_header_callback` and
+ * :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_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ * Again, use ``frame->push_promise.promised_stream_id`` as stream_id
+ * parameter in `nghttp2_submit_rst_stream()` if frame is
+ * PUSH_PROMISE.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * It may return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` or
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. For
+ * other critical failures, it must return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other
+ * nonzero value is returned, it is treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned,
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_header_callback()`.
+ *
+ * .. warning::
+ *
+ * Application should properly limit the total buffer size to store
+ * incoming header fields. Without it, peer may send large number
+ * of header fields or large header fields to cause out of memory in
+ * local endpoint. Due to how HPACK works, peer can do this
+ * effectively without using much memory on their own.
+ */
+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 a header name/value pair is received
+ * for the |frame|. The |name| is header name. The |value| is header
+ * value. The |flags| is bitwise OR of one or more of
+ * :type:`nghttp2_nv_flag`.
+ *
+ * This callback behaves like :type:`nghttp2_on_header_callback`,
+ * except that |name| and |value| are stored in reference counted
+ * buffer. If application wishes to keep these references without
+ * copying them, use `nghttp2_rcbuf_incref()` to increment their
+ * reference count. It is the application's responsibility to call
+ * `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so
+ * as not to leak memory. If the |session| is created by
+ * `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`,
+ * the function to free memory is the one belongs to the mem
+ * parameter. As long as this free function alives, |name| and
+ * |value| can live after |session| was destroyed.
+ */
+typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, uint8_t flags,
+ void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when a invalid header name/value pair is
+ * received for the |frame|.
+ *
+ * The parameter and behaviour are similar to
+ * :type:`nghttp2_on_header_callback`. The difference is that this
+ * callback is only invoked when a invalid header name/value pair is
+ * received which is treated as stream error if this callback is not
+ * set. Only invalid regular header field are passed to this
+ * callback. In other words, invalid pseudo header field is not
+ * passed to this callback. Also header fields which includes upper
+ * cased latter are also treated as error without passing them to this
+ * callback.
+ *
+ * This callback is only considered if HTTP messaging validation is
+ * turned on (which is on by default, see
+ * `nghttp2_option_set_no_http_messaging()`).
+ *
+ * With this callback, application inspects the incoming invalid
+ * field, and it also can reset stream from this callback by returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By
+ * default, the error code is
+ * :enum:`nghttp2_error_code.NGHTTP2_PROTOCOL_ERROR`. To change the
+ * error code, call `nghttp2_submit_rst_stream()` with the error code
+ * of choice in addition to returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ *
+ * If 0 is returned, the header field is ignored, and the stream is
+ * not reset.
+ */
+typedef int (*nghttp2_on_invalid_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 a invalid header name/value pair is
+ * received for the |frame|.
+ *
+ * The parameter and behaviour are similar to
+ * :type:`nghttp2_on_header_callback2`. The difference is that this
+ * callback is only invoked when a invalid header name/value pair is
+ * received which is silently ignored if this callback is not set.
+ * Only invalid regular header field are passed to this callback. In
+ * other words, invalid pseudo header field is not passed to this
+ * callback. Also header fields which includes upper cased latter are
+ * also treated as error without passing them to this callback.
+ *
+ * This callback is only considered if HTTP messaging validation is
+ * turned on (which is on by default, see
+ * `nghttp2_option_set_no_http_messaging()`).
+ *
+ * With this callback, application inspects the incoming invalid
+ * field, and it also can reset stream from this callback by returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By
+ * default, the error code is
+ * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. To change the
+ * error code, call `nghttp2_submit_rst_stream()` with the error code
+ * of choice in addition to returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ */
+typedef int (*nghttp2_on_invalid_header_callback2)(
+ nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, 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_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning
+ * ``frame->hd.length`` means no padding is added. Returning
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make
+ * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions
+ * immediately return
+ * :enum:`nghttp2_error.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_error.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_frame_type.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_error.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_error.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);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when chunk of extension frame payload is
+ * received. The |hd| points to frame header. The received
+ * chunk is |data| of length |len|.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ *
+ * To abort processing this extension frame, return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`.
+ *
+ * If fatal error occurred, application should return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other
+ * values are returned, currently they are treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ */
+typedef int (*nghttp2_on_extension_chunk_recv_callback)(
+ nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data,
+ size_t len, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when library asks the application to
+ * unpack extension payload from its wire format. The extension
+ * payload has been passed to the application using
+ * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header
+ * is already unpacked by the library and provided as |hd|.
+ *
+ * To receive extension frames, the application must tell desired
+ * extension frame type to the library using
+ * `nghttp2_option_set_user_recv_extension_type()`.
+ *
+ * The implementation of this function may store the pointer to the
+ * created object as a result of unpacking in |*payload|, and returns
+ * 0. The pointer stored in |*payload| is opaque to the library, and
+ * the library does not own its pointer. |*payload| is initialized as
+ * ``NULL``. The |*payload| is available as ``frame->ext.payload`` in
+ * :type:`nghttp2_on_frame_recv_callback`. Therefore if application
+ * can free that memory inside :type:`nghttp2_on_frame_recv_callback`
+ * callback. Of course, application has a liberty not to use
+ * |*payload|, and do its own mechanism to process extension frames.
+ *
+ * To abort processing this extension frame, return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`.
+ *
+ * If fatal error occurred, application should return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other
+ * values are returned, currently they are treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ */
+typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session,
+ void **payload,
+ const nghttp2_frame_hd *hd,
+ void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when library asks the application to pack
+ * extension payload in its wire format. The frame header will be
+ * packed by library. Application must pack payload only.
+ * ``frame->ext.payload`` is the object passed to
+ * `nghttp2_submit_extension()` as payload parameter. Application
+ * must pack extension payload to the |buf| of its capacity |len|
+ * bytes. The |len| is at least 16KiB.
+ *
+ * The implementation of this function should return the number of
+ * bytes written into |buf| when it succeeds.
+ *
+ * To abort processing this extension frame, return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and
+ * :type:`nghttp2_on_frame_not_send_callback` will be invoked.
+ *
+ * If fatal error occurred, application should return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
+ * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions
+ * immediately return
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other
+ * values are returned, currently they are treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return
+ * value is strictly larger than |len|, it is treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ */
+typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session,
+ uint8_t *buf, size_t len,
+ const nghttp2_frame *frame,
+ void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when library provides the error message
+ * intended for human consumption. This callback is solely for
+ * debugging purpose. The |msg| is typically NULL-terminated string
+ * of length |len|. |len| does not include the sentinel NULL
+ * character.
+ *
+ * This function is deprecated. The new application should use
+ * :type:`nghttp2_error_callback2`.
+ *
+ * The format of error message may change between nghttp2 library
+ * versions. The application should not depend on the particular
+ * format.
+ *
+ * Normally, application should return 0 from this callback. If fatal
+ * error occurred while doing something in this callback, application
+ * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ * In this case, library will return immediately with return value
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if
+ * nonzero value is returned from this callback, they are treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application
+ * should not rely on this details.
+ */
+typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg,
+ size_t len, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when library provides the error code, and
+ * message. This callback is solely for debugging purpose.
+ * |lib_error_code| is one of error code defined in
+ * :enum:`nghttp2_error`. The |msg| is typically NULL-terminated
+ * string of length |len|, and intended for human consumption. |len|
+ * does not include the sentinel NULL character.
+ *
+ * The format of error message may change between nghttp2 library
+ * versions. The application should not depend on the particular
+ * format.
+ *
+ * Normally, application should return 0 from this callback. If fatal
+ * error occurred while doing something in this callback, application
+ * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`.
+ * In this case, library will return immediately with return value
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if
+ * nonzero value is returned from this callback, they are treated as
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application
+ * should not rely on this details.
+ */
+typedef int (*nghttp2_error_callback2)(nghttp2_session *session,
+ int lib_error_code, const char *msg,
+ size_t len, 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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()` and
+ * `nghttp2_session_mem_recv()` when a frame is received.
+ */
+NGHTTP2_EXTERN 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()` and
+ * `nghttp2_session_mem_recv()` when an invalid non-DATA frame is
+ * received.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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. If both
+ * `nghttp2_session_callbacks_set_on_header_callback()` and
+ * `nghttp2_session_callbacks_set_on_header_callback2()` are used to
+ * set callbacks, the latter has the precedence.
+ */
+NGHTTP2_EXTERN 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 a header name/value pair is
+ * received.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_header_callback2 on_header_callback2);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a invalid header name/value
+ * pair is received. If both
+ * `nghttp2_session_callbacks_set_on_invalid_header_callback()` and
+ * `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are
+ * used to set callbacks, the latter takes the precedence.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_invalid_header_callback on_invalid_header_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a invalid header name/value
+ * pair is received.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_invalid_header_callback2 on_invalid_header_callback2);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the library asks application
+ * how many padding bytes are required for the transmission of the
+ * given frame.
+ */
+NGHTTP2_EXTERN 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`.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_frame_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_begin_frame_callback on_begin_frame_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in
+ * :type:`nghttp2_data_source_read_callback` to avoid data copy.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_send_data_callback send_data_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the library asks the
+ * application to pack extension frame payload in wire format.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_pack_extension_callback pack_extension_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the library asks the
+ * application to unpack extension frame payload from wire format.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_unpack_extension_callback unpack_extension_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when chunk of extension frame
+ * payload is received.
+ */
+NGHTTP2_EXTERN void
+nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when library tells error message to
+ * the application.
+ *
+ * This function is deprecated. The new application should use
+ * `nghttp2_session_callbacks_set_error_callback2()`.
+ *
+ * If both :type:`nghttp2_error_callback` and
+ * :type:`nghttp2_error_callback2` are set, the latter takes
+ * precedence.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback(
+ nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when library tells error code, and
+ * message to the application.
+ *
+ * If both :type:`nghttp2_error_callback` and
+ * :type:`nghttp2_error_callback2` are set, the latter takes
+ * precedence.
+ */
+NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback2(
+ nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2);
+
+/**
+ * @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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN int nghttp2_option_new(nghttp2_option **option_ptr);
+
+/**
+ * @function
+ *
+ * Frees any resources allocated for |option|. If |option| is
+ * ``NULL``, this function does nothing.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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, the maximum number of outgoing concurrent
+ * streams is initially limited to 100 to avoid issues when the local
+ * endpoint submits lots of requests before receiving initial SETTINGS
+ * frame from the remote endpoint, since sending them at once to the
+ * remote endpoint could lead to rejection of some of the requests.
+ * This value will be overwritten when the local endpoint receives
+ * initial SETTINGS frame from the remote endpoint, either to the
+ * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the
+ * default value (unlimited) if none was advertised.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,
+ uint32_t val);
+
+/**
+ * @function
+ *
+ * By default, nghttp2 library, if configured as server, requires
+ * first 24 bytes of client magic byte string (MAGIC). In most cases,
+ * this will simplify the implementation of server. But sometimes
+ * server may want to detect the application protocol based on first
+ * few bytes on clear text communication.
+ *
+ * If this option is used with nonzero |val|, nghttp2 library does not
+ * handle MAGIC. It still checks following SETTINGS frame. This
+ * means that applications should deal with MAGIC by themselves.
+ *
+ * If this option is not used or used with zero value, if MAGIC does
+ * not match :macro:`NGHTTP2_CLIENT_MAGIC`, `nghttp2_session_recv()`
+ * and `nghttp2_session_mem_recv()` will return error
+ * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`, which is fatal
+ * error.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val);
+
+/**
+ * @function
+ *
+ * By default, nghttp2 library enforces subset of HTTP Messaging rules
+ * described in `HTTP/2 specification, section 8
+ * <https://tools.ietf.org/html/rfc7540#section-8>`_. See
+ * :ref:`http-messaging` section for details. For those applications
+ * who use nghttp2 library as non-HTTP use, give nonzero to |val| to
+ * disable this enforcement. Please note that disabling this feature
+ * does not change the fundamental client and server model of HTTP.
+ * That is, even if the validation is disabled, only client can send
+ * requests.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_no_http_messaging(nghttp2_option *option,
+ int val);
+
+/**
+ * @function
+ *
+ * RFC 7540 does not enforce any limit on the number of incoming
+ * reserved streams (in RFC 7540 terms, streams in reserved (remote)
+ * state). This only affects client side, since only server can push
+ * streams. Malicious server can push arbitrary number of streams,
+ * and make client's memory exhausted. This option can set the
+ * maximum number of such incoming streams to avoid possible memory
+ * exhaustion. If this option is set, and pushed streams are
+ * automatically closed on reception, without calling user provided
+ * callback, if they exceed the given limit. The default value is
+ * 200. If session is configured as server side, this option has no
+ * effect. Server can control the number of streams to push.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
+ uint32_t val);
+
+/**
+ * @function
+ *
+ * Sets extension frame type the application is willing to handle with
+ * user defined callbacks (see
+ * :type:`nghttp2_on_extension_chunk_recv_callback` and
+ * :type:`nghttp2_unpack_extension_callback`). The |type| is
+ * extension frame type, and must be strictly greater than 0x9.
+ * Otherwise, this function does nothing. The application can call
+ * this function multiple times to set more than one frame type to
+ * receive. The application does not have to call this function if it
+ * just sends extension frames.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
+ uint8_t type);
+
+/**
+ * @function
+ *
+ * Sets extension frame type the application is willing to receive
+ * using builtin handler. The |type| is the extension frame type to
+ * receive, and must be strictly greater than 0x9. Otherwise, this
+ * function does nothing. The application can call this function
+ * multiple times to set more than one frame type to receive. The
+ * application does not have to call this function if it just sends
+ * extension frames.
+ *
+ * If same frame type is passed to both
+ * `nghttp2_option_set_builtin_recv_extension_type()` and
+ * `nghttp2_option_set_user_recv_extension_type()`, the latter takes
+ * precedence.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option,
+ uint8_t type);
+
+/**
+ * @function
+ *
+ * This option prevents the library from sending PING frame with ACK
+ * flag set automatically when PING frame without ACK flag set is
+ * received. If this option is set to nonzero, the library won't send
+ * PING frame with ACK flag set in the response for incoming PING
+ * frame. The application can send PING frame with ACK flag set using
+ * `nghttp2_submit_ping()` with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK`
+ * as flags parameter.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option,
+ int val);
+
+/**
+ * @function
+ *
+ * This option sets the maximum length of header block (a set of
+ * header fields per one HEADERS frame) to send. The length of a
+ * given set of header fields is calculated using
+ * `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If
+ * application attempts to send header fields larger than this limit,
+ * the transmission of the frame fails with error code
+ * :enum:`nghttp2_error.NGHTTP2_ERR_FRAME_SIZE_ERROR`.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_max_send_header_block_length(nghttp2_option *option,
+ size_t val);
+
+/**
+ * @function
+ *
+ * This option sets the maximum dynamic table size for deflating
+ * header fields. The default value is 4KiB. In HTTP/2, receiver of
+ * deflated header block can specify maximum dynamic table size. The
+ * actual maximum size is the minimum of the size receiver specified
+ * and this option value.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
+ size_t val);
+
+/**
+ * @function
+ *
+ * This option prevents the library from retaining closed streams to
+ * maintain the priority tree. If this option is set to nonzero,
+ * applications can discard closed stream completely to save memory.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is submitted via `nghttp2_submit_settings()`, any
+ * closed streams are not retained regardless of this option.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
+ int val);
+
+/**
+ * @function
+ *
+ * This function sets the maximum number of outgoing SETTINGS ACK and
+ * PING ACK frames retained in :type:`nghttp2_session` object. If
+ * more than those frames are retained, the peer is considered to be
+ * misbehaving and session will be closed. The default value is 1000.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
+ size_t val);
+
+/**
+ * @function
+ *
+ * This function sets the maximum number of SETTINGS entries per
+ * SETTINGS frame that will be accepted. If more than those entries
+ * are received, the peer is considered to be misbehaving and session
+ * will be closed. The default value is 32.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
+ size_t val);
+
+/**
+ * @function
+ *
+ * This option, if set to nonzero, allows server to fallback to
+ * :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not
+ * received from client, and server submitted
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * = 1 via `nghttp2_submit_settings()`. Most of the advanced
+ * functionality for RFC 7540 priorities are still disabled. This
+ * fallback only enables the minimal feature set of RFC 7540
+ * priorities to deal with priority signaling from client.
+ *
+ * Client session ignores this option.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option,
+ int val);
+
+/**
+ * @function
+ *
+ * This option, if set to nonzero, turns off RFC 9113 leading and
+ * trailing white spaces validation against HTTP field value. Some
+ * important fields, such as HTTP/2 pseudo header fields, are
+ * validated more strictly and this option does not apply to them.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(
+ nghttp2_option *option, int val);
+
+/**
+ * @function
+ *
+ * This function sets the rate limit for the incoming stream reset
+ * (RST_STREAM frame). It is server use only. It is a token-bucket
+ * based rate limiter. |burst| specifies the number of tokens that is
+ * initially available. The maximum number of tokens is capped to
+ * this value. |rate| specifies the number of tokens that are
+ * regenerated per second. An incoming RST_STREAM consumes one token.
+ * If there is no token available, GOAWAY is sent to tear down the
+ * connection. |burst| and |rate| default to 1000 and 33
+ * respectively.
+ */
+NGHTTP2_EXTERN void
+nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
+ uint64_t burst, uint64_t rate);
+
+/**
+ * @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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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 times as possible until the user callback
+ * :type:`nghttp2_send_callback` returns
+ * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`, the outbound queue
+ * becomes empty or flow control is triggered (remote window size
+ * becomes depleted or maximum number of concurrent streams is
+ * reached). 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. If :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` is returned from
+ * :type:`nghttp2_before_frame_send_callback`, the current frame
+ * transmission is canceled, and
+ * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort
+ * the following steps.
+ *
+ * 8. :type:`nghttp2_send_callback` is invoked one or more times to
+ * send the frame.
+ *
+ * 9. :type:`nghttp2_on_frame_send_callback` is invoked.
+ *
+ * 10. 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`
+ * The callback function failed.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ *
+ * .. note::
+ *
+ * This function may produce very small byte string. If that is the
+ * case, and application disables Nagle algorithm (``TCP_NODELAY``),
+ * then writing this small chunk leads to very small packet, and it
+ * is very inefficient. An application should be responsible to
+ * buffer up small chunks of data as necessary to avoid this
+ * situation.
+ */
+NGHTTP2_EXTERN 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_error.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. For invalid header field,
+ * :type:`nghttp2_on_invalid_header_callback` is called. 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_error.NGHTTP2_ERR_EOF`
+ * The remote peer did shutdown on the connection.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`
+ * The callback function failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`
+ * Invalid client magic was detected. This error only returns
+ * when |session| was configured as server and
+ * `nghttp2_option_set_no_recv_client_magic()` is not used with
+ * nonzero value.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED`
+ * Flooding was detected in this HTTP/2 session, and it must be
+ * closed. This is most likely caused by misbehaviour of peer.
+ */
+NGHTTP2_EXTERN 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 to receive 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 |inlen| bytes of input data unless either an error occurs or
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from
+ * :type:`nghttp2_on_header_callback` or
+ * :type:`nghttp2_on_data_chunk_recv_callback`. If
+ * :enum:`nghttp2_error.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`
+ * The callback function failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`
+ * Invalid client magic was detected. This error only returns
+ * when |session| was configured as server and
+ * `nghttp2_option_set_no_recv_client_magic()` is not used with
+ * nonzero value.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED`
+ * Flooding was detected in this HTTP/2 session, and it must be
+ * closed. This is most likely caused by misbehaviour of peer.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The stream does not exist; or no deferred data exist.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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``.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The stream does not exist
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_set_stream_user_data(nghttp2_session *session,
+ int32_t stream_id, void *stream_user_data);
+
+/**
+ * @function
+ *
+ * Sets |user_data| to |session|, overwriting the existing user data
+ * specified in `nghttp2_session_client_new()`, or
+ * `nghttp2_session_server_new()`.
+ */
+NGHTTP2_EXTERN void nghttp2_session_set_user_data(nghttp2_session *session,
+ void *user_data);
+
+/**
+ * @function
+ *
+ * Returns the number of frames in the outbound queue. This does not
+ * include the deferred DATA frames.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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 does not take into account the amount of received
+ * data from the remote endpoint. Use
+ * `nghttp2_session_get_stream_local_window_size()` to know the amount
+ * of data the remote endpoint can send without receiving stream level
+ * WINDOW_UPDATE frame. Note that each stream is still subject to the
+ * connection level flow control.
+ *
+ * This function returns -1 if it fails.
+ */
+NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size(
+ nghttp2_session *session, int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns the amount of flow-controlled payload (e.g., DATA) that the
+ * remote endpoint can send without receiving stream level
+ * WINDOW_UPDATE frame. It is also subject to the connection level
+ * flow control. So the actual amount of data to send is
+ * min(`nghttp2_session_get_stream_local_window_size()`,
+ * `nghttp2_session_get_local_window_size()`).
+ *
+ * This function returns -1 if it fails.
+ */
+NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_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.
+ */
+NGHTTP2_EXTERN 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 does not take into account the amount of received
+ * data from the remote endpoint. Use
+ * `nghttp2_session_get_local_window_size()` to know the amount of
+ * data the remote endpoint can send without receiving
+ * connection-level WINDOW_UPDATE frame. Note that each stream is
+ * still subject to the stream level flow control.
+ *
+ * This function returns -1 if it fails.
+ */
+NGHTTP2_EXTERN int32_t
+nghttp2_session_get_effective_local_window_size(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the amount of flow-controlled payload (e.g., DATA) that the
+ * remote endpoint can send without receiving connection level
+ * WINDOW_UPDATE frame. Note that each stream is still subject to the
+ * stream level flow control (see
+ * `nghttp2_session_get_stream_local_window_size()`).
+ *
+ * This function returns -1 if it fails.
+ */
+NGHTTP2_EXTERN int32_t
+nghttp2_session_get_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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_get_stream_remote_close(nghttp2_session *session,
+ int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns the current dynamic table size of HPACK inflater, including
+ * the overhead 32 bytes per entry described in RFC 7541.
+ */
+NGHTTP2_EXTERN size_t
+nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the current dynamic table size of HPACK deflater including
+ * the overhead 32 bytes per entry described in RFC 7541.
+ */
+NGHTTP2_EXTERN size_t
+nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session);
+
+/**
+ * @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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |last_stream_id| is invalid.
+ */
+NGHTTP2_EXTERN 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_error.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The |session| is initialized as client.
+ */
+NGHTTP2_EXTERN 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`.
+ */
+NGHTTP2_EXTERN uint32_t nghttp2_session_get_remote_settings(
+ nghttp2_session *session, nghttp2_settings_id id);
+
+/**
+ * @function
+ *
+ * Returns the value of SETTINGS |id| of local endpoint acknowledged
+ * by the remote endpoint. The |id| must be one of the values defined
+ * in :enum:`nghttp2_settings_id`.
+ */
+NGHTTP2_EXTERN uint32_t nghttp2_session_get_local_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_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |next_stream_id| is strictly less than the value
+ * `nghttp2_session_get_next_stream_id()` returns; or
+ * |next_stream_id| is invalid (e.g., even integer for client, or
+ * odd integer for server).
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN 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. The consumed bytes are counted towards both
+ * connection and stream level WINDOW_UPDATE (see
+ * `nghttp2_session_consume_connection()` and
+ * `nghttp2_session_consume_stream()` to update consumption
+ * independently). 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * Automatic WINDOW_UPDATE is not disabled.
+ */
+NGHTTP2_EXTERN int nghttp2_session_consume(nghttp2_session *session,
+ int32_t stream_id, size_t size);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_consume()`, but this only tells library that
+ * |size| bytes were consumed only for connection level. Note that
+ * HTTP/2 maintains connection and stream level flow control windows
+ * independently.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * Automatic WINDOW_UPDATE is not disabled.
+ */
+NGHTTP2_EXTERN int nghttp2_session_consume_connection(nghttp2_session *session,
+ size_t size);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_consume()`, but this only tells library that
+ * |size| bytes were consumed only for stream denoted by |stream_id|.
+ * Note that HTTP/2 maintains connection and stream level flow control
+ * windows independently.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * Automatic WINDOW_UPDATE is not disabled.
+ */
+NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session,
+ int32_t stream_id,
+ size_t size);
+
+/**
+ * @function
+ *
+ * Changes priority of existing stream denoted by |stream_id|. The
+ * new priority specification is |pri_spec|.
+ *
+ * The priority is changed silently and instantly, and no PRIORITY
+ * frame will be sent to notify the peer of this change. This
+ * function may be useful for server to change the priority of pushed
+ * stream.
+ *
+ * If |session| is initialized as server, and ``pri_spec->stream_id``
+ * points to the idle stream, the idle stream is created if it does
+ * not exist. The created idle stream will depend on root stream
+ * (stream 0) with weight 16.
+ *
+ * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
+ * found, we use default priority instead of given |pri_spec|. That
+ * is make stream depend on root stream with weight 16.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is submitted via `nghttp2_submit_settings()`, this
+ * function does nothing and returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * Attempted to depend on itself; or no stream exist for the given
+ * |stream_id|; or |stream_id| is 0
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_change_stream_priority(nghttp2_session *session,
+ int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec);
+
+/**
+ * @function
+ *
+ * Creates idle stream with the given |stream_id|, and priority
+ * |pri_spec|.
+ *
+ * The stream creation is done without sending PRIORITY frame, which
+ * means that peer does not know about the existence of this idle
+ * stream in the local endpoint.
+ *
+ * RFC 7540 does not disallow the use of creation of idle stream with
+ * odd or even stream ID regardless of client or server. So this
+ * function can create odd or even stream ID regardless of client or
+ * server. But probably it is a bit safer to use the stream ID the
+ * local endpoint can initiate (in other words, use odd stream ID for
+ * client, and even stream ID for server), to avoid potential
+ * collision from peer's instruction. Also we can use
+ * `nghttp2_session_set_next_stream_id()` to avoid to open created
+ * idle streams accidentally if we follow this recommendation.
+ *
+ * If |session| is initialized as server, and ``pri_spec->stream_id``
+ * points to the idle stream, the idle stream is created if it does
+ * not exist. The created idle stream will depend on root stream
+ * (stream 0) with weight 16.
+ *
+ * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
+ * found, we use default priority instead of given |pri_spec|. That
+ * is make stream depend on root stream with weight 16.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is submitted via `nghttp2_submit_settings()`, this
+ * function does nothing and returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * Attempted to depend on itself; or stream denoted by |stream_id|
+ * already exists; or |stream_id| cannot be used to create idle
+ * stream (in other words, local endpoint has already opened
+ * stream ID greater than or equal to the given stream ID; or
+ * |stream_id| is 0
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec);
+
+/**
+ * @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.
+ *
+ * .. warning::
+ *
+ * This function is deprecated in favor of
+ * `nghttp2_session_upgrade2()`, because this function lacks the
+ * parameter to tell the library the request method used in the
+ * original HTTP request. This information is required for client
+ * to validate actual response body length against content-length
+ * header field (see `nghttp2_option_set_no_http_messaging()`). If
+ * HEAD is used in request, the length of response body must be 0
+ * regardless of value included in content-length header field.
+ *
+ * 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |settings_payload| is badly formed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * The stream ID 1 is already used or closed; or is not available.
+ */
+NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session,
+ const uint8_t *settings_payload,
+ size_t settings_payloadlen,
+ void *stream_user_data);
+
+/**
+ * @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).
+ *
+ * If the request method is HEAD, pass nonzero value to
+ * |head_request|. Otherwise, pass 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |settings_payload| is badly formed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * The stream ID 1 is already used or closed; or is not available.
+ */
+NGHTTP2_EXTERN int nghttp2_session_upgrade2(nghttp2_session *session,
+ const uint8_t *settings_payload,
+ size_t settings_payloadlen,
+ int head_request,
+ 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 ``6*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_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |iv| contains duplicate settings ID or invalid value.
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`
+ * The provided |buflen| size is too small to hold the output.
+ */
+NGHTTP2_EXTERN 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`.
+ */
+NGHTTP2_EXTERN const char *nghttp2_strerror(int lib_error_code);
+
+/**
+ * @function
+ *
+ * Returns string representation of HTTP/2 error code |error_code|
+ * (e.g., ``PROTOCOL_ERROR`` is returned if ``error_code ==
+ * NGHTTP2_PROTOCOL_ERROR``). If string representation is unknown for
+ * given |error_code|, this function returns string ``unknown``.
+ */
+NGHTTP2_EXTERN const char *nghttp2_http2_strerror(uint32_t 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 [:macro:`NGHTTP2_MIN_WEIGHT`,
+ * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN void
+nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the |pri_spec| is filled with default values.
+ */
+NGHTTP2_EXTERN 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 [:macro:`NGHTTP2_MIN_WEIGHT`,
+ * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight``
+ * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than
+ * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes
+ * :macro:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is received by a remote endpoint, |pri_spec| is
+ * ignored, and treated as if ``NULL`` is specified.
+ *
+ * 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. For header fields with
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set,
+ * header field name and value are not copied respectively. With
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application
+ * is responsible to pass header field name in lowercase. The
+ * application should maintain the references to them until
+ * :type:`nghttp2_on_frame_send_callback` or
+ * :type:`nghttp2_on_frame_not_send_callback` is called.
+ *
+ * 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
+ * (https://tools.ietf.org/html/rfc7231#section-4) 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ * No stream ID is available because maximum stream ID was
+ * reached.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * Trying to depend on itself (new stream ID equals
+ * ``pri_spec->stream_id``).
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * The |session| is server session.
+ *
+ * .. warning::
+ *
+ * This function returns assigned stream ID if it succeeds. But
+ * that stream is not created yet. The application must not submit
+ * frame to that stream ID before
+ * :type:`nghttp2_before_frame_send_callback` is called for this
+ * frame. This means `nghttp2_session_get_stream_user_data()` does
+ * not work before the callback. But
+ * `nghttp2_session_set_stream_user_data()` handles this situation
+ * specially, and it can set data to a stream during this period.
+ *
+ */
+NGHTTP2_EXTERN 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. For header fields with
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set,
+ * header field name and value are not copied respectively. With
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application
+ * is responsible to pass header field name in lowercase. The
+ * application should maintain the references to them until
+ * :type:`nghttp2_on_frame_send_callback` or
+ * :type:`nghttp2_on_frame_not_send_callback` is called.
+ *
+ * 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST`
+ * DATA or HEADERS has been already submitted and not fully
+ * processed yet. Normally, this does not happen, but when
+ * application wrongly calls `nghttp2_submit_response()` twice,
+ * this may happen.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * The |session| is client session.
+ *
+ * .. 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.
+ */
+NGHTTP2_EXTERN 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 trailer fields HEADERS against the stream |stream_id|.
+ *
+ * The |nva| is an array of name/value pair :type:`nghttp2_nv` with
+ * |nvlen| elements. The application must not include pseudo-header
+ * fields (headers whose names starts with ":") in |nva|.
+ *
+ * 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. For header fields with
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set,
+ * header field name and value are not copied respectively. With
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application
+ * is responsible to pass header field name in lowercase. The
+ * application should maintain the references to them until
+ * :type:`nghttp2_on_frame_send_callback` or
+ * :type:`nghttp2_on_frame_not_send_callback` is called.
+ *
+ * For server, trailer fields must follow response HEADERS or response
+ * DATA without END_STREAM flat set. The library does not enforce
+ * this requirement, and applications should do this for themselves.
+ * If `nghttp2_submit_trailer()` is called before any response HEADERS
+ * submission (usually by `nghttp2_submit_response()`), the content of
+ * |nva| will be sent as response headers, which will result in error.
+ *
+ * This function has the same effect with `nghttp2_submit_headers()`,
+ * with flags = :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` and both
+ * pri_spec and stream_user_data to NULL.
+ *
+ * To submit trailer fields after `nghttp2_submit_response()` is
+ * called, the application has to specify
+ * :type:`nghttp2_data_provider` to `nghttp2_submit_response()`.
+ * Inside of :type:`nghttp2_data_source_read_callback`, when setting
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF`, also set
+ * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM`. After
+ * that, the application can send trailer fields using
+ * `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used
+ * inside :type:`nghttp2_data_source_read_callback`.
+ *
+ * This function returns 0 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ */
+NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
+ int32_t stream_id,
+ const nghttp2_nv *nva, size_t nvlen);
+
+/**
+ * @function
+ *
+ * Submits HEADERS frame. The |flags| is bitwise OR of the
+ * following values:
+ *
+ * * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`
+ *
+ * If |flags| includes :enum:`nghttp2_flag.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 [:macro:`NGHTTP2_MIN_WEIGHT`,
+ * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight``
+ * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than
+ * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes :macro:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is received by a remote endpoint, |pri_spec| is
+ * ignored, and treated as if ``NULL`` is specified.
+ *
+ * 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. For header fields with
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set,
+ * header field name and value are not copied respectively. With
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application
+ * is responsible to pass header field name in lowercase. The
+ * application should maintain the references to them until
+ * :type:`nghttp2_on_frame_send_callback` or
+ * :type:`nghttp2_on_frame_not_send_callback` is called.
+ *
+ * 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. Likewise, for HTTP response,
+ * prefer `nghttp2_submit_response()`.
+ *
+ * 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ * No stream ID is available because maximum stream ID was
+ * reached.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0; or trying to depend on itself (stream ID
+ * equals ``pri_spec->stream_id``).
+ * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST`
+ * DATA or HEADERS has been already submitted and not fully
+ * processed yet. This happens if stream denoted by |stream_id|
+ * is in reserved state.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * The |stream_id| is -1, and |session| is server session.
+ *
+ * .. 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.
+ *
+ */
+NGHTTP2_EXTERN 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.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST`
+ * DATA or HEADERS has been already submitted and not fully
+ * processed yet.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED`
+ * The stream was already closed; or the |stream_id| is invalid.
+ *
+ * .. note::
+ *
+ * Currently, only one DATA or HEADERS is allowed for a stream at a
+ * time. Submitting these frames more than once before first DATA
+ * or HEADERS is finished results in
+ * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The
+ * earliest callback which tells that previous frame 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.NGHTTP2_FLAG_END_STREAM` flag set in |flags|.
+ * This sounds a bit complicated, and we recommend to use
+ * `nghttp2_submit_request()` and `nghttp2_submit_response()` to
+ * avoid this cascading issue. The experience shows that for HTTP
+ * use, these two functions are enough to implement both client and
+ * server.
+ */
+NGHTTP2_EXTERN 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.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 [:macro:`NGHTTP2_MIN_WEIGHT`,
+ * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight``
+ * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than
+ * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes
+ * :macro:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is received by a remote endpoint, this function does
+ * nothing and returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0; or the |pri_spec| is NULL; or trying to
+ * depend on itself.
+ */
+NGHTTP2_EXTERN int
+nghttp2_submit_priority(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec);
+
+/**
+ * @macro
+ *
+ * :macro:`NGHTTP2_EXTPRI_DEFAULT_URGENCY` is the default urgency
+ * level for :rfc:`9218` extensible priorities.
+ */
+#define NGHTTP2_EXTPRI_DEFAULT_URGENCY 3
+
+/**
+ * @macro
+ *
+ * :macro:`NGHTTP2_EXTPRI_URGENCY_HIGH` is the highest urgency level
+ * for :rfc:`9218` extensible priorities.
+ */
+#define NGHTTP2_EXTPRI_URGENCY_HIGH 0
+
+/**
+ * @macro
+ *
+ * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW` is the lowest urgency level for
+ * :rfc:`9218` extensible priorities.
+ */
+#define NGHTTP2_EXTPRI_URGENCY_LOW 7
+
+/**
+ * @macro
+ *
+ * :macro:`NGHTTP2_EXTPRI_URGENCY_LEVELS` is the number of urgency
+ * levels for :rfc:`9218` extensible priorities.
+ */
+#define NGHTTP2_EXTPRI_URGENCY_LEVELS (NGHTTP2_EXTPRI_URGENCY_LOW + 1)
+
+/**
+ * @struct
+ *
+ * :type:`nghttp2_extpri` is :rfc:`9218` extensible priorities
+ * specification for a stream.
+ */
+typedef struct nghttp2_extpri {
+ /**
+ * :member:`urgency` is the urgency of a stream, it must be in
+ * [:macro:`NGHTTP2_EXTPRI_URGENCY_HIGH`,
+ * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`], inclusive, and 0 is the
+ * highest urgency.
+ */
+ uint32_t urgency;
+ /**
+ * :member:`inc` indicates that a content can be processed
+ * incrementally or not. If inc is 0, it cannot be processed
+ * incrementally. If inc is 1, it can be processed incrementally.
+ * Other value is not permitted.
+ */
+ int inc;
+} nghttp2_extpri;
+
+/**
+ * @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.NGHTTP2_FLAG_NONE`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0.
+ */
+NGHTTP2_EXTERN 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.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.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_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |iv| contains invalid value (e.g., initial window size
+ * strictly greater than (1 << 31) - 1.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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. For header fields with
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set,
+ * header field name and value are not copied respectively. With
+ * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application
+ * is responsible to pass header field name in lowercase. The
+ * application should maintain the references to them until
+ * :type:`nghttp2_on_frame_send_callback` or
+ * :type:`nghttp2_on_frame_not_send_callback` is called.
+ *
+ * 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.
+ *
+ * To submit response headers and data, use
+ * `nghttp2_submit_response()`.
+ *
+ * This function returns assigned promised stream ID if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO`
+ * This function was invoked when |session| is initialized as
+ * client.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ * No stream ID is available because maximum stream ID was
+ * reached.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is 0; The |stream_id| does not designate stream
+ * that peer initiated.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED`
+ * The stream was already closed; or the |stream_id| is invalid.
+ *
+ * .. warning::
+ *
+ * This function returns assigned promised stream ID if it succeeds.
+ * As of 1.16.0, stream object for pushed resource is created when
+ * this function succeeds. In that case, the application can submit
+ * push response for the promised frame.
+ *
+ * In 1.15.0 or prior versions, pushed stream is not opened yet when
+ * this function succeeds. The application must not submit frame to
+ * that stream ID before :type:`nghttp2_before_frame_send_callback`
+ * is called for this frame.
+ *
+ */
+NGHTTP2_EXTERN 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 bitwise OR of 0 or more of the following value.
+ *
+ * * :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK`
+ *
+ * Unless `nghttp2_option_set_no_auto_ping_ack()` is used, the |flags|
+ * should be :enum:`nghttp2_flag.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |opaque_data_len| is too large; the |last_stream_id| is
+ * invalid.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN int32_t
+nghttp2_session_get_last_proc_stream_id(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns nonzero if new request can be sent from local endpoint.
+ *
+ * This function return 0 if request is not allowed for this session.
+ * There are several reasons why request is not allowed. Some of the
+ * reasons are: session is server; stream ID has been spent; GOAWAY
+ * has been sent or received.
+ *
+ * The application can call `nghttp2_submit_request()` without
+ * consulting this function. In that case, `nghttp2_submit_request()`
+ * may return error. Or, request is failed to sent, and
+ * :type:`nghttp2_on_stream_close_callback` is called.
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_check_request_allowed(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns nonzero if |session| is initialized as server side session.
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_check_server_session(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Submits WINDOW_UPDATE frame.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`.
+ *
+ * The |stream_id| is the stream ID to send this WINDOW_UPDATE. To
+ * send connection level WINDOW_UPDATE, specify 0 to |stream_id|.
+ *
+ * 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 sole purpose is to increase the local window
+ * size, consider to use `nghttp2_session_set_local_window_size()`.
+ *
+ * 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 sole purpose is to decrease the local window size, consider to
+ * use `nghttp2_session_set_local_window_size()`.
+ *
+ * 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_error.NGHTTP2_ERR_FLOW_CONTROL`
+ * The local window size overflow or gets negative.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session,
+ uint8_t flags,
+ int32_t stream_id,
+ int32_t window_size_increment);
+
+/**
+ * @function
+ *
+ * Set local window size (local endpoints's window size) to the given
+ * |window_size| for the given stream denoted by |stream_id|. To
+ * change connection level window size, specify 0 to |stream_id|. To
+ * increase window size, this function may submit WINDOW_UPDATE frame
+ * to transmission queue.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`.
+ *
+ * This sounds similar to `nghttp2_submit_window_update()`, but there
+ * are 2 differences. The first difference is that this function
+ * takes the absolute value of window size to set, rather than the
+ * delta. To change the window size, this may be easier to use since
+ * the application just declares the intended window size, rather than
+ * calculating delta. The second difference is that
+ * `nghttp2_submit_window_update()` affects the received bytes count
+ * which has not acked yet. By the specification of
+ * `nghttp2_submit_window_update()`, to strictly increase the local
+ * window size, we have to submit delta including all received bytes
+ * count, which might not be desirable in some cases. On the other
+ * hand, this function does not affect the received bytes count. It
+ * just sets the local window size to the given value.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |stream_id| is negative.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN int
+nghttp2_session_set_local_window_size(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, int32_t window_size);
+
+/**
+ * @function
+ *
+ * Submits extension frame.
+ *
+ * Application can pass arbitrary frame flags and stream ID in |flags|
+ * and |stream_id| respectively. The |payload| is opaque pointer, and
+ * it can be accessible though ``frame->ext.payload`` in
+ * :type:`nghttp2_pack_extension_callback`. The library will not own
+ * passed |payload| pointer.
+ *
+ * The application must set :type:`nghttp2_pack_extension_callback`
+ * using `nghttp2_session_callbacks_set_pack_extension_callback()`.
+ *
+ * The application should retain the memory pointed by |payload| until
+ * the transmission of extension frame is done (which is indicated by
+ * :type:`nghttp2_on_frame_send_callback`), or transmission fails
+ * (which is indicated by :type:`nghttp2_on_frame_not_send_callback`).
+ * If application does not touch this memory region after packing it
+ * into a wire format, application can free it inside
+ * :type:`nghttp2_pack_extension_callback`.
+ *
+ * The standard HTTP/2 frame cannot be sent with this function, so
+ * |type| must be strictly grater than 0x9. Otherwise, this function
+ * will fail with error code
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * If :type:`nghttp2_pack_extension_callback` is not set.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * If |type| specifies standard HTTP/2 frame type. The frame
+ * types in the rage [0x0, 0x9], both inclusive, are standard
+ * HTTP/2 frame type, and cannot be sent using this function.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory
+ */
+NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session,
+ uint8_t type, uint8_t flags,
+ int32_t stream_id, void *payload);
+
+/**
+ * @struct
+ *
+ * The payload of ALTSVC frame. ALTSVC frame is a non-critical
+ * extension to HTTP/2. If this frame is received, and
+ * `nghttp2_option_set_user_recv_extension_type()` is not set, and
+ * `nghttp2_option_set_builtin_recv_extension_type()` is set for
+ * :enum:`nghttp2_frame_type.NGHTTP2_ALTSVC`,
+ * ``nghttp2_extension.payload`` will point to this struct.
+ *
+ * It has the following members:
+ */
+typedef struct {
+ /**
+ * The pointer to origin which this alternative service is
+ * associated with. This is not necessarily NULL-terminated.
+ */
+ uint8_t *origin;
+ /**
+ * The length of the |origin|.
+ */
+ size_t origin_len;
+ /**
+ * The pointer to Alt-Svc field value contained in ALTSVC frame.
+ * This is not necessarily NULL-terminated.
+ */
+ uint8_t *field_value;
+ /**
+ * The length of the |field_value|.
+ */
+ size_t field_value_len;
+} nghttp2_ext_altsvc;
+
+/**
+ * @function
+ *
+ * Submits ALTSVC frame.
+ *
+ * ALTSVC frame is a non-critical extension to HTTP/2, and defined in
+ * `RFC 7383 <https://tools.ietf.org/html/rfc7838#section-4>`_.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`.
+ *
+ * The |origin| points to the origin this alternative service is
+ * associated with. The |origin_len| is the length of the origin. If
+ * |stream_id| is 0, the origin must be specified. If |stream_id| is
+ * not zero, the origin must be empty (in other words, |origin_len|
+ * must be 0).
+ *
+ * The ALTSVC frame is only usable from server side. If this function
+ * is invoked with client side session, this function returns
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The function is called from client side session
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The sum of |origin_len| and |field_value_len| is larger than
+ * 16382; or |origin_len| is 0 while |stream_id| is 0; or
+ * |origin_len| is not 0 while |stream_id| is not 0.
+ */
+NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session,
+ uint8_t flags, int32_t stream_id,
+ const uint8_t *origin,
+ size_t origin_len,
+ const uint8_t *field_value,
+ size_t field_value_len);
+
+/**
+ * @struct
+ *
+ * The single entry of an origin.
+ */
+typedef struct {
+ /**
+ * The pointer to origin. No validation is made against this field
+ * by the library. This is not necessarily NULL-terminated.
+ */
+ uint8_t *origin;
+ /**
+ * The length of the |origin|.
+ */
+ size_t origin_len;
+} nghttp2_origin_entry;
+
+/**
+ * @struct
+ *
+ * The payload of ORIGIN frame. ORIGIN frame is a non-critical
+ * extension to HTTP/2 and defined by `RFC 8336
+ * <https://tools.ietf.org/html/rfc8336>`_.
+ *
+ * If this frame is received, and
+ * `nghttp2_option_set_user_recv_extension_type()` is not set, and
+ * `nghttp2_option_set_builtin_recv_extension_type()` is set for
+ * :enum:`nghttp2_frame_type.NGHTTP2_ORIGIN`,
+ * ``nghttp2_extension.payload`` will point to this struct.
+ *
+ * It has the following members:
+ */
+typedef struct {
+ /**
+ * The number of origins contained in |ov|.
+ */
+ size_t nov;
+ /**
+ * The pointer to the array of origins contained in ORIGIN frame.
+ */
+ nghttp2_origin_entry *ov;
+} nghttp2_ext_origin;
+
+/**
+ * @function
+ *
+ * Submits ORIGIN frame.
+ *
+ * ORIGIN frame is a non-critical extension to HTTP/2 and defined by
+ * `RFC 8336 <https://tools.ietf.org/html/rfc8336>`_.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`.
+ *
+ * The |ov| points to the array of origins. The |nov| specifies the
+ * number of origins included in |ov|. This function creates copies
+ * of all elements in |ov|.
+ *
+ * The ORIGIN frame is only usable by a server. If this function is
+ * invoked with client side session, this function returns
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`.
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The function is called from client side session.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * There are too many origins, or an origin is too large to fit
+ * into a default frame payload.
+ */
+NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session,
+ uint8_t flags,
+ const nghttp2_origin_entry *ov,
+ size_t nov);
+
+/**
+ * @struct
+ *
+ * The payload of PRIORITY_UPDATE frame. PRIORITY_UPDATE frame is a
+ * non-critical extension to HTTP/2. If this frame is received, and
+ * `nghttp2_option_set_user_recv_extension_type()` is not set, and
+ * `nghttp2_option_set_builtin_recv_extension_type()` is set for
+ * :enum:`nghttp2_frame_type.NGHTTP2_PRIORITY_UPDATE`,
+ * ``nghttp2_extension.payload`` will point to this struct.
+ *
+ * It has the following members:
+ */
+typedef struct {
+ /**
+ * The stream ID of the stream whose priority is updated.
+ */
+ int32_t stream_id;
+ /**
+ * The pointer to Priority field value. It is not necessarily
+ * NULL-terminated.
+ */
+ uint8_t *field_value;
+ /**
+ * The length of the :member:`field_value`.
+ */
+ size_t field_value_len;
+} nghttp2_ext_priority_update;
+
+/**
+ * @function
+ *
+ * Submits PRIORITY_UPDATE frame.
+ *
+ * PRIORITY_UPDATE frame is a non-critical extension to HTTP/2, and
+ * defined in :rfc:`9218#section-7.1`.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`.
+ *
+ * The |stream_id| is the ID of stream which is prioritized. The
+ * |field_value| points to the Priority field value. The
+ * |field_value_len| is the length of the Priority field value.
+ *
+ * If this function is called by server,
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` is returned.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 0 is received by a remote endpoint (or it is omitted),
+ * this function does nothing and returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The function is called from server side session
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * The |field_value_len| is larger than 16380; or |stream_id| is
+ * 0.
+ */
+NGHTTP2_EXTERN int nghttp2_submit_priority_update(nghttp2_session *session,
+ uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *field_value,
+ size_t field_value_len);
+
+/**
+ * @function
+ *
+ * Changes the priority of the existing stream denoted by |stream_id|.
+ * The new priority is |extpri|. This function is meant to be used by
+ * server for :rfc:`9218` extensible prioritization scheme.
+ *
+ * If |session| is initialized as client, this function returns
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. For client, use
+ * `nghttp2_submit_priority_update()` instead.
+ *
+ * If :member:`extpri->urgency <nghttp2_extpri.urgency>` is out of
+ * bound, it is set to :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`.
+ *
+ * If |ignore_client_signal| is nonzero, server starts to ignore
+ * client priority signals for this stream.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is not submitted via `nghttp2_submit_settings()`,
+ * this function does nothing and returns 0.
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The |session| is initialized as client.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * |stream_id| is zero; or a stream denoted by |stream_id| is not
+ * found.
+ */
+NGHTTP2_EXTERN int nghttp2_session_change_extpri_stream_priority(
+ nghttp2_session *session, int32_t stream_id, const nghttp2_extpri *extpri,
+ int ignore_client_signal);
+
+/**
+ * @function
+ *
+ * Stores the stream priority of the existing stream denoted by
+ * |stream_id| in the object pointed by |extpri|. This function is
+ * meant to be used by server for :rfc:`9218` extensible
+ * prioritization scheme.
+ *
+ * If |session| is initialized as client, this function returns
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`.
+ *
+ * If
+ * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
+ * of value of 1 is not submitted via `nghttp2_submit_settings()`,
+ * this function does nothing and returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The |session| is initialized as client.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * |stream_id| is zero; or a stream denoted by |stream_id| is not
+ * found.
+ */
+NGHTTP2_EXTERN int nghttp2_session_get_extpri_stream_priority(
+ nghttp2_session *session, nghttp2_extpri *extpri, int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Parses Priority header field value pointed by |value| of length
+ * |len|, and stores the result in the object pointed by |extpri|.
+ * Priority header field is defined in :rfc:`9218`.
+ *
+ * This function does not initialize the object pointed by |extpri|
+ * before storing the result. It only assigns the values that the
+ * parser correctly extracted to fields.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`
+ * Failed to parse the header field value.
+ */
+NGHTTP2_EXTERN int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri,
+ const uint8_t *value,
+ size_t 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.
+ */
+NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs,
+ const nghttp2_nv *rhs);
+
+/**
+ * @function
+ *
+ * .. warning::
+ *
+ * Deprecated. Use `nghttp2_select_alpn` instead.
+ *
+ * A helper function for dealing with 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,
+ * ``h2`` and ``http/1.1`` stored in |in| like this::
+ *
+ * in[0] = 2
+ * in[1..2] = "h2"
+ * in[3] = 8
+ * in[4..11] = "http/1.1"
+ * inlen = 12
+ *
+ * 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 ``h2`` means that ``h2`` is written into |*out| and its
+ * length (which is 2) is assigned to |*outlen|.
+ *
+ * For ALPN, refer to https://tools.ietf.org/html/rfc7301
+ *
+ * To use this method you should do something like::
+ *
+ * static int alpn_select_proto_cb(SSL* ssl,
+ * const unsigned char **out,
+ * unsigned char *outlen,
+ * const unsigned char *in,
+ * unsigned int inlen,
+ * void *arg)
+ * {
+ * int rv;
+ * rv = nghttp2_select_next_protocol((unsigned char**)out, outlen,
+ * in, inlen);
+ * if (rv == -1) {
+ * return SSL_TLSEXT_ERR_NOACK;
+ * }
+ * if (rv == 1) {
+ * ((MyType*)arg)->http2_selected = 1;
+ * }
+ * return SSL_TLSEXT_ERR_OK;
+ * }
+ * ...
+ * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj);
+ *
+ */
+NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen);
+
+/**
+ * @function
+ *
+ * A helper function for dealing with 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,
+ * ``h2`` and ``http/1.1`` stored in |in| like this::
+ *
+ * in[0] = 2
+ * in[1..2] = "h2"
+ * in[3] = 8
+ * in[4..11] = "http/1.1"
+ * inlen = 12
+ *
+ * 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 ``h2`` means that ``h2`` is written into |*out| and its
+ * length (which is 2) is assigned to |*outlen|.
+ *
+ * For ALPN, refer to https://tools.ietf.org/html/rfc7301
+ *
+ * To use this method you should do something like::
+ *
+ * static int alpn_select_proto_cb(SSL* ssl,
+ * const unsigned char **out,
+ * unsigned char *outlen,
+ * const unsigned char *in,
+ * unsigned int inlen,
+ * void *arg)
+ * {
+ * int rv;
+ * rv = nghttp2_select_alpn(out, outlen, in, inlen);
+ * if (rv == -1) {
+ * return SSL_TLSEXT_ERR_NOACK;
+ * }
+ * if (rv == 1) {
+ * ((MyType*)arg)->http2_selected = 1;
+ * }
+ * return SSL_TLSEXT_ERR_OK;
+ * }
+ * ...
+ * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj);
+ *
+ */
+NGHTTP2_EXTERN int nghttp2_select_alpn(const 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_EXTERN nghttp2_info *nghttp2_version(int least_version);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the :type:`nghttp2_error` library error code
+ * |lib_error| is fatal.
+ */
+NGHTTP2_EXTERN int nghttp2_is_fatal(int lib_error_code);
+
+/**
+ * @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.
+ */
+NGHTTP2_EXTERN 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
+ *
+ * This function is considered obsolete, and application should
+ * consider to use `nghttp2_check_header_value_rfc9113()` instead.
+ */
+NGHTTP2_EXTERN int nghttp2_check_header_value(const uint8_t *value, 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, plus
+ * https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1
+ */
+NGHTTP2_EXTERN int nghttp2_check_header_value_rfc9113(const uint8_t *value,
+ size_t len);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the |value| which is supposed to be the value of
+ * the :method header field is valid according to
+ * https://datatracker.ietf.org/doc/html/rfc7231#section-4 and
+ * https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
+ */
+NGHTTP2_EXTERN int nghttp2_check_method(const uint8_t *value, size_t len);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the |value| which is supposed to be the value of
+ * the :path header field is valid according to
+ * https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3
+ *
+ * |value| is valid if it merely consists of the allowed characters.
+ * In particular, it does not check whether |value| follows the syntax
+ * of path. The allowed characters are all characters valid by
+ * `nghttp2_check_header_value` minus SPC and HT.
+ */
+NGHTTP2_EXTERN int nghttp2_check_path(const uint8_t *value, size_t len);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the |value| which is supposed to be the value of the
+ * :authority or host header field is valid according to
+ * https://tools.ietf.org/html/rfc3986#section-3.2
+ *
+ * |value| is valid if it merely consists of the allowed characters.
+ * In particular, it does not check whether |value| follows the syntax
+ * of authority.
+ */
+NGHTTP2_EXTERN int nghttp2_check_authority(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 |max_deflate_dynamic_table_size| 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN int
+nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+ size_t max_deflate_dynamic_table_size);
+
+/**
+ * @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.
+ */
+NGHTTP2_EXTERN int
+nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr,
+ size_t max_deflate_dynamic_table_size,
+ nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Deallocates any resources allocated for |deflater|.
+ */
+NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater);
+
+/**
+ * @function
+ *
+ * Changes header table size of the |deflater| to
+ * |settings_max_dynamic_table_size| bytes. This may trigger eviction
+ * in the dynamic table.
+ *
+ * The |settings_max_dynamic_table_size| should be the value received
+ * in SETTINGS_HEADER_TABLE_SIZE.
+ *
+ * The deflater never uses more memory than
+ * ``max_deflate_dynamic_table_size`` bytes specified in
+ * `nghttp2_hd_deflate_new()`. Therefore, if
+ * |settings_max_dynamic_table_size| >
+ * ``max_deflate_dynamic_table_size``, resulting maximum table size
+ * becomes ``max_deflate_dynamic_table_size``.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN int
+nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
+ size_t settings_max_dynamic_table_size);
+
+/**
+ * @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_error.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_error.NGHTTP2_ERR_HEADER_COMP`.
+ *
+ * After this function returns, it is safe to delete the |nva|.
+ *
+ * This function returns the number of bytes written to |buf| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`
+ * Deflation process has failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`
+ * The provided |buflen| size is too small to hold the output.
+ */
+NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
+ uint8_t *buf, size_t buflen,
+ const nghttp2_nv *nva,
+ size_t nvlen);
+
+/**
+ * @function
+ *
+ * Deflates the |nva|, which has the |nvlen| name/value pairs, into
+ * the |veclen| size of buf vector |vec|. The each size of buffer
+ * must be set in len field of :type:`nghttp2_vec`. If and only if
+ * one chunk is filled up completely, next chunk will be used. If
+ * |vec| is not large enough to store the deflated header block, this
+ * function fails with
+ * :enum:`nghttp2_error.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_error.NGHTTP2_ERR_HEADER_COMP`.
+ *
+ * After this function returns, it is safe to delete the |nva|.
+ *
+ * This function returns the number of bytes written to |vec| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`
+ * Deflation process has failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`
+ * The provided |buflen| size is too small to hold the output.
+ */
+NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater,
+ const nghttp2_vec *vec,
+ size_t veclen,
+ const nghttp2_nv *nva,
+ size_t nvlen);
+
+/**
+ * @function
+ *
+ * Returns an upper bound on the compressed size after deflation of
+ * |nva| of length |nvlen|.
+ */
+NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+ const nghttp2_nv *nva,
+ size_t nvlen);
+
+/**
+ * @function
+ *
+ * Returns the number of entries that header table of |deflater|
+ * contains. This is the sum of the number of static table and
+ * dynamic table, so the return value is at least 61.
+ */
+NGHTTP2_EXTERN
+size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater);
+
+/**
+ * @function
+ *
+ * Returns the table entry denoted by |idx| from header table of
+ * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of
+ * static table. idx=62 returns first entry of dynamic table if it
+ * exists. Specifying idx=0 is error, and this function returns NULL.
+ * If |idx| is strictly greater than the number of entries the tables
+ * contain, this function returns NULL.
+ */
+NGHTTP2_EXTERN
+const nghttp2_nv *
+nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx);
+
+/**
+ * @function
+ *
+ * Returns the used dynamic table size, including the overhead 32
+ * bytes per entry described in RFC 7541.
+ */
+NGHTTP2_EXTERN
+size_t nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater);
+
+/**
+ * @function
+ *
+ * Returns the maximum dynamic table size.
+ */
+NGHTTP2_EXTERN
+size_t
+nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater);
+
+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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ */
+NGHTTP2_EXTERN 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.
+ */
+NGHTTP2_EXTERN int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr,
+ nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Deallocates any resources allocated for |inflater|.
+ */
+NGHTTP2_EXTERN 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_max_dynamic_table_size| should be the value
+ * transmitted in SETTINGS_HEADER_TABLE_SIZE.
+ *
+ * This function must not be called while header block is being
+ * inflated. In other words, this function must be called after
+ * initialization of |inflater|, but before calling
+ * `nghttp2_hd_inflate_hd2()`, or after
+ * `nghttp2_hd_inflate_end_headers()`. Otherwise,
+ * `NGHTTP2_ERR_INVALID_STATE` was returned.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`
+ * The function is called while header block is being inflated.
+ * Probably, application missed to call
+ * `nghttp2_hd_inflate_end_headers()`.
+ */
+NGHTTP2_EXTERN int
+nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
+ size_t settings_max_dynamic_table_size);
+
+/**
+ * @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
+ *
+ * .. warning::
+ *
+ * Deprecated. Use `nghttp2_hd_inflate_hd2()` instead.
+ *
+ * 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_flag.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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`
+ * Inflation process has failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR`
+ * The header 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;
+ * }
+ *
+ */
+NGHTTP2_EXTERN 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
+ *
+ * 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_flag.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. If that happens, all given input
+ * data (|inlen| bytes) are processed successfully. Then the
+ * application must call `nghttp2_hd_inflate_end_headers()` to prepare
+ * for the next header block input.
+ *
+ * In other words, if |in_final| is nonzero, and this function returns
+ * |inlen|, you can assert that
+ * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in
+ * |*inflate_flags|.
+ *
+ * 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_error.NGHTTP2_ERR_NOMEM`
+ * Out of memory.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`
+ * Inflation process has failed.
+ * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR`
+ * The header 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_hd2(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;
+ * }
+ *
+ */
+NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
+ nghttp2_nv *nv_out,
+ int *inflate_flags,
+ const 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.
+ */
+NGHTTP2_EXTERN int
+nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater);
+
+/**
+ * @function
+ *
+ * Returns the number of entries that header table of |inflater|
+ * contains. This is the sum of the number of static table and
+ * dynamic table, so the return value is at least 61.
+ */
+NGHTTP2_EXTERN
+size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater);
+
+/**
+ * @function
+ *
+ * Returns the table entry denoted by |idx| from header table of
+ * |inflater|. The |idx| is 1-based, and idx=1 returns first entry of
+ * static table. idx=62 returns first entry of dynamic table if it
+ * exists. Specifying idx=0 is error, and this function returns NULL.
+ * If |idx| is strictly greater than the number of entries the tables
+ * contain, this function returns NULL.
+ */
+NGHTTP2_EXTERN
+const nghttp2_nv *
+nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx);
+
+/**
+ * @function
+ *
+ * Returns the used dynamic table size, including the overhead 32
+ * bytes per entry described in RFC 7541.
+ */
+NGHTTP2_EXTERN
+size_t nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater);
+
+/**
+ * @function
+ *
+ * Returns the maximum dynamic table size.
+ */
+NGHTTP2_EXTERN
+size_t
+nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater);
+
+struct nghttp2_stream;
+
+/**
+ * @struct
+ *
+ * The structure to represent HTTP/2 stream. The details of this
+ * structure are intentionally hidden from the public API.
+ */
+typedef struct nghttp2_stream nghttp2_stream;
+
+/**
+ * @function
+ *
+ * Returns pointer to :type:`nghttp2_stream` object denoted by
+ * |stream_id|. If stream was not found, returns NULL.
+ *
+ * Returns imaginary root stream (see
+ * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|.
+ *
+ * Unless |stream_id| == 0, the returned pointer is valid until next
+ * call of `nghttp2_session_send()`, `nghttp2_session_mem_send()`,
+ * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv()`.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_session_find_stream(nghttp2_session *session, int32_t stream_id);
+
+/**
+ * @enum
+ *
+ * State of stream as described in RFC 7540.
+ */
+typedef enum {
+ /**
+ * idle state.
+ */
+ NGHTTP2_STREAM_STATE_IDLE = 1,
+ /**
+ * open state.
+ */
+ NGHTTP2_STREAM_STATE_OPEN,
+ /**
+ * reserved (local) state.
+ */
+ NGHTTP2_STREAM_STATE_RESERVED_LOCAL,
+ /**
+ * reserved (remote) state.
+ */
+ NGHTTP2_STREAM_STATE_RESERVED_REMOTE,
+ /**
+ * half closed (local) state.
+ */
+ NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL,
+ /**
+ * half closed (remote) state.
+ */
+ NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE,
+ /**
+ * closed state.
+ */
+ NGHTTP2_STREAM_STATE_CLOSED
+} nghttp2_stream_proto_state;
+
+/**
+ * @function
+ *
+ * Returns state of |stream|. The root stream retrieved by
+ * `nghttp2_session_get_root_stream()` will have stream state
+ * :enum:`nghttp2_stream_proto_state.NGHTTP2_STREAM_STATE_IDLE`.
+ */
+NGHTTP2_EXTERN nghttp2_stream_proto_state
+nghttp2_stream_get_state(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns root of dependency tree, which is imaginary stream with
+ * stream ID 0. The returned pointer is valid until |session| is
+ * freed by `nghttp2_session_del()`.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_session_get_root_stream(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the parent stream of |stream| in dependency tree. Returns
+ * NULL if there is no such stream.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_stream_get_parent(nghttp2_stream *stream);
+
+NGHTTP2_EXTERN int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns the next sibling stream of |stream| in dependency tree.
+ * Returns NULL if there is no such stream.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_stream_get_next_sibling(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns the previous sibling stream of |stream| in dependency tree.
+ * Returns NULL if there is no such stream.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_stream_get_previous_sibling(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns the first child stream of |stream| in dependency tree.
+ * Returns NULL if there is no such stream.
+ */
+NGHTTP2_EXTERN nghttp2_stream *
+nghttp2_stream_get_first_child(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns dependency weight to the parent stream of |stream|.
+ */
+NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream);
+
+/**
+ * @function
+ *
+ * Returns the sum of the weight for |stream|'s children.
+ */
+NGHTTP2_EXTERN int32_t
+nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when the library outputs debug logging.
+ * The function is called with arguments suitable for ``vfprintf(3)``
+ *
+ * The debug output is only enabled if the library is built with
+ * ``DEBUGBUILD`` macro defined.
+ */
+typedef void (*nghttp2_debug_vprintf_callback)(const char *format,
+ va_list args);
+
+/**
+ * @function
+ *
+ * Sets a debug output callback called by the library when built with
+ * ``DEBUGBUILD`` macro defined. If this option is not used, debug
+ * log is written into standard error output.
+ *
+ * For builds without ``DEBUGBUILD`` macro defined, this function is
+ * noop.
+ *
+ * Note that building with ``DEBUGBUILD`` may cause significant
+ * performance penalty to libnghttp2 because of extra processing. It
+ * should be used for debugging purpose only.
+ *
+ * .. Warning::
+ *
+ * Building with ``DEBUGBUILD`` may cause significant performance
+ * penalty to libnghttp2 because of extra processing. It should be
+ * used for debugging purpose only. We write this two times because
+ * this is important.
+ */
+NGHTTP2_EXTERN void nghttp2_set_debug_vprintf_callback(
+ nghttp2_debug_vprintf_callback debug_vprintf_callback);
+
+#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
index 0000000..7717a64
--- /dev/null
+++ b/lib/includes/nghttp2/nghttp2ver.h.in
@@ -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
index 0000000..da6938f
--- /dev/null
+++ b/lib/libnghttp2.pc.in
@@ -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=@libdir@
+includedir=@includedir@
+
+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_alpn.c b/lib/nghttp2_alpn.c
new file mode 100644
index 0000000..33c5885
--- /dev/null
+++ b/lib/nghttp2_alpn.c
@@ -0,0 +1,70 @@
+/*
+ * 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_alpn.h"
+
+#include <string.h>
+
+static int select_alpn(const 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 += (unsigned int)(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_alpn((const unsigned char **)out, outlen, in, inlen,
+ NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN) == 0) {
+ return 1;
+ }
+ if (select_alpn((const unsigned char **)out, outlen, in, inlen,
+ NGHTTP2_HTTP_1_1_ALPN, NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) {
+ return 0;
+ }
+ return -1;
+}
+
+int nghttp2_select_alpn(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen) {
+ if (select_alpn(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
+ NGHTTP2_PROTO_ALPN_LEN) == 0) {
+ return 1;
+ }
+ if (select_alpn(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_alpn.h b/lib/nghttp2_alpn.h
new file mode 100644
index 0000000..09810fd
--- /dev/null
+++ b/lib/nghttp2_alpn.h
@@ -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_ALPN_H
+#define NGHTTP2_ALPN_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#endif /* NGHTTP2_ALPN_H */
diff --git a/lib/nghttp2_buf.c b/lib/nghttp2_buf.c
new file mode 100644
index 0000000..a328447
--- /dev/null
+++ b/lib/nghttp2_buf.c
@@ -0,0 +1,527 @@
+/*
+ * 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"
+#include "nghttp2_debug.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 = buf->end = begin;
+ if (len) {
+ buf->end += 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;
+}
+
+int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec,
+ size_t veclen, nghttp2_mem *mem) {
+ size_t i = 0;
+ nghttp2_buf_chain *cur_chain;
+ nghttp2_buf_chain *head_chain;
+ nghttp2_buf_chain **dst_chain = &head_chain;
+
+ if (veclen == 0) {
+ return nghttp2_bufs_wrap_init(bufs, NULL, 0, mem);
+ }
+
+ head_chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain) * veclen);
+ if (head_chain == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ for (i = 0; i < veclen; ++i) {
+ cur_chain = &head_chain[i];
+ cur_chain->next = NULL;
+ nghttp2_buf_wrap_init(&cur_chain->buf, vec[i].base, vec[i].len);
+
+ *dst_chain = cur_chain;
+ dst_chain = &cur_chain->next;
+ }
+
+ bufs->mem = mem;
+ bufs->offset = 0;
+
+ bufs->head = head_chain;
+ bufs->cur = bufs->head;
+
+ /* We don't use chunk_length since no allocation is expected. */
+ bufs->chunk_length = 0;
+ bufs->chunk_used = veclen;
+ bufs->max_chunk = veclen;
+ bufs->chunk_keep = veclen;
+
+ return 0;
+}
+
+void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) {
+ if (bufs == NULL) {
+ return;
+ }
+
+ if (bufs->head) {
+ nghttp2_mem_free(bufs->mem, bufs->head);
+ }
+}
+
+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;
+ }
+ }
+}
+
+size_t nghttp2_bufs_len(nghttp2_bufs *bufs) {
+ nghttp2_buf_chain *ci;
+ size_t len;
+
+ len = 0;
+ for (ci = bufs->head; ci; ci = ci->next) {
+ len += nghttp2_buf_len(&ci->buf);
+ }
+
+ return len;
+}
+
+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("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;
+
+ p = data;
+
+ while (len) {
+ buf = &bufs->cur->buf;
+
+ nwrite = nghttp2_min(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 += nwrite;
+ 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 == 0) {
+ res = NULL;
+ return 0;
+ }
+
+ 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;
+ resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf));
+ }
+
+ *out = res;
+
+ return (ssize_t)len;
+}
+
+size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) {
+ size_t len;
+ nghttp2_buf_chain *chain;
+ nghttp2_buf *buf;
+ nghttp2_buf resbuf;
+
+ len = nghttp2_bufs_len(bufs);
+
+ nghttp2_buf_wrap_init(&resbuf, out, len);
+
+ for (chain = bufs->head; chain; chain = chain->next) {
+ buf = &chain->buf;
+ resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf));
+ }
+
+ return 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
index 0000000..45f62f1
--- /dev/null
+++ b/lib/nghttp2_buf.h
@@ -0,0 +1,412 @@
+/*
+ * 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) ((size_t)((BUF)->last - (BUF)->pos))
+#define nghttp2_buf_avail(BUF) ((size_t)((BUF)->end - (BUF)->last))
+#define nghttp2_buf_mark_avail(BUF) ((size_t)((BUF)->mark - (BUF)->last))
+#define nghttp2_buf_cap(BUF) ((size_t)((BUF)->end - (BUF)->begin))
+
+#define nghttp2_buf_pos_offset(BUF) ((size_t)((BUF)->pos - (BUF)->begin))
+#define nghttp2_buf_last_offset(BUF) ((size_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() 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 following
+ * 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. This field may be 0 if
+ nghttp2_bufs is initialized by nghttp2_bufs_wrap_init* family
+ functions. */
+ 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 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().
+ *
+ * Don't use the function which performs allocation, such as
+ * nghttp2_bufs_realloc().
+ *
+ * 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);
+
+/*
+ * Initializes |bufs| using supplied |veclen| size of buf vector
+ * |vec|. The number of buffers is fixed and no extra chunk buffer is
+ * allocated. In other words, max_chunk = chunk_keep = |in_len|. To
+ * free the resource allocated for |bufs|, use
+ * nghttp2_bufs_wrap_free().
+ *
+ * Don't use the function which performs allocation, such as
+ * nghttp2_bufs_realloc().
+ *
+ * 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_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec,
+ size_t veclen, 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 { \
+ uint8_t **p = &(BUFS)->cur->buf.last; \
+ **p = (uint8_t)(**p | (B)); \
+ ++(*p); \
+ } while (0)
+
+#define nghttp2_bufs_fast_orb_hold(BUFS, B) \
+ do { \
+ uint8_t *p = (BUFS)->cur->buf.last; \
+ *p = (uint8_t)(*p | (B)); \
+ } while (0)
+
+/*
+ * Copies all data stored in |bufs| to the contiguous buffer. This
+ * function allocates the contiguous memory to store all data in
+ * |bufs| and assigns it to |*out|.
+ *
+ * The contents of |bufs| is left unchanged.
+ *
+ * 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);
+
+/*
+ * Copies all data stored in |bufs| to |out|. This function assumes
+ * that the buffer space pointed by |out| has at least
+ * nghttp2_bufs(bufs) bytes.
+ *
+ * The contents of |bufs| is left unchanged.
+ *
+ * This function returns the length of copied data.
+ */
+size_t nghttp2_bufs_remove_copy(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 empty.
+ */
+int nghttp2_bufs_next_present(nghttp2_bufs *bufs);
+
+#define nghttp2_bufs_cur_avail(BUFS) nghttp2_buf_avail(&(BUFS)->cur->buf)
+
+/*
+ * Returns the total buffer length of |bufs|.
+ */
+size_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
index 0000000..3c38214
--- /dev/null
+++ b/lib/nghttp2_callbacks.c
@@ -0,0 +1,175 @@
+/*
+ * 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_on_header_callback2(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_header_callback2 on_header_callback2) {
+ cbs->on_header_callback2 = on_header_callback2;
+}
+
+void nghttp2_session_callbacks_set_on_invalid_header_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_invalid_header_callback on_invalid_header_callback) {
+ cbs->on_invalid_header_callback = on_invalid_header_callback;
+}
+
+void nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_invalid_header_callback2 on_invalid_header_callback2) {
+ cbs->on_invalid_header_callback2 = on_invalid_header_callback2;
+}
+
+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;
+}
+
+void nghttp2_session_callbacks_set_send_data_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_send_data_callback send_data_callback) {
+ cbs->send_data_callback = send_data_callback;
+}
+
+void nghttp2_session_callbacks_set_pack_extension_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_pack_extension_callback pack_extension_callback) {
+ cbs->pack_extension_callback = pack_extension_callback;
+}
+
+void nghttp2_session_callbacks_set_unpack_extension_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_unpack_extension_callback unpack_extension_callback) {
+ cbs->unpack_extension_callback = unpack_extension_callback;
+}
+
+void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
+ nghttp2_session_callbacks *cbs,
+ nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback) {
+ cbs->on_extension_chunk_recv_callback = on_extension_chunk_recv_callback;
+}
+
+void nghttp2_session_callbacks_set_error_callback(
+ nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback) {
+ cbs->error_callback = error_callback;
+}
+
+void nghttp2_session_callbacks_set_error_callback2(
+ nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2) {
+ cbs->error_callback2 = error_callback2;
+}
diff --git a/lib/nghttp2_callbacks.h b/lib/nghttp2_callbacks.h
new file mode 100644
index 0000000..61e51fa
--- /dev/null
+++ b/lib/nghttp2_callbacks.h
@@ -0,0 +1,125 @@
+/*
+ * 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;
+ nghttp2_on_header_callback2 on_header_callback2;
+ /**
+ * Callback function invoked when a invalid header name/value pair
+ * is received which is silently ignored if these callbacks are not
+ * set.
+ */
+ nghttp2_on_invalid_header_callback on_invalid_header_callback;
+ nghttp2_on_invalid_header_callback2 on_invalid_header_callback2;
+ /**
+ * 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;
+ nghttp2_send_data_callback send_data_callback;
+ nghttp2_pack_extension_callback pack_extension_callback;
+ nghttp2_unpack_extension_callback unpack_extension_callback;
+ nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback;
+ nghttp2_error_callback error_callback;
+ nghttp2_error_callback2 error_callback2;
+};
+
+#endif /* NGHTTP2_CALLBACKS_H */
diff --git a/lib/nghttp2_debug.c b/lib/nghttp2_debug.c
new file mode 100644
index 0000000..cb27797
--- /dev/null
+++ b/lib/nghttp2_debug.c
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_debug.h"
+
+#include <stdio.h>
+
+#ifdef DEBUGBUILD
+
+static void nghttp2_default_debug_vfprintf_callback(const char *fmt,
+ va_list args) {
+ vfprintf(stderr, fmt, args);
+}
+
+static nghttp2_debug_vprintf_callback static_debug_vprintf_callback =
+ nghttp2_default_debug_vfprintf_callback;
+
+void nghttp2_debug_vprintf(const char *format, ...) {
+ if (static_debug_vprintf_callback) {
+ va_list args;
+ va_start(args, format);
+ static_debug_vprintf_callback(format, args);
+ va_end(args);
+ }
+}
+
+void nghttp2_set_debug_vprintf_callback(
+ nghttp2_debug_vprintf_callback debug_vprintf_callback) {
+ static_debug_vprintf_callback = debug_vprintf_callback;
+}
+
+#else /* !DEBUGBUILD */
+
+void nghttp2_set_debug_vprintf_callback(
+ nghttp2_debug_vprintf_callback debug_vprintf_callback) {
+ (void)debug_vprintf_callback;
+}
+
+#endif /* !DEBUGBUILD */
diff --git a/lib/nghttp2_debug.h b/lib/nghttp2_debug.h
new file mode 100644
index 0000000..cbb4dd5
--- /dev/null
+++ b/lib/nghttp2_debug.h
@@ -0,0 +1,43 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_DEBUG_H
+#define NGHTTP2_DEBUG_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef DEBUGBUILD
+# define DEBUGF(...) nghttp2_debug_vprintf(__VA_ARGS__)
+void nghttp2_debug_vprintf(const char *format, ...);
+#else
+# define DEBUGF(...) \
+ do { \
+ } while (0)
+#endif
+
+#endif /* NGHTTP2_DEBUG_H */
diff --git a/lib/nghttp2_extpri.c b/lib/nghttp2_extpri.c
new file mode 100644
index 0000000..ba0263e
--- /dev/null
+++ b/lib/nghttp2_extpri.c
@@ -0,0 +1,41 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_extpri.h"
+#include "nghttp2_http.h"
+
+uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri) {
+ return (uint8_t)((uint32_t)extpri->inc << 7 | extpri->urgency);
+}
+
+void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri) {
+ extpri->urgency = nghttp2_extpri_uint8_urgency(u8extpri);
+ extpri->inc = nghttp2_extpri_uint8_inc(u8extpri);
+}
+
+int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, const uint8_t *value,
+ size_t len) {
+ return nghttp2_http_parse_priority(extpri, value, len);
+}
diff --git a/lib/nghttp2_extpri.h b/lib/nghttp2_extpri.h
new file mode 100644
index 0000000..23c6ddc
--- /dev/null
+++ b/lib/nghttp2_extpri.h
@@ -0,0 +1,65 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_EXTPRI_H
+#define NGHTTP2_EXTPRI_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+/*
+ * NGHTTP2_EXTPRI_INC_MASK is a bit mask to retrieve incremental bit
+ * from a value produced by nghttp2_extpri_to_uint8.
+ */
+#define NGHTTP2_EXTPRI_INC_MASK (1 << 7)
+
+/*
+ * nghttp2_extpri_to_uint8 encodes |pri| into uint8_t variable.
+ */
+uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri);
+
+/*
+ * nghttp2_extpri_from_uint8 decodes |u8extpri|, which is produced by
+ * nghttp2_extpri_to_uint8, intto |extpri|.
+ */
+void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri);
+
+/*
+ * nghttp2_extpri_uint8_urgency extracts urgency from |PRI| which is
+ * supposed to be constructed by nghttp2_extpri_to_uint8.
+ */
+#define nghttp2_extpri_uint8_urgency(PRI) \
+ ((uint32_t)((PRI) & ~NGHTTP2_EXTPRI_INC_MASK))
+
+/*
+ * nghttp2_extpri_uint8_inc extracts inc from |PRI| which is supposed to
+ * be constructed by nghttp2_extpri_to_uint8.
+ */
+#define nghttp2_extpri_uint8_inc(PRI) (((PRI)&NGHTTP2_EXTPRI_INC_MASK) != 0)
+
+#endif /* NGHTTP2_EXTPRI_H */
diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c
new file mode 100644
index 0000000..77cb463
--- /dev/null
+++ b/lib/nghttp2_frame.c
@@ -0,0 +1,1214 @@
+/*
+ * 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"
+#include "nghttp2_debug.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], (uint32_t)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) { (void)frame; }
+
+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) { (void)frame; }
+
+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) { (void)frame; }
+
+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) {
+ (void)frame;
+}
+
+size_t nghttp2_frame_trail_padlen(nghttp2_frame *frame, size_t padlen) {
+ /* We have iframe->padlen == 0, but iframe->frame.hd.flags may have
+ NGHTTP2_FLAG_PADDED set. This happens when receiving
+ CONTINUATION frame, since we don't reset flags after HEADERS was
+ received. */
+ if (padlen == 0) {
+ return 0;
+ }
+ 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) { (void)frame; }
+
+void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
+ uint8_t flags, int32_t stream_id,
+ void *payload) {
+ nghttp2_frame_hd_init(&frame->hd, 0, type, flags, stream_id);
+ frame->payload = payload;
+}
+
+void nghttp2_frame_extension_free(nghttp2_extension *frame) { (void)frame; }
+
+void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id,
+ uint8_t *origin, size_t origin_len,
+ uint8_t *field_value, size_t field_value_len) {
+ nghttp2_ext_altsvc *altsvc;
+
+ nghttp2_frame_hd_init(&frame->hd, 2 + origin_len + field_value_len,
+ NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, stream_id);
+
+ altsvc = frame->payload;
+ altsvc->origin = origin;
+ altsvc->origin_len = origin_len;
+ altsvc->field_value = field_value;
+ altsvc->field_value_len = field_value_len;
+}
+
+void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) {
+ nghttp2_ext_altsvc *altsvc;
+
+ altsvc = frame->payload;
+ if (altsvc == NULL) {
+ return;
+ }
+ /* We use the same buffer for altsvc->origin and
+ altsvc->field_value. */
+ nghttp2_mem_free(mem, altsvc->origin);
+}
+
+void nghttp2_frame_origin_init(nghttp2_extension *frame,
+ nghttp2_origin_entry *ov, size_t nov) {
+ nghttp2_ext_origin *origin;
+ size_t payloadlen = 0;
+ size_t i;
+
+ for (i = 0; i < nov; ++i) {
+ payloadlen += 2 + ov[i].origin_len;
+ }
+
+ nghttp2_frame_hd_init(&frame->hd, payloadlen, NGHTTP2_ORIGIN,
+ NGHTTP2_FLAG_NONE, 0);
+
+ origin = frame->payload;
+ origin->ov = ov;
+ origin->nov = nov;
+}
+
+void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem) {
+ nghttp2_ext_origin *origin;
+
+ origin = frame->payload;
+ if (origin == NULL) {
+ return;
+ }
+ /* We use the same buffer for all resources pointed by the field of
+ origin directly or indirectly. */
+ nghttp2_mem_free(mem, origin->ov);
+}
+
+void nghttp2_frame_priority_update_init(nghttp2_extension *frame,
+ int32_t stream_id, uint8_t *field_value,
+ size_t field_value_len) {
+ nghttp2_ext_priority_update *priority_update;
+
+ nghttp2_frame_hd_init(&frame->hd, 4 + field_value_len,
+ NGHTTP2_PRIORITY_UPDATE, NGHTTP2_FLAG_NONE, 0);
+
+ priority_update = frame->payload;
+ priority_update->stream_id = stream_id;
+ priority_update->field_value = field_value;
+ priority_update->field_value_len = field_value_len;
+}
+
+void nghttp2_frame_priority_update_free(nghttp2_extension *frame,
+ nghttp2_mem *mem) {
+ nghttp2_ext_priority_update *priority_update;
+
+ priority_update = frame->payload;
+ if (priority_update == NULL) {
+ return;
+ }
+ nghttp2_mem_free(mem, priority_update->field_value);
+}
+
+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("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 = (uint8_t)(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("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("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, (uint32_t)pri_spec->stream_id);
+ if (pri_spec->exclusive) {
+ buf[0] |= 0x80;
+ }
+ buf[4] = (uint8_t)(pri_spec->weight - 1);
+}
+
+void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec,
+ const uint8_t *payload) {
+ 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);
+}
+
+void nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
+ const uint8_t *payload) {
+ if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
+ nghttp2_frame_unpack_priority_spec(&frame->pri_spec, payload);
+ } else {
+ nghttp2_priority_spec_default_init(&frame->pri_spec);
+ }
+
+ frame->nva = NULL;
+ frame->nvlen = 0;
+}
+
+void 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;
+}
+
+void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame,
+ const uint8_t *payload) {
+ nghttp2_frame_unpack_priority_spec(&frame->pri_spec, payload);
+}
+
+void 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;
+}
+
+void nghttp2_frame_unpack_rst_stream_payload(nghttp2_rst_stream *frame,
+ const uint8_t *payload) {
+ 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) < 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, (uint16_t)iv[i].settings_id);
+ nghttp2_put_uint32be(buf + 2, iv[i].value);
+ }
+ return NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH * niv;
+}
+
+void nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame,
+ nghttp2_settings_entry *iv,
+ size_t niv) {
+ frame->iv = iv;
+ frame->niv = niv;
+}
+
+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, (uint32_t)frame->promised_stream_id);
+
+ frame->padlen = 0;
+ frame->hd.length = nghttp2_bufs_len(bufs);
+
+ return frame_pack_headers_shared(bufs, &frame->hd);
+}
+
+void nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,
+ const uint8_t *payload) {
+ frame->promised_stream_id =
+ nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK;
+ frame->nva = NULL;
+ frame->nvlen = 0;
+}
+
+void 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));
+}
+
+void nghttp2_frame_unpack_ping_payload(nghttp2_ping *frame,
+ const uint8_t *payload) {
+ 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, (uint32_t)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,
+ 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;
+ }
+
+ 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, var_gift_payload,
+ var_gift_payloadlen);
+
+ return 0;
+}
+
+void 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, (uint32_t)frame->window_size_increment);
+ buf->last += 4;
+}
+
+void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
+ const uint8_t *payload) {
+ frame->window_size_increment =
+ nghttp2_get_uint32(payload) & NGHTTP2_WINDOW_SIZE_INCREMENT_MASK;
+}
+
+void nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *frame) {
+ int rv;
+ nghttp2_buf *buf;
+ nghttp2_ext_altsvc *altsvc;
+
+ /* This is required with --disable-assert. */
+ (void)rv;
+
+ altsvc = frame->payload;
+
+ buf = &bufs->head->buf;
+
+ assert(nghttp2_buf_avail(buf) >=
+ 2 + altsvc->origin_len + altsvc->field_value_len);
+
+ buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+ nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+ nghttp2_put_uint16be(buf->last, (uint16_t)altsvc->origin_len);
+ buf->last += 2;
+
+ rv = nghttp2_bufs_add(bufs, altsvc->origin, altsvc->origin_len);
+
+ assert(rv == 0);
+
+ rv = nghttp2_bufs_add(bufs, altsvc->field_value, altsvc->field_value_len);
+
+ assert(rv == 0);
+}
+
+void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame,
+ size_t origin_len, uint8_t *payload,
+ size_t payloadlen) {
+ nghttp2_ext_altsvc *altsvc;
+ uint8_t *p;
+
+ altsvc = frame->payload;
+ p = payload;
+
+ altsvc->origin = p;
+
+ p += origin_len;
+
+ altsvc->origin_len = origin_len;
+
+ altsvc->field_value = p;
+ altsvc->field_value_len = (size_t)(payload + payloadlen - p);
+}
+
+int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame,
+ const uint8_t *payload,
+ size_t payloadlen, nghttp2_mem *mem) {
+ uint8_t *buf;
+ size_t origin_len;
+
+ if (payloadlen < 2) {
+ return NGHTTP2_FRAME_SIZE_ERROR;
+ }
+
+ origin_len = nghttp2_get_uint16(payload);
+
+ buf = nghttp2_mem_malloc(mem, payloadlen - 2);
+ if (!buf) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_cpymem(buf, payload + 2, payloadlen - 2);
+
+ nghttp2_frame_unpack_altsvc_payload(frame, origin_len, buf, payloadlen - 2);
+
+ return 0;
+}
+
+int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *frame) {
+ nghttp2_buf *buf;
+ nghttp2_ext_origin *origin;
+ nghttp2_origin_entry *orig;
+ size_t i;
+
+ origin = frame->payload;
+
+ buf = &bufs->head->buf;
+
+ if (nghttp2_buf_avail(buf) < frame->hd.length) {
+ return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+ }
+
+ buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+ nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+ for (i = 0; i < origin->nov; ++i) {
+ orig = &origin->ov[i];
+ nghttp2_put_uint16be(buf->last, (uint16_t)orig->origin_len);
+ buf->last += 2;
+ buf->last = nghttp2_cpymem(buf->last, orig->origin, orig->origin_len);
+ }
+
+ assert(nghttp2_buf_len(buf) == NGHTTP2_FRAME_HDLEN + frame->hd.length);
+
+ return 0;
+}
+
+int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame,
+ const uint8_t *payload,
+ size_t payloadlen, nghttp2_mem *mem) {
+ nghttp2_ext_origin *origin;
+ const uint8_t *p, *end;
+ uint8_t *dst;
+ size_t originlen;
+ nghttp2_origin_entry *ov;
+ size_t nov = 0;
+ size_t len = 0;
+
+ origin = frame->payload;
+ p = end = payload;
+ if (payloadlen) {
+ end += payloadlen;
+ }
+
+ for (; p != end;) {
+ if (end - p < 2) {
+ return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+ }
+ originlen = nghttp2_get_uint16(p);
+ p += 2;
+ if (originlen == 0) {
+ continue;
+ }
+ if (originlen > (size_t)(end - p)) {
+ return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+ }
+ p += originlen;
+ /* 1 for terminal NULL */
+ len += originlen + 1;
+ ++nov;
+ }
+
+ if (nov == 0) {
+ origin->ov = NULL;
+ origin->nov = 0;
+
+ return 0;
+ }
+
+ len += nov * sizeof(nghttp2_origin_entry);
+
+ ov = nghttp2_mem_malloc(mem, len);
+ if (ov == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ origin->ov = ov;
+ origin->nov = nov;
+
+ dst = (uint8_t *)ov + nov * sizeof(nghttp2_origin_entry);
+ p = payload;
+
+ for (; p != end;) {
+ originlen = nghttp2_get_uint16(p);
+ p += 2;
+ if (originlen == 0) {
+ continue;
+ }
+ ov->origin = dst;
+ ov->origin_len = originlen;
+ dst = nghttp2_cpymem(dst, p, originlen);
+ *dst++ = '\0';
+ p += originlen;
+ ++ov;
+ }
+
+ return 0;
+}
+
+void nghttp2_frame_pack_priority_update(nghttp2_bufs *bufs,
+ nghttp2_extension *frame) {
+ int rv;
+ nghttp2_buf *buf;
+ nghttp2_ext_priority_update *priority_update;
+
+ /* This is required with --disable-assert. */
+ (void)rv;
+
+ priority_update = frame->payload;
+
+ buf = &bufs->head->buf;
+
+ assert(nghttp2_buf_avail(buf) >= 4 + priority_update->field_value_len);
+
+ buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+ nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+ nghttp2_put_uint32be(buf->last, (uint32_t)priority_update->stream_id);
+ buf->last += 4;
+
+ rv = nghttp2_bufs_add(bufs, priority_update->field_value,
+ priority_update->field_value_len);
+
+ assert(rv == 0);
+}
+
+void nghttp2_frame_unpack_priority_update_payload(nghttp2_extension *frame,
+ uint8_t *payload,
+ size_t payloadlen) {
+ nghttp2_ext_priority_update *priority_update;
+
+ assert(payloadlen >= 4);
+
+ priority_update = frame->payload;
+
+ priority_update->stream_id =
+ nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK;
+
+ if (payloadlen > 4) {
+ priority_update->field_value = payload + 4;
+ priority_update->field_value_len = payloadlen - 4;
+ } else {
+ priority_update->field_value = NULL;
+ priority_update->field_value_len = 0;
+ }
+}
+
+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) {
+ if (a->namelen != b->namelen || a->valuelen != b->valuelen) {
+ return 0;
+ }
+
+ if (a->name == NULL || b->name == NULL) {
+ assert(a->namelen == 0);
+ assert(b->namelen == 0);
+ } else if (memcmp(a->name, b->name, a->namelen) != 0) {
+ return 0;
+ }
+
+ if (a->value == NULL || b->value == NULL) {
+ assert(a->valuelen == 0);
+ assert(b->valuelen == 0);
+ } else if (memcmp(a->value, b->value, a->valuelen) != 0) {
+ return 0;
+ }
+
+ return 1;
+}
+
+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 = NULL;
+ size_t buflen = 0;
+ nghttp2_nv *p;
+
+ if (nvlen == 0) {
+ *nva_ptr = NULL;
+
+ return 0;
+ }
+
+ for (i = 0; i < nvlen; ++i) {
+ /* + 1 for null-termination */
+ if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) == 0) {
+ buflen += nva[i].namelen + 1;
+ }
+ if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) == 0) {
+ buflen += nva[i].valuelen + 1;
+ }
+ }
+
+ 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;
+
+ if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) {
+ p->name = nva[i].name;
+ p->namelen = nva[i].namelen;
+ } else {
+ if (nva[i].namelen) {
+ memcpy(data, nva[i].name, nva[i].namelen);
+ }
+ p->name = data;
+ p->namelen = nva[i].namelen;
+ data[p->namelen] = '\0';
+ nghttp2_downcase(p->name, p->namelen);
+ data += nva[i].namelen + 1;
+ }
+
+ if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) {
+ p->value = nva[i].value;
+ p->valuelen = nva[i].valuelen;
+ } else {
+ if (nva[i].valuelen) {
+ memcpy(data, nva[i].value, nva[i].valuelen);
+ }
+ p->value = data;
+ p->valuelen = nva[i].valuelen;
+ data[p->valuelen] = '\0';
+ data += nva[i].valuelen + 1;
+ }
+
+ ++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:
+ 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;
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ if (iv[i].value != 0 && iv[i].value != 1) {
+ return 0;
+ }
+ break;
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ if (iv[i].value != 0 && iv[i].value != 1) {
+ return 0;
+ }
+ break;
+ }
+ }
+ return 1;
+}
+
+static void frame_set_pad(nghttp2_buf *buf, size_t padlen, int framehd_only) {
+ size_t trail_padlen;
+ size_t newlen;
+
+ DEBUGF("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]));
+
+ if (framehd_only) {
+ return;
+ }
+
+ trail_padlen = padlen - 1;
+ buf->pos[NGHTTP2_FRAME_HDLEN] = (uint8_t)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;
+}
+
+void nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
+ size_t padlen, int framehd_only) {
+ nghttp2_buf *buf;
+
+ if (padlen == 0) {
+ DEBUGF("send: padlen = 0, nothing to do\n");
+
+ return;
+ }
+
+ /*
+ * 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) >= padlen - 1);
+
+ frame_set_pad(buf, padlen, framehd_only);
+
+ hd->length += padlen;
+ hd->flags |= NGHTTP2_FLAG_PADDED;
+
+ DEBUGF("send: final payloadlen=%zu, padlen=%zu\n", hd->length, padlen);
+}
diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h
new file mode 100644
index 0000000..d586688
--- /dev/null
+++ b/lib/nghttp2_frame.h
@@ -0,0 +1,637 @@
+/*
+ * 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 transmission. 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)
+
+/* The default length of DATA frame payload. */
+#define NGHTTP2_DATA_PAYLOADLEN NGHTTP2_MAX_FRAME_SIZE_MIN
+
+/* Maximum headers block size to send, calculated using
+ nghttp2_hd_deflate_bound(). This is the default value, and can be
+ overridden by nghttp2_option_set_max_send_header_block_length(). */
+#define NGHTTP2_MAX_HEADERSLEN 65536
+
+/* The number of bytes for each SETTINGS entry */
+#define NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH 6
+
+/* 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_origin origin;
+ nghttp2_ext_priority_update priority_update;
+} 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,
+ const uint8_t *payload);
+
+/*
+ * 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.
+ */
+void nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
+ const uint8_t *payload);
+
+/*
+ * 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.
+ */
+void 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);
+
+/*
+ * 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.
+ */
+void 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);
+
+/*
+ * 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);
+
+/*
+ * Initializes payload of frame->settings. The |frame| takes
+ * ownership of |iv|.
+ */
+void nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame,
+ nghttp2_settings_entry *iv,
+ size_t niv);
+
+/*
+ * 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.
+ */
+void nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,
+ const uint8_t *payload);
+
+/*
+ * 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.
+ */
+void 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);
+
+/*
+ * 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,
+ 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.
+ */
+void 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);
+
+/*
+ * Packs ALTSVC 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.
+ */
+void nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *ext);
+
+/*
+ * Unpacks ALTSVC wire format into |frame|. The |payload| of
+ * |payloadlen| bytes contains frame payload. This function assumes
+ * that frame->payload points to the nghttp2_ext_altsvc object.
+ */
+void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame,
+ size_t origin_len, uint8_t *payload,
+ size_t payloadlen);
+
+/*
+ * Unpacks ALTSVC wire format into |frame|. This function only exists
+ * for unit test. After allocating buffer for fields, this function
+ * internally calls nghttp2_frame_unpack_altsvc_payload().
+ *
+ * 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 payload is too small.
+ */
+int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame,
+ const uint8_t *payload,
+ size_t payloadlen, nghttp2_mem *mem);
+
+/*
+ * Packs ORIGIN 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 returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_FRAME_SIZE_ERROR
+ * The length of the frame is too large.
+ */
+int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *ext);
+
+/*
+ * Unpacks ORIGIN wire format into |frame|. The |payload| of length
+ * |payloadlen| contains the frame payload.
+ *
+ * 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 payload is too small.
+ */
+int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame,
+ const uint8_t *payload,
+ size_t payloadlen, nghttp2_mem *mem);
+
+/*
+ * Packs PRIORITY_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.
+ */
+void nghttp2_frame_pack_priority_update(nghttp2_bufs *bufs,
+ nghttp2_extension *ext);
+
+/*
+ * Unpacks PRIORITY_UPDATE wire format into |frame|. The |payload| of
+ * |payloadlen| bytes contains frame payload. This function assumes
+ * that frame->payload points to the nghttp2_ext_priority_update
+ * object.
+ */
+void nghttp2_frame_unpack_priority_update_payload(nghttp2_extension *frame,
+ 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);
+
+void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
+ uint8_t flags, int32_t stream_id,
+ void *payload);
+
+void nghttp2_frame_extension_free(nghttp2_extension *frame);
+
+/*
+ * Initializes ALTSVC frame |frame| with given values. This function
+ * assumes that frame->payload points to nghttp2_ext_altsvc object.
+ * Also |origin| and |field_value| are allocated in single buffer,
+ * starting |origin|. On success, this function takes ownership of
+ * |origin|, so caller must not free it.
+ */
+void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id,
+ uint8_t *origin, size_t origin_len,
+ uint8_t *field_value, size_t field_value_len);
+
+/*
+ * Frees up resources under |frame|. This function does not free
+ * nghttp2_ext_altsvc object pointed by frame->payload. This function
+ * only frees origin pointed by nghttp2_ext_altsvc.origin. Therefore,
+ * other fields must be allocated in the same buffer with origin.
+ */
+void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem);
+
+/*
+ * Initializes ORIGIN frame |frame| with given values. This function
+ * assumes that frame->payload points to nghttp2_ext_origin object.
+ * Also |ov| and the memory pointed by the field of its elements are
+ * allocated in single buffer, starting with |ov|. On success, this
+ * function takes ownership of |ov|, so caller must not free it.
+ */
+void nghttp2_frame_origin_init(nghttp2_extension *frame,
+ nghttp2_origin_entry *ov, size_t nov);
+
+/*
+ * Frees up resources under |frame|. This function does not free
+ * nghttp2_ext_origin object pointed by frame->payload. This function
+ * only frees nghttp2_ext_origin.ov. Therefore, other fields must be
+ * allocated in the same buffer with ov.
+ */
+void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem);
+
+/*
+ * Initializes PRIORITY_UPDATE frame |frame| with given values. This
+ * function assumes that frame->payload points to
+ * nghttp2_ext_priority_update object. On success, this function
+ * takes ownership of |field_value|, so caller must not free it.
+ */
+void nghttp2_frame_priority_update_init(nghttp2_extension *frame,
+ int32_t stream_id, uint8_t *field_value,
+ size_t field_value_len);
+
+/*
+ * Frees up resources under |frame|. This function does not free
+ * nghttp2_ext_priority_update object pointed by frame->payload. This
+ * function only frees field_value pointed by
+ * nghttp2_ext_priority_update.field_value.
+ */
+void nghttp2_frame_priority_update_free(nghttp2_extension *frame,
+ nghttp2_mem *mem);
+
+/*
+ * 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. If |padlen| is 0, this function
+ * returns 0, regardless of frame->hd.flags.
+ */
+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 resultant name and value in nghttp2_nv are
+ * guaranteed to be NULL-terminated even if the input is not
+ * null-terminated.
+ *
+ * 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 fills zeros padding region
+ * unless framehd_only is nonzero.
+ */
+void nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
+ size_t padlen, int framehd_only);
+
+#endif /* NGHTTP2_FRAME_H */
diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c
new file mode 100644
index 0000000..8a2bda6
--- /dev/null
+++ b/lib/nghttp2_hd.c
@@ -0,0 +1,2357 @@
+/*
+ * 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"
+#include "nghttp2_debug.h"
+
+/* Make scalar initialization form of nghttp2_hd_entry */
+#define MAKE_STATIC_ENT(N, V, T, H) \
+ { \
+ {NULL, NULL, (uint8_t *)(N), sizeof((N)) - 1, -1}, \
+ {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \
+ {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \
+ T, H \
+ }
+
+/* Generated by mkstatictbl.py */
+/* 3rd parameter is nghttp2_token value for header field name. We use
+ first enum value if same header names are repeated (e.g.,
+ :status). */
+static const nghttp2_hd_static_entry static_table[] = {
+ MAKE_STATIC_ENT(":authority", "", 0, 3153725150u),
+ MAKE_STATIC_ENT(":method", "GET", 1, 695666056u),
+ MAKE_STATIC_ENT(":method", "POST", 1, 695666056u),
+ MAKE_STATIC_ENT(":path", "/", 3, 3292848686u),
+ MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u),
+ MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u),
+ MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u),
+ MAKE_STATIC_ENT(":status", "200", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "204", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "206", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "304", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "400", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "404", 7, 4000288983u),
+ MAKE_STATIC_ENT(":status", "500", 7, 4000288983u),
+ MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u),
+ MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u),
+ MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u),
+ MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u),
+ MAKE_STATIC_ENT("accept", "", 18, 136609321u),
+ MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u),
+ MAKE_STATIC_ENT("age", "", 20, 742476188u),
+ MAKE_STATIC_ENT("allow", "", 21, 2930878514u),
+ MAKE_STATIC_ENT("authorization", "", 22, 2436257726u),
+ MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u),
+ MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u),
+ MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u),
+ MAKE_STATIC_ENT("content-language", "", 26, 24973587u),
+ MAKE_STATIC_ENT("content-length", "", 27, 1308181789u),
+ MAKE_STATIC_ENT("content-location", "", 28, 2302364718u),
+ MAKE_STATIC_ENT("content-range", "", 29, 3555523146u),
+ MAKE_STATIC_ENT("content-type", "", 30, 4244048277u),
+ MAKE_STATIC_ENT("cookie", "", 31, 2007449791u),
+ MAKE_STATIC_ENT("date", "", 32, 3564297305u),
+ MAKE_STATIC_ENT("etag", "", 33, 113792960u),
+ MAKE_STATIC_ENT("expect", "", 34, 2530896728u),
+ MAKE_STATIC_ENT("expires", "", 35, 1049544579u),
+ MAKE_STATIC_ENT("from", "", 36, 2513272949u),
+ MAKE_STATIC_ENT("host", "", 37, 2952701295u),
+ MAKE_STATIC_ENT("if-match", "", 38, 3597694698u),
+ MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u),
+ MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u),
+ MAKE_STATIC_ENT("if-range", "", 41, 2340978238u),
+ MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u),
+ MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u),
+ MAKE_STATIC_ENT("link", "", 44, 232457833u),
+ MAKE_STATIC_ENT("location", "", 45, 200649126u),
+ MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u),
+ MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u),
+ MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u),
+ MAKE_STATIC_ENT("range", "", 49, 4208725202u),
+ MAKE_STATIC_ENT("referer", "", 50, 3969579366u),
+ MAKE_STATIC_ENT("refresh", "", 51, 3572655668u),
+ MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u),
+ MAKE_STATIC_ENT("server", "", 53, 1085029842u),
+ MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u),
+ MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u),
+ MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u),
+ MAKE_STATIC_ENT("user-agent", "", 57, 606444526u),
+ MAKE_STATIC_ENT("vary", "", 58, 1085005381u),
+ MAKE_STATIC_ENT("via", "", 59, 1762798611u),
+ MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u),
+};
+
+static int memeq(const void *s1, const void *s2, size_t n) {
+ return memcmp(s1, s2, n) == 0;
+}
+
+/*
+ * This function was generated by genlibtokenlookup.py. Inspired by
+ * h2o header lookup. https://github.com/h2o/h2o
+ */
+static int32_t lookup_token(const uint8_t *name, size_t namelen) {
+ switch (namelen) {
+ case 2:
+ switch (name[1]) {
+ case 'e':
+ if (memeq("t", name, 1)) {
+ return NGHTTP2_TOKEN_TE;
+ }
+ break;
+ }
+ break;
+ case 3:
+ switch (name[2]) {
+ case 'a':
+ if (memeq("vi", name, 2)) {
+ return NGHTTP2_TOKEN_VIA;
+ }
+ break;
+ case 'e':
+ if (memeq("ag", name, 2)) {
+ return NGHTTP2_TOKEN_AGE;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'e':
+ if (memeq("dat", name, 3)) {
+ return NGHTTP2_TOKEN_DATE;
+ }
+ break;
+ case 'g':
+ if (memeq("eta", name, 3)) {
+ return NGHTTP2_TOKEN_ETAG;
+ }
+ break;
+ case 'k':
+ if (memeq("lin", name, 3)) {
+ return NGHTTP2_TOKEN_LINK;
+ }
+ break;
+ case 'm':
+ if (memeq("fro", name, 3)) {
+ return NGHTTP2_TOKEN_FROM;
+ }
+ break;
+ case 't':
+ if (memeq("hos", name, 3)) {
+ return NGHTTP2_TOKEN_HOST;
+ }
+ break;
+ case 'y':
+ if (memeq("var", name, 3)) {
+ return NGHTTP2_TOKEN_VARY;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (name[4]) {
+ case 'e':
+ if (memeq("rang", name, 4)) {
+ return NGHTTP2_TOKEN_RANGE;
+ }
+ break;
+ case 'h':
+ if (memeq(":pat", name, 4)) {
+ return NGHTTP2_TOKEN__PATH;
+ }
+ break;
+ case 'w':
+ if (memeq("allo", name, 4)) {
+ return NGHTTP2_TOKEN_ALLOW;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'e':
+ if (memeq("cooki", name, 5)) {
+ return NGHTTP2_TOKEN_COOKIE;
+ }
+ break;
+ case 'r':
+ if (memeq("serve", name, 5)) {
+ return NGHTTP2_TOKEN_SERVER;
+ }
+ break;
+ case 't':
+ if (memeq("accep", name, 5)) {
+ return NGHTTP2_TOKEN_ACCEPT;
+ }
+ if (memeq("expec", name, 5)) {
+ return NGHTTP2_TOKEN_EXPECT;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'd':
+ if (memeq(":metho", name, 6)) {
+ return NGHTTP2_TOKEN__METHOD;
+ }
+ break;
+ case 'e':
+ if (memeq(":schem", name, 6)) {
+ return NGHTTP2_TOKEN__SCHEME;
+ }
+ if (memeq("upgrad", name, 6)) {
+ return NGHTTP2_TOKEN_UPGRADE;
+ }
+ break;
+ case 'h':
+ if (memeq("refres", name, 6)) {
+ return NGHTTP2_TOKEN_REFRESH;
+ }
+ break;
+ case 'r':
+ if (memeq("refere", name, 6)) {
+ return NGHTTP2_TOKEN_REFERER;
+ }
+ break;
+ case 's':
+ if (memeq(":statu", name, 6)) {
+ return NGHTTP2_TOKEN__STATUS;
+ }
+ if (memeq("expire", name, 6)) {
+ return NGHTTP2_TOKEN_EXPIRES;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'e':
+ if (memeq("if-rang", name, 7)) {
+ return NGHTTP2_TOKEN_IF_RANGE;
+ }
+ break;
+ case 'h':
+ if (memeq("if-matc", name, 7)) {
+ return NGHTTP2_TOKEN_IF_MATCH;
+ }
+ break;
+ case 'n':
+ if (memeq("locatio", name, 7)) {
+ return NGHTTP2_TOKEN_LOCATION;
+ }
+ break;
+ case 'y':
+ if (memeq("priorit", name, 7)) {
+ return NGHTTP2_TOKEN_PRIORITY;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'l':
+ if (memeq(":protoco", name, 8)) {
+ return NGHTTP2_TOKEN__PROTOCOL;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'e':
+ if (memeq("keep-aliv", name, 9)) {
+ return NGHTTP2_TOKEN_KEEP_ALIVE;
+ }
+ if (memeq("set-cooki", name, 9)) {
+ return NGHTTP2_TOKEN_SET_COOKIE;
+ }
+ break;
+ case 'n':
+ if (memeq("connectio", name, 9)) {
+ return NGHTTP2_TOKEN_CONNECTION;
+ }
+ break;
+ case 't':
+ if (memeq("user-agen", name, 9)) {
+ return NGHTTP2_TOKEN_USER_AGENT;
+ }
+ break;
+ case 'y':
+ if (memeq(":authorit", name, 9)) {
+ return NGHTTP2_TOKEN__AUTHORITY;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'r':
+ if (memeq("retry-afte", name, 10)) {
+ return NGHTTP2_TOKEN_RETRY_AFTER;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case 'e':
+ if (memeq("content-typ", name, 11)) {
+ return NGHTTP2_TOKEN_CONTENT_TYPE;
+ }
+ break;
+ case 's':
+ if (memeq("max-forward", name, 11)) {
+ return NGHTTP2_TOKEN_MAX_FORWARDS;
+ }
+ break;
+ }
+ break;
+ case 13:
+ switch (name[12]) {
+ case 'd':
+ if (memeq("last-modifie", name, 12)) {
+ return NGHTTP2_TOKEN_LAST_MODIFIED;
+ }
+ break;
+ case 'e':
+ if (memeq("content-rang", name, 12)) {
+ return NGHTTP2_TOKEN_CONTENT_RANGE;
+ }
+ break;
+ case 'h':
+ if (memeq("if-none-matc", name, 12)) {
+ return NGHTTP2_TOKEN_IF_NONE_MATCH;
+ }
+ break;
+ case 'l':
+ if (memeq("cache-contro", name, 12)) {
+ return NGHTTP2_TOKEN_CACHE_CONTROL;
+ }
+ break;
+ case 'n':
+ if (memeq("authorizatio", name, 12)) {
+ return NGHTTP2_TOKEN_AUTHORIZATION;
+ }
+ break;
+ case 's':
+ if (memeq("accept-range", name, 12)) {
+ return NGHTTP2_TOKEN_ACCEPT_RANGES;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'h':
+ if (memeq("content-lengt", name, 13)) {
+ return NGHTTP2_TOKEN_CONTENT_LENGTH;
+ }
+ break;
+ case 't':
+ if (memeq("accept-charse", name, 13)) {
+ return NGHTTP2_TOKEN_ACCEPT_CHARSET;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 'e':
+ if (memeq("accept-languag", name, 14)) {
+ return NGHTTP2_TOKEN_ACCEPT_LANGUAGE;
+ }
+ break;
+ case 'g':
+ if (memeq("accept-encodin", name, 14)) {
+ return NGHTTP2_TOKEN_ACCEPT_ENCODING;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'e':
+ if (memeq("content-languag", name, 15)) {
+ return NGHTTP2_TOKEN_CONTENT_LANGUAGE;
+ }
+ if (memeq("www-authenticat", name, 15)) {
+ return NGHTTP2_TOKEN_WWW_AUTHENTICATE;
+ }
+ break;
+ case 'g':
+ if (memeq("content-encodin", name, 15)) {
+ return NGHTTP2_TOKEN_CONTENT_ENCODING;
+ }
+ break;
+ case 'n':
+ if (memeq("content-locatio", name, 15)) {
+ return NGHTTP2_TOKEN_CONTENT_LOCATION;
+ }
+ if (memeq("proxy-connectio", name, 15)) {
+ return NGHTTP2_TOKEN_PROXY_CONNECTION;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'e':
+ if (memeq("if-modified-sinc", name, 16)) {
+ return NGHTTP2_TOKEN_IF_MODIFIED_SINCE;
+ }
+ break;
+ case 'g':
+ if (memeq("transfer-encodin", name, 16)) {
+ return NGHTTP2_TOKEN_TRANSFER_ENCODING;
+ }
+ break;
+ }
+ break;
+ case 18:
+ switch (name[17]) {
+ case 'e':
+ if (memeq("proxy-authenticat", name, 17)) {
+ return NGHTTP2_TOKEN_PROXY_AUTHENTICATE;
+ }
+ break;
+ }
+ break;
+ case 19:
+ switch (name[18]) {
+ case 'e':
+ if (memeq("if-unmodified-sinc", name, 18)) {
+ return NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE;
+ }
+ break;
+ case 'n':
+ if (memeq("content-dispositio", name, 18)) {
+ return NGHTTP2_TOKEN_CONTENT_DISPOSITION;
+ }
+ if (memeq("proxy-authorizatio", name, 18)) {
+ return NGHTTP2_TOKEN_PROXY_AUTHORIZATION;
+ }
+ break;
+ }
+ break;
+ case 25:
+ switch (name[24]) {
+ case 'y':
+ if (memeq("strict-transport-securit", name, 24)) {
+ return NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY;
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (name[26]) {
+ case 'n':
+ if (memeq("access-control-allow-origi", name, 26)) {
+ return NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv) {
+ ent->nv = *nv;
+ ent->cnv.name = nv->name->base;
+ ent->cnv.namelen = nv->name->len;
+ ent->cnv.value = nv->value->base;
+ ent->cnv.valuelen = nv->value->len;
+ ent->cnv.flags = nv->flags;
+ ent->next = NULL;
+ ent->hash = 0;
+
+ nghttp2_rcbuf_incref(ent->nv.name);
+ nghttp2_rcbuf_incref(ent->nv.value);
+}
+
+void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) {
+ nghttp2_rcbuf_decref(ent->nv.value);
+ nghttp2_rcbuf_decref(ent->nv.name);
+}
+
+static int name_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) {
+ return a->name->len == b->namelen &&
+ memeq(a->name->base, b->name, b->namelen);
+}
+
+static int value_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) {
+ return a->value->len == b->valuelen &&
+ memeq(a->value->base, b->value, b->valuelen);
+}
+
+static uint32_t name_hash(const nghttp2_nv *nv) {
+ /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */
+ uint32_t h = 2166136261u;
+ size_t i;
+
+ for (i = 0; i < nv->namelen; ++i) {
+ h ^= nv->name[i];
+ h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
+ }
+
+ return h;
+}
+
+static void hd_map_init(nghttp2_hd_map *map) {
+ memset(map, 0, sizeof(nghttp2_hd_map));
+}
+
+static void hd_map_insert(nghttp2_hd_map *map, nghttp2_hd_entry *ent) {
+ nghttp2_hd_entry **bucket;
+
+ bucket = &map->table[ent->hash & (HD_MAP_SIZE - 1)];
+
+ if (*bucket == NULL) {
+ *bucket = ent;
+ return;
+ }
+
+ /* lower index is linked near the root */
+ ent->next = *bucket;
+ *bucket = ent;
+}
+
+static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match,
+ const nghttp2_nv *nv, int32_t token,
+ uint32_t hash, int name_only) {
+ nghttp2_hd_entry *p;
+ nghttp2_hd_entry *res = NULL;
+
+ *exact_match = 0;
+
+ for (p = map->table[hash & (HD_MAP_SIZE - 1)]; p; p = p->next) {
+ if (token != p->nv.token ||
+ (token == -1 && (hash != p->hash || !name_eq(&p->nv, nv)))) {
+ continue;
+ }
+ if (!res) {
+ res = p;
+ if (name_only) {
+ break;
+ }
+ }
+ if (value_eq(&p->nv, nv)) {
+ res = p;
+ *exact_match = 1;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static void hd_map_remove(nghttp2_hd_map *map, nghttp2_hd_entry *ent) {
+ nghttp2_hd_entry **dst;
+
+ dst = &map->table[ent->hash & (HD_MAP_SIZE - 1)];
+
+ for (; *dst; dst = &(*dst)->next) {
+ if (*dst != ent) {
+ continue;
+ }
+
+ *dst = ent->next;
+ ent->next = NULL;
+ return;
+ }
+}
+
+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);
+
+ nghttp2_hd_entry_free(ent);
+ 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;
+ context->next_seq = 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 max_deflate_dynamic_table_size,
+ nghttp2_mem *mem) {
+ int rv;
+ rv = hd_context_init(&deflater->ctx, mem);
+ if (rv != 0) {
+ return rv;
+ }
+
+ hd_map_init(&deflater->map);
+
+ if (max_deflate_dynamic_table_size < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) {
+ deflater->notify_table_size_change = 1;
+ deflater->ctx.hd_table_bufsize_max = max_deflate_dynamic_table_size;
+ } else {
+ deflater->notify_table_size_change = 0;
+ }
+
+ deflater->deflate_hd_table_bufsize_max = max_deflate_dynamic_table_size;
+ 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->min_hd_table_bufsize_max = UINT32_MAX;
+
+ inflater->nv_name_keep = NULL;
+ inflater->nv_value_keep = NULL;
+
+ inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
+ inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
+
+ nghttp2_buf_init(&inflater->namebuf);
+ nghttp2_buf_init(&inflater->valuebuf);
+
+ inflater->namercbuf = NULL;
+ inflater->valuercbuf = NULL;
+
+ inflater->huffman_encoded = 0;
+ inflater->index = 0;
+ inflater->left = 0;
+ inflater->shift = 0;
+ inflater->index_required = 0;
+ inflater->no_index = 0;
+
+ return 0;
+
+fail:
+ return rv;
+}
+
+static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) {
+ nghttp2_rcbuf_decref(inflater->nv_value_keep);
+ nghttp2_rcbuf_decref(inflater->nv_name_keep);
+
+ inflater->nv_value_keep = NULL;
+ inflater->nv_name_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_rcbuf_decref(inflater->valuercbuf);
+ nghttp2_rcbuf_decref(inflater->namercbuf);
+
+ hd_context_free(&inflater->ctx);
+}
+
+static size_t entry_room(size_t namelen, size_t valuelen) {
+ return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen;
+}
+
+static void emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) {
+ DEBUGF("inflatehd: header emission: %s: %s\n", nv->name->base,
+ nv->value->base);
+ /* ent->ref may be 0. This happens if the encoder emits literal
+ block larger than header table capacity with indexing. */
+ *nv_out = *nv;
+}
+
+static size_t count_encoded_length(size_t n, size_t prefix) {
+ size_t k = (size_t)((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 = (size_t)((1 << prefix) - 1);
+ uint8_t *begin = buf;
+
+ *buf = (uint8_t)(*buf & ~k);
+
+ if (n < k) {
+ *buf = (uint8_t)(*buf | n);
+ return 1;
+ }
+
+ *buf = (uint8_t)(*buf | k);
+ ++buf;
+
+ n -= k;
+
+ for (; n >= 128; n >>= 7) {
+ *buf++ = (uint8_t)((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 |*fin| 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 *fin,
+ uint32_t initial, size_t shift, const uint8_t *in,
+ const uint8_t *last, size_t prefix) {
+ uint32_t k = (uint8_t)((1 << prefix) - 1);
+ uint32_t n = initial;
+ const uint8_t *start = in;
+
+ *shift_ptr = 0;
+ *fin = 0;
+
+ if (n == 0) {
+ if ((*in & k) != k) {
+ *res = (*in) & k;
+ *fin = 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 (shift >= 32) {
+ DEBUGF("inflate: shift exponent overflow\n");
+ return -1;
+ }
+
+ if ((UINT32_MAX >> shift) < add) {
+ DEBUGF("inflate: integer overflow on shift\n");
+ return -1;
+ }
+
+ add <<= shift;
+
+ if (UINT32_MAX - add < n) {
+ DEBUGF("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;
+ *fin = 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("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("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("deflatehd: emit string str=%.*s, length=%zu, huffman=%d, "
+ "encoded_length=%zu\n",
+ (int)len, (const char *)str, 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 indexing_mode) {
+ switch (indexing_mode) {
+ case NGHTTP2_HD_WITH_INDEXING:
+ return 0x40u;
+ case NGHTTP2_HD_WITHOUT_INDEXING:
+ return 0;
+ case NGHTTP2_HD_NEVER_INDEXING:
+ return 0x10u;
+ default:
+ assert(0);
+ }
+ /* This is required to compile with android NDK r10d +
+ --enable-werror */
+ return 0;
+}
+
+static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
+ const nghttp2_nv *nv, int indexing_mode) {
+ int rv;
+ uint8_t *bufp;
+ size_t blocklen;
+ uint8_t sb[16];
+ size_t prefixlen;
+
+ if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
+ prefixlen = 6;
+ } else {
+ prefixlen = 4;
+ }
+
+ DEBUGF("deflatehd: emit indname index=%zu, valuelen=%zu, indexing_mode=%d\n",
+ idx, nv->valuelen, indexing_mode);
+
+ blocklen = count_encoded_length(idx + 1, prefixlen);
+
+ if (sizeof(sb) < blocklen) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ bufp = sb;
+
+ *bufp = pack_first_byte(indexing_mode);
+
+ 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 indexing_mode) {
+ int rv;
+
+ DEBUGF(
+ "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n",
+ nv->namelen, nv->valuelen, indexing_mode);
+
+ rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode));
+ 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 int add_hd_table_incremental(nghttp2_hd_context *context,
+ nghttp2_hd_nv *nv, nghttp2_hd_map *map,
+ uint32_t hash) {
+ int rv;
+ nghttp2_hd_entry *new_ent;
+ size_t room;
+ nghttp2_mem *mem;
+
+ mem = context->mem;
+ room = entry_room(nv->name->len, nv->value->len);
+
+ 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.name->len, ent->nv.value->len);
+
+ DEBUGF("hpack: remove item from header table: %s: %s\n",
+ (char *)ent->nv.name->base, (char *)ent->nv.value->base);
+
+ hd_ringbuf_pop_back(&context->hd_table);
+ if (map) {
+ hd_map_remove(map, ent);
+ }
+
+ nghttp2_hd_entry_free(ent);
+ nghttp2_mem_free(mem, ent);
+ }
+
+ if (room > context->hd_table_bufsize_max) {
+ /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is
+ immediately evicted. So we don't allocate memory for it. */
+ return 0;
+ }
+
+ new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry));
+ if (new_ent == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_hd_entry_init(new_ent, nv);
+
+ rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem);
+
+ if (rv != 0) {
+ nghttp2_hd_entry_free(new_ent);
+ nghttp2_mem_free(mem, new_ent);
+
+ return rv;
+ }
+
+ new_ent->seq = context->next_seq++;
+ new_ent->hash = hash;
+
+ if (map) {
+ hd_map_insert(map, new_ent);
+ }
+
+ context->hd_table_bufsize += room;
+
+ return 0;
+}
+
+typedef struct {
+ ssize_t index;
+ /* Nonzero if both name and value are matched. */
+ int name_value_match;
+} search_result;
+
+static search_result search_static_table(const nghttp2_nv *nv, int32_t token,
+ int name_only) {
+ search_result res = {token, 0};
+ int i;
+ const nghttp2_hd_static_entry *ent;
+
+ if (name_only) {
+ return res;
+ }
+
+ for (i = token;
+ i <= NGHTTP2_TOKEN_WWW_AUTHENTICATE && static_table[i].token == token;
+ ++i) {
+ ent = &static_table[i];
+ if (ent->value.len == nv->valuelen &&
+ memcmp(ent->value.base, nv->value, nv->valuelen) == 0) {
+ res.index = i;
+ res.name_value_match = 1;
+ return res;
+ }
+ }
+ return res;
+}
+
+static search_result search_hd_table(nghttp2_hd_context *context,
+ const nghttp2_nv *nv, int32_t token,
+ int indexing_mode, nghttp2_hd_map *map,
+ uint32_t hash) {
+ search_result res = {-1, 0};
+ const nghttp2_hd_entry *ent;
+ int exact_match;
+ int name_only = indexing_mode == NGHTTP2_HD_NEVER_INDEXING;
+
+ exact_match = 0;
+ ent = hd_map_find(map, &exact_match, nv, token, hash, name_only);
+
+ if (!exact_match && token >= 0 && token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) {
+ return search_static_table(nv, token, name_only);
+ }
+
+ if (ent == NULL) {
+ return res;
+ }
+
+ res.index =
+ (ssize_t)(context->next_seq - 1 - ent->seq + NGHTTP2_STATIC_TABLE_LENGTH);
+ res.name_value_match = exact_match;
+
+ return res;
+}
+
+static void hd_context_shrink_table_size(nghttp2_hd_context *context,
+ nghttp2_hd_map *map) {
+ 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.name->len, ent->nv.value->len);
+ hd_ringbuf_pop_back(&context->hd_table);
+ if (map) {
+ hd_map_remove(map, ent);
+ }
+
+ nghttp2_hd_entry_free(ent);
+ nghttp2_mem_free(mem, ent);
+ }
+}
+
+int nghttp2_hd_deflate_change_table_size(
+ nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) {
+ size_t next_bufsize = nghttp2_min(settings_max_dynamic_table_size,
+ 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, &deflater->map);
+ return 0;
+}
+
+int nghttp2_hd_inflate_change_table_size(
+ nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) {
+ switch (inflater->state) {
+ case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
+ case NGHTTP2_HD_STATE_INFLATE_START:
+ break;
+ default:
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size;
+
+ /* It seems that encoder is not required to send dynamic table size
+ update if the table size is not changed after applying
+ SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this
+ is the intention of the editor. If new maximum table size is
+ strictly smaller than the current negotiated maximum size,
+ encoder must send dynamic table size update. In other cases, we
+ cannot expect it to do so. */
+ if (inflater->ctx.hd_table_bufsize_max > settings_max_dynamic_table_size) {
+ inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE;
+ /* Remember minimum value, and validate that encoder sends the
+ value less than or equal to this. */
+ inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size;
+
+ inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size;
+
+ hd_context_shrink_table_size(&inflater->ctx, NULL);
+ }
+
+ 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;
+}
+
+nghttp2_hd_nv 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)
+ ->nv;
+ } else {
+ const nghttp2_hd_static_entry *ent = &static_table[idx];
+ nghttp2_hd_nv nv = {(nghttp2_rcbuf *)&ent->name,
+ (nghttp2_rcbuf *)&ent->value, ent->token,
+ NGHTTP2_NV_FLAG_NONE};
+ return nv;
+ }
+}
+
+static const nghttp2_nv *nghttp2_hd_table_get2(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)
+ ->cnv;
+ }
+
+ return &static_table[idx].cnv;
+}
+
+static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater,
+ const nghttp2_nv *nv, int32_t token) {
+ if (token == NGHTTP2_TOKEN__PATH || token == NGHTTP2_TOKEN_AGE ||
+ token == NGHTTP2_TOKEN_CONTENT_LENGTH || token == NGHTTP2_TOKEN_ETAG ||
+ token == NGHTTP2_TOKEN_IF_MODIFIED_SINCE ||
+ token == NGHTTP2_TOKEN_IF_NONE_MATCH || token == NGHTTP2_TOKEN_LOCATION ||
+ token == NGHTTP2_TOKEN_SET_COOKIE ||
+ entry_room(nv->namelen, nv->valuelen) >
+ deflater->ctx.hd_table_bufsize_max * 3 / 4) {
+ return NGHTTP2_HD_WITHOUT_INDEXING;
+ }
+
+ return NGHTTP2_HD_WITH_INDEXING;
+}
+
+static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
+ const nghttp2_nv *nv) {
+ int rv;
+ search_result res;
+ ssize_t idx;
+ int indexing_mode;
+ int32_t token;
+ nghttp2_mem *mem;
+ uint32_t hash = 0;
+
+ DEBUGF("deflatehd: deflating %.*s: %.*s\n", (int)nv->namelen, nv->name,
+ (int)nv->valuelen, nv->value);
+
+ mem = deflater->ctx.mem;
+
+ token = lookup_token(nv->name, nv->namelen);
+ if (token == -1) {
+ hash = name_hash(nv);
+ } else if (token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) {
+ hash = static_table[token].hash;
+ }
+
+ /* Don't index authorization header field since it may contain low
+ entropy secret data (e.g., id/password). Also cookie header
+ field with less than 20 bytes value is also never indexed. This
+ is the same criteria used in Firefox codebase. */
+ indexing_mode =
+ token == NGHTTP2_TOKEN_AUTHORIZATION ||
+ (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) ||
+ (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX)
+ ? NGHTTP2_HD_NEVER_INDEXING
+ : hd_deflate_decide_indexing(deflater, nv, token);
+
+ res = search_hd_table(&deflater->ctx, nv, token, indexing_mode,
+ &deflater->map, hash);
+
+ idx = res.index;
+
+ if (res.name_value_match) {
+
+ DEBUGF("deflatehd: name/value match index=%zd\n", idx);
+
+ rv = emit_indexed_block(bufs, (size_t)idx);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+ }
+
+ if (res.index != -1) {
+ DEBUGF("deflatehd: name match index=%zd\n", res.index);
+ }
+
+ if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
+ nghttp2_hd_nv hd_nv;
+
+ if (idx != -1) {
+ hd_nv.name = nghttp2_hd_table_get(&deflater->ctx, (size_t)idx).name;
+ nghttp2_rcbuf_incref(hd_nv.name);
+ } else {
+ rv = nghttp2_rcbuf_new2(&hd_nv.name, nv->name, nv->namelen, mem);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ rv = nghttp2_rcbuf_new2(&hd_nv.value, nv->value, nv->valuelen, mem);
+
+ if (rv != 0) {
+ nghttp2_rcbuf_decref(hd_nv.name);
+ return rv;
+ }
+
+ hd_nv.token = token;
+ hd_nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ rv = add_hd_table_incremental(&deflater->ctx, &hd_nv, &deflater->map, hash);
+
+ nghttp2_rcbuf_decref(hd_nv.value);
+ nghttp2_rcbuf_decref(hd_nv.name);
+
+ if (rv != 0) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+ }
+ if (idx == -1) {
+ rv = emit_newname_block(bufs, nv, indexing_mode);
+ } else {
+ rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode);
+ }
+ 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("deflatehd: all input name/value pairs were deflated\n");
+
+ return 0;
+fail:
+ DEBUGF("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;
+}
+
+ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater,
+ const nghttp2_vec *vec, size_t veclen,
+ const nghttp2_nv *nv, size_t nvlen) {
+ nghttp2_bufs bufs;
+ int rv;
+ nghttp2_mem *mem;
+ size_t buflen;
+
+ mem = deflater->ctx.mem;
+
+ rv = nghttp2_bufs_wrap_init2(&bufs, vec, veclen, 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,
+ const nghttp2_nv *nva, size_t nvlen) {
+ size_t n = 0;
+ size_t i;
+ (void)deflater;
+
+ /* 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,
+ const uint8_t *in, const 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("inflatehd: integer decoding failed\n");
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ if (out > maxlen) {
+ DEBUGF("inflatehd: integer exceeded the maximum value %zu\n", maxlen);
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ inflater->left = out;
+
+ DEBUGF("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
+ */
+static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater,
+ nghttp2_buf *buf, const uint8_t *in,
+ const uint8_t *last) {
+ ssize_t readlen;
+ int fin = 0;
+ if ((size_t)(last - in) >= inflater->left) {
+ last = in + inflater->left;
+ fin = 1;
+ }
+ readlen = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, buf, in,
+ (size_t)(last - in), fin);
+
+ if (readlen < 0) {
+ DEBUGF("inflatehd: huffman decoding failed\n");
+ return readlen;
+ }
+ if (nghttp2_hd_huff_decode_failure_state(&inflater->huff_decode_ctx)) {
+ DEBUGF("inflatehd: huffman decoding failed\n");
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ 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
+ */
+static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater, nghttp2_buf *buf,
+ const uint8_t *in, const uint8_t *last) {
+ size_t len = nghttp2_min((size_t)(last - in), inflater->left);
+
+ buf->last = nghttp2_cpymem(buf->last, in, len);
+
+ inflater->left -= len;
+ return (ssize_t)len;
+}
+
+/*
+ * Finalize indexed header representation reception. The referenced
+ * header is always emitted, and |*nv_out| is filled with that value.
+ */
+static void hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater,
+ nghttp2_hd_nv *nv_out) {
+ nghttp2_hd_nv nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
+
+ emit_header(nv_out, &nv);
+}
+
+/*
+ * 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_hd_nv *nv_out) {
+ nghttp2_hd_nv nv;
+ int rv;
+
+ if (inflater->no_index) {
+ nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
+ } else {
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+ }
+
+ nv.name = inflater->namercbuf;
+ nv.value = inflater->valuercbuf;
+ nv.token = lookup_token(inflater->namercbuf->base, inflater->namercbuf->len);
+
+ if (inflater->index_required) {
+ rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0);
+
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ emit_header(nv_out, &nv);
+
+ inflater->nv_name_keep = nv.name;
+ inflater->nv_value_keep = nv.value;
+
+ inflater->namercbuf = NULL;
+ inflater->valuercbuf = NULL;
+
+ 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_hd_nv *nv_out) {
+ nghttp2_hd_nv nv;
+ int rv;
+
+ nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
+
+ if (inflater->no_index) {
+ nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
+ } else {
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+ }
+
+ nghttp2_rcbuf_incref(nv.name);
+
+ nv.value = inflater->valuercbuf;
+
+ if (inflater->index_required) {
+ rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0);
+ if (rv != 0) {
+ nghttp2_rcbuf_decref(nv.name);
+ return NGHTTP2_ERR_NOMEM;
+ }
+ }
+
+ emit_header(nv_out, &nv);
+
+ inflater->nv_name_keep = nv.name;
+ inflater->nv_value_keep = nv.value;
+
+ inflater->valuercbuf = NULL;
+
+ 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) {
+ return nghttp2_hd_inflate_hd2(inflater, nv_out, inflate_flags, in, inlen,
+ in_final);
+}
+
+ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
+ nghttp2_nv *nv_out, int *inflate_flags,
+ const uint8_t *in, size_t inlen, int in_final) {
+ ssize_t rv;
+ nghttp2_hd_nv hd_nv;
+
+ rv = nghttp2_hd_inflate_hd_nv(inflater, &hd_nv, inflate_flags, in, inlen,
+ in_final);
+
+ if (rv < 0) {
+ return rv;
+ }
+
+ if (*inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ nv_out->name = hd_nv.name->base;
+ nv_out->namelen = hd_nv.name->len;
+
+ nv_out->value = hd_nv.value->base;
+ nv_out->valuelen = hd_nv.value->len;
+
+ nv_out->flags = hd_nv.flags;
+ }
+
+ return rv;
+}
+
+ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater,
+ nghttp2_hd_nv *nv_out, int *inflate_flags,
+ const uint8_t *in, size_t inlen,
+ int in_final) {
+ ssize_t rv = 0;
+ const uint8_t *first = in;
+ const uint8_t *last = in + inlen;
+ int rfin = 0;
+ int busy = 0;
+ nghttp2_mem *mem;
+
+ mem = inflater->ctx.mem;
+
+ if (inflater->ctx.bad) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ DEBUGF("inflatehd: start state=%d\n", inflater->state);
+ hd_inflate_keep_free(inflater);
+ *inflate_flags = NGHTTP2_HD_INFLATE_NONE;
+ for (; in != last || busy;) {
+ busy = 0;
+ switch (inflater->state) {
+ case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
+ if ((*in & 0xe0u) != 0x20u) {
+ DEBUGF("inflatehd: header table size change was expected, but saw "
+ "0x%02x as first byte",
+ *in);
+ rv = NGHTTP2_ERR_HEADER_COMP;
+ goto fail;
+ }
+ /* fall through */
+ case NGHTTP2_HD_STATE_INFLATE_START:
+ case NGHTTP2_HD_STATE_OPCODE:
+ if ((*in & 0xe0u) == 0x20u) {
+ DEBUGF("inflatehd: header table size change\n");
+ if (inflater->state == NGHTTP2_HD_STATE_OPCODE) {
+ DEBUGF("inflatehd: header table size change must appear at the head "
+ "of header block\n");
+ rv = NGHTTP2_ERR_HEADER_COMP;
+ goto fail;
+ }
+ inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
+ inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE;
+ } else if (*in & 0x80u) {
+ DEBUGF("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("inflatehd: literal header repr - new name\n");
+ inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME;
+ inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN;
+ } else {
+ DEBUGF("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("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,
+ nghttp2_min(inflater->min_hd_table_bufsize_max,
+ inflater->settings_hd_table_bufsize_max));
+ if (rv < 0) {
+ goto fail;
+ }
+ in += rv;
+ if (!rfin) {
+ goto almost_ok;
+ }
+ DEBUGF("inflatehd: table_size=%zu\n", inflater->left);
+ inflater->min_hd_table_bufsize_max = UINT32_MAX;
+ inflater->ctx.hd_table_bufsize_max = inflater->left;
+ hd_context_shrink_table_size(&inflater->ctx, NULL);
+ inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
+ 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));
+ if (rv < 0) {
+ goto fail;
+ }
+
+ in += rv;
+
+ if (!rfin) {
+ goto almost_ok;
+ }
+
+ if (inflater->left == 0) {
+ rv = NGHTTP2_ERR_HEADER_COMP;
+ goto fail;
+ }
+
+ DEBUGF("inflatehd: index=%zu\n", inflater->left);
+ if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
+ inflater->index = inflater->left;
+ --inflater->index;
+
+ hd_inflate_commit_indexed(inflater, nv_out);
+
+ inflater->state = NGHTTP2_HD_STATE_OPCODE;
+ *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("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("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;
+
+ rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1,
+ mem);
+ } else {
+ inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME;
+ rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left + 1, mem);
+ }
+
+ if (rv != 0) {
+ goto fail;
+ }
+
+ nghttp2_buf_wrap_init(&inflater->namebuf, inflater->namercbuf->base,
+ inflater->namercbuf->len);
+
+ break;
+ case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF:
+ rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last);
+ if (rv < 0) {
+ goto fail;
+ }
+
+ in += rv;
+
+ DEBUGF("inflatehd: %zd bytes read\n", rv);
+
+ if (inflater->left) {
+ DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left);
+
+ goto almost_ok;
+ }
+
+ *inflater->namebuf.last = '\0';
+ inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf);
+
+ inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
+
+ break;
+ case NGHTTP2_HD_STATE_NEWNAME_READ_NAME:
+ rv = hd_inflate_read(inflater, &inflater->namebuf, in, last);
+ if (rv < 0) {
+ goto fail;
+ }
+
+ in += rv;
+
+ DEBUGF("inflatehd: %zd bytes read\n", rv);
+ if (inflater->left) {
+ DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left);
+
+ goto almost_ok;
+ }
+
+ *inflater->namebuf.last = '\0';
+ inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf);
+
+ 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("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("inflatehd: valuelen=%zu\n", inflater->left);
+
+ if (inflater->huffman_encoded) {
+ nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx);
+
+ inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF;
+
+ rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1,
+ mem);
+ } else {
+ inflater->state = NGHTTP2_HD_STATE_READ_VALUE;
+
+ rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left + 1, mem);
+ }
+
+ if (rv != 0) {
+ goto fail;
+ }
+
+ nghttp2_buf_wrap_init(&inflater->valuebuf, inflater->valuercbuf->base,
+ inflater->valuercbuf->len);
+
+ busy = 1;
+
+ break;
+ case NGHTTP2_HD_STATE_READ_VALUEHUFF:
+ rv = hd_inflate_read_huff(inflater, &inflater->valuebuf, in, last);
+ if (rv < 0) {
+ goto fail;
+ }
+
+ in += rv;
+
+ DEBUGF("inflatehd: %zd bytes read\n", rv);
+
+ if (inflater->left) {
+ DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left);
+
+ goto almost_ok;
+ }
+
+ *inflater->valuebuf.last = '\0';
+ inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf);
+
+ 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->valuebuf, in, last);
+ if (rv < 0) {
+ DEBUGF("inflatehd: value read failure %zd: %s\n", rv,
+ nghttp2_strerror((int)rv));
+ goto fail;
+ }
+
+ in += rv;
+
+ DEBUGF("inflatehd: %zd bytes read\n", rv);
+
+ if (inflater->left) {
+ DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left);
+ goto almost_ok;
+ }
+
+ *inflater->valuebuf.last = '\0';
+ inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf);
+
+ 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("inflatehd: all input bytes were processed\n");
+
+ if (in_final) {
+ DEBUGF("inflatehd: in_final set\n");
+
+ if (inflater->state != NGHTTP2_HD_STATE_OPCODE &&
+ inflater->state != NGHTTP2_HD_STATE_INFLATE_START) {
+ DEBUGF("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) {
+ DEBUGF("inflatehd: input ended prematurely\n");
+
+ rv = NGHTTP2_ERR_HEADER_COMP;
+
+ goto fail;
+ }
+ return (ssize_t)(in - first);
+
+fail:
+ DEBUGF("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);
+ inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
+ 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 indexing_mode) {
+
+ return emit_indname_block(bufs, idx, nv, indexing_mode);
+}
+
+int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
+ int indexing_mode) {
+ return emit_newname_block(bufs, nv, indexing_mode);
+}
+
+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 *fin,
+ uint32_t initial, size_t shift, uint8_t *in,
+ uint8_t *last, size_t prefix) {
+ return decode_length(res, shift_ptr, fin, initial, shift, in, last, prefix);
+}
+
+static const nghttp2_nv *hd_get_table_entry(nghttp2_hd_context *context,
+ size_t idx) {
+ if (idx == 0) {
+ return NULL;
+ }
+
+ --idx;
+
+ if (!INDEX_RANGE_VALID(context, idx)) {
+ return NULL;
+ }
+
+ return nghttp2_hd_table_get2(context, idx);
+}
+
+size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) {
+ return get_max_index(&deflater->ctx);
+}
+
+const nghttp2_nv *
+nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx) {
+ return hd_get_table_entry(&deflater->ctx, idx);
+}
+
+size_t
+nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater) {
+ return deflater->ctx.hd_table_bufsize;
+}
+
+size_t
+nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater) {
+ return deflater->ctx.hd_table_bufsize_max;
+}
+
+size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater) {
+ return get_max_index(&inflater->ctx);
+}
+
+const nghttp2_nv *
+nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx) {
+ return hd_get_table_entry(&inflater->ctx, idx);
+}
+
+size_t
+nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater) {
+ return inflater->ctx.hd_table_bufsize;
+}
+
+size_t
+nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater) {
+ return inflater->ctx.hd_table_bufsize_max;
+}
diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h
new file mode 100644
index 0000000..6de0052
--- /dev/null
+++ b/lib/nghttp2_hd.h
@@ -0,0 +1,440 @@
+/*
+ * 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"
+#include "nghttp2_rcbuf.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 */
+#define NGHTTP2_STATIC_TABLE_LENGTH 61
+
+/* Generated by genlibtokenlookup.py */
+typedef enum {
+ NGHTTP2_TOKEN__AUTHORITY = 0,
+ NGHTTP2_TOKEN__METHOD = 1,
+ NGHTTP2_TOKEN__PATH = 3,
+ NGHTTP2_TOKEN__SCHEME = 5,
+ NGHTTP2_TOKEN__STATUS = 7,
+ NGHTTP2_TOKEN_ACCEPT_CHARSET = 14,
+ NGHTTP2_TOKEN_ACCEPT_ENCODING = 15,
+ NGHTTP2_TOKEN_ACCEPT_LANGUAGE = 16,
+ NGHTTP2_TOKEN_ACCEPT_RANGES = 17,
+ NGHTTP2_TOKEN_ACCEPT = 18,
+ NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN = 19,
+ NGHTTP2_TOKEN_AGE = 20,
+ NGHTTP2_TOKEN_ALLOW = 21,
+ NGHTTP2_TOKEN_AUTHORIZATION = 22,
+ NGHTTP2_TOKEN_CACHE_CONTROL = 23,
+ NGHTTP2_TOKEN_CONTENT_DISPOSITION = 24,
+ NGHTTP2_TOKEN_CONTENT_ENCODING = 25,
+ NGHTTP2_TOKEN_CONTENT_LANGUAGE = 26,
+ NGHTTP2_TOKEN_CONTENT_LENGTH = 27,
+ NGHTTP2_TOKEN_CONTENT_LOCATION = 28,
+ NGHTTP2_TOKEN_CONTENT_RANGE = 29,
+ NGHTTP2_TOKEN_CONTENT_TYPE = 30,
+ NGHTTP2_TOKEN_COOKIE = 31,
+ NGHTTP2_TOKEN_DATE = 32,
+ NGHTTP2_TOKEN_ETAG = 33,
+ NGHTTP2_TOKEN_EXPECT = 34,
+ NGHTTP2_TOKEN_EXPIRES = 35,
+ NGHTTP2_TOKEN_FROM = 36,
+ NGHTTP2_TOKEN_HOST = 37,
+ NGHTTP2_TOKEN_IF_MATCH = 38,
+ NGHTTP2_TOKEN_IF_MODIFIED_SINCE = 39,
+ NGHTTP2_TOKEN_IF_NONE_MATCH = 40,
+ NGHTTP2_TOKEN_IF_RANGE = 41,
+ NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE = 42,
+ NGHTTP2_TOKEN_LAST_MODIFIED = 43,
+ NGHTTP2_TOKEN_LINK = 44,
+ NGHTTP2_TOKEN_LOCATION = 45,
+ NGHTTP2_TOKEN_MAX_FORWARDS = 46,
+ NGHTTP2_TOKEN_PROXY_AUTHENTICATE = 47,
+ NGHTTP2_TOKEN_PROXY_AUTHORIZATION = 48,
+ NGHTTP2_TOKEN_RANGE = 49,
+ NGHTTP2_TOKEN_REFERER = 50,
+ NGHTTP2_TOKEN_REFRESH = 51,
+ NGHTTP2_TOKEN_RETRY_AFTER = 52,
+ NGHTTP2_TOKEN_SERVER = 53,
+ NGHTTP2_TOKEN_SET_COOKIE = 54,
+ NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY = 55,
+ NGHTTP2_TOKEN_TRANSFER_ENCODING = 56,
+ NGHTTP2_TOKEN_USER_AGENT = 57,
+ NGHTTP2_TOKEN_VARY = 58,
+ NGHTTP2_TOKEN_VIA = 59,
+ NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60,
+ NGHTTP2_TOKEN_TE,
+ NGHTTP2_TOKEN_CONNECTION,
+ NGHTTP2_TOKEN_KEEP_ALIVE,
+ NGHTTP2_TOKEN_PROXY_CONNECTION,
+ NGHTTP2_TOKEN_UPGRADE,
+ NGHTTP2_TOKEN__PROTOCOL,
+ NGHTTP2_TOKEN_PRIORITY,
+} nghttp2_token;
+
+struct nghttp2_hd_entry;
+typedef struct nghttp2_hd_entry nghttp2_hd_entry;
+
+typedef struct {
+ /* The buffer containing header field name. NULL-termination is
+ guaranteed. */
+ nghttp2_rcbuf *name;
+ /* The buffer containing header field value. NULL-termination is
+ guaranteed. */
+ nghttp2_rcbuf *value;
+ /* nghttp2_token value for name. It could be -1 if we have no token
+ for that header field name. */
+ int32_t token;
+ /* Bitwise OR of one or more of nghttp2_nv_flag. */
+ uint8_t flags;
+} nghttp2_hd_nv;
+
+struct nghttp2_hd_entry {
+ /* The header field name/value pair */
+ nghttp2_hd_nv nv;
+ /* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry
+ APIs to keep backward compatibility. */
+ nghttp2_nv cnv;
+ /* The next entry which shares same bucket in hash table. */
+ nghttp2_hd_entry *next;
+ /* The sequence number. We will increment it by one whenever we
+ store nghttp2_hd_entry to dynamic header table. */
+ uint32_t seq;
+ /* The hash value for header name (nv.name). */
+ uint32_t hash;
+};
+
+/* The entry used for static header table. */
+typedef struct {
+ nghttp2_rcbuf name;
+ nghttp2_rcbuf value;
+ nghttp2_nv cnv;
+ int32_t token;
+ uint32_t hash;
+} 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_EXPECT_TABLE_SIZE,
+ NGHTTP2_HD_STATE_INFLATE_START,
+ 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 enum {
+ NGHTTP2_HD_WITH_INDEXING,
+ NGHTTP2_HD_WITHOUT_INDEXING,
+ NGHTTP2_HD_NEVER_INDEXING
+} nghttp2_hd_indexing_mode;
+
+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;
+ /* Next sequence number for nghttp2_hd_entry */
+ uint32_t next_seq;
+ /* 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;
+
+#define HD_MAP_SIZE 128
+
+typedef struct {
+ nghttp2_hd_entry *table[HD_MAP_SIZE];
+} nghttp2_hd_map;
+
+struct nghttp2_hd_deflater {
+ nghttp2_hd_context ctx;
+ nghttp2_hd_map map;
+ /* 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;
+ /* Stores current state of huffman decoding */
+ nghttp2_hd_huff_decode_context huff_decode_ctx;
+ /* header buffer */
+ nghttp2_buf namebuf, valuebuf;
+ nghttp2_rcbuf *namercbuf, *valuercbuf;
+ /* Pointer to the name/value pair which are used in the current
+ header emission. */
+ nghttp2_rcbuf *nv_name_keep, *nv_value_keep;
+ /* The number of bytes to read */
+ size_t left;
+ /* The index in indexed repr or indexed name */
+ size_t index;
+ /* 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;
+ /* Minimum header table size set by nghttp2_hd_inflate_change_table_size */
+ size_t min_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. The reference counts of nv->name
+ * and nv->value are increased by one for each.
+ */
+void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv);
+
+/*
+ * This function decreases the reference counts of nv->name and
+ * nv->value.
+ */
+void nghttp2_hd_entry_free(nghttp2_hd_entry *ent);
+
+/*
+ * 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 |max_deflate_dynamic_table_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_init2(nghttp2_hd_deflater *deflater,
+ size_t max_deflate_dynamic_table_size,
+ 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
+ * function 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);
+
+/*
+ * Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv
+ * instead of nghttp2_nv as output parameter |nv_out|. Other than
+ * that return values and semantics are the same as
+ * nghttp2_hd_inflate_hd().
+ */
+ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater,
+ nghttp2_hd_nv *nv_out, int *inflate_flags,
+ const uint8_t *in, size_t inlen, int in_final);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index,
+ nghttp2_nv *nv, int indexing_mode);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
+ int indexing_mode);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size);
+
+/* For unittesting purpose */
+nghttp2_hd_nv 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 *fin,
+ 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 written to |buf|. This function assumes that |buf| has the
+ * enough room to store the decoded byte string.
+ *
+ * The caller must set the |fin| 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_HEADER_COMP
+ * Decoding process has failed.
+ */
+ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
+ nghttp2_buf *buf, const uint8_t *src,
+ size_t srclen, int fin);
+
+/*
+ * nghttp2_hd_huff_decode_failure_state returns nonzero if |ctx|
+ * indicates that huffman decoding context is in failure state.
+ */
+int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx);
+
+#endif /* NGHTTP2_HD_H */
diff --git a/lib/nghttp2_hd_huffman.c b/lib/nghttp2_hd_huffman.c
new file mode 100644
index 0000000..ac90f49
--- /dev/null
+++ b/lib/nghttp2_hd_huffman.c
@@ -0,0 +1,144 @@
+/*
+ * 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"
+#include "nghttp2_net.h"
+
+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) {
+ const nghttp2_huff_sym *sym;
+ const uint8_t *end = src + srclen;
+ uint64_t code = 0;
+ uint32_t x;
+ size_t nbits = 0;
+ size_t avail;
+ int rv;
+
+ avail = nghttp2_bufs_cur_avail(bufs);
+
+ for (; src != end;) {
+ sym = &huff_sym_table[*src++];
+ code |= (uint64_t)sym->code << (32 - nbits);
+ nbits += sym->nbits;
+ if (nbits < 32) {
+ continue;
+ }
+ if (avail >= 4) {
+ x = htonl((uint32_t)(code >> 32));
+ memcpy(bufs->cur->buf.last, &x, 4);
+ bufs->cur->buf.last += 4;
+ avail -= 4;
+ code <<= 32;
+ nbits -= 32;
+ continue;
+ }
+
+ for (; nbits >= 8;) {
+ rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56));
+ if (rv != 0) {
+ return rv;
+ }
+ code <<= 8;
+ nbits -= 8;
+ }
+
+ avail = nghttp2_bufs_cur_avail(bufs);
+ }
+
+ for (; nbits >= 8;) {
+ rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56));
+ if (rv != 0) {
+ return rv;
+ }
+ code <<= 8;
+ nbits -= 8;
+ }
+
+ if (nbits) {
+ rv = nghttp2_bufs_addb(
+ bufs, (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1)));
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) {
+ ctx->fstate = NGHTTP2_HUFF_ACCEPTED;
+}
+
+ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
+ nghttp2_buf *buf, const uint8_t *src,
+ size_t srclen, int final) {
+ const uint8_t *end = src + srclen;
+ nghttp2_huff_decode node = {ctx->fstate, 0};
+ const nghttp2_huff_decode *t = &node;
+ uint8_t c;
+
+ /* We use the decoding algorithm described in
+ http://graphics.ics.uci.edu/pub/Prefix.pdf */
+ for (; src != end;) {
+ c = *src++;
+ t = &huff_decode_table[t->fstate & 0x1ff][c >> 4];
+ if (t->fstate & NGHTTP2_HUFF_SYM) {
+ *buf->last++ = t->sym;
+ }
+
+ t = &huff_decode_table[t->fstate & 0x1ff][c & 0xf];
+ if (t->fstate & NGHTTP2_HUFF_SYM) {
+ *buf->last++ = t->sym;
+ }
+ }
+
+ ctx->fstate = t->fstate;
+
+ if (final && !(ctx->fstate & NGHTTP2_HUFF_ACCEPTED)) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ return (ssize_t)srclen;
+}
+
+int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx) {
+ return ctx->fstate == 0x100;
+}
diff --git a/lib/nghttp2_hd_huffman.h b/lib/nghttp2_hd_huffman.h
new file mode 100644
index 0000000..2bfd531
--- /dev/null
+++ b/lib/nghttp2_hd_huffman.h
@@ -0,0 +1,72 @@
+/*
+ * 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 << 14,
+ /* This state emits symbol */
+ NGHTTP2_HUFF_SYM = 1 << 15,
+} nghttp2_huff_decode_flag;
+
+typedef struct {
+ /* fstate is the current huffman decoding state, which is actually
+ the node ID of internal huffman tree with
+ nghttp2_huff_decode_flag OR-ed. 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. The node ID 256 is
+ a special node and it is a terminal state that means decoding
+ failed. */
+ uint16_t fstate;
+ /* symbol if NGHTTP2_HUFF_SYM flag set */
+ uint8_t sym;
+} nghttp2_huff_decode;
+
+typedef nghttp2_huff_decode huff_decode_table_type[16];
+
+typedef struct {
+ /* fstate is the current huffman decoding state. */
+ uint16_t fstate;
+} 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;
+
+extern const nghttp2_huff_sym huff_sym_table[];
+extern const nghttp2_huff_decode huff_decode_table[][16];
+
+#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
index 0000000..2e2e13f
--- /dev/null
+++ b/lib/nghttp2_hd_huffman_data.c
@@ -0,0 +1,4980 @@
+/*
+ * 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, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u},
+ {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u},
+ {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u},
+ {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u},
+ {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u},
+ {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u},
+ {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u},
+ {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u},
+ {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u},
+ {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u},
+ {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u},
+ {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u},
+ {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u},
+ {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u},
+ {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u},
+ {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u},
+ {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u},
+ {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u},
+ {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u},
+ {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u},
+ {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u},
+ {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u},
+ {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u},
+ {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u},
+ {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u},
+ {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u},
+ {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u},
+ {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u},
+ {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u},
+ {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u},
+ {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u},
+ {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u},
+ {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u},
+ {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u},
+ {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u},
+ {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u},
+ {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u},
+ {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u},
+ {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u},
+ {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u},
+ {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u},
+ {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u},
+ {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u},
+ {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u},
+ {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u},
+ {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u},
+ {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u},
+ {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u},
+ {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u},
+ {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u},
+ {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u},
+ {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u},
+ {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u},
+ {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u},
+ {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u},
+ {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u},
+ {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u},
+ {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u},
+ {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u},
+ {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u},
+ {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u},
+ {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u},
+ {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u},
+ {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u},
+ {30, 0xfffffffcu}};
+
+const nghttp2_huff_decode huff_decode_table[][16] = {
+ /* 0 */
+ {
+ {0x04, 0},
+ {0x05, 0},
+ {0x07, 0},
+ {0x08, 0},
+ {0x0b, 0},
+ {0x0c, 0},
+ {0x10, 0},
+ {0x13, 0},
+ {0x19, 0},
+ {0x1c, 0},
+ {0x20, 0},
+ {0x23, 0},
+ {0x2a, 0},
+ {0x31, 0},
+ {0x39, 0},
+ {0x4040, 0},
+ },
+ /* 1 */
+ {
+ {0xc000, 48},
+ {0xc000, 49},
+ {0xc000, 50},
+ {0xc000, 97},
+ {0xc000, 99},
+ {0xc000, 101},
+ {0xc000, 105},
+ {0xc000, 111},
+ {0xc000, 115},
+ {0xc000, 116},
+ {0x0d, 0},
+ {0x0e, 0},
+ {0x11, 0},
+ {0x12, 0},
+ {0x14, 0},
+ {0x15, 0},
+ },
+ /* 2 */
+ {
+ {0x8001, 48},
+ {0xc016, 48},
+ {0x8001, 49},
+ {0xc016, 49},
+ {0x8001, 50},
+ {0xc016, 50},
+ {0x8001, 97},
+ {0xc016, 97},
+ {0x8001, 99},
+ {0xc016, 99},
+ {0x8001, 101},
+ {0xc016, 101},
+ {0x8001, 105},
+ {0xc016, 105},
+ {0x8001, 111},
+ {0xc016, 111},
+ },
+ /* 3 */
+ {
+ {0x8002, 48},
+ {0x8009, 48},
+ {0x8017, 48},
+ {0xc028, 48},
+ {0x8002, 49},
+ {0x8009, 49},
+ {0x8017, 49},
+ {0xc028, 49},
+ {0x8002, 50},
+ {0x8009, 50},
+ {0x8017, 50},
+ {0xc028, 50},
+ {0x8002, 97},
+ {0x8009, 97},
+ {0x8017, 97},
+ {0xc028, 97},
+ },
+ /* 4 */
+ {
+ {0x8003, 48},
+ {0x8006, 48},
+ {0x800a, 48},
+ {0x800f, 48},
+ {0x8018, 48},
+ {0x801f, 48},
+ {0x8029, 48},
+ {0xc038, 48},
+ {0x8003, 49},
+ {0x8006, 49},
+ {0x800a, 49},
+ {0x800f, 49},
+ {0x8018, 49},
+ {0x801f, 49},
+ {0x8029, 49},
+ {0xc038, 49},
+ },
+ /* 5 */
+ {
+ {0x8003, 50},
+ {0x8006, 50},
+ {0x800a, 50},
+ {0x800f, 50},
+ {0x8018, 50},
+ {0x801f, 50},
+ {0x8029, 50},
+ {0xc038, 50},
+ {0x8003, 97},
+ {0x8006, 97},
+ {0x800a, 97},
+ {0x800f, 97},
+ {0x8018, 97},
+ {0x801f, 97},
+ {0x8029, 97},
+ {0xc038, 97},
+ },
+ /* 6 */
+ {
+ {0x8002, 99},
+ {0x8009, 99},
+ {0x8017, 99},
+ {0xc028, 99},
+ {0x8002, 101},
+ {0x8009, 101},
+ {0x8017, 101},
+ {0xc028, 101},
+ {0x8002, 105},
+ {0x8009, 105},
+ {0x8017, 105},
+ {0xc028, 105},
+ {0x8002, 111},
+ {0x8009, 111},
+ {0x8017, 111},
+ {0xc028, 111},
+ },
+ /* 7 */
+ {
+ {0x8003, 99},
+ {0x8006, 99},
+ {0x800a, 99},
+ {0x800f, 99},
+ {0x8018, 99},
+ {0x801f, 99},
+ {0x8029, 99},
+ {0xc038, 99},
+ {0x8003, 101},
+ {0x8006, 101},
+ {0x800a, 101},
+ {0x800f, 101},
+ {0x8018, 101},
+ {0x801f, 101},
+ {0x8029, 101},
+ {0xc038, 101},
+ },
+ /* 8 */
+ {
+ {0x8003, 105},
+ {0x8006, 105},
+ {0x800a, 105},
+ {0x800f, 105},
+ {0x8018, 105},
+ {0x801f, 105},
+ {0x8029, 105},
+ {0xc038, 105},
+ {0x8003, 111},
+ {0x8006, 111},
+ {0x800a, 111},
+ {0x800f, 111},
+ {0x8018, 111},
+ {0x801f, 111},
+ {0x8029, 111},
+ {0xc038, 111},
+ },
+ /* 9 */
+ {
+ {0x8001, 115},
+ {0xc016, 115},
+ {0x8001, 116},
+ {0xc016, 116},
+ {0xc000, 32},
+ {0xc000, 37},
+ {0xc000, 45},
+ {0xc000, 46},
+ {0xc000, 47},
+ {0xc000, 51},
+ {0xc000, 52},
+ {0xc000, 53},
+ {0xc000, 54},
+ {0xc000, 55},
+ {0xc000, 56},
+ {0xc000, 57},
+ },
+ /* 10 */
+ {
+ {0x8002, 115},
+ {0x8009, 115},
+ {0x8017, 115},
+ {0xc028, 115},
+ {0x8002, 116},
+ {0x8009, 116},
+ {0x8017, 116},
+ {0xc028, 116},
+ {0x8001, 32},
+ {0xc016, 32},
+ {0x8001, 37},
+ {0xc016, 37},
+ {0x8001, 45},
+ {0xc016, 45},
+ {0x8001, 46},
+ {0xc016, 46},
+ },
+ /* 11 */
+ {
+ {0x8003, 115},
+ {0x8006, 115},
+ {0x800a, 115},
+ {0x800f, 115},
+ {0x8018, 115},
+ {0x801f, 115},
+ {0x8029, 115},
+ {0xc038, 115},
+ {0x8003, 116},
+ {0x8006, 116},
+ {0x800a, 116},
+ {0x800f, 116},
+ {0x8018, 116},
+ {0x801f, 116},
+ {0x8029, 116},
+ {0xc038, 116},
+ },
+ /* 12 */
+ {
+ {0x8002, 32},
+ {0x8009, 32},
+ {0x8017, 32},
+ {0xc028, 32},
+ {0x8002, 37},
+ {0x8009, 37},
+ {0x8017, 37},
+ {0xc028, 37},
+ {0x8002, 45},
+ {0x8009, 45},
+ {0x8017, 45},
+ {0xc028, 45},
+ {0x8002, 46},
+ {0x8009, 46},
+ {0x8017, 46},
+ {0xc028, 46},
+ },
+ /* 13 */
+ {
+ {0x8003, 32},
+ {0x8006, 32},
+ {0x800a, 32},
+ {0x800f, 32},
+ {0x8018, 32},
+ {0x801f, 32},
+ {0x8029, 32},
+ {0xc038, 32},
+ {0x8003, 37},
+ {0x8006, 37},
+ {0x800a, 37},
+ {0x800f, 37},
+ {0x8018, 37},
+ {0x801f, 37},
+ {0x8029, 37},
+ {0xc038, 37},
+ },
+ /* 14 */
+ {
+ {0x8003, 45},
+ {0x8006, 45},
+ {0x800a, 45},
+ {0x800f, 45},
+ {0x8018, 45},
+ {0x801f, 45},
+ {0x8029, 45},
+ {0xc038, 45},
+ {0x8003, 46},
+ {0x8006, 46},
+ {0x800a, 46},
+ {0x800f, 46},
+ {0x8018, 46},
+ {0x801f, 46},
+ {0x8029, 46},
+ {0xc038, 46},
+ },
+ /* 15 */
+ {
+ {0x8001, 47},
+ {0xc016, 47},
+ {0x8001, 51},
+ {0xc016, 51},
+ {0x8001, 52},
+ {0xc016, 52},
+ {0x8001, 53},
+ {0xc016, 53},
+ {0x8001, 54},
+ {0xc016, 54},
+ {0x8001, 55},
+ {0xc016, 55},
+ {0x8001, 56},
+ {0xc016, 56},
+ {0x8001, 57},
+ {0xc016, 57},
+ },
+ /* 16 */
+ {
+ {0x8002, 47},
+ {0x8009, 47},
+ {0x8017, 47},
+ {0xc028, 47},
+ {0x8002, 51},
+ {0x8009, 51},
+ {0x8017, 51},
+ {0xc028, 51},
+ {0x8002, 52},
+ {0x8009, 52},
+ {0x8017, 52},
+ {0xc028, 52},
+ {0x8002, 53},
+ {0x8009, 53},
+ {0x8017, 53},
+ {0xc028, 53},
+ },
+ /* 17 */
+ {
+ {0x8003, 47},
+ {0x8006, 47},
+ {0x800a, 47},
+ {0x800f, 47},
+ {0x8018, 47},
+ {0x801f, 47},
+ {0x8029, 47},
+ {0xc038, 47},
+ {0x8003, 51},
+ {0x8006, 51},
+ {0x800a, 51},
+ {0x800f, 51},
+ {0x8018, 51},
+ {0x801f, 51},
+ {0x8029, 51},
+ {0xc038, 51},
+ },
+ /* 18 */
+ {
+ {0x8003, 52},
+ {0x8006, 52},
+ {0x800a, 52},
+ {0x800f, 52},
+ {0x8018, 52},
+ {0x801f, 52},
+ {0x8029, 52},
+ {0xc038, 52},
+ {0x8003, 53},
+ {0x8006, 53},
+ {0x800a, 53},
+ {0x800f, 53},
+ {0x8018, 53},
+ {0x801f, 53},
+ {0x8029, 53},
+ {0xc038, 53},
+ },
+ /* 19 */
+ {
+ {0x8002, 54},
+ {0x8009, 54},
+ {0x8017, 54},
+ {0xc028, 54},
+ {0x8002, 55},
+ {0x8009, 55},
+ {0x8017, 55},
+ {0xc028, 55},
+ {0x8002, 56},
+ {0x8009, 56},
+ {0x8017, 56},
+ {0xc028, 56},
+ {0x8002, 57},
+ {0x8009, 57},
+ {0x8017, 57},
+ {0xc028, 57},
+ },
+ /* 20 */
+ {
+ {0x8003, 54},
+ {0x8006, 54},
+ {0x800a, 54},
+ {0x800f, 54},
+ {0x8018, 54},
+ {0x801f, 54},
+ {0x8029, 54},
+ {0xc038, 54},
+ {0x8003, 55},
+ {0x8006, 55},
+ {0x800a, 55},
+ {0x800f, 55},
+ {0x8018, 55},
+ {0x801f, 55},
+ {0x8029, 55},
+ {0xc038, 55},
+ },
+ /* 21 */
+ {
+ {0x8003, 56},
+ {0x8006, 56},
+ {0x800a, 56},
+ {0x800f, 56},
+ {0x8018, 56},
+ {0x801f, 56},
+ {0x8029, 56},
+ {0xc038, 56},
+ {0x8003, 57},
+ {0x8006, 57},
+ {0x800a, 57},
+ {0x800f, 57},
+ {0x8018, 57},
+ {0x801f, 57},
+ {0x8029, 57},
+ {0xc038, 57},
+ },
+ /* 22 */
+ {
+ {0x1a, 0},
+ {0x1b, 0},
+ {0x1d, 0},
+ {0x1e, 0},
+ {0x21, 0},
+ {0x22, 0},
+ {0x24, 0},
+ {0x25, 0},
+ {0x2b, 0},
+ {0x2e, 0},
+ {0x32, 0},
+ {0x35, 0},
+ {0x3a, 0},
+ {0x3d, 0},
+ {0x41, 0},
+ {0x4044, 0},
+ },
+ /* 23 */
+ {
+ {0xc000, 61},
+ {0xc000, 65},
+ {0xc000, 95},
+ {0xc000, 98},
+ {0xc000, 100},
+ {0xc000, 102},
+ {0xc000, 103},
+ {0xc000, 104},
+ {0xc000, 108},
+ {0xc000, 109},
+ {0xc000, 110},
+ {0xc000, 112},
+ {0xc000, 114},
+ {0xc000, 117},
+ {0x26, 0},
+ {0x27, 0},
+ },
+ /* 24 */
+ {
+ {0x8001, 61},
+ {0xc016, 61},
+ {0x8001, 65},
+ {0xc016, 65},
+ {0x8001, 95},
+ {0xc016, 95},
+ {0x8001, 98},
+ {0xc016, 98},
+ {0x8001, 100},
+ {0xc016, 100},
+ {0x8001, 102},
+ {0xc016, 102},
+ {0x8001, 103},
+ {0xc016, 103},
+ {0x8001, 104},
+ {0xc016, 104},
+ },
+ /* 25 */
+ {
+ {0x8002, 61},
+ {0x8009, 61},
+ {0x8017, 61},
+ {0xc028, 61},
+ {0x8002, 65},
+ {0x8009, 65},
+ {0x8017, 65},
+ {0xc028, 65},
+ {0x8002, 95},
+ {0x8009, 95},
+ {0x8017, 95},
+ {0xc028, 95},
+ {0x8002, 98},
+ {0x8009, 98},
+ {0x8017, 98},
+ {0xc028, 98},
+ },
+ /* 26 */
+ {
+ {0x8003, 61},
+ {0x8006, 61},
+ {0x800a, 61},
+ {0x800f, 61},
+ {0x8018, 61},
+ {0x801f, 61},
+ {0x8029, 61},
+ {0xc038, 61},
+ {0x8003, 65},
+ {0x8006, 65},
+ {0x800a, 65},
+ {0x800f, 65},
+ {0x8018, 65},
+ {0x801f, 65},
+ {0x8029, 65},
+ {0xc038, 65},
+ },
+ /* 27 */
+ {
+ {0x8003, 95},
+ {0x8006, 95},
+ {0x800a, 95},
+ {0x800f, 95},
+ {0x8018, 95},
+ {0x801f, 95},
+ {0x8029, 95},
+ {0xc038, 95},
+ {0x8003, 98},
+ {0x8006, 98},
+ {0x800a, 98},
+ {0x800f, 98},
+ {0x8018, 98},
+ {0x801f, 98},
+ {0x8029, 98},
+ {0xc038, 98},
+ },
+ /* 28 */
+ {
+ {0x8002, 100},
+ {0x8009, 100},
+ {0x8017, 100},
+ {0xc028, 100},
+ {0x8002, 102},
+ {0x8009, 102},
+ {0x8017, 102},
+ {0xc028, 102},
+ {0x8002, 103},
+ {0x8009, 103},
+ {0x8017, 103},
+ {0xc028, 103},
+ {0x8002, 104},
+ {0x8009, 104},
+ {0x8017, 104},
+ {0xc028, 104},
+ },
+ /* 29 */
+ {
+ {0x8003, 100},
+ {0x8006, 100},
+ {0x800a, 100},
+ {0x800f, 100},
+ {0x8018, 100},
+ {0x801f, 100},
+ {0x8029, 100},
+ {0xc038, 100},
+ {0x8003, 102},
+ {0x8006, 102},
+ {0x800a, 102},
+ {0x800f, 102},
+ {0x8018, 102},
+ {0x801f, 102},
+ {0x8029, 102},
+ {0xc038, 102},
+ },
+ /* 30 */
+ {
+ {0x8003, 103},
+ {0x8006, 103},
+ {0x800a, 103},
+ {0x800f, 103},
+ {0x8018, 103},
+ {0x801f, 103},
+ {0x8029, 103},
+ {0xc038, 103},
+ {0x8003, 104},
+ {0x8006, 104},
+ {0x800a, 104},
+ {0x800f, 104},
+ {0x8018, 104},
+ {0x801f, 104},
+ {0x8029, 104},
+ {0xc038, 104},
+ },
+ /* 31 */
+ {
+ {0x8001, 108},
+ {0xc016, 108},
+ {0x8001, 109},
+ {0xc016, 109},
+ {0x8001, 110},
+ {0xc016, 110},
+ {0x8001, 112},
+ {0xc016, 112},
+ {0x8001, 114},
+ {0xc016, 114},
+ {0x8001, 117},
+ {0xc016, 117},
+ {0xc000, 58},
+ {0xc000, 66},
+ {0xc000, 67},
+ {0xc000, 68},
+ },
+ /* 32 */
+ {
+ {0x8002, 108},
+ {0x8009, 108},
+ {0x8017, 108},
+ {0xc028, 108},
+ {0x8002, 109},
+ {0x8009, 109},
+ {0x8017, 109},
+ {0xc028, 109},
+ {0x8002, 110},
+ {0x8009, 110},
+ {0x8017, 110},
+ {0xc028, 110},
+ {0x8002, 112},
+ {0x8009, 112},
+ {0x8017, 112},
+ {0xc028, 112},
+ },
+ /* 33 */
+ {
+ {0x8003, 108},
+ {0x8006, 108},
+ {0x800a, 108},
+ {0x800f, 108},
+ {0x8018, 108},
+ {0x801f, 108},
+ {0x8029, 108},
+ {0xc038, 108},
+ {0x8003, 109},
+ {0x8006, 109},
+ {0x800a, 109},
+ {0x800f, 109},
+ {0x8018, 109},
+ {0x801f, 109},
+ {0x8029, 109},
+ {0xc038, 109},
+ },
+ /* 34 */
+ {
+ {0x8003, 110},
+ {0x8006, 110},
+ {0x800a, 110},
+ {0x800f, 110},
+ {0x8018, 110},
+ {0x801f, 110},
+ {0x8029, 110},
+ {0xc038, 110},
+ {0x8003, 112},
+ {0x8006, 112},
+ {0x800a, 112},
+ {0x800f, 112},
+ {0x8018, 112},
+ {0x801f, 112},
+ {0x8029, 112},
+ {0xc038, 112},
+ },
+ /* 35 */
+ {
+ {0x8002, 114},
+ {0x8009, 114},
+ {0x8017, 114},
+ {0xc028, 114},
+ {0x8002, 117},
+ {0x8009, 117},
+ {0x8017, 117},
+ {0xc028, 117},
+ {0x8001, 58},
+ {0xc016, 58},
+ {0x8001, 66},
+ {0xc016, 66},
+ {0x8001, 67},
+ {0xc016, 67},
+ {0x8001, 68},
+ {0xc016, 68},
+ },
+ /* 36 */
+ {
+ {0x8003, 114},
+ {0x8006, 114},
+ {0x800a, 114},
+ {0x800f, 114},
+ {0x8018, 114},
+ {0x801f, 114},
+ {0x8029, 114},
+ {0xc038, 114},
+ {0x8003, 117},
+ {0x8006, 117},
+ {0x800a, 117},
+ {0x800f, 117},
+ {0x8018, 117},
+ {0x801f, 117},
+ {0x8029, 117},
+ {0xc038, 117},
+ },
+ /* 37 */
+ {
+ {0x8002, 58},
+ {0x8009, 58},
+ {0x8017, 58},
+ {0xc028, 58},
+ {0x8002, 66},
+ {0x8009, 66},
+ {0x8017, 66},
+ {0xc028, 66},
+ {0x8002, 67},
+ {0x8009, 67},
+ {0x8017, 67},
+ {0xc028, 67},
+ {0x8002, 68},
+ {0x8009, 68},
+ {0x8017, 68},
+ {0xc028, 68},
+ },
+ /* 38 */
+ {
+ {0x8003, 58},
+ {0x8006, 58},
+ {0x800a, 58},
+ {0x800f, 58},
+ {0x8018, 58},
+ {0x801f, 58},
+ {0x8029, 58},
+ {0xc038, 58},
+ {0x8003, 66},
+ {0x8006, 66},
+ {0x800a, 66},
+ {0x800f, 66},
+ {0x8018, 66},
+ {0x801f, 66},
+ {0x8029, 66},
+ {0xc038, 66},
+ },
+ /* 39 */
+ {
+ {0x8003, 67},
+ {0x8006, 67},
+ {0x800a, 67},
+ {0x800f, 67},
+ {0x8018, 67},
+ {0x801f, 67},
+ {0x8029, 67},
+ {0xc038, 67},
+ {0x8003, 68},
+ {0x8006, 68},
+ {0x800a, 68},
+ {0x800f, 68},
+ {0x8018, 68},
+ {0x801f, 68},
+ {0x8029, 68},
+ {0xc038, 68},
+ },
+ /* 40 */
+ {
+ {0x2c, 0},
+ {0x2d, 0},
+ {0x2f, 0},
+ {0x30, 0},
+ {0x33, 0},
+ {0x34, 0},
+ {0x36, 0},
+ {0x37, 0},
+ {0x3b, 0},
+ {0x3c, 0},
+ {0x3e, 0},
+ {0x3f, 0},
+ {0x42, 0},
+ {0x43, 0},
+ {0x45, 0},
+ {0x4048, 0},
+ },
+ /* 41 */
+ {
+ {0xc000, 69},
+ {0xc000, 70},
+ {0xc000, 71},
+ {0xc000, 72},
+ {0xc000, 73},
+ {0xc000, 74},
+ {0xc000, 75},
+ {0xc000, 76},
+ {0xc000, 77},
+ {0xc000, 78},
+ {0xc000, 79},
+ {0xc000, 80},
+ {0xc000, 81},
+ {0xc000, 82},
+ {0xc000, 83},
+ {0xc000, 84},
+ },
+ /* 42 */
+ {
+ {0x8001, 69},
+ {0xc016, 69},
+ {0x8001, 70},
+ {0xc016, 70},
+ {0x8001, 71},
+ {0xc016, 71},
+ {0x8001, 72},
+ {0xc016, 72},
+ {0x8001, 73},
+ {0xc016, 73},
+ {0x8001, 74},
+ {0xc016, 74},
+ {0x8001, 75},
+ {0xc016, 75},
+ {0x8001, 76},
+ {0xc016, 76},
+ },
+ /* 43 */
+ {
+ {0x8002, 69},
+ {0x8009, 69},
+ {0x8017, 69},
+ {0xc028, 69},
+ {0x8002, 70},
+ {0x8009, 70},
+ {0x8017, 70},
+ {0xc028, 70},
+ {0x8002, 71},
+ {0x8009, 71},
+ {0x8017, 71},
+ {0xc028, 71},
+ {0x8002, 72},
+ {0x8009, 72},
+ {0x8017, 72},
+ {0xc028, 72},
+ },
+ /* 44 */
+ {
+ {0x8003, 69},
+ {0x8006, 69},
+ {0x800a, 69},
+ {0x800f, 69},
+ {0x8018, 69},
+ {0x801f, 69},
+ {0x8029, 69},
+ {0xc038, 69},
+ {0x8003, 70},
+ {0x8006, 70},
+ {0x800a, 70},
+ {0x800f, 70},
+ {0x8018, 70},
+ {0x801f, 70},
+ {0x8029, 70},
+ {0xc038, 70},
+ },
+ /* 45 */
+ {
+ {0x8003, 71},
+ {0x8006, 71},
+ {0x800a, 71},
+ {0x800f, 71},
+ {0x8018, 71},
+ {0x801f, 71},
+ {0x8029, 71},
+ {0xc038, 71},
+ {0x8003, 72},
+ {0x8006, 72},
+ {0x800a, 72},
+ {0x800f, 72},
+ {0x8018, 72},
+ {0x801f, 72},
+ {0x8029, 72},
+ {0xc038, 72},
+ },
+ /* 46 */
+ {
+ {0x8002, 73},
+ {0x8009, 73},
+ {0x8017, 73},
+ {0xc028, 73},
+ {0x8002, 74},
+ {0x8009, 74},
+ {0x8017, 74},
+ {0xc028, 74},
+ {0x8002, 75},
+ {0x8009, 75},
+ {0x8017, 75},
+ {0xc028, 75},
+ {0x8002, 76},
+ {0x8009, 76},
+ {0x8017, 76},
+ {0xc028, 76},
+ },
+ /* 47 */
+ {
+ {0x8003, 73},
+ {0x8006, 73},
+ {0x800a, 73},
+ {0x800f, 73},
+ {0x8018, 73},
+ {0x801f, 73},
+ {0x8029, 73},
+ {0xc038, 73},
+ {0x8003, 74},
+ {0x8006, 74},
+ {0x800a, 74},
+ {0x800f, 74},
+ {0x8018, 74},
+ {0x801f, 74},
+ {0x8029, 74},
+ {0xc038, 74},
+ },
+ /* 48 */
+ {
+ {0x8003, 75},
+ {0x8006, 75},
+ {0x800a, 75},
+ {0x800f, 75},
+ {0x8018, 75},
+ {0x801f, 75},
+ {0x8029, 75},
+ {0xc038, 75},
+ {0x8003, 76},
+ {0x8006, 76},
+ {0x800a, 76},
+ {0x800f, 76},
+ {0x8018, 76},
+ {0x801f, 76},
+ {0x8029, 76},
+ {0xc038, 76},
+ },
+ /* 49 */
+ {
+ {0x8001, 77},
+ {0xc016, 77},
+ {0x8001, 78},
+ {0xc016, 78},
+ {0x8001, 79},
+ {0xc016, 79},
+ {0x8001, 80},
+ {0xc016, 80},
+ {0x8001, 81},
+ {0xc016, 81},
+ {0x8001, 82},
+ {0xc016, 82},
+ {0x8001, 83},
+ {0xc016, 83},
+ {0x8001, 84},
+ {0xc016, 84},
+ },
+ /* 50 */
+ {
+ {0x8002, 77},
+ {0x8009, 77},
+ {0x8017, 77},
+ {0xc028, 77},
+ {0x8002, 78},
+ {0x8009, 78},
+ {0x8017, 78},
+ {0xc028, 78},
+ {0x8002, 79},
+ {0x8009, 79},
+ {0x8017, 79},
+ {0xc028, 79},
+ {0x8002, 80},
+ {0x8009, 80},
+ {0x8017, 80},
+ {0xc028, 80},
+ },
+ /* 51 */
+ {
+ {0x8003, 77},
+ {0x8006, 77},
+ {0x800a, 77},
+ {0x800f, 77},
+ {0x8018, 77},
+ {0x801f, 77},
+ {0x8029, 77},
+ {0xc038, 77},
+ {0x8003, 78},
+ {0x8006, 78},
+ {0x800a, 78},
+ {0x800f, 78},
+ {0x8018, 78},
+ {0x801f, 78},
+ {0x8029, 78},
+ {0xc038, 78},
+ },
+ /* 52 */
+ {
+ {0x8003, 79},
+ {0x8006, 79},
+ {0x800a, 79},
+ {0x800f, 79},
+ {0x8018, 79},
+ {0x801f, 79},
+ {0x8029, 79},
+ {0xc038, 79},
+ {0x8003, 80},
+ {0x8006, 80},
+ {0x800a, 80},
+ {0x800f, 80},
+ {0x8018, 80},
+ {0x801f, 80},
+ {0x8029, 80},
+ {0xc038, 80},
+ },
+ /* 53 */
+ {
+ {0x8002, 81},
+ {0x8009, 81},
+ {0x8017, 81},
+ {0xc028, 81},
+ {0x8002, 82},
+ {0x8009, 82},
+ {0x8017, 82},
+ {0xc028, 82},
+ {0x8002, 83},
+ {0x8009, 83},
+ {0x8017, 83},
+ {0xc028, 83},
+ {0x8002, 84},
+ {0x8009, 84},
+ {0x8017, 84},
+ {0xc028, 84},
+ },
+ /* 54 */
+ {
+ {0x8003, 81},
+ {0x8006, 81},
+ {0x800a, 81},
+ {0x800f, 81},
+ {0x8018, 81},
+ {0x801f, 81},
+ {0x8029, 81},
+ {0xc038, 81},
+ {0x8003, 82},
+ {0x8006, 82},
+ {0x800a, 82},
+ {0x800f, 82},
+ {0x8018, 82},
+ {0x801f, 82},
+ {0x8029, 82},
+ {0xc038, 82},
+ },
+ /* 55 */
+ {
+ {0x8003, 83},
+ {0x8006, 83},
+ {0x800a, 83},
+ {0x800f, 83},
+ {0x8018, 83},
+ {0x801f, 83},
+ {0x8029, 83},
+ {0xc038, 83},
+ {0x8003, 84},
+ {0x8006, 84},
+ {0x800a, 84},
+ {0x800f, 84},
+ {0x8018, 84},
+ {0x801f, 84},
+ {0x8029, 84},
+ {0xc038, 84},
+ },
+ /* 56 */
+ {
+ {0xc000, 85},
+ {0xc000, 86},
+ {0xc000, 87},
+ {0xc000, 89},
+ {0xc000, 106},
+ {0xc000, 107},
+ {0xc000, 113},
+ {0xc000, 118},
+ {0xc000, 119},
+ {0xc000, 120},
+ {0xc000, 121},
+ {0xc000, 122},
+ {0x46, 0},
+ {0x47, 0},
+ {0x49, 0},
+ {0x404a, 0},
+ },
+ /* 57 */
+ {
+ {0x8001, 85},
+ {0xc016, 85},
+ {0x8001, 86},
+ {0xc016, 86},
+ {0x8001, 87},
+ {0xc016, 87},
+ {0x8001, 89},
+ {0xc016, 89},
+ {0x8001, 106},
+ {0xc016, 106},
+ {0x8001, 107},
+ {0xc016, 107},
+ {0x8001, 113},
+ {0xc016, 113},
+ {0x8001, 118},
+ {0xc016, 118},
+ },
+ /* 58 */
+ {
+ {0x8002, 85},
+ {0x8009, 85},
+ {0x8017, 85},
+ {0xc028, 85},
+ {0x8002, 86},
+ {0x8009, 86},
+ {0x8017, 86},
+ {0xc028, 86},
+ {0x8002, 87},
+ {0x8009, 87},
+ {0x8017, 87},
+ {0xc028, 87},
+ {0x8002, 89},
+ {0x8009, 89},
+ {0x8017, 89},
+ {0xc028, 89},
+ },
+ /* 59 */
+ {
+ {0x8003, 85},
+ {0x8006, 85},
+ {0x800a, 85},
+ {0x800f, 85},
+ {0x8018, 85},
+ {0x801f, 85},
+ {0x8029, 85},
+ {0xc038, 85},
+ {0x8003, 86},
+ {0x8006, 86},
+ {0x800a, 86},
+ {0x800f, 86},
+ {0x8018, 86},
+ {0x801f, 86},
+ {0x8029, 86},
+ {0xc038, 86},
+ },
+ /* 60 */
+ {
+ {0x8003, 87},
+ {0x8006, 87},
+ {0x800a, 87},
+ {0x800f, 87},
+ {0x8018, 87},
+ {0x801f, 87},
+ {0x8029, 87},
+ {0xc038, 87},
+ {0x8003, 89},
+ {0x8006, 89},
+ {0x800a, 89},
+ {0x800f, 89},
+ {0x8018, 89},
+ {0x801f, 89},
+ {0x8029, 89},
+ {0xc038, 89},
+ },
+ /* 61 */
+ {
+ {0x8002, 106},
+ {0x8009, 106},
+ {0x8017, 106},
+ {0xc028, 106},
+ {0x8002, 107},
+ {0x8009, 107},
+ {0x8017, 107},
+ {0xc028, 107},
+ {0x8002, 113},
+ {0x8009, 113},
+ {0x8017, 113},
+ {0xc028, 113},
+ {0x8002, 118},
+ {0x8009, 118},
+ {0x8017, 118},
+ {0xc028, 118},
+ },
+ /* 62 */
+ {
+ {0x8003, 106},
+ {0x8006, 106},
+ {0x800a, 106},
+ {0x800f, 106},
+ {0x8018, 106},
+ {0x801f, 106},
+ {0x8029, 106},
+ {0xc038, 106},
+ {0x8003, 107},
+ {0x8006, 107},
+ {0x800a, 107},
+ {0x800f, 107},
+ {0x8018, 107},
+ {0x801f, 107},
+ {0x8029, 107},
+ {0xc038, 107},
+ },
+ /* 63 */
+ {
+ {0x8003, 113},
+ {0x8006, 113},
+ {0x800a, 113},
+ {0x800f, 113},
+ {0x8018, 113},
+ {0x801f, 113},
+ {0x8029, 113},
+ {0xc038, 113},
+ {0x8003, 118},
+ {0x8006, 118},
+ {0x800a, 118},
+ {0x800f, 118},
+ {0x8018, 118},
+ {0x801f, 118},
+ {0x8029, 118},
+ {0xc038, 118},
+ },
+ /* 64 */
+ {
+ {0x8001, 119},
+ {0xc016, 119},
+ {0x8001, 120},
+ {0xc016, 120},
+ {0x8001, 121},
+ {0xc016, 121},
+ {0x8001, 122},
+ {0xc016, 122},
+ {0xc000, 38},
+ {0xc000, 42},
+ {0xc000, 44},
+ {0xc000, 59},
+ {0xc000, 88},
+ {0xc000, 90},
+ {0x4b, 0},
+ {0x4e, 0},
+ },
+ /* 65 */
+ {
+ {0x8002, 119},
+ {0x8009, 119},
+ {0x8017, 119},
+ {0xc028, 119},
+ {0x8002, 120},
+ {0x8009, 120},
+ {0x8017, 120},
+ {0xc028, 120},
+ {0x8002, 121},
+ {0x8009, 121},
+ {0x8017, 121},
+ {0xc028, 121},
+ {0x8002, 122},
+ {0x8009, 122},
+ {0x8017, 122},
+ {0xc028, 122},
+ },
+ /* 66 */
+ {
+ {0x8003, 119},
+ {0x8006, 119},
+ {0x800a, 119},
+ {0x800f, 119},
+ {0x8018, 119},
+ {0x801f, 119},
+ {0x8029, 119},
+ {0xc038, 119},
+ {0x8003, 120},
+ {0x8006, 120},
+ {0x800a, 120},
+ {0x800f, 120},
+ {0x8018, 120},
+ {0x801f, 120},
+ {0x8029, 120},
+ {0xc038, 120},
+ },
+ /* 67 */
+ {
+ {0x8003, 121},
+ {0x8006, 121},
+ {0x800a, 121},
+ {0x800f, 121},
+ {0x8018, 121},
+ {0x801f, 121},
+ {0x8029, 121},
+ {0xc038, 121},
+ {0x8003, 122},
+ {0x8006, 122},
+ {0x800a, 122},
+ {0x800f, 122},
+ {0x8018, 122},
+ {0x801f, 122},
+ {0x8029, 122},
+ {0xc038, 122},
+ },
+ /* 68 */
+ {
+ {0x8001, 38},
+ {0xc016, 38},
+ {0x8001, 42},
+ {0xc016, 42},
+ {0x8001, 44},
+ {0xc016, 44},
+ {0x8001, 59},
+ {0xc016, 59},
+ {0x8001, 88},
+ {0xc016, 88},
+ {0x8001, 90},
+ {0xc016, 90},
+ {0x4c, 0},
+ {0x4d, 0},
+ {0x4f, 0},
+ {0x51, 0},
+ },
+ /* 69 */
+ {
+ {0x8002, 38},
+ {0x8009, 38},
+ {0x8017, 38},
+ {0xc028, 38},
+ {0x8002, 42},
+ {0x8009, 42},
+ {0x8017, 42},
+ {0xc028, 42},
+ {0x8002, 44},
+ {0x8009, 44},
+ {0x8017, 44},
+ {0xc028, 44},
+ {0x8002, 59},
+ {0x8009, 59},
+ {0x8017, 59},
+ {0xc028, 59},
+ },
+ /* 70 */
+ {
+ {0x8003, 38},
+ {0x8006, 38},
+ {0x800a, 38},
+ {0x800f, 38},
+ {0x8018, 38},
+ {0x801f, 38},
+ {0x8029, 38},
+ {0xc038, 38},
+ {0x8003, 42},
+ {0x8006, 42},
+ {0x800a, 42},
+ {0x800f, 42},
+ {0x8018, 42},
+ {0x801f, 42},
+ {0x8029, 42},
+ {0xc038, 42},
+ },
+ /* 71 */
+ {
+ {0x8003, 44},
+ {0x8006, 44},
+ {0x800a, 44},
+ {0x800f, 44},
+ {0x8018, 44},
+ {0x801f, 44},
+ {0x8029, 44},
+ {0xc038, 44},
+ {0x8003, 59},
+ {0x8006, 59},
+ {0x800a, 59},
+ {0x800f, 59},
+ {0x8018, 59},
+ {0x801f, 59},
+ {0x8029, 59},
+ {0xc038, 59},
+ },
+ /* 72 */
+ {
+ {0x8002, 88},
+ {0x8009, 88},
+ {0x8017, 88},
+ {0xc028, 88},
+ {0x8002, 90},
+ {0x8009, 90},
+ {0x8017, 90},
+ {0xc028, 90},
+ {0xc000, 33},
+ {0xc000, 34},
+ {0xc000, 40},
+ {0xc000, 41},
+ {0xc000, 63},
+ {0x50, 0},
+ {0x52, 0},
+ {0x54, 0},
+ },
+ /* 73 */
+ {
+ {0x8003, 88},
+ {0x8006, 88},
+ {0x800a, 88},
+ {0x800f, 88},
+ {0x8018, 88},
+ {0x801f, 88},
+ {0x8029, 88},
+ {0xc038, 88},
+ {0x8003, 90},
+ {0x8006, 90},
+ {0x800a, 90},
+ {0x800f, 90},
+ {0x8018, 90},
+ {0x801f, 90},
+ {0x8029, 90},
+ {0xc038, 90},
+ },
+ /* 74 */
+ {
+ {0x8001, 33},
+ {0xc016, 33},
+ {0x8001, 34},
+ {0xc016, 34},
+ {0x8001, 40},
+ {0xc016, 40},
+ {0x8001, 41},
+ {0xc016, 41},
+ {0x8001, 63},
+ {0xc016, 63},
+ {0xc000, 39},
+ {0xc000, 43},
+ {0xc000, 124},
+ {0x53, 0},
+ {0x55, 0},
+ {0x58, 0},
+ },
+ /* 75 */
+ {
+ {0x8002, 33},
+ {0x8009, 33},
+ {0x8017, 33},
+ {0xc028, 33},
+ {0x8002, 34},
+ {0x8009, 34},
+ {0x8017, 34},
+ {0xc028, 34},
+ {0x8002, 40},
+ {0x8009, 40},
+ {0x8017, 40},
+ {0xc028, 40},
+ {0x8002, 41},
+ {0x8009, 41},
+ {0x8017, 41},
+ {0xc028, 41},
+ },
+ /* 76 */
+ {
+ {0x8003, 33},
+ {0x8006, 33},
+ {0x800a, 33},
+ {0x800f, 33},
+ {0x8018, 33},
+ {0x801f, 33},
+ {0x8029, 33},
+ {0xc038, 33},
+ {0x8003, 34},
+ {0x8006, 34},
+ {0x800a, 34},
+ {0x800f, 34},
+ {0x8018, 34},
+ {0x801f, 34},
+ {0x8029, 34},
+ {0xc038, 34},
+ },
+ /* 77 */
+ {
+ {0x8003, 40},
+ {0x8006, 40},
+ {0x800a, 40},
+ {0x800f, 40},
+ {0x8018, 40},
+ {0x801f, 40},
+ {0x8029, 40},
+ {0xc038, 40},
+ {0x8003, 41},
+ {0x8006, 41},
+ {0x800a, 41},
+ {0x800f, 41},
+ {0x8018, 41},
+ {0x801f, 41},
+ {0x8029, 41},
+ {0xc038, 41},
+ },
+ /* 78 */
+ {
+ {0x8002, 63},
+ {0x8009, 63},
+ {0x8017, 63},
+ {0xc028, 63},
+ {0x8001, 39},
+ {0xc016, 39},
+ {0x8001, 43},
+ {0xc016, 43},
+ {0x8001, 124},
+ {0xc016, 124},
+ {0xc000, 35},
+ {0xc000, 62},
+ {0x56, 0},
+ {0x57, 0},
+ {0x59, 0},
+ {0x5a, 0},
+ },
+ /* 79 */
+ {
+ {0x8003, 63},
+ {0x8006, 63},
+ {0x800a, 63},
+ {0x800f, 63},
+ {0x8018, 63},
+ {0x801f, 63},
+ {0x8029, 63},
+ {0xc038, 63},
+ {0x8002, 39},
+ {0x8009, 39},
+ {0x8017, 39},
+ {0xc028, 39},
+ {0x8002, 43},
+ {0x8009, 43},
+ {0x8017, 43},
+ {0xc028, 43},
+ },
+ /* 80 */
+ {
+ {0x8003, 39},
+ {0x8006, 39},
+ {0x800a, 39},
+ {0x800f, 39},
+ {0x8018, 39},
+ {0x801f, 39},
+ {0x8029, 39},
+ {0xc038, 39},
+ {0x8003, 43},
+ {0x8006, 43},
+ {0x800a, 43},
+ {0x800f, 43},
+ {0x8018, 43},
+ {0x801f, 43},
+ {0x8029, 43},
+ {0xc038, 43},
+ },
+ /* 81 */
+ {
+ {0x8002, 124},
+ {0x8009, 124},
+ {0x8017, 124},
+ {0xc028, 124},
+ {0x8001, 35},
+ {0xc016, 35},
+ {0x8001, 62},
+ {0xc016, 62},
+ {0xc000, 0},
+ {0xc000, 36},
+ {0xc000, 64},
+ {0xc000, 91},
+ {0xc000, 93},
+ {0xc000, 126},
+ {0x5b, 0},
+ {0x5c, 0},
+ },
+ /* 82 */
+ {
+ {0x8003, 124},
+ {0x8006, 124},
+ {0x800a, 124},
+ {0x800f, 124},
+ {0x8018, 124},
+ {0x801f, 124},
+ {0x8029, 124},
+ {0xc038, 124},
+ {0x8002, 35},
+ {0x8009, 35},
+ {0x8017, 35},
+ {0xc028, 35},
+ {0x8002, 62},
+ {0x8009, 62},
+ {0x8017, 62},
+ {0xc028, 62},
+ },
+ /* 83 */
+ {
+ {0x8003, 35},
+ {0x8006, 35},
+ {0x800a, 35},
+ {0x800f, 35},
+ {0x8018, 35},
+ {0x801f, 35},
+ {0x8029, 35},
+ {0xc038, 35},
+ {0x8003, 62},
+ {0x8006, 62},
+ {0x800a, 62},
+ {0x800f, 62},
+ {0x8018, 62},
+ {0x801f, 62},
+ {0x8029, 62},
+ {0xc038, 62},
+ },
+ /* 84 */
+ {
+ {0x8001, 0},
+ {0xc016, 0},
+ {0x8001, 36},
+ {0xc016, 36},
+ {0x8001, 64},
+ {0xc016, 64},
+ {0x8001, 91},
+ {0xc016, 91},
+ {0x8001, 93},
+ {0xc016, 93},
+ {0x8001, 126},
+ {0xc016, 126},
+ {0xc000, 94},
+ {0xc000, 125},
+ {0x5d, 0},
+ {0x5e, 0},
+ },
+ /* 85 */
+ {
+ {0x8002, 0},
+ {0x8009, 0},
+ {0x8017, 0},
+ {0xc028, 0},
+ {0x8002, 36},
+ {0x8009, 36},
+ {0x8017, 36},
+ {0xc028, 36},
+ {0x8002, 64},
+ {0x8009, 64},
+ {0x8017, 64},
+ {0xc028, 64},
+ {0x8002, 91},
+ {0x8009, 91},
+ {0x8017, 91},
+ {0xc028, 91},
+ },
+ /* 86 */
+ {
+ {0x8003, 0},
+ {0x8006, 0},
+ {0x800a, 0},
+ {0x800f, 0},
+ {0x8018, 0},
+ {0x801f, 0},
+ {0x8029, 0},
+ {0xc038, 0},
+ {0x8003, 36},
+ {0x8006, 36},
+ {0x800a, 36},
+ {0x800f, 36},
+ {0x8018, 36},
+ {0x801f, 36},
+ {0x8029, 36},
+ {0xc038, 36},
+ },
+ /* 87 */
+ {
+ {0x8003, 64},
+ {0x8006, 64},
+ {0x800a, 64},
+ {0x800f, 64},
+ {0x8018, 64},
+ {0x801f, 64},
+ {0x8029, 64},
+ {0xc038, 64},
+ {0x8003, 91},
+ {0x8006, 91},
+ {0x800a, 91},
+ {0x800f, 91},
+ {0x8018, 91},
+ {0x801f, 91},
+ {0x8029, 91},
+ {0xc038, 91},
+ },
+ /* 88 */
+ {
+ {0x8002, 93},
+ {0x8009, 93},
+ {0x8017, 93},
+ {0xc028, 93},
+ {0x8002, 126},
+ {0x8009, 126},
+ {0x8017, 126},
+ {0xc028, 126},
+ {0x8001, 94},
+ {0xc016, 94},
+ {0x8001, 125},
+ {0xc016, 125},
+ {0xc000, 60},
+ {0xc000, 96},
+ {0xc000, 123},
+ {0x5f, 0},
+ },
+ /* 89 */
+ {
+ {0x8003, 93},
+ {0x8006, 93},
+ {0x800a, 93},
+ {0x800f, 93},
+ {0x8018, 93},
+ {0x801f, 93},
+ {0x8029, 93},
+ {0xc038, 93},
+ {0x8003, 126},
+ {0x8006, 126},
+ {0x800a, 126},
+ {0x800f, 126},
+ {0x8018, 126},
+ {0x801f, 126},
+ {0x8029, 126},
+ {0xc038, 126},
+ },
+ /* 90 */
+ {
+ {0x8002, 94},
+ {0x8009, 94},
+ {0x8017, 94},
+ {0xc028, 94},
+ {0x8002, 125},
+ {0x8009, 125},
+ {0x8017, 125},
+ {0xc028, 125},
+ {0x8001, 60},
+ {0xc016, 60},
+ {0x8001, 96},
+ {0xc016, 96},
+ {0x8001, 123},
+ {0xc016, 123},
+ {0x60, 0},
+ {0x6e, 0},
+ },
+ /* 91 */
+ {
+ {0x8003, 94},
+ {0x8006, 94},
+ {0x800a, 94},
+ {0x800f, 94},
+ {0x8018, 94},
+ {0x801f, 94},
+ {0x8029, 94},
+ {0xc038, 94},
+ {0x8003, 125},
+ {0x8006, 125},
+ {0x800a, 125},
+ {0x800f, 125},
+ {0x8018, 125},
+ {0x801f, 125},
+ {0x8029, 125},
+ {0xc038, 125},
+ },
+ /* 92 */
+ {
+ {0x8002, 60},
+ {0x8009, 60},
+ {0x8017, 60},
+ {0xc028, 60},
+ {0x8002, 96},
+ {0x8009, 96},
+ {0x8017, 96},
+ {0xc028, 96},
+ {0x8002, 123},
+ {0x8009, 123},
+ {0x8017, 123},
+ {0xc028, 123},
+ {0x61, 0},
+ {0x65, 0},
+ {0x6f, 0},
+ {0x85, 0},
+ },
+ /* 93 */
+ {
+ {0x8003, 60},
+ {0x8006, 60},
+ {0x800a, 60},
+ {0x800f, 60},
+ {0x8018, 60},
+ {0x801f, 60},
+ {0x8029, 60},
+ {0xc038, 60},
+ {0x8003, 96},
+ {0x8006, 96},
+ {0x800a, 96},
+ {0x800f, 96},
+ {0x8018, 96},
+ {0x801f, 96},
+ {0x8029, 96},
+ {0xc038, 96},
+ },
+ /* 94 */
+ {
+ {0x8003, 123},
+ {0x8006, 123},
+ {0x800a, 123},
+ {0x800f, 123},
+ {0x8018, 123},
+ {0x801f, 123},
+ {0x8029, 123},
+ {0xc038, 123},
+ {0x62, 0},
+ {0x63, 0},
+ {0x66, 0},
+ {0x69, 0},
+ {0x70, 0},
+ {0x77, 0},
+ {0x86, 0},
+ {0x99, 0},
+ },
+ /* 95 */
+ {
+ {0xc000, 92},
+ {0xc000, 195},
+ {0xc000, 208},
+ {0x64, 0},
+ {0x67, 0},
+ {0x68, 0},
+ {0x6a, 0},
+ {0x6b, 0},
+ {0x71, 0},
+ {0x74, 0},
+ {0x78, 0},
+ {0x7e, 0},
+ {0x87, 0},
+ {0x8e, 0},
+ {0x9a, 0},
+ {0xa9, 0},
+ },
+ /* 96 */
+ {
+ {0x8001, 92},
+ {0xc016, 92},
+ {0x8001, 195},
+ {0xc016, 195},
+ {0x8001, 208},
+ {0xc016, 208},
+ {0xc000, 128},
+ {0xc000, 130},
+ {0xc000, 131},
+ {0xc000, 162},
+ {0xc000, 184},
+ {0xc000, 194},
+ {0xc000, 224},
+ {0xc000, 226},
+ {0x6c, 0},
+ {0x6d, 0},
+ },
+ /* 97 */
+ {
+ {0x8002, 92},
+ {0x8009, 92},
+ {0x8017, 92},
+ {0xc028, 92},
+ {0x8002, 195},
+ {0x8009, 195},
+ {0x8017, 195},
+ {0xc028, 195},
+ {0x8002, 208},
+ {0x8009, 208},
+ {0x8017, 208},
+ {0xc028, 208},
+ {0x8001, 128},
+ {0xc016, 128},
+ {0x8001, 130},
+ {0xc016, 130},
+ },
+ /* 98 */
+ {
+ {0x8003, 92},
+ {0x8006, 92},
+ {0x800a, 92},
+ {0x800f, 92},
+ {0x8018, 92},
+ {0x801f, 92},
+ {0x8029, 92},
+ {0xc038, 92},
+ {0x8003, 195},
+ {0x8006, 195},
+ {0x800a, 195},
+ {0x800f, 195},
+ {0x8018, 195},
+ {0x801f, 195},
+ {0x8029, 195},
+ {0xc038, 195},
+ },
+ /* 99 */
+ {
+ {0x8003, 208},
+ {0x8006, 208},
+ {0x800a, 208},
+ {0x800f, 208},
+ {0x8018, 208},
+ {0x801f, 208},
+ {0x8029, 208},
+ {0xc038, 208},
+ {0x8002, 128},
+ {0x8009, 128},
+ {0x8017, 128},
+ {0xc028, 128},
+ {0x8002, 130},
+ {0x8009, 130},
+ {0x8017, 130},
+ {0xc028, 130},
+ },
+ /* 100 */
+ {
+ {0x8003, 128},
+ {0x8006, 128},
+ {0x800a, 128},
+ {0x800f, 128},
+ {0x8018, 128},
+ {0x801f, 128},
+ {0x8029, 128},
+ {0xc038, 128},
+ {0x8003, 130},
+ {0x8006, 130},
+ {0x800a, 130},
+ {0x800f, 130},
+ {0x8018, 130},
+ {0x801f, 130},
+ {0x8029, 130},
+ {0xc038, 130},
+ },
+ /* 101 */
+ {
+ {0x8001, 131},
+ {0xc016, 131},
+ {0x8001, 162},
+ {0xc016, 162},
+ {0x8001, 184},
+ {0xc016, 184},
+ {0x8001, 194},
+ {0xc016, 194},
+ {0x8001, 224},
+ {0xc016, 224},
+ {0x8001, 226},
+ {0xc016, 226},
+ {0xc000, 153},
+ {0xc000, 161},
+ {0xc000, 167},
+ {0xc000, 172},
+ },
+ /* 102 */
+ {
+ {0x8002, 131},
+ {0x8009, 131},
+ {0x8017, 131},
+ {0xc028, 131},
+ {0x8002, 162},
+ {0x8009, 162},
+ {0x8017, 162},
+ {0xc028, 162},
+ {0x8002, 184},
+ {0x8009, 184},
+ {0x8017, 184},
+ {0xc028, 184},
+ {0x8002, 194},
+ {0x8009, 194},
+ {0x8017, 194},
+ {0xc028, 194},
+ },
+ /* 103 */
+ {
+ {0x8003, 131},
+ {0x8006, 131},
+ {0x800a, 131},
+ {0x800f, 131},
+ {0x8018, 131},
+ {0x801f, 131},
+ {0x8029, 131},
+ {0xc038, 131},
+ {0x8003, 162},
+ {0x8006, 162},
+ {0x800a, 162},
+ {0x800f, 162},
+ {0x8018, 162},
+ {0x801f, 162},
+ {0x8029, 162},
+ {0xc038, 162},
+ },
+ /* 104 */
+ {
+ {0x8003, 184},
+ {0x8006, 184},
+ {0x800a, 184},
+ {0x800f, 184},
+ {0x8018, 184},
+ {0x801f, 184},
+ {0x8029, 184},
+ {0xc038, 184},
+ {0x8003, 194},
+ {0x8006, 194},
+ {0x800a, 194},
+ {0x800f, 194},
+ {0x8018, 194},
+ {0x801f, 194},
+ {0x8029, 194},
+ {0xc038, 194},
+ },
+ /* 105 */
+ {
+ {0x8002, 224},
+ {0x8009, 224},
+ {0x8017, 224},
+ {0xc028, 224},
+ {0x8002, 226},
+ {0x8009, 226},
+ {0x8017, 226},
+ {0xc028, 226},
+ {0x8001, 153},
+ {0xc016, 153},
+ {0x8001, 161},
+ {0xc016, 161},
+ {0x8001, 167},
+ {0xc016, 167},
+ {0x8001, 172},
+ {0xc016, 172},
+ },
+ /* 106 */
+ {
+ {0x8003, 224},
+ {0x8006, 224},
+ {0x800a, 224},
+ {0x800f, 224},
+ {0x8018, 224},
+ {0x801f, 224},
+ {0x8029, 224},
+ {0xc038, 224},
+ {0x8003, 226},
+ {0x8006, 226},
+ {0x800a, 226},
+ {0x800f, 226},
+ {0x8018, 226},
+ {0x801f, 226},
+ {0x8029, 226},
+ {0xc038, 226},
+ },
+ /* 107 */
+ {
+ {0x8002, 153},
+ {0x8009, 153},
+ {0x8017, 153},
+ {0xc028, 153},
+ {0x8002, 161},
+ {0x8009, 161},
+ {0x8017, 161},
+ {0xc028, 161},
+ {0x8002, 167},
+ {0x8009, 167},
+ {0x8017, 167},
+ {0xc028, 167},
+ {0x8002, 172},
+ {0x8009, 172},
+ {0x8017, 172},
+ {0xc028, 172},
+ },
+ /* 108 */
+ {
+ {0x8003, 153},
+ {0x8006, 153},
+ {0x800a, 153},
+ {0x800f, 153},
+ {0x8018, 153},
+ {0x801f, 153},
+ {0x8029, 153},
+ {0xc038, 153},
+ {0x8003, 161},
+ {0x8006, 161},
+ {0x800a, 161},
+ {0x800f, 161},
+ {0x8018, 161},
+ {0x801f, 161},
+ {0x8029, 161},
+ {0xc038, 161},
+ },
+ /* 109 */
+ {
+ {0x8003, 167},
+ {0x8006, 167},
+ {0x800a, 167},
+ {0x800f, 167},
+ {0x8018, 167},
+ {0x801f, 167},
+ {0x8029, 167},
+ {0xc038, 167},
+ {0x8003, 172},
+ {0x8006, 172},
+ {0x800a, 172},
+ {0x800f, 172},
+ {0x8018, 172},
+ {0x801f, 172},
+ {0x8029, 172},
+ {0xc038, 172},
+ },
+ /* 110 */
+ {
+ {0x72, 0},
+ {0x73, 0},
+ {0x75, 0},
+ {0x76, 0},
+ {0x79, 0},
+ {0x7b, 0},
+ {0x7f, 0},
+ {0x82, 0},
+ {0x88, 0},
+ {0x8b, 0},
+ {0x8f, 0},
+ {0x92, 0},
+ {0x9b, 0},
+ {0xa2, 0},
+ {0xaa, 0},
+ {0xb4, 0},
+ },
+ /* 111 */
+ {
+ {0xc000, 176},
+ {0xc000, 177},
+ {0xc000, 179},
+ {0xc000, 209},
+ {0xc000, 216},
+ {0xc000, 217},
+ {0xc000, 227},
+ {0xc000, 229},
+ {0xc000, 230},
+ {0x7a, 0},
+ {0x7c, 0},
+ {0x7d, 0},
+ {0x80, 0},
+ {0x81, 0},
+ {0x83, 0},
+ {0x84, 0},
+ },
+ /* 112 */
+ {
+ {0x8001, 176},
+ {0xc016, 176},
+ {0x8001, 177},
+ {0xc016, 177},
+ {0x8001, 179},
+ {0xc016, 179},
+ {0x8001, 209},
+ {0xc016, 209},
+ {0x8001, 216},
+ {0xc016, 216},
+ {0x8001, 217},
+ {0xc016, 217},
+ {0x8001, 227},
+ {0xc016, 227},
+ {0x8001, 229},
+ {0xc016, 229},
+ },
+ /* 113 */
+ {
+ {0x8002, 176},
+ {0x8009, 176},
+ {0x8017, 176},
+ {0xc028, 176},
+ {0x8002, 177},
+ {0x8009, 177},
+ {0x8017, 177},
+ {0xc028, 177},
+ {0x8002, 179},
+ {0x8009, 179},
+ {0x8017, 179},
+ {0xc028, 179},
+ {0x8002, 209},
+ {0x8009, 209},
+ {0x8017, 209},
+ {0xc028, 209},
+ },
+ /* 114 */
+ {
+ {0x8003, 176},
+ {0x8006, 176},
+ {0x800a, 176},
+ {0x800f, 176},
+ {0x8018, 176},
+ {0x801f, 176},
+ {0x8029, 176},
+ {0xc038, 176},
+ {0x8003, 177},
+ {0x8006, 177},
+ {0x800a, 177},
+ {0x800f, 177},
+ {0x8018, 177},
+ {0x801f, 177},
+ {0x8029, 177},
+ {0xc038, 177},
+ },
+ /* 115 */
+ {
+ {0x8003, 179},
+ {0x8006, 179},
+ {0x800a, 179},
+ {0x800f, 179},
+ {0x8018, 179},
+ {0x801f, 179},
+ {0x8029, 179},
+ {0xc038, 179},
+ {0x8003, 209},
+ {0x8006, 209},
+ {0x800a, 209},
+ {0x800f, 209},
+ {0x8018, 209},
+ {0x801f, 209},
+ {0x8029, 209},
+ {0xc038, 209},
+ },
+ /* 116 */
+ {
+ {0x8002, 216},
+ {0x8009, 216},
+ {0x8017, 216},
+ {0xc028, 216},
+ {0x8002, 217},
+ {0x8009, 217},
+ {0x8017, 217},
+ {0xc028, 217},
+ {0x8002, 227},
+ {0x8009, 227},
+ {0x8017, 227},
+ {0xc028, 227},
+ {0x8002, 229},
+ {0x8009, 229},
+ {0x8017, 229},
+ {0xc028, 229},
+ },
+ /* 117 */
+ {
+ {0x8003, 216},
+ {0x8006, 216},
+ {0x800a, 216},
+ {0x800f, 216},
+ {0x8018, 216},
+ {0x801f, 216},
+ {0x8029, 216},
+ {0xc038, 216},
+ {0x8003, 217},
+ {0x8006, 217},
+ {0x800a, 217},
+ {0x800f, 217},
+ {0x8018, 217},
+ {0x801f, 217},
+ {0x8029, 217},
+ {0xc038, 217},
+ },
+ /* 118 */
+ {
+ {0x8003, 227},
+ {0x8006, 227},
+ {0x800a, 227},
+ {0x800f, 227},
+ {0x8018, 227},
+ {0x801f, 227},
+ {0x8029, 227},
+ {0xc038, 227},
+ {0x8003, 229},
+ {0x8006, 229},
+ {0x800a, 229},
+ {0x800f, 229},
+ {0x8018, 229},
+ {0x801f, 229},
+ {0x8029, 229},
+ {0xc038, 229},
+ },
+ /* 119 */
+ {
+ {0x8001, 230},
+ {0xc016, 230},
+ {0xc000, 129},
+ {0xc000, 132},
+ {0xc000, 133},
+ {0xc000, 134},
+ {0xc000, 136},
+ {0xc000, 146},
+ {0xc000, 154},
+ {0xc000, 156},
+ {0xc000, 160},
+ {0xc000, 163},
+ {0xc000, 164},
+ {0xc000, 169},
+ {0xc000, 170},
+ {0xc000, 173},
+ },
+ /* 120 */
+ {
+ {0x8002, 230},
+ {0x8009, 230},
+ {0x8017, 230},
+ {0xc028, 230},
+ {0x8001, 129},
+ {0xc016, 129},
+ {0x8001, 132},
+ {0xc016, 132},
+ {0x8001, 133},
+ {0xc016, 133},
+ {0x8001, 134},
+ {0xc016, 134},
+ {0x8001, 136},
+ {0xc016, 136},
+ {0x8001, 146},
+ {0xc016, 146},
+ },
+ /* 121 */
+ {
+ {0x8003, 230},
+ {0x8006, 230},
+ {0x800a, 230},
+ {0x800f, 230},
+ {0x8018, 230},
+ {0x801f, 230},
+ {0x8029, 230},
+ {0xc038, 230},
+ {0x8002, 129},
+ {0x8009, 129},
+ {0x8017, 129},
+ {0xc028, 129},
+ {0x8002, 132},
+ {0x8009, 132},
+ {0x8017, 132},
+ {0xc028, 132},
+ },
+ /* 122 */
+ {
+ {0x8003, 129},
+ {0x8006, 129},
+ {0x800a, 129},
+ {0x800f, 129},
+ {0x8018, 129},
+ {0x801f, 129},
+ {0x8029, 129},
+ {0xc038, 129},
+ {0x8003, 132},
+ {0x8006, 132},
+ {0x800a, 132},
+ {0x800f, 132},
+ {0x8018, 132},
+ {0x801f, 132},
+ {0x8029, 132},
+ {0xc038, 132},
+ },
+ /* 123 */
+ {
+ {0x8002, 133},
+ {0x8009, 133},
+ {0x8017, 133},
+ {0xc028, 133},
+ {0x8002, 134},
+ {0x8009, 134},
+ {0x8017, 134},
+ {0xc028, 134},
+ {0x8002, 136},
+ {0x8009, 136},
+ {0x8017, 136},
+ {0xc028, 136},
+ {0x8002, 146},
+ {0x8009, 146},
+ {0x8017, 146},
+ {0xc028, 146},
+ },
+ /* 124 */
+ {
+ {0x8003, 133},
+ {0x8006, 133},
+ {0x800a, 133},
+ {0x800f, 133},
+ {0x8018, 133},
+ {0x801f, 133},
+ {0x8029, 133},
+ {0xc038, 133},
+ {0x8003, 134},
+ {0x8006, 134},
+ {0x800a, 134},
+ {0x800f, 134},
+ {0x8018, 134},
+ {0x801f, 134},
+ {0x8029, 134},
+ {0xc038, 134},
+ },
+ /* 125 */
+ {
+ {0x8003, 136},
+ {0x8006, 136},
+ {0x800a, 136},
+ {0x800f, 136},
+ {0x8018, 136},
+ {0x801f, 136},
+ {0x8029, 136},
+ {0xc038, 136},
+ {0x8003, 146},
+ {0x8006, 146},
+ {0x800a, 146},
+ {0x800f, 146},
+ {0x8018, 146},
+ {0x801f, 146},
+ {0x8029, 146},
+ {0xc038, 146},
+ },
+ /* 126 */
+ {
+ {0x8001, 154},
+ {0xc016, 154},
+ {0x8001, 156},
+ {0xc016, 156},
+ {0x8001, 160},
+ {0xc016, 160},
+ {0x8001, 163},
+ {0xc016, 163},
+ {0x8001, 164},
+ {0xc016, 164},
+ {0x8001, 169},
+ {0xc016, 169},
+ {0x8001, 170},
+ {0xc016, 170},
+ {0x8001, 173},
+ {0xc016, 173},
+ },
+ /* 127 */
+ {
+ {0x8002, 154},
+ {0x8009, 154},
+ {0x8017, 154},
+ {0xc028, 154},
+ {0x8002, 156},
+ {0x8009, 156},
+ {0x8017, 156},
+ {0xc028, 156},
+ {0x8002, 160},
+ {0x8009, 160},
+ {0x8017, 160},
+ {0xc028, 160},
+ {0x8002, 163},
+ {0x8009, 163},
+ {0x8017, 163},
+ {0xc028, 163},
+ },
+ /* 128 */
+ {
+ {0x8003, 154},
+ {0x8006, 154},
+ {0x800a, 154},
+ {0x800f, 154},
+ {0x8018, 154},
+ {0x801f, 154},
+ {0x8029, 154},
+ {0xc038, 154},
+ {0x8003, 156},
+ {0x8006, 156},
+ {0x800a, 156},
+ {0x800f, 156},
+ {0x8018, 156},
+ {0x801f, 156},
+ {0x8029, 156},
+ {0xc038, 156},
+ },
+ /* 129 */
+ {
+ {0x8003, 160},
+ {0x8006, 160},
+ {0x800a, 160},
+ {0x800f, 160},
+ {0x8018, 160},
+ {0x801f, 160},
+ {0x8029, 160},
+ {0xc038, 160},
+ {0x8003, 163},
+ {0x8006, 163},
+ {0x800a, 163},
+ {0x800f, 163},
+ {0x8018, 163},
+ {0x801f, 163},
+ {0x8029, 163},
+ {0xc038, 163},
+ },
+ /* 130 */
+ {
+ {0x8002, 164},
+ {0x8009, 164},
+ {0x8017, 164},
+ {0xc028, 164},
+ {0x8002, 169},
+ {0x8009, 169},
+ {0x8017, 169},
+ {0xc028, 169},
+ {0x8002, 170},
+ {0x8009, 170},
+ {0x8017, 170},
+ {0xc028, 170},
+ {0x8002, 173},
+ {0x8009, 173},
+ {0x8017, 173},
+ {0xc028, 173},
+ },
+ /* 131 */
+ {
+ {0x8003, 164},
+ {0x8006, 164},
+ {0x800a, 164},
+ {0x800f, 164},
+ {0x8018, 164},
+ {0x801f, 164},
+ {0x8029, 164},
+ {0xc038, 164},
+ {0x8003, 169},
+ {0x8006, 169},
+ {0x800a, 169},
+ {0x800f, 169},
+ {0x8018, 169},
+ {0x801f, 169},
+ {0x8029, 169},
+ {0xc038, 169},
+ },
+ /* 132 */
+ {
+ {0x8003, 170},
+ {0x8006, 170},
+ {0x800a, 170},
+ {0x800f, 170},
+ {0x8018, 170},
+ {0x801f, 170},
+ {0x8029, 170},
+ {0xc038, 170},
+ {0x8003, 173},
+ {0x8006, 173},
+ {0x800a, 173},
+ {0x800f, 173},
+ {0x8018, 173},
+ {0x801f, 173},
+ {0x8029, 173},
+ {0xc038, 173},
+ },
+ /* 133 */
+ {
+ {0x89, 0},
+ {0x8a, 0},
+ {0x8c, 0},
+ {0x8d, 0},
+ {0x90, 0},
+ {0x91, 0},
+ {0x93, 0},
+ {0x96, 0},
+ {0x9c, 0},
+ {0x9f, 0},
+ {0xa3, 0},
+ {0xa6, 0},
+ {0xab, 0},
+ {0xae, 0},
+ {0xb5, 0},
+ {0xbe, 0},
+ },
+ /* 134 */
+ {
+ {0xc000, 178},
+ {0xc000, 181},
+ {0xc000, 185},
+ {0xc000, 186},
+ {0xc000, 187},
+ {0xc000, 189},
+ {0xc000, 190},
+ {0xc000, 196},
+ {0xc000, 198},
+ {0xc000, 228},
+ {0xc000, 232},
+ {0xc000, 233},
+ {0x94, 0},
+ {0x95, 0},
+ {0x97, 0},
+ {0x98, 0},
+ },
+ /* 135 */
+ {
+ {0x8001, 178},
+ {0xc016, 178},
+ {0x8001, 181},
+ {0xc016, 181},
+ {0x8001, 185},
+ {0xc016, 185},
+ {0x8001, 186},
+ {0xc016, 186},
+ {0x8001, 187},
+ {0xc016, 187},
+ {0x8001, 189},
+ {0xc016, 189},
+ {0x8001, 190},
+ {0xc016, 190},
+ {0x8001, 196},
+ {0xc016, 196},
+ },
+ /* 136 */
+ {
+ {0x8002, 178},
+ {0x8009, 178},
+ {0x8017, 178},
+ {0xc028, 178},
+ {0x8002, 181},
+ {0x8009, 181},
+ {0x8017, 181},
+ {0xc028, 181},
+ {0x8002, 185},
+ {0x8009, 185},
+ {0x8017, 185},
+ {0xc028, 185},
+ {0x8002, 186},
+ {0x8009, 186},
+ {0x8017, 186},
+ {0xc028, 186},
+ },
+ /* 137 */
+ {
+ {0x8003, 178},
+ {0x8006, 178},
+ {0x800a, 178},
+ {0x800f, 178},
+ {0x8018, 178},
+ {0x801f, 178},
+ {0x8029, 178},
+ {0xc038, 178},
+ {0x8003, 181},
+ {0x8006, 181},
+ {0x800a, 181},
+ {0x800f, 181},
+ {0x8018, 181},
+ {0x801f, 181},
+ {0x8029, 181},
+ {0xc038, 181},
+ },
+ /* 138 */
+ {
+ {0x8003, 185},
+ {0x8006, 185},
+ {0x800a, 185},
+ {0x800f, 185},
+ {0x8018, 185},
+ {0x801f, 185},
+ {0x8029, 185},
+ {0xc038, 185},
+ {0x8003, 186},
+ {0x8006, 186},
+ {0x800a, 186},
+ {0x800f, 186},
+ {0x8018, 186},
+ {0x801f, 186},
+ {0x8029, 186},
+ {0xc038, 186},
+ },
+ /* 139 */
+ {
+ {0x8002, 187},
+ {0x8009, 187},
+ {0x8017, 187},
+ {0xc028, 187},
+ {0x8002, 189},
+ {0x8009, 189},
+ {0x8017, 189},
+ {0xc028, 189},
+ {0x8002, 190},
+ {0x8009, 190},
+ {0x8017, 190},
+ {0xc028, 190},
+ {0x8002, 196},
+ {0x8009, 196},
+ {0x8017, 196},
+ {0xc028, 196},
+ },
+ /* 140 */
+ {
+ {0x8003, 187},
+ {0x8006, 187},
+ {0x800a, 187},
+ {0x800f, 187},
+ {0x8018, 187},
+ {0x801f, 187},
+ {0x8029, 187},
+ {0xc038, 187},
+ {0x8003, 189},
+ {0x8006, 189},
+ {0x800a, 189},
+ {0x800f, 189},
+ {0x8018, 189},
+ {0x801f, 189},
+ {0x8029, 189},
+ {0xc038, 189},
+ },
+ /* 141 */
+ {
+ {0x8003, 190},
+ {0x8006, 190},
+ {0x800a, 190},
+ {0x800f, 190},
+ {0x8018, 190},
+ {0x801f, 190},
+ {0x8029, 190},
+ {0xc038, 190},
+ {0x8003, 196},
+ {0x8006, 196},
+ {0x800a, 196},
+ {0x800f, 196},
+ {0x8018, 196},
+ {0x801f, 196},
+ {0x8029, 196},
+ {0xc038, 196},
+ },
+ /* 142 */
+ {
+ {0x8001, 198},
+ {0xc016, 198},
+ {0x8001, 228},
+ {0xc016, 228},
+ {0x8001, 232},
+ {0xc016, 232},
+ {0x8001, 233},
+ {0xc016, 233},
+ {0xc000, 1},
+ {0xc000, 135},
+ {0xc000, 137},
+ {0xc000, 138},
+ {0xc000, 139},
+ {0xc000, 140},
+ {0xc000, 141},
+ {0xc000, 143},
+ },
+ /* 143 */
+ {
+ {0x8002, 198},
+ {0x8009, 198},
+ {0x8017, 198},
+ {0xc028, 198},
+ {0x8002, 228},
+ {0x8009, 228},
+ {0x8017, 228},
+ {0xc028, 228},
+ {0x8002, 232},
+ {0x8009, 232},
+ {0x8017, 232},
+ {0xc028, 232},
+ {0x8002, 233},
+ {0x8009, 233},
+ {0x8017, 233},
+ {0xc028, 233},
+ },
+ /* 144 */
+ {
+ {0x8003, 198},
+ {0x8006, 198},
+ {0x800a, 198},
+ {0x800f, 198},
+ {0x8018, 198},
+ {0x801f, 198},
+ {0x8029, 198},
+ {0xc038, 198},
+ {0x8003, 228},
+ {0x8006, 228},
+ {0x800a, 228},
+ {0x800f, 228},
+ {0x8018, 228},
+ {0x801f, 228},
+ {0x8029, 228},
+ {0xc038, 228},
+ },
+ /* 145 */
+ {
+ {0x8003, 232},
+ {0x8006, 232},
+ {0x800a, 232},
+ {0x800f, 232},
+ {0x8018, 232},
+ {0x801f, 232},
+ {0x8029, 232},
+ {0xc038, 232},
+ {0x8003, 233},
+ {0x8006, 233},
+ {0x800a, 233},
+ {0x800f, 233},
+ {0x8018, 233},
+ {0x801f, 233},
+ {0x8029, 233},
+ {0xc038, 233},
+ },
+ /* 146 */
+ {
+ {0x8001, 1},
+ {0xc016, 1},
+ {0x8001, 135},
+ {0xc016, 135},
+ {0x8001, 137},
+ {0xc016, 137},
+ {0x8001, 138},
+ {0xc016, 138},
+ {0x8001, 139},
+ {0xc016, 139},
+ {0x8001, 140},
+ {0xc016, 140},
+ {0x8001, 141},
+ {0xc016, 141},
+ {0x8001, 143},
+ {0xc016, 143},
+ },
+ /* 147 */
+ {
+ {0x8002, 1},
+ {0x8009, 1},
+ {0x8017, 1},
+ {0xc028, 1},
+ {0x8002, 135},
+ {0x8009, 135},
+ {0x8017, 135},
+ {0xc028, 135},
+ {0x8002, 137},
+ {0x8009, 137},
+ {0x8017, 137},
+ {0xc028, 137},
+ {0x8002, 138},
+ {0x8009, 138},
+ {0x8017, 138},
+ {0xc028, 138},
+ },
+ /* 148 */
+ {
+ {0x8003, 1},
+ {0x8006, 1},
+ {0x800a, 1},
+ {0x800f, 1},
+ {0x8018, 1},
+ {0x801f, 1},
+ {0x8029, 1},
+ {0xc038, 1},
+ {0x8003, 135},
+ {0x8006, 135},
+ {0x800a, 135},
+ {0x800f, 135},
+ {0x8018, 135},
+ {0x801f, 135},
+ {0x8029, 135},
+ {0xc038, 135},
+ },
+ /* 149 */
+ {
+ {0x8003, 137},
+ {0x8006, 137},
+ {0x800a, 137},
+ {0x800f, 137},
+ {0x8018, 137},
+ {0x801f, 137},
+ {0x8029, 137},
+ {0xc038, 137},
+ {0x8003, 138},
+ {0x8006, 138},
+ {0x800a, 138},
+ {0x800f, 138},
+ {0x8018, 138},
+ {0x801f, 138},
+ {0x8029, 138},
+ {0xc038, 138},
+ },
+ /* 150 */
+ {
+ {0x8002, 139},
+ {0x8009, 139},
+ {0x8017, 139},
+ {0xc028, 139},
+ {0x8002, 140},
+ {0x8009, 140},
+ {0x8017, 140},
+ {0xc028, 140},
+ {0x8002, 141},
+ {0x8009, 141},
+ {0x8017, 141},
+ {0xc028, 141},
+ {0x8002, 143},
+ {0x8009, 143},
+ {0x8017, 143},
+ {0xc028, 143},
+ },
+ /* 151 */
+ {
+ {0x8003, 139},
+ {0x8006, 139},
+ {0x800a, 139},
+ {0x800f, 139},
+ {0x8018, 139},
+ {0x801f, 139},
+ {0x8029, 139},
+ {0xc038, 139},
+ {0x8003, 140},
+ {0x8006, 140},
+ {0x800a, 140},
+ {0x800f, 140},
+ {0x8018, 140},
+ {0x801f, 140},
+ {0x8029, 140},
+ {0xc038, 140},
+ },
+ /* 152 */
+ {
+ {0x8003, 141},
+ {0x8006, 141},
+ {0x800a, 141},
+ {0x800f, 141},
+ {0x8018, 141},
+ {0x801f, 141},
+ {0x8029, 141},
+ {0xc038, 141},
+ {0x8003, 143},
+ {0x8006, 143},
+ {0x800a, 143},
+ {0x800f, 143},
+ {0x8018, 143},
+ {0x801f, 143},
+ {0x8029, 143},
+ {0xc038, 143},
+ },
+ /* 153 */
+ {
+ {0x9d, 0},
+ {0x9e, 0},
+ {0xa0, 0},
+ {0xa1, 0},
+ {0xa4, 0},
+ {0xa5, 0},
+ {0xa7, 0},
+ {0xa8, 0},
+ {0xac, 0},
+ {0xad, 0},
+ {0xaf, 0},
+ {0xb1, 0},
+ {0xb6, 0},
+ {0xb9, 0},
+ {0xbf, 0},
+ {0xcf, 0},
+ },
+ /* 154 */
+ {
+ {0xc000, 147},
+ {0xc000, 149},
+ {0xc000, 150},
+ {0xc000, 151},
+ {0xc000, 152},
+ {0xc000, 155},
+ {0xc000, 157},
+ {0xc000, 158},
+ {0xc000, 165},
+ {0xc000, 166},
+ {0xc000, 168},
+ {0xc000, 174},
+ {0xc000, 175},
+ {0xc000, 180},
+ {0xc000, 182},
+ {0xc000, 183},
+ },
+ /* 155 */
+ {
+ {0x8001, 147},
+ {0xc016, 147},
+ {0x8001, 149},
+ {0xc016, 149},
+ {0x8001, 150},
+ {0xc016, 150},
+ {0x8001, 151},
+ {0xc016, 151},
+ {0x8001, 152},
+ {0xc016, 152},
+ {0x8001, 155},
+ {0xc016, 155},
+ {0x8001, 157},
+ {0xc016, 157},
+ {0x8001, 158},
+ {0xc016, 158},
+ },
+ /* 156 */
+ {
+ {0x8002, 147},
+ {0x8009, 147},
+ {0x8017, 147},
+ {0xc028, 147},
+ {0x8002, 149},
+ {0x8009, 149},
+ {0x8017, 149},
+ {0xc028, 149},
+ {0x8002, 150},
+ {0x8009, 150},
+ {0x8017, 150},
+ {0xc028, 150},
+ {0x8002, 151},
+ {0x8009, 151},
+ {0x8017, 151},
+ {0xc028, 151},
+ },
+ /* 157 */
+ {
+ {0x8003, 147},
+ {0x8006, 147},
+ {0x800a, 147},
+ {0x800f, 147},
+ {0x8018, 147},
+ {0x801f, 147},
+ {0x8029, 147},
+ {0xc038, 147},
+ {0x8003, 149},
+ {0x8006, 149},
+ {0x800a, 149},
+ {0x800f, 149},
+ {0x8018, 149},
+ {0x801f, 149},
+ {0x8029, 149},
+ {0xc038, 149},
+ },
+ /* 158 */
+ {
+ {0x8003, 150},
+ {0x8006, 150},
+ {0x800a, 150},
+ {0x800f, 150},
+ {0x8018, 150},
+ {0x801f, 150},
+ {0x8029, 150},
+ {0xc038, 150},
+ {0x8003, 151},
+ {0x8006, 151},
+ {0x800a, 151},
+ {0x800f, 151},
+ {0x8018, 151},
+ {0x801f, 151},
+ {0x8029, 151},
+ {0xc038, 151},
+ },
+ /* 159 */
+ {
+ {0x8002, 152},
+ {0x8009, 152},
+ {0x8017, 152},
+ {0xc028, 152},
+ {0x8002, 155},
+ {0x8009, 155},
+ {0x8017, 155},
+ {0xc028, 155},
+ {0x8002, 157},
+ {0x8009, 157},
+ {0x8017, 157},
+ {0xc028, 157},
+ {0x8002, 158},
+ {0x8009, 158},
+ {0x8017, 158},
+ {0xc028, 158},
+ },
+ /* 160 */
+ {
+ {0x8003, 152},
+ {0x8006, 152},
+ {0x800a, 152},
+ {0x800f, 152},
+ {0x8018, 152},
+ {0x801f, 152},
+ {0x8029, 152},
+ {0xc038, 152},
+ {0x8003, 155},
+ {0x8006, 155},
+ {0x800a, 155},
+ {0x800f, 155},
+ {0x8018, 155},
+ {0x801f, 155},
+ {0x8029, 155},
+ {0xc038, 155},
+ },
+ /* 161 */
+ {
+ {0x8003, 157},
+ {0x8006, 157},
+ {0x800a, 157},
+ {0x800f, 157},
+ {0x8018, 157},
+ {0x801f, 157},
+ {0x8029, 157},
+ {0xc038, 157},
+ {0x8003, 158},
+ {0x8006, 158},
+ {0x800a, 158},
+ {0x800f, 158},
+ {0x8018, 158},
+ {0x801f, 158},
+ {0x8029, 158},
+ {0xc038, 158},
+ },
+ /* 162 */
+ {
+ {0x8001, 165},
+ {0xc016, 165},
+ {0x8001, 166},
+ {0xc016, 166},
+ {0x8001, 168},
+ {0xc016, 168},
+ {0x8001, 174},
+ {0xc016, 174},
+ {0x8001, 175},
+ {0xc016, 175},
+ {0x8001, 180},
+ {0xc016, 180},
+ {0x8001, 182},
+ {0xc016, 182},
+ {0x8001, 183},
+ {0xc016, 183},
+ },
+ /* 163 */
+ {
+ {0x8002, 165},
+ {0x8009, 165},
+ {0x8017, 165},
+ {0xc028, 165},
+ {0x8002, 166},
+ {0x8009, 166},
+ {0x8017, 166},
+ {0xc028, 166},
+ {0x8002, 168},
+ {0x8009, 168},
+ {0x8017, 168},
+ {0xc028, 168},
+ {0x8002, 174},
+ {0x8009, 174},
+ {0x8017, 174},
+ {0xc028, 174},
+ },
+ /* 164 */
+ {
+ {0x8003, 165},
+ {0x8006, 165},
+ {0x800a, 165},
+ {0x800f, 165},
+ {0x8018, 165},
+ {0x801f, 165},
+ {0x8029, 165},
+ {0xc038, 165},
+ {0x8003, 166},
+ {0x8006, 166},
+ {0x800a, 166},
+ {0x800f, 166},
+ {0x8018, 166},
+ {0x801f, 166},
+ {0x8029, 166},
+ {0xc038, 166},
+ },
+ /* 165 */
+ {
+ {0x8003, 168},
+ {0x8006, 168},
+ {0x800a, 168},
+ {0x800f, 168},
+ {0x8018, 168},
+ {0x801f, 168},
+ {0x8029, 168},
+ {0xc038, 168},
+ {0x8003, 174},
+ {0x8006, 174},
+ {0x800a, 174},
+ {0x800f, 174},
+ {0x8018, 174},
+ {0x801f, 174},
+ {0x8029, 174},
+ {0xc038, 174},
+ },
+ /* 166 */
+ {
+ {0x8002, 175},
+ {0x8009, 175},
+ {0x8017, 175},
+ {0xc028, 175},
+ {0x8002, 180},
+ {0x8009, 180},
+ {0x8017, 180},
+ {0xc028, 180},
+ {0x8002, 182},
+ {0x8009, 182},
+ {0x8017, 182},
+ {0xc028, 182},
+ {0x8002, 183},
+ {0x8009, 183},
+ {0x8017, 183},
+ {0xc028, 183},
+ },
+ /* 167 */
+ {
+ {0x8003, 175},
+ {0x8006, 175},
+ {0x800a, 175},
+ {0x800f, 175},
+ {0x8018, 175},
+ {0x801f, 175},
+ {0x8029, 175},
+ {0xc038, 175},
+ {0x8003, 180},
+ {0x8006, 180},
+ {0x800a, 180},
+ {0x800f, 180},
+ {0x8018, 180},
+ {0x801f, 180},
+ {0x8029, 180},
+ {0xc038, 180},
+ },
+ /* 168 */
+ {
+ {0x8003, 182},
+ {0x8006, 182},
+ {0x800a, 182},
+ {0x800f, 182},
+ {0x8018, 182},
+ {0x801f, 182},
+ {0x8029, 182},
+ {0xc038, 182},
+ {0x8003, 183},
+ {0x8006, 183},
+ {0x800a, 183},
+ {0x800f, 183},
+ {0x8018, 183},
+ {0x801f, 183},
+ {0x8029, 183},
+ {0xc038, 183},
+ },
+ /* 169 */
+ {
+ {0xc000, 188},
+ {0xc000, 191},
+ {0xc000, 197},
+ {0xc000, 231},
+ {0xc000, 239},
+ {0xb0, 0},
+ {0xb2, 0},
+ {0xb3, 0},
+ {0xb7, 0},
+ {0xb8, 0},
+ {0xba, 0},
+ {0xbb, 0},
+ {0xc0, 0},
+ {0xc7, 0},
+ {0xd0, 0},
+ {0xdf, 0},
+ },
+ /* 170 */
+ {
+ {0x8001, 188},
+ {0xc016, 188},
+ {0x8001, 191},
+ {0xc016, 191},
+ {0x8001, 197},
+ {0xc016, 197},
+ {0x8001, 231},
+ {0xc016, 231},
+ {0x8001, 239},
+ {0xc016, 239},
+ {0xc000, 9},
+ {0xc000, 142},
+ {0xc000, 144},
+ {0xc000, 145},
+ {0xc000, 148},
+ {0xc000, 159},
+ },
+ /* 171 */
+ {
+ {0x8002, 188},
+ {0x8009, 188},
+ {0x8017, 188},
+ {0xc028, 188},
+ {0x8002, 191},
+ {0x8009, 191},
+ {0x8017, 191},
+ {0xc028, 191},
+ {0x8002, 197},
+ {0x8009, 197},
+ {0x8017, 197},
+ {0xc028, 197},
+ {0x8002, 231},
+ {0x8009, 231},
+ {0x8017, 231},
+ {0xc028, 231},
+ },
+ /* 172 */
+ {
+ {0x8003, 188},
+ {0x8006, 188},
+ {0x800a, 188},
+ {0x800f, 188},
+ {0x8018, 188},
+ {0x801f, 188},
+ {0x8029, 188},
+ {0xc038, 188},
+ {0x8003, 191},
+ {0x8006, 191},
+ {0x800a, 191},
+ {0x800f, 191},
+ {0x8018, 191},
+ {0x801f, 191},
+ {0x8029, 191},
+ {0xc038, 191},
+ },
+ /* 173 */
+ {
+ {0x8003, 197},
+ {0x8006, 197},
+ {0x800a, 197},
+ {0x800f, 197},
+ {0x8018, 197},
+ {0x801f, 197},
+ {0x8029, 197},
+ {0xc038, 197},
+ {0x8003, 231},
+ {0x8006, 231},
+ {0x800a, 231},
+ {0x800f, 231},
+ {0x8018, 231},
+ {0x801f, 231},
+ {0x8029, 231},
+ {0xc038, 231},
+ },
+ /* 174 */
+ {
+ {0x8002, 239},
+ {0x8009, 239},
+ {0x8017, 239},
+ {0xc028, 239},
+ {0x8001, 9},
+ {0xc016, 9},
+ {0x8001, 142},
+ {0xc016, 142},
+ {0x8001, 144},
+ {0xc016, 144},
+ {0x8001, 145},
+ {0xc016, 145},
+ {0x8001, 148},
+ {0xc016, 148},
+ {0x8001, 159},
+ {0xc016, 159},
+ },
+ /* 175 */
+ {
+ {0x8003, 239},
+ {0x8006, 239},
+ {0x800a, 239},
+ {0x800f, 239},
+ {0x8018, 239},
+ {0x801f, 239},
+ {0x8029, 239},
+ {0xc038, 239},
+ {0x8002, 9},
+ {0x8009, 9},
+ {0x8017, 9},
+ {0xc028, 9},
+ {0x8002, 142},
+ {0x8009, 142},
+ {0x8017, 142},
+ {0xc028, 142},
+ },
+ /* 176 */
+ {
+ {0x8003, 9},
+ {0x8006, 9},
+ {0x800a, 9},
+ {0x800f, 9},
+ {0x8018, 9},
+ {0x801f, 9},
+ {0x8029, 9},
+ {0xc038, 9},
+ {0x8003, 142},
+ {0x8006, 142},
+ {0x800a, 142},
+ {0x800f, 142},
+ {0x8018, 142},
+ {0x801f, 142},
+ {0x8029, 142},
+ {0xc038, 142},
+ },
+ /* 177 */
+ {
+ {0x8002, 144},
+ {0x8009, 144},
+ {0x8017, 144},
+ {0xc028, 144},
+ {0x8002, 145},
+ {0x8009, 145},
+ {0x8017, 145},
+ {0xc028, 145},
+ {0x8002, 148},
+ {0x8009, 148},
+ {0x8017, 148},
+ {0xc028, 148},
+ {0x8002, 159},
+ {0x8009, 159},
+ {0x8017, 159},
+ {0xc028, 159},
+ },
+ /* 178 */
+ {
+ {0x8003, 144},
+ {0x8006, 144},
+ {0x800a, 144},
+ {0x800f, 144},
+ {0x8018, 144},
+ {0x801f, 144},
+ {0x8029, 144},
+ {0xc038, 144},
+ {0x8003, 145},
+ {0x8006, 145},
+ {0x800a, 145},
+ {0x800f, 145},
+ {0x8018, 145},
+ {0x801f, 145},
+ {0x8029, 145},
+ {0xc038, 145},
+ },
+ /* 179 */
+ {
+ {0x8003, 148},
+ {0x8006, 148},
+ {0x800a, 148},
+ {0x800f, 148},
+ {0x8018, 148},
+ {0x801f, 148},
+ {0x8029, 148},
+ {0xc038, 148},
+ {0x8003, 159},
+ {0x8006, 159},
+ {0x800a, 159},
+ {0x800f, 159},
+ {0x8018, 159},
+ {0x801f, 159},
+ {0x8029, 159},
+ {0xc038, 159},
+ },
+ /* 180 */
+ {
+ {0xc000, 171},
+ {0xc000, 206},
+ {0xc000, 215},
+ {0xc000, 225},
+ {0xc000, 236},
+ {0xc000, 237},
+ {0xbc, 0},
+ {0xbd, 0},
+ {0xc1, 0},
+ {0xc4, 0},
+ {0xc8, 0},
+ {0xcb, 0},
+ {0xd1, 0},
+ {0xd8, 0},
+ {0xe0, 0},
+ {0xee, 0},
+ },
+ /* 181 */
+ {
+ {0x8001, 171},
+ {0xc016, 171},
+ {0x8001, 206},
+ {0xc016, 206},
+ {0x8001, 215},
+ {0xc016, 215},
+ {0x8001, 225},
+ {0xc016, 225},
+ {0x8001, 236},
+ {0xc016, 236},
+ {0x8001, 237},
+ {0xc016, 237},
+ {0xc000, 199},
+ {0xc000, 207},
+ {0xc000, 234},
+ {0xc000, 235},
+ },
+ /* 182 */
+ {
+ {0x8002, 171},
+ {0x8009, 171},
+ {0x8017, 171},
+ {0xc028, 171},
+ {0x8002, 206},
+ {0x8009, 206},
+ {0x8017, 206},
+ {0xc028, 206},
+ {0x8002, 215},
+ {0x8009, 215},
+ {0x8017, 215},
+ {0xc028, 215},
+ {0x8002, 225},
+ {0x8009, 225},
+ {0x8017, 225},
+ {0xc028, 225},
+ },
+ /* 183 */
+ {
+ {0x8003, 171},
+ {0x8006, 171},
+ {0x800a, 171},
+ {0x800f, 171},
+ {0x8018, 171},
+ {0x801f, 171},
+ {0x8029, 171},
+ {0xc038, 171},
+ {0x8003, 206},
+ {0x8006, 206},
+ {0x800a, 206},
+ {0x800f, 206},
+ {0x8018, 206},
+ {0x801f, 206},
+ {0x8029, 206},
+ {0xc038, 206},
+ },
+ /* 184 */
+ {
+ {0x8003, 215},
+ {0x8006, 215},
+ {0x800a, 215},
+ {0x800f, 215},
+ {0x8018, 215},
+ {0x801f, 215},
+ {0x8029, 215},
+ {0xc038, 215},
+ {0x8003, 225},
+ {0x8006, 225},
+ {0x800a, 225},
+ {0x800f, 225},
+ {0x8018, 225},
+ {0x801f, 225},
+ {0x8029, 225},
+ {0xc038, 225},
+ },
+ /* 185 */
+ {
+ {0x8002, 236},
+ {0x8009, 236},
+ {0x8017, 236},
+ {0xc028, 236},
+ {0x8002, 237},
+ {0x8009, 237},
+ {0x8017, 237},
+ {0xc028, 237},
+ {0x8001, 199},
+ {0xc016, 199},
+ {0x8001, 207},
+ {0xc016, 207},
+ {0x8001, 234},
+ {0xc016, 234},
+ {0x8001, 235},
+ {0xc016, 235},
+ },
+ /* 186 */
+ {
+ {0x8003, 236},
+ {0x8006, 236},
+ {0x800a, 236},
+ {0x800f, 236},
+ {0x8018, 236},
+ {0x801f, 236},
+ {0x8029, 236},
+ {0xc038, 236},
+ {0x8003, 237},
+ {0x8006, 237},
+ {0x800a, 237},
+ {0x800f, 237},
+ {0x8018, 237},
+ {0x801f, 237},
+ {0x8029, 237},
+ {0xc038, 237},
+ },
+ /* 187 */
+ {
+ {0x8002, 199},
+ {0x8009, 199},
+ {0x8017, 199},
+ {0xc028, 199},
+ {0x8002, 207},
+ {0x8009, 207},
+ {0x8017, 207},
+ {0xc028, 207},
+ {0x8002, 234},
+ {0x8009, 234},
+ {0x8017, 234},
+ {0xc028, 234},
+ {0x8002, 235},
+ {0x8009, 235},
+ {0x8017, 235},
+ {0xc028, 235},
+ },
+ /* 188 */
+ {
+ {0x8003, 199},
+ {0x8006, 199},
+ {0x800a, 199},
+ {0x800f, 199},
+ {0x8018, 199},
+ {0x801f, 199},
+ {0x8029, 199},
+ {0xc038, 199},
+ {0x8003, 207},
+ {0x8006, 207},
+ {0x800a, 207},
+ {0x800f, 207},
+ {0x8018, 207},
+ {0x801f, 207},
+ {0x8029, 207},
+ {0xc038, 207},
+ },
+ /* 189 */
+ {
+ {0x8003, 234},
+ {0x8006, 234},
+ {0x800a, 234},
+ {0x800f, 234},
+ {0x8018, 234},
+ {0x801f, 234},
+ {0x8029, 234},
+ {0xc038, 234},
+ {0x8003, 235},
+ {0x8006, 235},
+ {0x800a, 235},
+ {0x800f, 235},
+ {0x8018, 235},
+ {0x801f, 235},
+ {0x8029, 235},
+ {0xc038, 235},
+ },
+ /* 190 */
+ {
+ {0xc2, 0},
+ {0xc3, 0},
+ {0xc5, 0},
+ {0xc6, 0},
+ {0xc9, 0},
+ {0xca, 0},
+ {0xcc, 0},
+ {0xcd, 0},
+ {0xd2, 0},
+ {0xd5, 0},
+ {0xd9, 0},
+ {0xdc, 0},
+ {0xe1, 0},
+ {0xe7, 0},
+ {0xef, 0},
+ {0xf6, 0},
+ },
+ /* 191 */
+ {
+ {0xc000, 192},
+ {0xc000, 193},
+ {0xc000, 200},
+ {0xc000, 201},
+ {0xc000, 202},
+ {0xc000, 205},
+ {0xc000, 210},
+ {0xc000, 213},
+ {0xc000, 218},
+ {0xc000, 219},
+ {0xc000, 238},
+ {0xc000, 240},
+ {0xc000, 242},
+ {0xc000, 243},
+ {0xc000, 255},
+ {0xce, 0},
+ },
+ /* 192 */
+ {
+ {0x8001, 192},
+ {0xc016, 192},
+ {0x8001, 193},
+ {0xc016, 193},
+ {0x8001, 200},
+ {0xc016, 200},
+ {0x8001, 201},
+ {0xc016, 201},
+ {0x8001, 202},
+ {0xc016, 202},
+ {0x8001, 205},
+ {0xc016, 205},
+ {0x8001, 210},
+ {0xc016, 210},
+ {0x8001, 213},
+ {0xc016, 213},
+ },
+ /* 193 */
+ {
+ {0x8002, 192},
+ {0x8009, 192},
+ {0x8017, 192},
+ {0xc028, 192},
+ {0x8002, 193},
+ {0x8009, 193},
+ {0x8017, 193},
+ {0xc028, 193},
+ {0x8002, 200},
+ {0x8009, 200},
+ {0x8017, 200},
+ {0xc028, 200},
+ {0x8002, 201},
+ {0x8009, 201},
+ {0x8017, 201},
+ {0xc028, 201},
+ },
+ /* 194 */
+ {
+ {0x8003, 192},
+ {0x8006, 192},
+ {0x800a, 192},
+ {0x800f, 192},
+ {0x8018, 192},
+ {0x801f, 192},
+ {0x8029, 192},
+ {0xc038, 192},
+ {0x8003, 193},
+ {0x8006, 193},
+ {0x800a, 193},
+ {0x800f, 193},
+ {0x8018, 193},
+ {0x801f, 193},
+ {0x8029, 193},
+ {0xc038, 193},
+ },
+ /* 195 */
+ {
+ {0x8003, 200},
+ {0x8006, 200},
+ {0x800a, 200},
+ {0x800f, 200},
+ {0x8018, 200},
+ {0x801f, 200},
+ {0x8029, 200},
+ {0xc038, 200},
+ {0x8003, 201},
+ {0x8006, 201},
+ {0x800a, 201},
+ {0x800f, 201},
+ {0x8018, 201},
+ {0x801f, 201},
+ {0x8029, 201},
+ {0xc038, 201},
+ },
+ /* 196 */
+ {
+ {0x8002, 202},
+ {0x8009, 202},
+ {0x8017, 202},
+ {0xc028, 202},
+ {0x8002, 205},
+ {0x8009, 205},
+ {0x8017, 205},
+ {0xc028, 205},
+ {0x8002, 210},
+ {0x8009, 210},
+ {0x8017, 210},
+ {0xc028, 210},
+ {0x8002, 213},
+ {0x8009, 213},
+ {0x8017, 213},
+ {0xc028, 213},
+ },
+ /* 197 */
+ {
+ {0x8003, 202},
+ {0x8006, 202},
+ {0x800a, 202},
+ {0x800f, 202},
+ {0x8018, 202},
+ {0x801f, 202},
+ {0x8029, 202},
+ {0xc038, 202},
+ {0x8003, 205},
+ {0x8006, 205},
+ {0x800a, 205},
+ {0x800f, 205},
+ {0x8018, 205},
+ {0x801f, 205},
+ {0x8029, 205},
+ {0xc038, 205},
+ },
+ /* 198 */
+ {
+ {0x8003, 210},
+ {0x8006, 210},
+ {0x800a, 210},
+ {0x800f, 210},
+ {0x8018, 210},
+ {0x801f, 210},
+ {0x8029, 210},
+ {0xc038, 210},
+ {0x8003, 213},
+ {0x8006, 213},
+ {0x800a, 213},
+ {0x800f, 213},
+ {0x8018, 213},
+ {0x801f, 213},
+ {0x8029, 213},
+ {0xc038, 213},
+ },
+ /* 199 */
+ {
+ {0x8001, 218},
+ {0xc016, 218},
+ {0x8001, 219},
+ {0xc016, 219},
+ {0x8001, 238},
+ {0xc016, 238},
+ {0x8001, 240},
+ {0xc016, 240},
+ {0x8001, 242},
+ {0xc016, 242},
+ {0x8001, 243},
+ {0xc016, 243},
+ {0x8001, 255},
+ {0xc016, 255},
+ {0xc000, 203},
+ {0xc000, 204},
+ },
+ /* 200 */
+ {
+ {0x8002, 218},
+ {0x8009, 218},
+ {0x8017, 218},
+ {0xc028, 218},
+ {0x8002, 219},
+ {0x8009, 219},
+ {0x8017, 219},
+ {0xc028, 219},
+ {0x8002, 238},
+ {0x8009, 238},
+ {0x8017, 238},
+ {0xc028, 238},
+ {0x8002, 240},
+ {0x8009, 240},
+ {0x8017, 240},
+ {0xc028, 240},
+ },
+ /* 201 */
+ {
+ {0x8003, 218},
+ {0x8006, 218},
+ {0x800a, 218},
+ {0x800f, 218},
+ {0x8018, 218},
+ {0x801f, 218},
+ {0x8029, 218},
+ {0xc038, 218},
+ {0x8003, 219},
+ {0x8006, 219},
+ {0x800a, 219},
+ {0x800f, 219},
+ {0x8018, 219},
+ {0x801f, 219},
+ {0x8029, 219},
+ {0xc038, 219},
+ },
+ /* 202 */
+ {
+ {0x8003, 238},
+ {0x8006, 238},
+ {0x800a, 238},
+ {0x800f, 238},
+ {0x8018, 238},
+ {0x801f, 238},
+ {0x8029, 238},
+ {0xc038, 238},
+ {0x8003, 240},
+ {0x8006, 240},
+ {0x800a, 240},
+ {0x800f, 240},
+ {0x8018, 240},
+ {0x801f, 240},
+ {0x8029, 240},
+ {0xc038, 240},
+ },
+ /* 203 */
+ {
+ {0x8002, 242},
+ {0x8009, 242},
+ {0x8017, 242},
+ {0xc028, 242},
+ {0x8002, 243},
+ {0x8009, 243},
+ {0x8017, 243},
+ {0xc028, 243},
+ {0x8002, 255},
+ {0x8009, 255},
+ {0x8017, 255},
+ {0xc028, 255},
+ {0x8001, 203},
+ {0xc016, 203},
+ {0x8001, 204},
+ {0xc016, 204},
+ },
+ /* 204 */
+ {
+ {0x8003, 242},
+ {0x8006, 242},
+ {0x800a, 242},
+ {0x800f, 242},
+ {0x8018, 242},
+ {0x801f, 242},
+ {0x8029, 242},
+ {0xc038, 242},
+ {0x8003, 243},
+ {0x8006, 243},
+ {0x800a, 243},
+ {0x800f, 243},
+ {0x8018, 243},
+ {0x801f, 243},
+ {0x8029, 243},
+ {0xc038, 243},
+ },
+ /* 205 */
+ {
+ {0x8003, 255},
+ {0x8006, 255},
+ {0x800a, 255},
+ {0x800f, 255},
+ {0x8018, 255},
+ {0x801f, 255},
+ {0x8029, 255},
+ {0xc038, 255},
+ {0x8002, 203},
+ {0x8009, 203},
+ {0x8017, 203},
+ {0xc028, 203},
+ {0x8002, 204},
+ {0x8009, 204},
+ {0x8017, 204},
+ {0xc028, 204},
+ },
+ /* 206 */
+ {
+ {0x8003, 203},
+ {0x8006, 203},
+ {0x800a, 203},
+ {0x800f, 203},
+ {0x8018, 203},
+ {0x801f, 203},
+ {0x8029, 203},
+ {0xc038, 203},
+ {0x8003, 204},
+ {0x8006, 204},
+ {0x800a, 204},
+ {0x800f, 204},
+ {0x8018, 204},
+ {0x801f, 204},
+ {0x8029, 204},
+ {0xc038, 204},
+ },
+ /* 207 */
+ {
+ {0xd3, 0},
+ {0xd4, 0},
+ {0xd6, 0},
+ {0xd7, 0},
+ {0xda, 0},
+ {0xdb, 0},
+ {0xdd, 0},
+ {0xde, 0},
+ {0xe2, 0},
+ {0xe4, 0},
+ {0xe8, 0},
+ {0xeb, 0},
+ {0xf0, 0},
+ {0xf3, 0},
+ {0xf7, 0},
+ {0xfa, 0},
+ },
+ /* 208 */
+ {
+ {0xc000, 211},
+ {0xc000, 212},
+ {0xc000, 214},
+ {0xc000, 221},
+ {0xc000, 222},
+ {0xc000, 223},
+ {0xc000, 241},
+ {0xc000, 244},
+ {0xc000, 245},
+ {0xc000, 246},
+ {0xc000, 247},
+ {0xc000, 248},
+ {0xc000, 250},
+ {0xc000, 251},
+ {0xc000, 252},
+ {0xc000, 253},
+ },
+ /* 209 */
+ {
+ {0x8001, 211},
+ {0xc016, 211},
+ {0x8001, 212},
+ {0xc016, 212},
+ {0x8001, 214},
+ {0xc016, 214},
+ {0x8001, 221},
+ {0xc016, 221},
+ {0x8001, 222},
+ {0xc016, 222},
+ {0x8001, 223},
+ {0xc016, 223},
+ {0x8001, 241},
+ {0xc016, 241},
+ {0x8001, 244},
+ {0xc016, 244},
+ },
+ /* 210 */
+ {
+ {0x8002, 211},
+ {0x8009, 211},
+ {0x8017, 211},
+ {0xc028, 211},
+ {0x8002, 212},
+ {0x8009, 212},
+ {0x8017, 212},
+ {0xc028, 212},
+ {0x8002, 214},
+ {0x8009, 214},
+ {0x8017, 214},
+ {0xc028, 214},
+ {0x8002, 221},
+ {0x8009, 221},
+ {0x8017, 221},
+ {0xc028, 221},
+ },
+ /* 211 */
+ {
+ {0x8003, 211},
+ {0x8006, 211},
+ {0x800a, 211},
+ {0x800f, 211},
+ {0x8018, 211},
+ {0x801f, 211},
+ {0x8029, 211},
+ {0xc038, 211},
+ {0x8003, 212},
+ {0x8006, 212},
+ {0x800a, 212},
+ {0x800f, 212},
+ {0x8018, 212},
+ {0x801f, 212},
+ {0x8029, 212},
+ {0xc038, 212},
+ },
+ /* 212 */
+ {
+ {0x8003, 214},
+ {0x8006, 214},
+ {0x800a, 214},
+ {0x800f, 214},
+ {0x8018, 214},
+ {0x801f, 214},
+ {0x8029, 214},
+ {0xc038, 214},
+ {0x8003, 221},
+ {0x8006, 221},
+ {0x800a, 221},
+ {0x800f, 221},
+ {0x8018, 221},
+ {0x801f, 221},
+ {0x8029, 221},
+ {0xc038, 221},
+ },
+ /* 213 */
+ {
+ {0x8002, 222},
+ {0x8009, 222},
+ {0x8017, 222},
+ {0xc028, 222},
+ {0x8002, 223},
+ {0x8009, 223},
+ {0x8017, 223},
+ {0xc028, 223},
+ {0x8002, 241},
+ {0x8009, 241},
+ {0x8017, 241},
+ {0xc028, 241},
+ {0x8002, 244},
+ {0x8009, 244},
+ {0x8017, 244},
+ {0xc028, 244},
+ },
+ /* 214 */
+ {
+ {0x8003, 222},
+ {0x8006, 222},
+ {0x800a, 222},
+ {0x800f, 222},
+ {0x8018, 222},
+ {0x801f, 222},
+ {0x8029, 222},
+ {0xc038, 222},
+ {0x8003, 223},
+ {0x8006, 223},
+ {0x800a, 223},
+ {0x800f, 223},
+ {0x8018, 223},
+ {0x801f, 223},
+ {0x8029, 223},
+ {0xc038, 223},
+ },
+ /* 215 */
+ {
+ {0x8003, 241},
+ {0x8006, 241},
+ {0x800a, 241},
+ {0x800f, 241},
+ {0x8018, 241},
+ {0x801f, 241},
+ {0x8029, 241},
+ {0xc038, 241},
+ {0x8003, 244},
+ {0x8006, 244},
+ {0x800a, 244},
+ {0x800f, 244},
+ {0x8018, 244},
+ {0x801f, 244},
+ {0x8029, 244},
+ {0xc038, 244},
+ },
+ /* 216 */
+ {
+ {0x8001, 245},
+ {0xc016, 245},
+ {0x8001, 246},
+ {0xc016, 246},
+ {0x8001, 247},
+ {0xc016, 247},
+ {0x8001, 248},
+ {0xc016, 248},
+ {0x8001, 250},
+ {0xc016, 250},
+ {0x8001, 251},
+ {0xc016, 251},
+ {0x8001, 252},
+ {0xc016, 252},
+ {0x8001, 253},
+ {0xc016, 253},
+ },
+ /* 217 */
+ {
+ {0x8002, 245},
+ {0x8009, 245},
+ {0x8017, 245},
+ {0xc028, 245},
+ {0x8002, 246},
+ {0x8009, 246},
+ {0x8017, 246},
+ {0xc028, 246},
+ {0x8002, 247},
+ {0x8009, 247},
+ {0x8017, 247},
+ {0xc028, 247},
+ {0x8002, 248},
+ {0x8009, 248},
+ {0x8017, 248},
+ {0xc028, 248},
+ },
+ /* 218 */
+ {
+ {0x8003, 245},
+ {0x8006, 245},
+ {0x800a, 245},
+ {0x800f, 245},
+ {0x8018, 245},
+ {0x801f, 245},
+ {0x8029, 245},
+ {0xc038, 245},
+ {0x8003, 246},
+ {0x8006, 246},
+ {0x800a, 246},
+ {0x800f, 246},
+ {0x8018, 246},
+ {0x801f, 246},
+ {0x8029, 246},
+ {0xc038, 246},
+ },
+ /* 219 */
+ {
+ {0x8003, 247},
+ {0x8006, 247},
+ {0x800a, 247},
+ {0x800f, 247},
+ {0x8018, 247},
+ {0x801f, 247},
+ {0x8029, 247},
+ {0xc038, 247},
+ {0x8003, 248},
+ {0x8006, 248},
+ {0x800a, 248},
+ {0x800f, 248},
+ {0x8018, 248},
+ {0x801f, 248},
+ {0x8029, 248},
+ {0xc038, 248},
+ },
+ /* 220 */
+ {
+ {0x8002, 250},
+ {0x8009, 250},
+ {0x8017, 250},
+ {0xc028, 250},
+ {0x8002, 251},
+ {0x8009, 251},
+ {0x8017, 251},
+ {0xc028, 251},
+ {0x8002, 252},
+ {0x8009, 252},
+ {0x8017, 252},
+ {0xc028, 252},
+ {0x8002, 253},
+ {0x8009, 253},
+ {0x8017, 253},
+ {0xc028, 253},
+ },
+ /* 221 */
+ {
+ {0x8003, 250},
+ {0x8006, 250},
+ {0x800a, 250},
+ {0x800f, 250},
+ {0x8018, 250},
+ {0x801f, 250},
+ {0x8029, 250},
+ {0xc038, 250},
+ {0x8003, 251},
+ {0x8006, 251},
+ {0x800a, 251},
+ {0x800f, 251},
+ {0x8018, 251},
+ {0x801f, 251},
+ {0x8029, 251},
+ {0xc038, 251},
+ },
+ /* 222 */
+ {
+ {0x8003, 252},
+ {0x8006, 252},
+ {0x800a, 252},
+ {0x800f, 252},
+ {0x8018, 252},
+ {0x801f, 252},
+ {0x8029, 252},
+ {0xc038, 252},
+ {0x8003, 253},
+ {0x8006, 253},
+ {0x800a, 253},
+ {0x800f, 253},
+ {0x8018, 253},
+ {0x801f, 253},
+ {0x8029, 253},
+ {0xc038, 253},
+ },
+ /* 223 */
+ {
+ {0xc000, 254},
+ {0xe3, 0},
+ {0xe5, 0},
+ {0xe6, 0},
+ {0xe9, 0},
+ {0xea, 0},
+ {0xec, 0},
+ {0xed, 0},
+ {0xf1, 0},
+ {0xf2, 0},
+ {0xf4, 0},
+ {0xf5, 0},
+ {0xf8, 0},
+ {0xf9, 0},
+ {0xfb, 0},
+ {0xfc, 0},
+ },
+ /* 224 */
+ {
+ {0x8001, 254},
+ {0xc016, 254},
+ {0xc000, 2},
+ {0xc000, 3},
+ {0xc000, 4},
+ {0xc000, 5},
+ {0xc000, 6},
+ {0xc000, 7},
+ {0xc000, 8},
+ {0xc000, 11},
+ {0xc000, 12},
+ {0xc000, 14},
+ {0xc000, 15},
+ {0xc000, 16},
+ {0xc000, 17},
+ {0xc000, 18},
+ },
+ /* 225 */
+ {
+ {0x8002, 254},
+ {0x8009, 254},
+ {0x8017, 254},
+ {0xc028, 254},
+ {0x8001, 2},
+ {0xc016, 2},
+ {0x8001, 3},
+ {0xc016, 3},
+ {0x8001, 4},
+ {0xc016, 4},
+ {0x8001, 5},
+ {0xc016, 5},
+ {0x8001, 6},
+ {0xc016, 6},
+ {0x8001, 7},
+ {0xc016, 7},
+ },
+ /* 226 */
+ {
+ {0x8003, 254},
+ {0x8006, 254},
+ {0x800a, 254},
+ {0x800f, 254},
+ {0x8018, 254},
+ {0x801f, 254},
+ {0x8029, 254},
+ {0xc038, 254},
+ {0x8002, 2},
+ {0x8009, 2},
+ {0x8017, 2},
+ {0xc028, 2},
+ {0x8002, 3},
+ {0x8009, 3},
+ {0x8017, 3},
+ {0xc028, 3},
+ },
+ /* 227 */
+ {
+ {0x8003, 2},
+ {0x8006, 2},
+ {0x800a, 2},
+ {0x800f, 2},
+ {0x8018, 2},
+ {0x801f, 2},
+ {0x8029, 2},
+ {0xc038, 2},
+ {0x8003, 3},
+ {0x8006, 3},
+ {0x800a, 3},
+ {0x800f, 3},
+ {0x8018, 3},
+ {0x801f, 3},
+ {0x8029, 3},
+ {0xc038, 3},
+ },
+ /* 228 */
+ {
+ {0x8002, 4},
+ {0x8009, 4},
+ {0x8017, 4},
+ {0xc028, 4},
+ {0x8002, 5},
+ {0x8009, 5},
+ {0x8017, 5},
+ {0xc028, 5},
+ {0x8002, 6},
+ {0x8009, 6},
+ {0x8017, 6},
+ {0xc028, 6},
+ {0x8002, 7},
+ {0x8009, 7},
+ {0x8017, 7},
+ {0xc028, 7},
+ },
+ /* 229 */
+ {
+ {0x8003, 4},
+ {0x8006, 4},
+ {0x800a, 4},
+ {0x800f, 4},
+ {0x8018, 4},
+ {0x801f, 4},
+ {0x8029, 4},
+ {0xc038, 4},
+ {0x8003, 5},
+ {0x8006, 5},
+ {0x800a, 5},
+ {0x800f, 5},
+ {0x8018, 5},
+ {0x801f, 5},
+ {0x8029, 5},
+ {0xc038, 5},
+ },
+ /* 230 */
+ {
+ {0x8003, 6},
+ {0x8006, 6},
+ {0x800a, 6},
+ {0x800f, 6},
+ {0x8018, 6},
+ {0x801f, 6},
+ {0x8029, 6},
+ {0xc038, 6},
+ {0x8003, 7},
+ {0x8006, 7},
+ {0x800a, 7},
+ {0x800f, 7},
+ {0x8018, 7},
+ {0x801f, 7},
+ {0x8029, 7},
+ {0xc038, 7},
+ },
+ /* 231 */
+ {
+ {0x8001, 8},
+ {0xc016, 8},
+ {0x8001, 11},
+ {0xc016, 11},
+ {0x8001, 12},
+ {0xc016, 12},
+ {0x8001, 14},
+ {0xc016, 14},
+ {0x8001, 15},
+ {0xc016, 15},
+ {0x8001, 16},
+ {0xc016, 16},
+ {0x8001, 17},
+ {0xc016, 17},
+ {0x8001, 18},
+ {0xc016, 18},
+ },
+ /* 232 */
+ {
+ {0x8002, 8},
+ {0x8009, 8},
+ {0x8017, 8},
+ {0xc028, 8},
+ {0x8002, 11},
+ {0x8009, 11},
+ {0x8017, 11},
+ {0xc028, 11},
+ {0x8002, 12},
+ {0x8009, 12},
+ {0x8017, 12},
+ {0xc028, 12},
+ {0x8002, 14},
+ {0x8009, 14},
+ {0x8017, 14},
+ {0xc028, 14},
+ },
+ /* 233 */
+ {
+ {0x8003, 8},
+ {0x8006, 8},
+ {0x800a, 8},
+ {0x800f, 8},
+ {0x8018, 8},
+ {0x801f, 8},
+ {0x8029, 8},
+ {0xc038, 8},
+ {0x8003, 11},
+ {0x8006, 11},
+ {0x800a, 11},
+ {0x800f, 11},
+ {0x8018, 11},
+ {0x801f, 11},
+ {0x8029, 11},
+ {0xc038, 11},
+ },
+ /* 234 */
+ {
+ {0x8003, 12},
+ {0x8006, 12},
+ {0x800a, 12},
+ {0x800f, 12},
+ {0x8018, 12},
+ {0x801f, 12},
+ {0x8029, 12},
+ {0xc038, 12},
+ {0x8003, 14},
+ {0x8006, 14},
+ {0x800a, 14},
+ {0x800f, 14},
+ {0x8018, 14},
+ {0x801f, 14},
+ {0x8029, 14},
+ {0xc038, 14},
+ },
+ /* 235 */
+ {
+ {0x8002, 15},
+ {0x8009, 15},
+ {0x8017, 15},
+ {0xc028, 15},
+ {0x8002, 16},
+ {0x8009, 16},
+ {0x8017, 16},
+ {0xc028, 16},
+ {0x8002, 17},
+ {0x8009, 17},
+ {0x8017, 17},
+ {0xc028, 17},
+ {0x8002, 18},
+ {0x8009, 18},
+ {0x8017, 18},
+ {0xc028, 18},
+ },
+ /* 236 */
+ {
+ {0x8003, 15},
+ {0x8006, 15},
+ {0x800a, 15},
+ {0x800f, 15},
+ {0x8018, 15},
+ {0x801f, 15},
+ {0x8029, 15},
+ {0xc038, 15},
+ {0x8003, 16},
+ {0x8006, 16},
+ {0x800a, 16},
+ {0x800f, 16},
+ {0x8018, 16},
+ {0x801f, 16},
+ {0x8029, 16},
+ {0xc038, 16},
+ },
+ /* 237 */
+ {
+ {0x8003, 17},
+ {0x8006, 17},
+ {0x800a, 17},
+ {0x800f, 17},
+ {0x8018, 17},
+ {0x801f, 17},
+ {0x8029, 17},
+ {0xc038, 17},
+ {0x8003, 18},
+ {0x8006, 18},
+ {0x800a, 18},
+ {0x800f, 18},
+ {0x8018, 18},
+ {0x801f, 18},
+ {0x8029, 18},
+ {0xc038, 18},
+ },
+ /* 238 */
+ {
+ {0xc000, 19},
+ {0xc000, 20},
+ {0xc000, 21},
+ {0xc000, 23},
+ {0xc000, 24},
+ {0xc000, 25},
+ {0xc000, 26},
+ {0xc000, 27},
+ {0xc000, 28},
+ {0xc000, 29},
+ {0xc000, 30},
+ {0xc000, 31},
+ {0xc000, 127},
+ {0xc000, 220},
+ {0xc000, 249},
+ {0xfd, 0},
+ },
+ /* 239 */
+ {
+ {0x8001, 19},
+ {0xc016, 19},
+ {0x8001, 20},
+ {0xc016, 20},
+ {0x8001, 21},
+ {0xc016, 21},
+ {0x8001, 23},
+ {0xc016, 23},
+ {0x8001, 24},
+ {0xc016, 24},
+ {0x8001, 25},
+ {0xc016, 25},
+ {0x8001, 26},
+ {0xc016, 26},
+ {0x8001, 27},
+ {0xc016, 27},
+ },
+ /* 240 */
+ {
+ {0x8002, 19},
+ {0x8009, 19},
+ {0x8017, 19},
+ {0xc028, 19},
+ {0x8002, 20},
+ {0x8009, 20},
+ {0x8017, 20},
+ {0xc028, 20},
+ {0x8002, 21},
+ {0x8009, 21},
+ {0x8017, 21},
+ {0xc028, 21},
+ {0x8002, 23},
+ {0x8009, 23},
+ {0x8017, 23},
+ {0xc028, 23},
+ },
+ /* 241 */
+ {
+ {0x8003, 19},
+ {0x8006, 19},
+ {0x800a, 19},
+ {0x800f, 19},
+ {0x8018, 19},
+ {0x801f, 19},
+ {0x8029, 19},
+ {0xc038, 19},
+ {0x8003, 20},
+ {0x8006, 20},
+ {0x800a, 20},
+ {0x800f, 20},
+ {0x8018, 20},
+ {0x801f, 20},
+ {0x8029, 20},
+ {0xc038, 20},
+ },
+ /* 242 */
+ {
+ {0x8003, 21},
+ {0x8006, 21},
+ {0x800a, 21},
+ {0x800f, 21},
+ {0x8018, 21},
+ {0x801f, 21},
+ {0x8029, 21},
+ {0xc038, 21},
+ {0x8003, 23},
+ {0x8006, 23},
+ {0x800a, 23},
+ {0x800f, 23},
+ {0x8018, 23},
+ {0x801f, 23},
+ {0x8029, 23},
+ {0xc038, 23},
+ },
+ /* 243 */
+ {
+ {0x8002, 24},
+ {0x8009, 24},
+ {0x8017, 24},
+ {0xc028, 24},
+ {0x8002, 25},
+ {0x8009, 25},
+ {0x8017, 25},
+ {0xc028, 25},
+ {0x8002, 26},
+ {0x8009, 26},
+ {0x8017, 26},
+ {0xc028, 26},
+ {0x8002, 27},
+ {0x8009, 27},
+ {0x8017, 27},
+ {0xc028, 27},
+ },
+ /* 244 */
+ {
+ {0x8003, 24},
+ {0x8006, 24},
+ {0x800a, 24},
+ {0x800f, 24},
+ {0x8018, 24},
+ {0x801f, 24},
+ {0x8029, 24},
+ {0xc038, 24},
+ {0x8003, 25},
+ {0x8006, 25},
+ {0x800a, 25},
+ {0x800f, 25},
+ {0x8018, 25},
+ {0x801f, 25},
+ {0x8029, 25},
+ {0xc038, 25},
+ },
+ /* 245 */
+ {
+ {0x8003, 26},
+ {0x8006, 26},
+ {0x800a, 26},
+ {0x800f, 26},
+ {0x8018, 26},
+ {0x801f, 26},
+ {0x8029, 26},
+ {0xc038, 26},
+ {0x8003, 27},
+ {0x8006, 27},
+ {0x800a, 27},
+ {0x800f, 27},
+ {0x8018, 27},
+ {0x801f, 27},
+ {0x8029, 27},
+ {0xc038, 27},
+ },
+ /* 246 */
+ {
+ {0x8001, 28},
+ {0xc016, 28},
+ {0x8001, 29},
+ {0xc016, 29},
+ {0x8001, 30},
+ {0xc016, 30},
+ {0x8001, 31},
+ {0xc016, 31},
+ {0x8001, 127},
+ {0xc016, 127},
+ {0x8001, 220},
+ {0xc016, 220},
+ {0x8001, 249},
+ {0xc016, 249},
+ {0xfe, 0},
+ {0xff, 0},
+ },
+ /* 247 */
+ {
+ {0x8002, 28},
+ {0x8009, 28},
+ {0x8017, 28},
+ {0xc028, 28},
+ {0x8002, 29},
+ {0x8009, 29},
+ {0x8017, 29},
+ {0xc028, 29},
+ {0x8002, 30},
+ {0x8009, 30},
+ {0x8017, 30},
+ {0xc028, 30},
+ {0x8002, 31},
+ {0x8009, 31},
+ {0x8017, 31},
+ {0xc028, 31},
+ },
+ /* 248 */
+ {
+ {0x8003, 28},
+ {0x8006, 28},
+ {0x800a, 28},
+ {0x800f, 28},
+ {0x8018, 28},
+ {0x801f, 28},
+ {0x8029, 28},
+ {0xc038, 28},
+ {0x8003, 29},
+ {0x8006, 29},
+ {0x800a, 29},
+ {0x800f, 29},
+ {0x8018, 29},
+ {0x801f, 29},
+ {0x8029, 29},
+ {0xc038, 29},
+ },
+ /* 249 */
+ {
+ {0x8003, 30},
+ {0x8006, 30},
+ {0x800a, 30},
+ {0x800f, 30},
+ {0x8018, 30},
+ {0x801f, 30},
+ {0x8029, 30},
+ {0xc038, 30},
+ {0x8003, 31},
+ {0x8006, 31},
+ {0x800a, 31},
+ {0x800f, 31},
+ {0x8018, 31},
+ {0x801f, 31},
+ {0x8029, 31},
+ {0xc038, 31},
+ },
+ /* 250 */
+ {
+ {0x8002, 127},
+ {0x8009, 127},
+ {0x8017, 127},
+ {0xc028, 127},
+ {0x8002, 220},
+ {0x8009, 220},
+ {0x8017, 220},
+ {0xc028, 220},
+ {0x8002, 249},
+ {0x8009, 249},
+ {0x8017, 249},
+ {0xc028, 249},
+ {0xc000, 10},
+ {0xc000, 13},
+ {0xc000, 22},
+ {0x100, 0},
+ },
+ /* 251 */
+ {
+ {0x8003, 127},
+ {0x8006, 127},
+ {0x800a, 127},
+ {0x800f, 127},
+ {0x8018, 127},
+ {0x801f, 127},
+ {0x8029, 127},
+ {0xc038, 127},
+ {0x8003, 220},
+ {0x8006, 220},
+ {0x800a, 220},
+ {0x800f, 220},
+ {0x8018, 220},
+ {0x801f, 220},
+ {0x8029, 220},
+ {0xc038, 220},
+ },
+ /* 252 */
+ {
+ {0x8003, 249},
+ {0x8006, 249},
+ {0x800a, 249},
+ {0x800f, 249},
+ {0x8018, 249},
+ {0x801f, 249},
+ {0x8029, 249},
+ {0xc038, 249},
+ {0x8001, 10},
+ {0xc016, 10},
+ {0x8001, 13},
+ {0xc016, 13},
+ {0x8001, 22},
+ {0xc016, 22},
+ {0x100, 0},
+ {0x100, 0},
+ },
+ /* 253 */
+ {
+ {0x8002, 10},
+ {0x8009, 10},
+ {0x8017, 10},
+ {0xc028, 10},
+ {0x8002, 13},
+ {0x8009, 13},
+ {0x8017, 13},
+ {0xc028, 13},
+ {0x8002, 22},
+ {0x8009, 22},
+ {0x8017, 22},
+ {0xc028, 22},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ },
+ /* 254 */
+ {
+ {0x8003, 10},
+ {0x8006, 10},
+ {0x800a, 10},
+ {0x800f, 10},
+ {0x8018, 10},
+ {0x801f, 10},
+ {0x8029, 10},
+ {0xc038, 10},
+ {0x8003, 13},
+ {0x8006, 13},
+ {0x800a, 13},
+ {0x800f, 13},
+ {0x8018, 13},
+ {0x801f, 13},
+ {0x8029, 13},
+ {0xc038, 13},
+ },
+ /* 255 */
+ {
+ {0x8003, 22},
+ {0x8006, 22},
+ {0x800a, 22},
+ {0x800f, 22},
+ {0x8018, 22},
+ {0x801f, 22},
+ {0x8029, 22},
+ {0xc038, 22},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ },
+ /* 256 */
+ {
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ {0x100, 0},
+ },
+};
diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c
new file mode 100644
index 0000000..93dd475
--- /dev/null
+++ b/lib/nghttp2_helper.c
@@ -0,0 +1,803 @@
+/*
+ * 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);
+}
+
+/* Generated by gendowncasetbl.py */
+static const uint8_t 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 paid 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_increase_local_window_size(int32_t *local_window_size_ptr,
+ int32_t *recv_window_size_ptr,
+ int32_t *recv_reduction_ptr,
+ int32_t *delta_ptr) {
+ int32_t recv_reduction_delta;
+ int32_t delta;
+
+ delta = *delta_ptr;
+
+ assert(delta >= 0);
+
+ /* 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;
+
+ *recv_window_size_ptr += recv_reduction_delta;
+
+ /* recv_reduction_delta must be paid from *delta_ptr, since it was
+ added in window size reduction (see below). */
+ *delta_ptr -= recv_reduction_delta;
+
+ return 0;
+}
+
+int nghttp2_should_send_window_update(int32_t local_window_size,
+ int32_t recv_window_size) {
+ return recv_window_size > 0 && 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_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 or HEADERS frame has already been submitted for the stream";
+ case NGHTTP2_ERR_SESSION_CLOSING:
+ return "The current session is closing";
+ case NGHTTP2_ERR_HTTP_HEADER:
+ return "Invalid HTTP header field was received";
+ case NGHTTP2_ERR_HTTP_MESSAGING:
+ return "Violation in HTTP messaging rule";
+ case NGHTTP2_ERR_REFUSED_STREAM:
+ return "Stream was refused";
+ case NGHTTP2_ERR_INTERNAL:
+ return "Internal error";
+ case NGHTTP2_ERR_CANCEL:
+ return "Cancel";
+ case NGHTTP2_ERR_SETTINGS_EXPECTED:
+ return "When a local endpoint expects to receive SETTINGS frame, it "
+ "receives an other type of frame";
+ case NGHTTP2_ERR_NOMEM:
+ return "Out of memory";
+ case NGHTTP2_ERR_CALLBACK_FAILURE:
+ return "The user callback function failed";
+ case NGHTTP2_ERR_BAD_CLIENT_MAGIC:
+ return "Received bad client magic byte string";
+ case NGHTTP2_ERR_FLOODED:
+ return "Flooding was detected in this HTTP/2 session, and it must be "
+ "closed";
+ case NGHTTP2_ERR_TOO_MANY_SETTINGS:
+ return "SETTINGS frame contained more than the maximum allowed entries";
+ default:
+ return "Unknown error code";
+ }
+}
+
+/* Generated by gennmchartbl.py */
+static const 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 const 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;
+}
+
+int nghttp2_check_header_value_rfc9113(const uint8_t *value, size_t len) {
+ if (len == 0) {
+ return 1;
+ }
+
+ if (*value == ' ' || *value == '\t' || *(value + len - 1) == ' ' ||
+ *(value + len - 1) == '\t') {
+ return 0;
+ }
+
+ return nghttp2_check_header_value(value, len);
+}
+
+/* Generated by genmethodchartbl.py */
+static char VALID_METHOD_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 /* @ */, 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 /* [ */,
+ 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_method(const uint8_t *value, size_t len) {
+ const uint8_t *last;
+ if (len == 0) {
+ return 0;
+ }
+ for (last = value + len; value != last; ++value) {
+ if (!VALID_METHOD_CHARS[*value]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* Generated by genpathchartbl.py */
+static char VALID_PATH_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 /* ! */, 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_path(const uint8_t *value, size_t len) {
+ const uint8_t *last;
+ for (last = value + len; value != last; ++value) {
+ if (!VALID_PATH_CHARS[*value]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* Generated by genauthoritychartbl.py */
+static char VALID_AUTHORITY_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 /* " */, 0 /* # */,
+ 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */,
+ 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */,
+ 1 /* , */, 1 /* - */, 1 /* . */, 0 /* / */,
+ 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */,
+ 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */,
+ 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */,
+ 0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */,
+ 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 /* [ */,
+ 0 /* \ */, 1 /* ] */, 0 /* ^ */, 1 /* _ */,
+ 0 /* ` */, 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 /* { */,
+ 0 /* | */, 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_authority(const uint8_t *value, size_t len) {
+ const uint8_t *last;
+ for (last = value + len; value != last; ++value) {
+ if (!VALID_AUTHORITY_CHARS[*value]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) {
+ if (len == 0) {
+ return dest;
+ }
+
+ memcpy(dest, src, len);
+
+ return dest + len;
+}
+
+const char *nghttp2_http2_strerror(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";
+ }
+}
diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h
new file mode 100644
index 0000000..b1f18ce
--- /dev/null
+++ b/lib/nghttp2_helper.h
@@ -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_HELPER_H
+#define NGHTTP2_HELPER_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <stddef.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))
+
+#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0)
+
+#define nghttp2_struct_of(ptr, type, member) \
+ ((type *)(void *)((char *)(ptr)-offsetof(type, member)))
+
+/*
+ * 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);
+
+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);
+
+/*
+ * This function works like nghttp2_adjust_local_window_size(). The
+ * difference is that this function assumes *delta_ptr >= 0, and
+ * *recv_window_size_ptr is not decreased by *delta_ptr.
+ *
+ * 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_increase_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_http.c b/lib/nghttp2_http.c
new file mode 100644
index 0000000..ecdeb21
--- /dev/null
+++ b/lib/nghttp2_http.c
@@ -0,0 +1,631 @@
+/*
+ * 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 "nghttp2_http.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp2_hd.h"
+#include "nghttp2_helper.h"
+#include "nghttp2_extpri.h"
+#include "sfparse.h"
+
+static uint8_t downcase(uint8_t c) {
+ return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c;
+}
+
+static int memieq(const void *a, const void *b, size_t n) {
+ size_t i;
+ const uint8_t *aa = a, *bb = b;
+
+ for (i = 0; i < n; ++i) {
+ if (downcase(aa[i]) != downcase(bb[i])) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#define lstrieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N)))
+
+static int64_t parse_uint(const uint8_t *s, size_t len) {
+ int64_t n = 0;
+ size_t i;
+ if (len == 0) {
+ return -1;
+ }
+ for (i = 0; i < len; ++i) {
+ if ('0' <= s[i] && s[i] <= '9') {
+ if (n > INT64_MAX / 10) {
+ return -1;
+ }
+ n *= 10;
+ if (n > INT64_MAX - (s[i] - '0')) {
+ return -1;
+ }
+ n += s[i] - '0';
+ continue;
+ }
+ return -1;
+ }
+ return n;
+}
+
+static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv,
+ uint32_t flag) {
+ if ((stream->http_flags & flag) || nv->value->len == 0) {
+ return 0;
+ }
+ stream->http_flags = stream->http_flags | flag;
+ return 1;
+}
+
+static int expect_response_body(nghttp2_stream *stream) {
+ return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 &&
+ stream->status_code / 100 != 1 && stream->status_code != 304 &&
+ stream->status_code != 204;
+}
+
+/* For "http" or "https" URIs, OPTIONS request may have "*" in :path
+ header field to represent system-wide OPTIONS request. Otherwise,
+ :path header field value must start with "/". This function must
+ be called after ":method" header field was received. This function
+ returns nonzero if path is valid.*/
+static int check_path(nghttp2_stream *stream) {
+ return (stream->http_flags & NGHTTP2_HTTP_FLAG_SCHEME_HTTP) == 0 ||
+ ((stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_REGULAR) ||
+ ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_OPTIONS) &&
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK)));
+}
+
+static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
+ int trailer, int connect_protocol) {
+ nghttp2_extpri extpri;
+
+ if (nv->name->base[0] == ':') {
+ if (trailer ||
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ }
+
+ switch (nv->token) {
+ case NGHTTP2_TOKEN__AUTHORITY:
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ case NGHTTP2_TOKEN__METHOD:
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ switch (nv->value->len) {
+ case 4:
+ if (lstreq("HEAD", nv->value->base, nv->value->len)) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
+ }
+ break;
+ case 7:
+ switch (nv->value->base[6]) {
+ case 'T':
+ if (lstreq("CONNECT", nv->value->base, nv->value->len)) {
+ if (stream->stream_id % 2 == 0) {
+ /* we won't allow CONNECT for push */
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
+ }
+ break;
+ case 'S':
+ if (lstreq("OPTIONS", nv->value->base, nv->value->len)) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
+ }
+ break;
+ }
+ break;
+ }
+ break;
+ case NGHTTP2_TOKEN__PATH:
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ if (nv->value->base[0] == '/') {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
+ } else if (nv->value->len == 1 && nv->value->base[0] == '*') {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
+ }
+ break;
+ case NGHTTP2_TOKEN__SCHEME:
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) ||
+ (nv->value->len == 5 && memieq("https", nv->value->base, 5))) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
+ }
+ break;
+ case NGHTTP2_TOKEN__PROTOCOL:
+ if (!connect_protocol) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ case NGHTTP2_TOKEN_HOST:
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ case NGHTTP2_TOKEN_CONTENT_LENGTH: {
+ if (stream->content_length != -1) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ stream->content_length = parse_uint(nv->value->base, nv->value->len);
+ if (stream->content_length == -1) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ }
+ /* disallowed header fields */
+ case NGHTTP2_TOKEN_CONNECTION:
+ case NGHTTP2_TOKEN_KEEP_ALIVE:
+ case NGHTTP2_TOKEN_PROXY_CONNECTION:
+ case NGHTTP2_TOKEN_TRANSFER_ENCODING:
+ case NGHTTP2_TOKEN_UPGRADE:
+ return NGHTTP2_ERR_HTTP_HEADER;
+ case NGHTTP2_TOKEN_TE:
+ if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ case NGHTTP2_TOKEN_PRIORITY:
+ if (!trailer &&
+ /* Do not parse the header field in PUSH_PROMISE. */
+ (stream->stream_id & 1) &&
+ (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) &&
+ !(stream->http_flags & NGHTTP2_HTTP_FLAG_BAD_PRIORITY)) {
+ nghttp2_extpri_from_uint8(&extpri, stream->http_extpri);
+ if (nghttp2_http_parse_priority(&extpri, nv->value->base,
+ nv->value->len) == 0) {
+ stream->http_extpri = nghttp2_extpri_to_uint8(&extpri);
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PRIORITY;
+ } else {
+ stream->http_flags &= (uint32_t)~NGHTTP2_HTTP_FLAG_PRIORITY;
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_BAD_PRIORITY;
+ }
+ }
+ break;
+ default:
+ if (nv->name->base[0] == ':') {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ }
+
+ if (nv->name->base[0] != ':') {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
+ }
+
+ return 0;
+}
+
+static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
+ int trailer) {
+ if (nv->name->base[0] == ':') {
+ if (trailer ||
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ }
+
+ switch (nv->token) {
+ case NGHTTP2_TOKEN__STATUS: {
+ if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ if (nv->value->len != 3) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len);
+ if (stream->status_code == -1 || stream->status_code == 101) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ }
+ case NGHTTP2_TOKEN_CONTENT_LENGTH: {
+ if (stream->status_code == 204) {
+ /* content-length header field in 204 response is prohibited by
+ RFC 7230. But some widely used servers send content-length:
+ 0. Until they get fixed, we ignore it. */
+ if (stream->content_length != -1) {
+ /* Found multiple content-length field */
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ if (!lstrieq("0", nv->value->base, nv->value->len)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ stream->content_length = 0;
+ return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
+ }
+ if (stream->status_code / 100 == 1) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ /* https://tools.ietf.org/html/rfc7230#section-3.3.3 */
+ if (stream->status_code / 100 == 2 &&
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
+ return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
+ }
+ if (stream->content_length != -1) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ stream->content_length = parse_uint(nv->value->base, nv->value->len);
+ if (stream->content_length == -1) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ }
+ /* disallowed header fields */
+ case NGHTTP2_TOKEN_CONNECTION:
+ case NGHTTP2_TOKEN_KEEP_ALIVE:
+ case NGHTTP2_TOKEN_PROXY_CONNECTION:
+ case NGHTTP2_TOKEN_TRANSFER_ENCODING:
+ case NGHTTP2_TOKEN_UPGRADE:
+ return NGHTTP2_ERR_HTTP_HEADER;
+ case NGHTTP2_TOKEN_TE:
+ if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ break;
+ default:
+ if (nv->name->base[0] == ':') {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ }
+
+ if (nv->name->base[0] != ':') {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
+ }
+
+ return 0;
+}
+
+static int check_scheme(const uint8_t *value, size_t len) {
+ const uint8_t *last;
+ if (len == 0) {
+ return 0;
+ }
+
+ if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) {
+ return 0;
+ }
+
+ last = value + len;
+ ++value;
+
+ for (; value != last; ++value) {
+ if (!(('A' <= *value && *value <= 'Z') ||
+ ('a' <= *value && *value <= 'z') ||
+ ('0' <= *value && *value <= '9') || *value == '+' || *value == '-' ||
+ *value == '.')) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int lws(const uint8_t *s, size_t n) {
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ if (s[i] != ' ' && s[i] != '\t') {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
+ nghttp2_frame *frame, nghttp2_hd_nv *nv,
+ int trailer) {
+ int rv;
+
+ /* We are strict for pseudo header field. One bad character should
+ lead to fail. OTOH, we should be a bit forgiving for regular
+ headers, since existing public internet has so much illegal
+ headers floating around and if we kill the stream because of
+ this, we may disrupt many web sites and/or libraries. So we
+ become conservative here, and just ignore those illegal regular
+ headers. */
+ if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) {
+ size_t i;
+ if (nv->name->len > 0 && nv->name->base[0] == ':') {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ /* header field name must be lower-cased without exception */
+ for (i = 0; i < nv->name->len; ++i) {
+ uint8_t c = nv->name->base[i];
+ if ('A' <= c && c <= 'Z') {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ }
+ /* When ignoring regular headers, we set this flag so that we
+ still enforce header field ordering rule for pseudo header
+ fields. */
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
+ return NGHTTP2_ERR_IGN_HTTP_HEADER;
+ }
+
+ switch (nv->token) {
+ case NGHTTP2_TOKEN__METHOD:
+ rv = nghttp2_check_method(nv->value->base, nv->value->len);
+ break;
+ case NGHTTP2_TOKEN__PATH:
+ rv = nghttp2_check_path(nv->value->base, nv->value->len);
+ break;
+ case NGHTTP2_TOKEN__AUTHORITY:
+ case NGHTTP2_TOKEN_HOST:
+ if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ rv = nghttp2_check_authority(nv->value->base, nv->value->len);
+ } else if (
+ stream->flags &
+ NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
+ rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
+ } else {
+ rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
+ }
+ break;
+ case NGHTTP2_TOKEN__SCHEME:
+ rv = check_scheme(nv->value->base, nv->value->len);
+ break;
+ case NGHTTP2_TOKEN__PROTOCOL:
+ /* Check the value consists of just white spaces, which was done
+ in check_pseudo_header before
+ nghttp2_check_header_value_rfc9113 has been introduced. */
+ if ((stream->flags &
+ NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) &&
+ lws(nv->value->base, nv->value->len)) {
+ rv = 0;
+ break;
+ }
+ /* fall through */
+ default:
+ if (stream->flags &
+ NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
+ rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
+ } else {
+ rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
+ }
+ }
+
+ if (rv == 0) {
+ assert(nv->name->len > 0);
+ if (nv->name->base[0] == ':') {
+ return NGHTTP2_ERR_HTTP_HEADER;
+ }
+ /* When ignoring regular headers, we set this flag so that we
+ still enforce header field ordering rule for pseudo header
+ fields. */
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
+ return NGHTTP2_ERR_IGN_HTTP_HEADER;
+ }
+
+ if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ return http_request_on_header(stream, nv, trailer,
+ session->server &&
+ session->pending_enable_connect_protocol);
+ }
+
+ return http_response_on_header(stream, nv, trailer);
+}
+
+int nghttp2_http_on_request_headers(nghttp2_stream *stream,
+ nghttp2_frame *frame) {
+ if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
+ if ((stream->http_flags &
+ (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) ||
+ (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
+ return -1;
+ }
+ stream->content_length = -1;
+ } else {
+ if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) !=
+ NGHTTP2_HTTP_FLAG_REQ_HEADERS ||
+ (stream->http_flags &
+ (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
+ return -1;
+ }
+ if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
+ ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 ||
+ (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) {
+ return -1;
+ }
+ if (!check_path(stream)) {
+ return -1;
+ }
+ }
+
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ /* we are going to reuse data fields for upcoming response. Clear
+ them now, except for method flags. */
+ stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL;
+ stream->content_length = -1;
+ }
+
+ return 0;
+}
+
+int nghttp2_http_on_response_headers(nghttp2_stream *stream) {
+ if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) {
+ return -1;
+ }
+
+ if (stream->status_code / 100 == 1) {
+ /* non-final response */
+ stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) |
+ NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
+ stream->content_length = -1;
+ stream->status_code = -1;
+ return 0;
+ }
+
+ stream->http_flags =
+ stream->http_flags & (uint32_t)~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
+
+ if (!expect_response_body(stream)) {
+ stream->content_length = 0;
+ } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT |
+ NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) {
+ stream->content_length = -1;
+ }
+
+ return 0;
+}
+
+int nghttp2_http_on_trailer_headers(nghttp2_stream *stream,
+ nghttp2_frame *frame) {
+ (void)stream;
+
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) {
+ if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
+ return -1;
+ }
+
+ if (stream->content_length != -1 &&
+ stream->content_length != stream->recv_content_length) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) {
+ stream->recv_content_length += (int64_t)n;
+
+ if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) ||
+ (stream->content_length != -1 &&
+ stream->recv_content_length > stream->content_length)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void nghttp2_http_record_request_method(nghttp2_stream *stream,
+ nghttp2_frame *frame) {
+ const nghttp2_nv *nva;
+ size_t nvlen;
+ size_t i;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ nva = frame->headers.nva;
+ nvlen = frame->headers.nvlen;
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ nva = frame->push_promise.nva;
+ nvlen = frame->push_promise.nvlen;
+ break;
+ default:
+ return;
+ }
+
+ /* TODO we should do this strictly. */
+ for (i = 0; i < nvlen; ++i) {
+ const nghttp2_nv *nv = &nva[i];
+ if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
+ memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
+ continue;
+ }
+ if (lstreq("CONNECT", nv->value, nv->valuelen)) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
+ return;
+ }
+ if (lstreq("HEAD", nv->value, nv->valuelen)) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
+ return;
+ }
+ return;
+ }
+}
+
+int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value,
+ size_t valuelen) {
+ nghttp2_extpri pri = *dest;
+ sf_parser sfp;
+ sf_vec key;
+ sf_value val;
+ int rv;
+
+ sf_parser_init(&sfp, value, valuelen);
+
+ for (;;) {
+ rv = sf_parser_dict(&sfp, &key, &val);
+ if (rv != 0) {
+ if (rv == SF_ERR_EOF) {
+ break;
+ }
+
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (key.len != 1) {
+ continue;
+ }
+
+ switch (key.base[0]) {
+ case 'i':
+ if (val.type != SF_TYPE_BOOLEAN) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ pri.inc = val.boolean;
+
+ break;
+ case 'u':
+ if (val.type != SF_TYPE_INTEGER ||
+ val.integer < NGHTTP2_EXTPRI_URGENCY_HIGH ||
+ NGHTTP2_EXTPRI_URGENCY_LOW < val.integer) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ pri.urgency = (uint32_t)val.integer;
+
+ break;
+ }
+ }
+
+ *dest = pri;
+
+ return 0;
+}
diff --git a/lib/nghttp2_http.h b/lib/nghttp2_http.h
new file mode 100644
index 0000000..d9992fe
--- /dev/null
+++ b/lib/nghttp2_http.h
@@ -0,0 +1,100 @@
+/*
+ * 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 NGHTTP2_HTTP_H
+#define NGHTTP2_HTTP_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_session.h"
+#include "nghttp2_stream.h"
+
+/*
+ * This function is called when HTTP header field |nv| in |frame| is
+ * received for |stream|. This function will validate |nv| against
+ * the current state of stream.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_HTTP_HEADER
+ * Invalid HTTP header field was received.
+ * NGHTTP2_ERR_IGN_HTTP_HEADER
+ * Invalid HTTP header field was received but it can be treated as
+ * if it was not received because of compatibility reasons.
+ */
+int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
+ nghttp2_frame *frame, nghttp2_hd_nv *nv,
+ int trailer);
+
+/*
+ * This function is called when request header is received. This
+ * function performs validation and returns 0 if it succeeds, or -1.
+ */
+int nghttp2_http_on_request_headers(nghttp2_stream *stream,
+ nghttp2_frame *frame);
+
+/*
+ * This function is called when response header is received. This
+ * function performs validation and returns 0 if it succeeds, or -1.
+ */
+int nghttp2_http_on_response_headers(nghttp2_stream *stream);
+
+/*
+ * This function is called trailer header (for both request and
+ * response) is received. This function performs validation and
+ * returns 0 if it succeeds, or -1.
+ */
+int nghttp2_http_on_trailer_headers(nghttp2_stream *stream,
+ nghttp2_frame *frame);
+
+/*
+ * This function is called when END_STREAM flag is seen in incoming
+ * frame. This function performs validation and returns 0 if it
+ * succeeds, or -1.
+ */
+int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream);
+
+/*
+ * This function is called when chunk of data is received. This
+ * function performs validation and returns 0 if it succeeds, or -1.
+ */
+int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n);
+
+/*
+ * This function inspects header field in |frame| and records its
+ * method in stream->http_flags. If frame->hd.type is neither
+ * NGHTTP2_HEADERS nor NGHTTP2_PUSH_PROMISE, this function does
+ * nothing.
+ */
+void nghttp2_http_record_request_method(nghttp2_stream *stream,
+ nghttp2_frame *frame);
+
+int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value,
+ size_t valuelen);
+
+#endif /* NGHTTP2_HTTP_H */
diff --git a/lib/nghttp2_int.h b/lib/nghttp2_int.h
new file mode 100644
index 0000000..b23585c
--- /dev/null
+++ b/lib/nghttp2_int.h
@@ -0,0 +1,58 @@
+/*
+ * 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 */
+
+#include <nghttp2/nghttp2.h>
+
+/* Macros, types and constants for internal use */
+
+/* "less" function, return nonzero if |lhs| is less than |rhs|. */
+typedef int (*nghttp2_less)(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,
+ /*
+ * Invalid HTTP header field was received but it can be treated as
+ * if it was not received because of compatibility reasons.
+ */
+ NGHTTP2_ERR_IGN_HTTP_HEADER = -105,
+ /*
+ * Invalid HTTP header field was received, and it is ignored.
+ * Unlike NGHTTP2_ERR_IGN_HTTP_HEADER, this does not invoke
+ * nghttp2_on_invalid_header_callback.
+ */
+ NGHTTP2_ERR_REMOVE_HTTP_HEADER = -106
+} nghttp2_internal_error;
+
+#endif /* NGHTTP2_INT_H */
diff --git a/lib/nghttp2_map.c b/lib/nghttp2_map.c
new file mode 100644
index 0000000..0aaaf29
--- /dev/null
+++ b/lib/nghttp2_map.c
@@ -0,0 +1,338 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * 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>
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp2_helper.h"
+
+#define NGHTTP2_INITIAL_TABLE_LENBITS 4
+
+void nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem) {
+ map->mem = mem;
+ map->tablelen = 0;
+ map->tablelenbits = 0;
+ map->table = NULL;
+ map->size = 0;
+}
+
+void nghttp2_map_free(nghttp2_map *map) {
+ if (!map) {
+ return;
+ }
+
+ nghttp2_mem_free(map->mem, map->table);
+}
+
+void nghttp2_map_each_free(nghttp2_map *map, int (*func)(void *data, void *ptr),
+ void *ptr) {
+ uint32_t i;
+ nghttp2_map_bucket *bkt;
+
+ for (i = 0; i < map->tablelen; ++i) {
+ bkt = &map->table[i];
+
+ if (bkt->data == NULL) {
+ continue;
+ }
+
+ func(bkt->data, ptr);
+ }
+}
+
+int nghttp2_map_each(nghttp2_map *map, int (*func)(void *data, void *ptr),
+ void *ptr) {
+ int rv;
+ uint32_t i;
+ nghttp2_map_bucket *bkt;
+
+ if (map->size == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < map->tablelen; ++i) {
+ bkt = &map->table[i];
+
+ if (bkt->data == NULL) {
+ continue;
+ }
+
+ rv = func(bkt->data, ptr);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+static uint32_t hash(nghttp2_map_key_type key) {
+ return (uint32_t)key * 2654435769u;
+}
+
+static size_t h2idx(uint32_t hash, uint32_t bits) {
+ return hash >> (32 - bits);
+}
+
+static size_t distance(uint32_t tablelen, uint32_t tablelenbits,
+ nghttp2_map_bucket *bkt, size_t idx) {
+ return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1);
+}
+
+static void map_bucket_swap(nghttp2_map_bucket *bkt, uint32_t *phash,
+ nghttp2_map_key_type *pkey, void **pdata) {
+ uint32_t h = bkt->hash;
+ nghttp2_map_key_type key = bkt->key;
+ void *data = bkt->data;
+
+ bkt->hash = *phash;
+ bkt->key = *pkey;
+ bkt->data = *pdata;
+
+ *phash = h;
+ *pkey = key;
+ *pdata = data;
+}
+
+static void map_bucket_set_data(nghttp2_map_bucket *bkt, uint32_t hash,
+ nghttp2_map_key_type key, void *data) {
+ bkt->hash = hash;
+ bkt->key = key;
+ bkt->data = data;
+}
+
+#ifndef WIN32
+void nghttp2_map_print_distance(nghttp2_map *map) {
+ uint32_t i;
+ size_t idx;
+ nghttp2_map_bucket *bkt;
+
+ for (i = 0; i < map->tablelen; ++i) {
+ bkt = &map->table[i];
+
+ if (bkt->data == NULL) {
+ fprintf(stderr, "@%u <EMPTY>\n", i);
+ continue;
+ }
+
+ idx = h2idx(bkt->hash, map->tablelenbits);
+ fprintf(stderr, "@%u hash=%08x key=%d base=%zu distance=%zu\n", i,
+ bkt->hash, bkt->key, idx,
+ distance(map->tablelen, map->tablelenbits, bkt, idx));
+ }
+}
+#endif /* !WIN32 */
+
+static int insert(nghttp2_map_bucket *table, uint32_t tablelen,
+ uint32_t tablelenbits, uint32_t hash,
+ nghttp2_map_key_type key, void *data) {
+ size_t idx = h2idx(hash, tablelenbits);
+ size_t d = 0, dd;
+ nghttp2_map_bucket *bkt;
+
+ for (;;) {
+ bkt = &table[idx];
+
+ if (bkt->data == NULL) {
+ map_bucket_set_data(bkt, hash, key, data);
+ return 0;
+ }
+
+ dd = distance(tablelen, tablelenbits, bkt, idx);
+ if (d > dd) {
+ map_bucket_swap(bkt, &hash, &key, &data);
+ d = dd;
+ } else if (bkt->key == key) {
+ /* TODO This check is just a waste after first swap or if this
+ function is called from map_resize. That said, there is no
+ difference with or without this conditional in performance
+ wise. */
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ ++d;
+ idx = (idx + 1) & (tablelen - 1);
+ }
+}
+
+/* new_tablelen must be power of 2 and new_tablelen == (1 <<
+ new_tablelenbits) must hold. */
+static int map_resize(nghttp2_map *map, uint32_t new_tablelen,
+ uint32_t new_tablelenbits) {
+ uint32_t i;
+ nghttp2_map_bucket *new_table;
+ nghttp2_map_bucket *bkt;
+ int rv;
+ (void)rv;
+
+ new_table =
+ nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_bucket));
+ if (new_table == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ for (i = 0; i < map->tablelen; ++i) {
+ bkt = &map->table[i];
+ if (bkt->data == NULL) {
+ continue;
+ }
+ rv = insert(new_table, new_tablelen, new_tablelenbits, bkt->hash, bkt->key,
+ bkt->data);
+
+ assert(0 == rv);
+ }
+
+ nghttp2_mem_free(map->mem, map->table);
+ map->tablelen = new_tablelen;
+ map->tablelenbits = new_tablelenbits;
+ map->table = new_table;
+
+ return 0;
+}
+
+int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_key_type key, void *data) {
+ int rv;
+
+ assert(data);
+
+ /* Load factor is 0.75 */
+ if ((map->size + 1) * 4 > map->tablelen * 3) {
+ if (map->tablelen) {
+ rv = map_resize(map, map->tablelen * 2, map->tablelenbits + 1);
+ if (rv != 0) {
+ return rv;
+ }
+ } else {
+ rv = map_resize(map, 1 << NGHTTP2_INITIAL_TABLE_LENBITS,
+ NGHTTP2_INITIAL_TABLE_LENBITS);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+
+ rv = insert(map->table, map->tablelen, map->tablelenbits, hash(key), key,
+ data);
+ if (rv != 0) {
+ return rv;
+ }
+ ++map->size;
+ return 0;
+}
+
+void *nghttp2_map_find(nghttp2_map *map, nghttp2_map_key_type key) {
+ uint32_t h;
+ size_t idx;
+ nghttp2_map_bucket *bkt;
+ size_t d = 0;
+
+ if (map->size == 0) {
+ return NULL;
+ }
+
+ h = hash(key);
+ idx = h2idx(h, map->tablelenbits);
+
+ for (;;) {
+ bkt = &map->table[idx];
+
+ if (bkt->data == NULL ||
+ d > distance(map->tablelen, map->tablelenbits, bkt, idx)) {
+ return NULL;
+ }
+
+ if (bkt->key == key) {
+ return bkt->data;
+ }
+
+ ++d;
+ idx = (idx + 1) & (map->tablelen - 1);
+ }
+}
+
+int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key) {
+ uint32_t h;
+ size_t idx, didx;
+ nghttp2_map_bucket *bkt;
+ size_t d = 0;
+
+ if (map->size == 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ h = hash(key);
+ idx = h2idx(h, map->tablelenbits);
+
+ for (;;) {
+ bkt = &map->table[idx];
+
+ if (bkt->data == NULL ||
+ d > distance(map->tablelen, map->tablelenbits, bkt, idx)) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (bkt->key == key) {
+ map_bucket_set_data(bkt, 0, 0, NULL);
+
+ didx = idx;
+ idx = (idx + 1) & (map->tablelen - 1);
+
+ for (;;) {
+ bkt = &map->table[idx];
+ if (bkt->data == NULL ||
+ distance(map->tablelen, map->tablelenbits, bkt, idx) == 0) {
+ break;
+ }
+
+ map->table[didx] = *bkt;
+ map_bucket_set_data(bkt, 0, 0, NULL);
+ didx = idx;
+
+ idx = (idx + 1) & (map->tablelen - 1);
+ }
+
+ --map->size;
+
+ return 0;
+ }
+
+ ++d;
+ idx = (idx + 1) & (map->tablelen - 1);
+ }
+}
+
+void nghttp2_map_clear(nghttp2_map *map) {
+ if (map->tablelen == 0) {
+ return;
+ }
+
+ memset(map->table, 0, sizeof(*map->table) * map->tablelen);
+ map->size = 0;
+}
+
+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
index 0000000..236d282
--- /dev/null
+++ b/lib/nghttp2_map.h
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * 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_mem.h"
+
+/* Implementation of unordered map */
+
+typedef int32_t nghttp2_map_key_type;
+
+typedef struct nghttp2_map_bucket {
+ uint32_t hash;
+ nghttp2_map_key_type key;
+ void *data;
+} nghttp2_map_bucket;
+
+typedef struct nghttp2_map {
+ nghttp2_map_bucket *table;
+ nghttp2_mem *mem;
+ size_t size;
+ uint32_t tablelen;
+ uint32_t tablelenbits;
+} nghttp2_map;
+
+/*
+ * Initializes the map |map|.
+ */
+void 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 |data| 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)(void *data, void *ptr),
+ void *ptr);
+
+/*
+ * Inserts the new |data| with the |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_key_type key, void *data);
+
+/*
+ * Returns the data associated by the key |key|. If there is no such
+ * data, this function returns NULL.
+ */
+void *nghttp2_map_find(nghttp2_map *map, nghttp2_map_key_type key);
+
+/*
+ * Removes the data associated by the key |key| from the |map|. The
+ * removed data 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 data associated by |key| does not exist.
+ */
+int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key);
+
+/*
+ * Removes all entries from |map|.
+ */
+void nghttp2_map_clear(nghttp2_map *map);
+
+/*
+ * Returns the number of items stored in the map |map|.
+ */
+size_t nghttp2_map_size(nghttp2_map *map);
+
+/*
+ * Applies the function |func| to each data in the |map| with the
+ * optional user supplied pointer |ptr|.
+ *
+ * If the |func| returns 0, this function calls the |func| with the
+ * next data. 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 data. Use
+ * nghttp2_map_each_free() instead.
+ */
+int nghttp2_map_each(nghttp2_map *map, int (*func)(void *data, void *ptr),
+ void *ptr);
+
+#ifndef WIN32
+void nghttp2_map_print_distance(nghttp2_map *map);
+#endif /* !WIN32 */
+
+#endif /* NGHTTP2_MAP_H */
diff --git a/lib/nghttp2_mem.c b/lib/nghttp2_mem.c
new file mode 100644
index 0000000..6a449cf
--- /dev/null
+++ b/lib/nghttp2_mem.c
@@ -0,0 +1,74 @@
+/*
+ * 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) {
+ (void)mem_user_data;
+
+ return malloc(size);
+}
+
+static void default_free(void *ptr, void *mem_user_data) {
+ (void)mem_user_data;
+
+ free(ptr);
+}
+
+static void *default_calloc(size_t nmemb, size_t size, void *mem_user_data) {
+ (void)mem_user_data;
+
+ return calloc(nmemb, size);
+}
+
+static void *default_realloc(void *ptr, size_t size, void *mem_user_data) {
+ (void)mem_user_data;
+
+ 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_free2(nghttp2_free free_func, void *ptr, void *mem_user_data) {
+ free_func(ptr, 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
index 0000000..f83dbcb
--- /dev/null
+++ b/lib/nghttp2_mem.h
@@ -0,0 +1,45 @@
+/*
+ * 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_free2(nghttp2_free free_func, void *ptr, void *mem_user_data);
+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
index 0000000..521f981
--- /dev/null
+++ b/lib/nghttp2_net.h
@@ -0,0 +1,91 @@
+/*
+ * 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 */
+
+#include <nghttp2/nghttp2.h>
+
+#if defined(WIN32)
+/* Windows requires ws2_32 library for ntonl family functions. We
+ define inline functions for those function so that we don't have
+ dependency on that lib. */
+
+# ifdef _MSC_VER
+# define STIN static __inline
+# else
+# define STIN static inline
+# endif
+
+STIN uint32_t htonl(uint32_t hostlong) {
+ uint32_t res;
+ unsigned char *p = (unsigned char *)&res;
+ *p++ = (unsigned char)(hostlong >> 24);
+ *p++ = (hostlong >> 16) & 0xffu;
+ *p++ = (hostlong >> 8) & 0xffu;
+ *p = hostlong & 0xffu;
+ return res;
+}
+
+STIN uint16_t htons(uint16_t hostshort) {
+ uint16_t res;
+ unsigned char *p = (unsigned char *)&res;
+ *p++ = (unsigned char)(hostshort >> 8);
+ *p = hostshort & 0xffu;
+ return res;
+}
+
+STIN uint32_t ntohl(uint32_t netlong) {
+ uint32_t res;
+ unsigned char *p = (unsigned char *)&netlong;
+ res = (uint32_t)(*p++ << 24);
+ res += (uint32_t)(*p++ << 16);
+ res += (uint32_t)(*p++ << 8);
+ res += *p;
+ return res;
+}
+
+STIN uint16_t ntohs(uint16_t netshort) {
+ uint16_t res;
+ unsigned char *p = (unsigned char *)&netshort;
+ res = (uint16_t)(*p++ << 8);
+ res += *p;
+ return res;
+}
+
+#endif /* WIN32 */
+
+#endif /* NGHTTP2_NET_H */
diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c
new file mode 100644
index 0000000..43d4e95
--- /dev/null
+++ b/lib/nghttp2_option.c
@@ -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 "nghttp2_option.h"
+
+#include "nghttp2_session.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_no_recv_client_magic(nghttp2_option *option, int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC;
+ option->no_recv_client_magic = val;
+}
+
+void nghttp2_option_set_no_http_messaging(nghttp2_option *option, int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_NO_HTTP_MESSAGING;
+ option->no_http_messaging = val;
+}
+
+void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
+ uint32_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS;
+ option->max_reserved_remote_streams = val;
+}
+
+static void set_ext_type(uint8_t *ext_types, uint8_t type) {
+ ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7)));
+}
+
+void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
+ uint8_t type) {
+ if (type < 10) {
+ return;
+ }
+
+ option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES;
+ set_ext_type(option->user_recv_ext_types, type);
+}
+
+void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option,
+ uint8_t type) {
+ switch (type) {
+ case NGHTTP2_ALTSVC:
+ option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES;
+ option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ALTSVC;
+ return;
+ case NGHTTP2_ORIGIN:
+ option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES;
+ option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ORIGIN;
+ return;
+ case NGHTTP2_PRIORITY_UPDATE:
+ option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES;
+ option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_PRIORITY_UPDATE;
+ return;
+ default:
+ return;
+ }
+}
+
+void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK;
+ option->no_auto_ping_ack = val;
+}
+
+void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option,
+ size_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH;
+ option->max_send_header_block_length = val;
+}
+
+void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
+ size_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE;
+ option->max_deflate_dynamic_table_size = val;
+}
+
+void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS;
+ option->no_closed_streams = val;
+}
+
+void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK;
+ option->max_outbound_ack = val;
+}
+
+void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
+ option->max_settings = val;
+}
+
+void nghttp2_option_set_server_fallback_rfc7540_priorities(
+ nghttp2_option *option, int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES;
+ option->server_fallback_rfc7540_priorities = val;
+}
+
+void nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(
+ nghttp2_option *option, int val) {
+ option->opt_set_mask |=
+ NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
+ option->no_rfc9113_leading_and_trailing_ws_validation = val;
+}
+
+void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
+ uint64_t burst, uint64_t rate) {
+ option->opt_set_mask |= NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT;
+ option->stream_reset_burst = burst;
+ option->stream_reset_rate = rate;
+}
diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h
new file mode 100644
index 0000000..2259e18
--- /dev/null
+++ b/lib/nghttp2_option.h
@@ -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.
+ */
+#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_NO_RECV_CLIENT_MAGIC = 1 << 2,
+ NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3,
+ NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4,
+ NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5,
+ NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6,
+ NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7,
+ NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8,
+ NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
+ NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
+ NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
+ NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
+ NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
+ NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
+ NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15,
+} nghttp2_option_flag;
+
+/**
+ * Struct to store option values for nghttp2_session.
+ */
+struct nghttp2_option {
+ /**
+ * NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT
+ */
+ uint64_t stream_reset_burst;
+ uint64_t stream_reset_rate;
+ /**
+ * NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH
+ */
+ size_t max_send_header_block_length;
+ /**
+ * NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE
+ */
+ size_t max_deflate_dynamic_table_size;
+ /**
+ * NGHTTP2_OPT_MAX_OUTBOUND_ACK
+ */
+ size_t max_outbound_ack;
+ /**
+ * NGHTTP2_OPT_MAX_SETTINGS
+ */
+ size_t max_settings;
+ /**
+ * 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_MAX_RESERVED_REMOTE_STREAMS
+ */
+ uint32_t max_reserved_remote_streams;
+ /**
+ * NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES
+ */
+ uint32_t builtin_recv_ext_types;
+ /**
+ * NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE
+ */
+ int no_auto_window_update;
+ /**
+ * NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC
+ */
+ int no_recv_client_magic;
+ /**
+ * NGHTTP2_OPT_NO_HTTP_MESSAGING
+ */
+ int no_http_messaging;
+ /**
+ * NGHTTP2_OPT_NO_AUTO_PING_ACK
+ */
+ int no_auto_ping_ack;
+ /**
+ * NGHTTP2_OPT_NO_CLOSED_STREAMS
+ */
+ int no_closed_streams;
+ /**
+ * NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES
+ */
+ int server_fallback_rfc7540_priorities;
+ /**
+ * NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
+ */
+ int no_rfc9113_leading_and_trailing_ws_validation;
+ /**
+ * NGHTTP2_OPT_USER_RECV_EXT_TYPES
+ */
+ uint8_t user_recv_ext_types[32];
+};
+
+#endif /* NGHTTP2_OPTION_H */
diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c
new file mode 100644
index 0000000..2a3041d
--- /dev/null
+++ b/lib/nghttp2_outbound_item.c
@@ -0,0 +1,130 @@
+/*
+ * 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>
+#include <string.h>
+
+void nghttp2_outbound_item_init(nghttp2_outbound_item *item) {
+ item->cycle = 0;
+ item->qnext = NULL;
+ item->queued = 0;
+
+ memset(&item->aux_data, 0, sizeof(nghttp2_aux_data));
+}
+
+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;
+ default: {
+ nghttp2_ext_aux_data *aux_data;
+
+ aux_data = &item->aux_data.ext;
+
+ if (aux_data->builtin == 0) {
+ nghttp2_frame_extension_free(&frame->ext);
+ break;
+ }
+
+ switch (frame->hd.type) {
+ case NGHTTP2_ALTSVC:
+ nghttp2_frame_altsvc_free(&frame->ext, mem);
+ break;
+ case NGHTTP2_ORIGIN:
+ nghttp2_frame_origin_free(&frame->ext, mem);
+ break;
+ case NGHTTP2_PRIORITY_UPDATE:
+ nghttp2_frame_priority_update_free(&frame->ext, mem);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+ }
+}
+
+void nghttp2_outbound_queue_init(nghttp2_outbound_queue *q) {
+ q->head = q->tail = NULL;
+ q->n = 0;
+}
+
+void nghttp2_outbound_queue_push(nghttp2_outbound_queue *q,
+ nghttp2_outbound_item *item) {
+ if (q->tail) {
+ q->tail = q->tail->qnext = item;
+ } else {
+ q->head = q->tail = item;
+ }
+ ++q->n;
+}
+
+void nghttp2_outbound_queue_pop(nghttp2_outbound_queue *q) {
+ nghttp2_outbound_item *item;
+ if (!q->head) {
+ return;
+ }
+ item = q->head;
+ q->head = q->head->qnext;
+ item->qnext = NULL;
+ if (!q->head) {
+ q->tail = NULL;
+ }
+ --q->n;
+}
diff --git a/lib/nghttp2_outbound_item.h b/lib/nghttp2_outbound_item.h
new file mode 100644
index 0000000..bd4611b
--- /dev/null
+++ b/lib/nghttp2_outbound_item.h
@@ -0,0 +1,166 @@
+/*
+ * 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"
+
+/* struct used for HEADERS and PUSH_PROMISE frame */
+typedef struct {
+ nghttp2_data_provider data_prd;
+ void *stream_user_data;
+ /* error code when request HEADERS is canceled by RST_STREAM while
+ it is in queue. */
+ uint32_t error_code;
+ /* nonzero if request HEADERS is canceled. The error code is stored
+ in |error_code|. */
+ uint8_t canceled;
+} 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;
+ /**
+ * The flag to indicate that NGHTTP2_DATA_FLAG_NO_COPY is used.
+ */
+ uint8_t no_copy;
+} 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;
+
+/* struct used for extension frame */
+typedef struct {
+ /* nonzero if this extension frame is serialized by library
+ function, instead of user-defined callbacks. */
+ uint8_t builtin;
+} nghttp2_ext_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_ext_aux_data ext;
+} nghttp2_aux_data;
+
+struct nghttp2_outbound_item;
+typedef struct nghttp2_outbound_item nghttp2_outbound_item;
+
+struct nghttp2_outbound_item {
+ 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;
+ nghttp2_aux_data aux_data;
+ /* The priority used in priority comparison. Smaller is served
+ earlier. For PING, SETTINGS and non-DATA frames (excluding
+ response HEADERS frame) have dedicated cycle value defined above.
+ For DATA frame, cycle is computed by taking into account of
+ effective weight and frame payload length previously sent, so
+ that the amount of transmission is distributed across streams
+ proportional to effective weight (inside a tree). */
+ uint64_t cycle;
+ nghttp2_outbound_item *qnext;
+ /* nonzero if this object is queued, except for DATA or HEADERS
+ which are attached to stream as item. */
+ uint8_t queued;
+};
+
+/*
+ * Initializes |item|. No memory allocation is done in this function.
+ * Don't call nghttp2_outbound_item_free() until frame member is
+ * initialized.
+ */
+void nghttp2_outbound_item_init(nghttp2_outbound_item *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);
+
+/*
+ * queue for nghttp2_outbound_item.
+ */
+typedef struct {
+ nghttp2_outbound_item *head, *tail;
+ /* number of items in this queue. */
+ size_t n;
+} nghttp2_outbound_queue;
+
+void nghttp2_outbound_queue_init(nghttp2_outbound_queue *q);
+
+/* Pushes |item| into |q| */
+void nghttp2_outbound_queue_push(nghttp2_outbound_queue *q,
+ nghttp2_outbound_item *item);
+
+/* Pops |item| at the top from |q|. If |q| is empty, nothing
+ happens. */
+void nghttp2_outbound_queue_pop(nghttp2_outbound_queue *q);
+
+/* Returns the top item. */
+#define nghttp2_outbound_queue_top(Q) ((Q)->head)
+
+/* Returns the size of the queue */
+#define nghttp2_outbound_queue_size(Q) ((Q)->n)
+
+#endif /* NGHTTP2_OUTBOUND_ITEM_H */
diff --git a/lib/nghttp2_pq.c b/lib/nghttp2_pq.c
new file mode 100644
index 0000000..64353ac
--- /dev/null
+++ b/lib/nghttp2_pq.c
@@ -0,0 +1,183 @@
+/*
+ * 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"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "nghttp2_helper.h"
+
+void nghttp2_pq_init(nghttp2_pq *pq, nghttp2_less less, nghttp2_mem *mem) {
+ pq->mem = mem;
+ pq->capacity = 0;
+ pq->q = NULL;
+ pq->length = 0;
+ pq->less = less;
+}
+
+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) {
+ nghttp2_pq_entry *a = pq->q[i];
+ nghttp2_pq_entry *b = pq->q[j];
+
+ pq->q[i] = b;
+ b->index = i;
+ pq->q[j] = a;
+ a->index = j;
+}
+
+static void bubble_up(nghttp2_pq *pq, size_t index) {
+ size_t parent;
+ while (index != 0) {
+ parent = (index - 1) / 2;
+ if (!pq->less(pq->q[index], pq->q[parent])) {
+ return;
+ }
+ swap(pq, parent, index);
+ index = parent;
+ }
+}
+
+int nghttp2_pq_push(nghttp2_pq *pq, nghttp2_pq_entry *item) {
+ if (pq->capacity <= pq->length) {
+ void *nq;
+ size_t ncapacity;
+
+ ncapacity = nghttp2_max(4, (pq->capacity * 2));
+
+ nq = nghttp2_mem_realloc(pq->mem, pq->q,
+ ncapacity * sizeof(nghttp2_pq_entry *));
+ if (nq == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+ pq->capacity = ncapacity;
+ pq->q = nq;
+ }
+ pq->q[pq->length] = item;
+ item->index = pq->length;
+ ++pq->length;
+ bubble_up(pq, pq->length - 1);
+ return 0;
+}
+
+nghttp2_pq_entry *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 i, j, minindex;
+ for (;;) {
+ j = index * 2 + 1;
+ minindex = index;
+ for (i = 0; i < 2; ++i, ++j) {
+ if (j >= pq->length) {
+ break;
+ }
+ if (pq->less(pq->q[j], pq->q[minindex])) {
+ minindex = j;
+ }
+ }
+ if (minindex == index) {
+ return;
+ }
+ swap(pq, index, minindex);
+ index = minindex;
+ }
+}
+
+void nghttp2_pq_pop(nghttp2_pq *pq) {
+ if (pq->length > 0) {
+ pq->q[0] = pq->q[pq->length - 1];
+ pq->q[0]->index = 0;
+ --pq->length;
+ bubble_down(pq, 0);
+ }
+}
+
+void nghttp2_pq_remove(nghttp2_pq *pq, nghttp2_pq_entry *item) {
+ assert(pq->q[item->index] == item);
+
+ if (item->index == 0) {
+ nghttp2_pq_pop(pq);
+ return;
+ }
+
+ if (item->index == pq->length - 1) {
+ --pq->length;
+ return;
+ }
+
+ pq->q[item->index] = pq->q[pq->length - 1];
+ pq->q[item->index]->index = item->index;
+ --pq->length;
+
+ if (pq->less(item, pq->q[item->index])) {
+ bubble_down(pq, item->index);
+ } else {
+ bubble_up(pq, item->index);
+ }
+}
+
+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);
+ }
+ }
+}
+
+int nghttp2_pq_each(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg) {
+ size_t i;
+
+ if (pq->length == 0) {
+ return 0;
+ }
+ for (i = 0; i < pq->length; ++i) {
+ if ((*fun)(pq->q[i], arg)) {
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/lib/nghttp2_pq.h b/lib/nghttp2_pq.h
new file mode 100644
index 0000000..c8d90ef
--- /dev/null
+++ b/lib/nghttp2_pq.h
@@ -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_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 {
+ size_t index;
+} nghttp2_pq_entry;
+
+typedef struct {
+ /* The pointer to the pointer to the item stored */
+ nghttp2_pq_entry **q;
+ /* Memory allocator */
+ nghttp2_mem *mem;
+ /* The number of items stored */
+ 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 less function between items */
+ nghttp2_less less;
+} nghttp2_pq;
+
+/*
+ * Initializes priority queue |pq| with compare function |cmp|.
+ */
+void nghttp2_pq_init(nghttp2_pq *pq, nghttp2_less less, 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 succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory.
+ */
+int nghttp2_pq_push(nghttp2_pq *pq, nghttp2_pq_entry *item);
+
+/*
+ * Returns item at the top of the queue |pq|. If the queue is empty,
+ * this function returns NULL.
+ */
+nghttp2_pq_entry *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)(nghttp2_pq_entry *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);
+
+/*
+ * Applies |fun| to each item in |pq|. The |arg| is passed as arg
+ * parameter to callback function. This function must not change the
+ * ordering key. If the return value from callback is nonzero, this
+ * function returns 1 immediately without iterating remaining items.
+ * Otherwise this function returns 0.
+ */
+int nghttp2_pq_each(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg);
+
+/*
+ * Removes |item| from priority queue.
+ */
+void nghttp2_pq_remove(nghttp2_pq *pq, nghttp2_pq_entry *item);
+
+#endif /* NGHTTP2_PQ_H */
diff --git a/lib/nghttp2_priority_spec.c b/lib/nghttp2_priority_spec.c
new file mode 100644
index 0000000..c2196e3
--- /dev/null
+++ b/lib/nghttp2_priority_spec.c
@@ -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.
+ */
+#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;
+}
+
+void nghttp2_priority_spec_normalize_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;
+ }
+}
diff --git a/lib/nghttp2_priority_spec.h b/lib/nghttp2_priority_spec.h
new file mode 100644
index 0000000..92ece82
--- /dev/null
+++ b/lib/nghttp2_priority_spec.h
@@ -0,0 +1,42 @@
+/*
+ * 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>
+
+/*
+ * This function normalizes pri_spec->weight if it is out of range.
+ * If pri_spec->weight is less than NGHTTP2_MIN_WEIGHT, it is set to
+ * NGHTTP2_MIN_WEIGHT. If pri_spec->weight is larger than
+ * NGHTTP2_MAX_WEIGHT, it is set to NGHTTP2_MAX_WEIGHT.
+ */
+void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec);
+
+#endif /* NGHTTP2_PRIORITY_SPEC_H */
diff --git a/lib/nghttp2_queue.c b/lib/nghttp2_queue.c
new file mode 100644
index 0000000..055eb69
--- /dev/null
+++ b/lib/nghttp2_queue.c
@@ -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
index 0000000..a06fa6c
--- /dev/null
+++ b/lib/nghttp2_queue.h
@@ -0,0 +1,51 @@
+/*
+ * 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_ratelim.c b/lib/nghttp2_ratelim.c
new file mode 100644
index 0000000..7011655
--- /dev/null
+++ b/lib/nghttp2_ratelim.c
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_ratelim.h"
+#include "nghttp2_helper.h"
+
+void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate) {
+ rl->val = rl->burst = burst;
+ rl->rate = rate;
+ rl->tstamp = 0;
+}
+
+void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp) {
+ uint64_t d, gain;
+
+ if (tstamp == rl->tstamp) {
+ return;
+ }
+
+ if (tstamp > rl->tstamp) {
+ d = tstamp - rl->tstamp;
+ } else {
+ d = 1;
+ }
+
+ rl->tstamp = tstamp;
+
+ if (UINT64_MAX / d < rl->rate) {
+ rl->val = rl->burst;
+
+ return;
+ }
+
+ gain = rl->rate * d;
+
+ if (UINT64_MAX - gain < rl->val) {
+ rl->val = rl->burst;
+
+ return;
+ }
+
+ rl->val += gain;
+ rl->val = nghttp2_min(rl->val, rl->burst);
+}
+
+int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n) {
+ if (rl->val < n) {
+ return -1;
+ }
+
+ rl->val -= n;
+
+ return 0;
+}
diff --git a/lib/nghttp2_ratelim.h b/lib/nghttp2_ratelim.h
new file mode 100644
index 0000000..866ed3f
--- /dev/null
+++ b/lib/nghttp2_ratelim.h
@@ -0,0 +1,57 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_RATELIM_H
+#define NGHTTP2_RATELIM_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+typedef struct nghttp2_ratelim {
+ /* burst is the maximum value of val. */
+ uint64_t burst;
+ /* rate is the amount of value that is regenerated per 1 tstamp. */
+ uint64_t rate;
+ /* val is the amount of value available to drain. */
+ uint64_t val;
+ /* tstamp is the last timestamp in second resolution that is known
+ to this object. */
+ uint64_t tstamp;
+} nghttp2_ratelim;
+
+/* nghttp2_ratelim_init initializes |rl| with the given parameters. */
+void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate);
+
+/* nghttp2_ratelim_update updates rl->val with the current |tstamp|
+ given in second resolution. */
+void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp);
+
+/* nghttp2_ratelim_drain drains |n| from rl->val. It returns 0 if it
+ succeeds, or -1. */
+int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n);
+
+#endif /* NGHTTP2_RATELIM_H */
diff --git a/lib/nghttp2_rcbuf.c b/lib/nghttp2_rcbuf.c
new file mode 100644
index 0000000..7e7814d
--- /dev/null
+++ b/lib/nghttp2_rcbuf.c
@@ -0,0 +1,102 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_rcbuf.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "nghttp2_mem.h"
+#include "nghttp2_helper.h"
+
+int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size,
+ nghttp2_mem *mem) {
+ uint8_t *p;
+
+ p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size);
+ if (p == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ *rcbuf_ptr = (void *)p;
+
+ (*rcbuf_ptr)->mem_user_data = mem->mem_user_data;
+ (*rcbuf_ptr)->free = mem->free;
+ (*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf);
+ (*rcbuf_ptr)->len = size;
+ (*rcbuf_ptr)->ref = 1;
+
+ return 0;
+}
+
+int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
+ size_t srclen, nghttp2_mem *mem) {
+ int rv;
+
+ rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem);
+ if (rv != 0) {
+ return rv;
+ }
+
+ (*rcbuf_ptr)->len = srclen;
+ *nghttp2_cpymem((*rcbuf_ptr)->base, src, srclen) = '\0';
+
+ return 0;
+}
+
+/*
+ * Frees |rcbuf| itself, regardless of its reference cout.
+ */
+void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) {
+ nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data);
+}
+
+void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) {
+ if (rcbuf->ref == -1) {
+ return;
+ }
+
+ ++rcbuf->ref;
+}
+
+void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) {
+ if (rcbuf == NULL || rcbuf->ref == -1) {
+ return;
+ }
+
+ assert(rcbuf->ref > 0);
+
+ if (--rcbuf->ref == 0) {
+ nghttp2_rcbuf_del(rcbuf);
+ }
+}
+
+nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) {
+ nghttp2_vec res = {rcbuf->base, rcbuf->len};
+ return res;
+}
+
+int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf) {
+ return rcbuf->ref == -1;
+}
diff --git a/lib/nghttp2_rcbuf.h b/lib/nghttp2_rcbuf.h
new file mode 100644
index 0000000..6814e70
--- /dev/null
+++ b/lib/nghttp2_rcbuf.h
@@ -0,0 +1,80 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_RCBUF_H
+#define NGHTTP2_RCBUF_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+struct nghttp2_rcbuf {
+ /* custom memory allocator belongs to the mem parameter when
+ creating this object. */
+ void *mem_user_data;
+ nghttp2_free free;
+ /* The pointer to the underlying buffer */
+ uint8_t *base;
+ /* Size of buffer pointed by |base|. */
+ size_t len;
+ /* Reference count */
+ int32_t ref;
+};
+
+/*
+ * Allocates nghttp2_rcbuf object with |size| as initial buffer size.
+ * When the function succeeds, the reference count becomes 1.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM:
+ * Out of memory.
+ */
+int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem);
+
+/*
+ * Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of
+ * length |srclen|. This function allocates additional byte at the
+ * end and puts '\0' into it, so that the resulting buffer could be
+ * used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to
+ * |srclen|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM:
+ * Out of memory.
+ */
+int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
+ size_t srclen, nghttp2_mem *mem);
+
+/*
+ * Frees |rcbuf| itself, regardless of its reference cout.
+ */
+void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf);
+
+#endif /* NGHTTP2_RCBUF_H */
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
new file mode 100644
index 0000000..ce21caf
--- /dev/null
+++ b/lib/nghttp2_session.c
@@ -0,0 +1,8395 @@
+/*
+ * 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 <stdarg.h>
+
+#include "nghttp2_helper.h"
+#include "nghttp2_net.h"
+#include "nghttp2_priority_spec.h"
+#include "nghttp2_option.h"
+#include "nghttp2_http.h"
+#include "nghttp2_pq.h"
+#include "nghttp2_extpri.h"
+#include "nghttp2_time.h"
+#include "nghttp2_debug.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_code) {
+ return lib_error_code < 0 && lib_error_code > NGHTTP2_ERR_FATAL;
+}
+
+int nghttp2_is_fatal(int lib_error_code) {
+ return lib_error_code < NGHTTP2_ERR_FATAL;
+}
+
+static int session_enforce_http_messaging(nghttp2_session *session) {
+ return (session->opt_flags & NGHTTP2_OPTMASK_NO_HTTP_MESSAGING) == 0;
+}
+
+/*
+ * Returns nonzero if |frame| is trailer headers.
+ */
+static int session_trailer_headers(nghttp2_session *session,
+ nghttp2_stream *stream,
+ nghttp2_frame *frame) {
+ if (!stream || frame->hd.type != NGHTTP2_HEADERS) {
+ return 0;
+ }
+ if (session->server) {
+ return frame->headers.cat == NGHTTP2_HCAT_HEADERS;
+ }
+
+ return frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) == 0;
+}
+
+/* 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->last_sent_stream_id < stream_id) {
+ return 1;
+ }
+ return 0;
+ }
+ if (session_is_new_peer_stream_id(session, stream_id)) {
+ return 1;
+ }
+ return 0;
+}
+
+static int session_no_rfc7540_pri_no_fallback(nghttp2_session *session) {
+ return session->pending_no_rfc7540_priorities == 1 &&
+ !session->fallback_rfc7540_priorities;
+}
+
+static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) {
+ return (ext_types[type / 8] & (1 << (type & 0x7))) > 0;
+}
+
+static int session_call_error_callback(nghttp2_session *session,
+ int lib_error_code, const char *fmt,
+ ...) {
+ size_t bufsize;
+ va_list ap;
+ char *buf;
+ int rv;
+ nghttp2_mem *mem;
+
+ if (!session->callbacks.error_callback &&
+ !session->callbacks.error_callback2) {
+ return 0;
+ }
+
+ mem = &session->mem;
+
+ va_start(ap, fmt);
+ rv = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (rv < 0) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ bufsize = (size_t)(rv + 1);
+
+ buf = nghttp2_mem_malloc(mem, bufsize);
+ if (buf == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ va_start(ap, fmt);
+ rv = vsnprintf(buf, bufsize, fmt, ap);
+ va_end(ap);
+
+ if (rv < 0) {
+ nghttp2_mem_free(mem, buf);
+ /* vsnprintf may return error because of various things we can
+ imagine, but typically we don't want to drop session just for
+ debug callback. */
+ DEBUGF("error_callback: vsnprintf failed. The template was %s\n", fmt);
+ return 0;
+ }
+
+ if (session->callbacks.error_callback2) {
+ rv = session->callbacks.error_callback2(session, lib_error_code, buf,
+ (size_t)rv, session->user_data);
+ } else {
+ rv = session->callbacks.error_callback(session, buf, (size_t)rv,
+ session->user_data);
+ }
+
+ nghttp2_mem_free(mem, buf);
+
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ 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;
+ }
+
+ /* Ignore all incoming frames because we are going to tear down the
+ session. */
+ session->iframe.state = NGHTTP2_IB_IGN_ALL;
+
+ 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 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_DATA:
+ break;
+ 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);
+
+ nghttp2_mem_free(mem, iframe->iv);
+
+ iframe->iv = NULL;
+ iframe->niv = 0;
+ iframe->max_niv = 0;
+
+ 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;
+ default:
+ /* extension frame */
+ if (check_ext_type_set(session->user_recv_ext_types,
+ iframe->frame.hd.type)) {
+ nghttp2_frame_extension_free(&iframe->frame.ext);
+ } else {
+ switch (iframe->frame.hd.type) {
+ case NGHTTP2_ALTSVC:
+ if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) == 0) {
+ break;
+ }
+ nghttp2_frame_altsvc_free(&iframe->frame.ext, mem);
+ break;
+ case NGHTTP2_ORIGIN:
+ if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN) == 0) {
+ break;
+ }
+ nghttp2_frame_origin_free(&iframe->frame.ext, mem);
+ break;
+ case NGHTTP2_PRIORITY_UPDATE:
+ if ((session->builtin_recv_ext_types &
+ NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) {
+ break;
+ }
+ /* Do not call nghttp2_frame_priority_update_free, because all
+ fields point to sbuf. */
+ break;
+ }
+ }
+
+ 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->raw_lbuf = NULL;
+
+ iframe->payloadleft = 0;
+ iframe->padlen = 0;
+}
+
+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_DEFAULT_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;
+ settings->no_rfc7540_priorities = UINT32_MAX;
+}
+
+static void active_outbound_item_reset(nghttp2_active_outbound_item *aob,
+ nghttp2_mem *mem) {
+ DEBUGF("send: reset nghttp2_active_outbound_item\n");
+ DEBUGF("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;
+}
+
+#define NGHTTP2_STREAM_MAX_CYCLE_GAP ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX)
+
+static int stream_less(const void *lhsx, const void *rhsx) {
+ const nghttp2_stream *lhs, *rhs;
+
+ lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
+ rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
+
+ if (lhs->cycle == rhs->cycle) {
+ return lhs->seq < rhs->seq;
+ }
+
+ return rhs->cycle - lhs->cycle <= NGHTTP2_STREAM_MAX_CYCLE_GAP;
+}
+
+int nghttp2_enable_strict_preface = 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;
+ size_t nbuffer;
+ size_t max_deflate_dynamic_table_size =
+ NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
+ size_t i;
+
+ 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 */
+
+ nghttp2_stream_init(&(*session_ptr)->root, 0, NGHTTP2_STREAM_FLAG_NONE,
+ NGHTTP2_STREAM_IDLE, NGHTTP2_DEFAULT_WEIGHT, 0, 0, NULL,
+ mem);
+
+ (*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)->pending_local_max_concurrent_stream =
+ NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
+ (*session_ptr)->pending_enable_push = 1;
+ (*session_ptr)->pending_no_rfc7540_priorities = UINT8_MAX;
+
+ nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim,
+ NGHTTP2_DEFAULT_STREAM_RESET_BURST,
+ NGHTTP2_DEFAULT_STREAM_RESET_RATE);
+
+ if (server) {
+ (*session_ptr)->server = 1;
+ }
+
+ init_settings(&(*session_ptr)->remote_settings);
+ init_settings(&(*session_ptr)->local_settings);
+
+ (*session_ptr)->max_incoming_reserved_streams =
+ NGHTTP2_MAX_INCOMING_RESERVED_STREAMS;
+
+ /* Limit max outgoing concurrent streams to sensible value */
+ (*session_ptr)->remote_settings.max_concurrent_streams = 100;
+
+ (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
+ (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM;
+ (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_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_MAX_RESERVED_REMOTE_STREAMS) {
+
+ (*session_ptr)->max_incoming_reserved_streams =
+ option->max_reserved_remote_streams;
+ }
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC) &&
+ option->no_recv_client_magic) {
+
+ (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC;
+ }
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_NO_HTTP_MESSAGING) &&
+ option->no_http_messaging) {
+
+ (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING;
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_USER_RECV_EXT_TYPES) {
+ memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types,
+ sizeof((*session_ptr)->user_recv_ext_types));
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES) {
+ (*session_ptr)->builtin_recv_ext_types = option->builtin_recv_ext_types;
+ }
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) &&
+ option->no_auto_ping_ack) {
+ (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK;
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH) {
+ (*session_ptr)->max_send_header_block_length =
+ option->max_send_header_block_length;
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE) {
+ max_deflate_dynamic_table_size = option->max_deflate_dynamic_table_size;
+ }
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_NO_CLOSED_STREAMS) &&
+ option->no_closed_streams) {
+ (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS;
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) {
+ (*session_ptr)->max_outbound_ack = option->max_outbound_ack;
+ }
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) &&
+ option->max_settings) {
+ (*session_ptr)->max_settings = option->max_settings;
+ }
+
+ if ((option->opt_set_mask &
+ NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES) &&
+ option->server_fallback_rfc7540_priorities) {
+ (*session_ptr)->opt_flags |=
+ NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES;
+ }
+
+ if ((option->opt_set_mask &
+ NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) &&
+ option->no_rfc9113_leading_and_trailing_ws_validation) {
+ (*session_ptr)->opt_flags |=
+ NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
+ }
+
+ if (option->opt_set_mask & NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT) {
+ nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim,
+ option->stream_reset_burst,
+ option->stream_reset_rate);
+ }
+ }
+
+ rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
+ max_deflate_dynamic_table_size, 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;
+ }
+
+ nbuffer = ((*session_ptr)->max_send_header_block_length +
+ NGHTTP2_FRAMEBUF_CHUNKLEN - 1) /
+ NGHTTP2_FRAMEBUF_CHUNKLEN;
+
+ if (nbuffer == 0) {
+ nbuffer = 1;
+ }
+
+ /* 1 for Pad Field. */
+ rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs,
+ NGHTTP2_FRAMEBUF_CHUNKLEN, nbuffer, 1,
+ NGHTTP2_FRAME_HDLEN + 1, mem);
+ if (rv != 0) {
+ goto fail_aob_framebuf;
+ }
+
+ nghttp2_map_init(&(*session_ptr)->streams, mem);
+
+ active_outbound_item_reset(&(*session_ptr)->aob, mem);
+
+ (*session_ptr)->callbacks = *callbacks;
+ (*session_ptr)->user_data = user_data;
+
+ session_inbound_frame_reset(*session_ptr);
+
+ if (nghttp2_enable_strict_preface) {
+ nghttp2_inbound_frame *iframe = &(*session_ptr)->iframe;
+
+ if (server && ((*session_ptr)->opt_flags &
+ NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC) == 0) {
+ iframe->state = NGHTTP2_IB_READ_CLIENT_MAGIC;
+ iframe->payloadleft = NGHTTP2_CLIENT_MAGIC_LEN;
+ } else {
+ iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS;
+ }
+
+ if (!server) {
+ (*session_ptr)->aob.state = NGHTTP2_OB_SEND_CLIENT_MAGIC;
+ nghttp2_bufs_add(&(*session_ptr)->aob.framebufs, NGHTTP2_CLIENT_MAGIC,
+ NGHTTP2_CLIENT_MAGIC_LEN);
+ }
+ }
+
+ for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) {
+ nghttp2_pq_init(&(*session_ptr)->sched[i].ob_data, stream_less, mem);
+ }
+
+ return 0;
+
+fail_aob_framebuf:
+ nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater);
+fail_hd_inflater:
+ nghttp2_hd_deflate_free(&(*session_ptr)->hd_deflater);
+fail_hd_deflater:
+ 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(void *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_q_free(nghttp2_outbound_queue *q, nghttp2_mem *mem) {
+ nghttp2_outbound_item *item, *next;
+ for (item = q->head; item;) {
+ next = item->qnext;
+ nghttp2_outbound_item_free(item, mem);
+ nghttp2_mem_free(mem, item);
+ item = next;
+ }
+}
+
+static int inflight_settings_new(nghttp2_inflight_settings **settings_ptr,
+ const nghttp2_settings_entry *iv, size_t niv,
+ nghttp2_mem *mem) {
+ *settings_ptr = nghttp2_mem_malloc(mem, sizeof(nghttp2_inflight_settings));
+ if (!*settings_ptr) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ if (niv > 0) {
+ (*settings_ptr)->iv = nghttp2_frame_iv_copy(iv, niv, mem);
+ if (!(*settings_ptr)->iv) {
+ nghttp2_mem_free(mem, *settings_ptr);
+ return NGHTTP2_ERR_NOMEM;
+ }
+ } else {
+ (*settings_ptr)->iv = NULL;
+ }
+
+ (*settings_ptr)->niv = niv;
+ (*settings_ptr)->next = NULL;
+
+ return 0;
+}
+
+static void inflight_settings_del(nghttp2_inflight_settings *settings,
+ nghttp2_mem *mem) {
+ if (!settings) {
+ return;
+ }
+
+ nghttp2_mem_free(mem, settings->iv);
+ nghttp2_mem_free(mem, settings);
+}
+
+void nghttp2_session_del(nghttp2_session *session) {
+ nghttp2_mem *mem;
+ nghttp2_inflight_settings *settings;
+ size_t i;
+
+ if (session == NULL) {
+ return;
+ }
+
+ mem = &session->mem;
+
+ for (settings = session->inflight_settings_head; settings;) {
+ nghttp2_inflight_settings *next = settings->next;
+ inflight_settings_del(settings, mem);
+ settings = next;
+ }
+
+ for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) {
+ nghttp2_pq_free(&session->sched[i].ob_data);
+ }
+ nghttp2_stream_free(&session->root);
+
+ /* 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_q_free(&session->ob_urgent, mem);
+ ob_q_free(&session->ob_reg, mem);
+ ob_q_free(&session->ob_syn, 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_priority_spec pri_spec_default;
+ const nghttp2_priority_spec *pri_spec = pri_spec_in;
+
+ assert((!session->server && session->pending_no_rfc7540_priorities != 1) ||
+ (session->server && !session_no_rfc7540_pri_no_fallback(session)));
+ assert(pri_spec->stream_id != stream->stream_id);
+
+ if (!nghttp2_stream_in_dep_tree(stream)) {
+ return 0;
+ }
+
+ if (pri_spec->stream_id != 0) {
+ dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
+
+ if (!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) {
+ dep_stream = &session->root;
+ } else if (nghttp2_stream_dep_find_ancestor(dep_stream, stream)) {
+ DEBUGF("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);
+ rv = nghttp2_stream_dep_add_subtree(stream->dep_prev, dep_stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ assert(dep_stream);
+
+ if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
+ /* This is minor optimization when just weight is changed. */
+ nghttp2_stream_change_weight(stream, pri_spec->weight);
+
+ return 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) {
+ rv = nghttp2_stream_dep_insert_subtree(dep_stream, stream);
+ } else {
+ rv = nghttp2_stream_dep_add_subtree(dep_stream, stream);
+ }
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+static uint64_t pq_get_first_cycle(nghttp2_pq *pq) {
+ nghttp2_stream *stream;
+
+ if (nghttp2_pq_empty(pq)) {
+ return 0;
+ }
+
+ stream = nghttp2_struct_of(nghttp2_pq_top(pq), nghttp2_stream, pq_entry);
+ return stream->cycle;
+}
+
+static int session_ob_data_push(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ int rv;
+ uint32_t urgency;
+ int inc;
+ nghttp2_pq *pq;
+
+ assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
+ assert(stream->queued == 0);
+
+ urgency = nghttp2_extpri_uint8_urgency(stream->extpri);
+ inc = nghttp2_extpri_uint8_inc(stream->extpri);
+
+ assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS);
+
+ pq = &session->sched[urgency].ob_data;
+
+ stream->cycle = pq_get_first_cycle(pq);
+ if (inc) {
+ stream->cycle += stream->last_writelen;
+ }
+
+ rv = nghttp2_pq_push(pq, &stream->pq_entry);
+ if (rv != 0) {
+ return rv;
+ }
+
+ stream->queued = 1;
+
+ return 0;
+}
+
+static void session_ob_data_remove(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ uint32_t urgency;
+
+ assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
+ assert(stream->queued == 1);
+
+ urgency = nghttp2_extpri_uint8_urgency(stream->extpri);
+
+ assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS);
+
+ nghttp2_pq_remove(&session->sched[urgency].ob_data, &stream->pq_entry);
+
+ stream->queued = 0;
+}
+
+static int session_attach_stream_item(nghttp2_session *session,
+ nghttp2_stream *stream,
+ nghttp2_outbound_item *item) {
+ int rv;
+
+ rv = nghttp2_stream_attach_item(stream, item);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) {
+ return 0;
+ }
+
+ return session_ob_data_push(session, stream);
+}
+
+static void session_detach_stream_item(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ nghttp2_stream_detach_item(stream);
+
+ if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
+ !stream->queued) {
+ return;
+ }
+
+ session_ob_data_remove(session, stream);
+}
+
+static void session_defer_stream_item(nghttp2_session *session,
+ nghttp2_stream *stream, uint8_t flags) {
+ nghttp2_stream_defer_item(stream, flags);
+
+ if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
+ !stream->queued) {
+ return;
+ }
+
+ session_ob_data_remove(session, stream);
+}
+
+static int session_resume_deferred_stream_item(nghttp2_session *session,
+ nghttp2_stream *stream,
+ uint8_t flags) {
+ int rv;
+
+ rv = nghttp2_stream_resume_deferred_item(stream, flags);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
+ (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL)) {
+ return 0;
+ }
+
+ return session_ob_data_push(session, stream);
+}
+
+static nghttp2_outbound_item *
+session_sched_get_next_outbound_item(nghttp2_session *session) {
+ size_t i;
+ nghttp2_pq_entry *ent;
+ nghttp2_stream *stream;
+
+ for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) {
+ ent = nghttp2_pq_top(&session->sched[i].ob_data);
+ if (!ent) {
+ continue;
+ }
+
+ stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry);
+ return stream->item;
+ }
+
+ return NULL;
+}
+
+static int session_sched_empty(nghttp2_session *session) {
+ size_t i;
+
+ for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) {
+ if (!nghttp2_pq_empty(&session->sched[i].ob_data)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void session_sched_reschedule_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ nghttp2_pq *pq;
+ uint32_t urgency = nghttp2_extpri_uint8_urgency(stream->extpri);
+ int inc = nghttp2_extpri_uint8_inc(stream->extpri);
+ uint64_t penalty = (uint64_t)stream->last_writelen;
+ int rv;
+
+ (void)rv;
+
+ assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS);
+
+ pq = &session->sched[urgency].ob_data;
+
+ if (!inc || nghttp2_pq_size(pq) == 1) {
+ return;
+ }
+
+ nghttp2_pq_remove(pq, &stream->pq_entry);
+
+ stream->cycle += penalty;
+
+ rv = nghttp2_pq_push(pq, &stream->pq_entry);
+
+ assert(0 == rv);
+}
+
+static int session_update_stream_priority(nghttp2_session *session,
+ nghttp2_stream *stream,
+ uint8_t u8extpri) {
+ if (stream->extpri == u8extpri) {
+ return 0;
+ }
+
+ if (stream->queued) {
+ session_ob_data_remove(session, stream);
+
+ stream->extpri = u8extpri;
+
+ return session_ob_data_push(session, stream);
+ }
+
+ stream->extpri = u8extpri;
+
+ return 0;
+}
+
+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);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ if (!stream) {
+ return NGHTTP2_ERR_STREAM_CLOSED;
+ }
+
+ if (stream->item) {
+ return NGHTTP2_ERR_DATA_EXIST;
+ }
+
+ rv = session_attach_stream_item(session, stream, item);
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+ case 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_syn, which is not
+ desirable. */
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST ||
+ (stream && stream->state == NGHTTP2_STREAM_RESERVED)) {
+ nghttp2_outbound_queue_push(&session->ob_syn, item);
+ item->queued = 1;
+ return 0;
+ ;
+ }
+
+ nghttp2_outbound_queue_push(&session->ob_reg, item);
+ item->queued = 1;
+ return 0;
+ case NGHTTP2_SETTINGS:
+ case NGHTTP2_PING:
+ nghttp2_outbound_queue_push(&session->ob_urgent, item);
+ item->queued = 1;
+ return 0;
+ case NGHTTP2_RST_STREAM:
+ if (stream) {
+ stream->state = NGHTTP2_STREAM_CLOSING;
+ }
+ nghttp2_outbound_queue_push(&session->ob_reg, item);
+ item->queued = 1;
+ return 0;
+ case NGHTTP2_PUSH_PROMISE: {
+ nghttp2_headers_aux_data *aux_data;
+ nghttp2_priority_spec pri_spec;
+
+ aux_data = &item->aux_data.headers;
+
+ if (!stream) {
+ return NGHTTP2_ERR_STREAM_CLOSED;
+ }
+
+ 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_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED,
+ aux_data->stream_user_data)) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ /* We don't have to call nghttp2_session_adjust_closed_stream()
+ here, since stream->stream_id is local stream_id, and it does
+ not affect closed stream count. */
+
+ nghttp2_outbound_queue_push(&session->ob_reg, item);
+ item->queued = 1;
+
+ return 0;
+ }
+ case NGHTTP2_WINDOW_UPDATE:
+ if (stream) {
+ stream->window_update_queued = 1;
+ } else if (frame->hd.stream_id == 0) {
+ session->window_update_queued = 1;
+ }
+ nghttp2_outbound_queue_push(&session->ob_reg, item);
+ item->queued = 1;
+ return 0;
+ default:
+ nghttp2_outbound_queue_push(&session->ob_reg, item);
+ item->queued = 1;
+ 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;
+ }
+
+ /* Sending RST_STREAM to an idle stream is subject to protocol
+ violation. Historically, nghttp2 allows this. In order not to
+ disrupt the existing applications, we don't error out this case
+ and simply ignore it. */
+ if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+ if ((uint32_t)stream_id >= session->next_stream_id) {
+ return 0;
+ }
+ } else if (session->last_recv_stream_id < stream_id) {
+ return 0;
+ }
+
+ /* Cancel pending request HEADERS in ob_syn if this RST_STREAM
+ refers to that stream. */
+ if (!session->server && nghttp2_session_is_my_stream_id(session, stream_id) &&
+ nghttp2_outbound_queue_top(&session->ob_syn)) {
+ nghttp2_headers_aux_data *aux_data;
+ nghttp2_frame *headers_frame;
+
+ headers_frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame;
+ assert(headers_frame->hd.type == NGHTTP2_HEADERS);
+
+ if (headers_frame->hd.stream_id <= stream_id) {
+
+ for (item = session->ob_syn.head; item; item = item->qnext) {
+ aux_data = &item->aux_data.headers;
+
+ if (item->frame.hd.stream_id < stream_id) {
+ continue;
+ }
+
+ /* stream_id in ob_syn queue must be strictly increasing. If
+ we found larger ID, then we can break here. */
+ if (item->frame.hd.stream_id > stream_id || aux_data->canceled) {
+ break;
+ }
+
+ aux_data->error_code = error_code;
+ aux_data->canceled = 1;
+
+ return 0;
+ }
+ }
+ }
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_outbound_item_init(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;
+ 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 (session->opt_flags &
+ NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
+ flags |= NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
+ }
+
+ if (stream) {
+ assert(stream->state == NGHTTP2_STREAM_IDLE);
+ assert((stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
+ nghttp2_stream_in_dep_tree(stream));
+
+ if (nghttp2_stream_in_dep_tree(stream)) {
+ assert(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
+ nghttp2_session_detach_idle_stream(session, stream);
+ rv = nghttp2_stream_dep_remove(stream);
+ if (rv != 0) {
+ return NULL;
+ }
+
+ if (session_no_rfc7540_pri_no_fallback(session)) {
+ stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
+ }
+ }
+ } else {
+ stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
+ if (stream == NULL) {
+ return NULL;
+ }
+
+ stream_alloc = 1;
+ }
+
+ if (session_no_rfc7540_pri_no_fallback(session) ||
+ session->remote_settings.no_rfc7540_priorities == 1) {
+ /* For client which has not received server
+ SETTINGS_NO_RFC7540_PRIORITIES = 1, send a priority signal
+ opportunistically. */
+ if (session->server ||
+ session->remote_settings.no_rfc7540_priorities == 1) {
+ nghttp2_priority_spec_default_init(&pri_spec_default);
+ pri_spec = &pri_spec_default;
+ }
+
+ if (session->pending_no_rfc7540_priorities == 1) {
+ flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
+ }
+ } else if (pri_spec->stream_id != 0) {
+ dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
+
+ if (!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. This handles the case when
+ pri_spec->stream_id == stream_id. This happens because we
+ don't check pri_spec->stream_id against new stream ID in
+ nghttp2_submit_request. This also handles the case when idle
+ stream created by PRIORITY frame was opened. Somehow we
+ first remove the idle stream from dependency tree. This is
+ done to simplify code base, but ideally we should retain old
+ dependency. But I'm not sure this adds values. */
+ nghttp2_priority_spec_default_init(&pri_spec_default);
+ pri_spec = &pri_spec_default;
+ }
+ }
+
+ if (initial_state == NGHTTP2_STREAM_RESERVED) {
+ flags |= NGHTTP2_STREAM_FLAG_PUSH;
+ }
+
+ if (stream_alloc) {
+ nghttp2_stream_init(stream, stream_id, flags, initial_state,
+ pri_spec->weight,
+ (int32_t)session->remote_settings.initial_window_size,
+ (int32_t)session->local_settings.initial_window_size,
+ stream_user_data, mem);
+
+ if (session_no_rfc7540_pri_no_fallback(session)) {
+ stream->seq = session->stream_seq++;
+ }
+
+ rv = nghttp2_map_insert(&session->streams, stream_id, stream);
+ if (rv != 0) {
+ nghttp2_stream_free(stream);
+ nghttp2_mem_free(mem, stream);
+ return NULL;
+ }
+ } else {
+ stream->flags = flags;
+ stream->state = initial_state;
+ stream->weight = pri_spec->weight;
+ stream->stream_user_data = stream_user_data;
+ }
+
+ switch (initial_state) {
+ case NGHTTP2_STREAM_RESERVED:
+ if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+ /* reserved (local) */
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ } else {
+ /* reserved (remote) */
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+ ++session->num_incoming_reserved_streams;
+ }
+ /* 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. */
+ 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;
+ }
+ }
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
+ return stream;
+ }
+
+ if (pri_spec->stream_id == 0) {
+ dep_stream = &session->root;
+ }
+
+ assert(dep_stream);
+
+ if (pri_spec->exclusive) {
+ rv = nghttp2_stream_dep_insert(dep_stream, stream);
+ if (rv != 0) {
+ return NULL;
+ }
+ } else {
+ nghttp2_stream_dep_add(dep_stream, 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;
+ int is_my_stream_id;
+
+ mem = &session->mem;
+ stream = nghttp2_session_get_stream(session, stream_id);
+
+ if (!stream) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ DEBUGF("stream: stream(%p)=%d close\n", stream, stream->stream_id);
+
+ if (stream->item) {
+ nghttp2_outbound_item *item;
+
+ item = stream->item;
+
+ session_detach_stream_item(session, stream);
+
+ /* 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;
+ }
+ }
+
+ is_my_stream_id = nghttp2_session_is_my_stream_id(session, stream_id);
+
+ /* pushed streams which is not opened yet is not counted toward max
+ concurrent limits */
+ if ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH)) {
+ if (!is_my_stream_id) {
+ --session->num_incoming_reserved_streams;
+ }
+ } else {
+ if (is_my_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->pending_no_rfc7540_priorities == 1) {
+ return nghttp2_session_destroy_stream(session, stream);
+ }
+
+ if ((session->opt_flags & NGHTTP2_OPTMASK_NO_CLOSED_STREAMS) == 0 &&
+ session->server && !is_my_stream_id &&
+ 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 {
+ rv = nghttp2_session_destroy_stream(session, stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+int nghttp2_session_destroy_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ nghttp2_mem *mem;
+ int rv;
+
+ DEBUGF("stream: destroy closed stream(%p)=%d\n", stream, stream->stream_id);
+
+ mem = &session->mem;
+
+ if (nghttp2_stream_in_dep_tree(stream)) {
+ rv = nghttp2_stream_dep_remove(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ nghttp2_map_remove(&session->streams, stream->stream_id);
+ nghttp2_stream_free(stream);
+ nghttp2_mem_free(mem, stream);
+
+ return 0;
+}
+
+void nghttp2_session_keep_closed_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ DEBUGF("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;
+}
+
+void nghttp2_session_keep_idle_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ DEBUGF("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;
+}
+
+void nghttp2_session_detach_idle_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ nghttp2_stream *prev_stream, *next_stream;
+
+ DEBUGF("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;
+}
+
+int nghttp2_session_adjust_closed_stream(nghttp2_session *session) {
+ size_t num_stream_max;
+ int rv;
+
+ if (session->local_settings.max_concurrent_streams ==
+ NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS) {
+ num_stream_max = session->pending_local_max_concurrent_stream;
+ } else {
+ num_stream_max = session->local_settings.max_concurrent_streams;
+ }
+
+ DEBUGF("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 >
+ num_stream_max) {
+ nghttp2_stream *head_stream;
+ nghttp2_stream *next;
+
+ head_stream = session->closed_stream_head;
+
+ assert(head_stream);
+
+ next = head_stream->closed_next;
+
+ rv = nghttp2_session_destroy_stream(session, head_stream);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* head_stream is now freed */
+
+ session->closed_stream_head = next;
+
+ if (session->closed_stream_head) {
+ session->closed_stream_head->closed_prev = NULL;
+ } else {
+ session->closed_stream_tail = NULL;
+ }
+
+ --session->num_closed_streams;
+ }
+
+ return 0;
+}
+
+int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
+ size_t max;
+ int rv;
+
+ /* Make minimum number of idle streams 16, and maximum 100, which
+ are arbitrary chosen numbers. */
+ max = nghttp2_min(
+ 100, nghttp2_max(
+ 16, nghttp2_min(session->local_settings.max_concurrent_streams,
+ session->pending_local_max_concurrent_stream)));
+
+ DEBUGF("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;
+ nghttp2_stream *next;
+
+ head = session->idle_stream_head;
+ assert(head);
+
+ next = head->closed_next;
+
+ rv = nghttp2_session_destroy_stream(session, head);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* head is now destroyed */
+
+ session->idle_stream_head = next;
+
+ if (session->idle_stream_head) {
+ session->idle_stream_head->closed_prev = NULL;
+ } else {
+ session->idle_stream_tail = NULL;
+ }
+
+ --session->num_idle_streams;
+ }
+
+ return 0;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * Returns nonzero if local endpoint allows reception of new stream
+ * from remote.
+ */
+static int session_allow_incoming_new_stream(nghttp2_session *session) {
+ return (session->goaway_flags &
+ (NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_SENT)) == 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 ||
+ (nghttp2_session_want_read(session) == 0 &&
+ nghttp2_session_want_write(session) == 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;
+}
+
+int nghttp2_session_check_request_allowed(nghttp2_session *session) {
+ return !session->server && session->next_stream_id <= INT32_MAX &&
+ (session->goaway_flags & NGHTTP2_GOAWAY_RECV) == 0 &&
+ !session_is_closing(session);
+}
+
+/*
+ * This function checks request HEADERS 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.
+ * NGHTTP2_ERR_STREAM_CLOSING
+ * request HEADERS was canceled by RST_STREAM while it is in queue.
+ */
+static int session_predicate_request_headers_send(nghttp2_session *session,
+ nghttp2_outbound_item *item) {
+ if (item->aux_data.headers.canceled) {
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+ /* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND),
+ GOAWAY was received from peer, or session is about to close, new
+ request is not allowed. */
+ if ((session->goaway_flags & NGHTTP2_GOAWAY_RECV) ||
+ session_is_closing(session)) {
+ 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.
+ * NGHTTP2_ERR_PROTO
+ * Client side attempted to send response.
+ */
+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 (!session->server) {
+ return NGHTTP2_ERR_PROTO;
+ }
+ if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+ return NGHTTP2_ERR_INVALID_STREAM_ID;
+ }
+ switch (stream->state) {
+ case NGHTTP2_STREAM_OPENING:
+ return 0;
+ case NGHTTP2_STREAM_CLOSING:
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ default:
+ 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.
+ * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
+ * New stream cannot be created because GOAWAY is already sent or
+ * received.
+ * NGHTTP2_ERR_PROTO
+ * Client side attempted to send push response.
+ */
+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 (!session->server) {
+ return NGHTTP2_ERR_PROTO;
+ }
+ if (stream->state != NGHTTP2_STREAM_RESERVED) {
+ return NGHTTP2_ERR_PROTO;
+ }
+ if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) {
+ return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
+ }
+ 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);
+
+ switch (stream->state) {
+ case NGHTTP2_STREAM_OPENED:
+ return 0;
+ case NGHTTP2_STREAM_CLOSING:
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ default:
+ if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+ return 0;
+ }
+ 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 (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+
+ 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 (stream->state == NGHTTP2_STREAM_CLOSING) {
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+ if (state_reserved_local(session, stream)) {
+ return NGHTTP2_ERR_INVALID_STREAM_STATE;
+ }
+ return 0;
+}
+
+static int session_predicate_altsvc_send(nghttp2_session *session,
+ int32_t stream_id) {
+ nghttp2_stream *stream;
+
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+
+ if (stream_id == 0) {
+ return 0;
+ }
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+ if (stream == NULL) {
+ return NGHTTP2_ERR_STREAM_CLOSED;
+ }
+ if (stream->state == NGHTTP2_STREAM_CLOSING) {
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+
+ return 0;
+}
+
+static int session_predicate_origin_send(nghttp2_session *session) {
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+ return 0;
+}
+
+static int session_predicate_priority_update_send(nghttp2_session *session,
+ int32_t stream_id) {
+ nghttp2_stream *stream;
+
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+ if (stream == NULL) {
+ return 0;
+ }
+ if (stream->state == NGHTTP2_STREAM_CLOSING) {
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+ if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+ 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("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("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 (ssize_t)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 (ssize_t)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) {
+ 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 = (size_t)padded_payloadlen - frame->hd.length;
+
+ DEBUGF("send: padding selected: payloadlen=%zd, padlen=%zu\n",
+ padded_payloadlen, padlen);
+
+ nghttp2_frame_add_pad(framebufs, &frame->hd, padlen, 0);
+
+ 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;
+}
+
+static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs,
+ nghttp2_frame *frame) {
+ ssize_t rv;
+ nghttp2_buf *buf;
+ size_t buflen;
+ size_t framelen;
+
+ assert(session->callbacks.pack_extension_callback);
+
+ buf = &bufs->head->buf;
+ buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN);
+
+ rv = session->callbacks.pack_extension_callback(session, buf->last, buflen,
+ frame, session->user_data);
+ if (rv == NGHTTP2_ERR_CANCEL) {
+ return (int)rv;
+ }
+
+ if (rv < 0 || (size_t)rv > buflen) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ framelen = (size_t)rv;
+
+ frame->hd.length = framelen;
+
+ assert(buf->pos == buf->last);
+ buf->last += framelen;
+ buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+ nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+ return 0;
+}
+
+/*
+ * 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 rv;
+ nghttp2_frame *frame;
+ nghttp2_mem *mem;
+
+ mem = &session->mem;
+ frame = &item->frame;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ 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 was already closed, nghttp2_session_get_stream()
+ // returns NULL, but item is still attached to the stream.
+ // Search stream including closed again.
+ stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
+ if (stream) {
+ session_detach_stream_item(session, stream);
+ }
+
+ 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);
+
+ session_defer_stream_item(session, stream,
+ NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
+
+ session->aob.item = NULL;
+ active_outbound_item_reset(&session->aob, mem);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ rv = nghttp2_session_pack_data(session, &session->aob.framebufs,
+ next_readmax, frame, &item->aux_data.data,
+ stream);
+ if (rv == NGHTTP2_ERR_PAUSE) {
+ return rv;
+ }
+ if (rv == NGHTTP2_ERR_DEFERRED) {
+ session_defer_stream_item(session, stream,
+ NGHTTP2_STREAM_FLAG_DEFERRED_USER);
+
+ session->aob.item = NULL;
+ active_outbound_item_reset(&session->aob, mem);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ session_detach_stream_item(session, stream);
+
+ rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ if (rv != 0) {
+ session_detach_stream_item(session, stream);
+
+ return rv;
+ }
+ return 0;
+ }
+ case NGHTTP2_HEADERS: {
+ nghttp2_headers_aux_data *aux_data;
+ size_t estimated_payloadlen;
+
+ aux_data = &item->aux_data.headers;
+
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ /* initial HEADERS, which opens stream */
+ nghttp2_stream *stream;
+
+ 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;
+ }
+
+ /* We don't call nghttp2_session_adjust_closed_stream() here,
+ since we don't keep closed stream in client side */
+
+ rv = session_predicate_request_headers_send(session, item);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (session_enforce_http_messaging(session)) {
+ nghttp2_http_record_request_method(stream, frame);
+ }
+ } else {
+ nghttp2_stream *stream;
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+ if (stream && stream->state == NGHTTP2_STREAM_RESERVED) {
+ rv = session_predicate_push_response_headers_send(session, stream);
+ if (rv == 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;
+ rv = 0;
+ } else {
+ frame->headers.cat = NGHTTP2_HCAT_HEADERS;
+
+ rv = session_predicate_headers_send(session, stream);
+ }
+
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ estimated_payloadlen = session_estimate_headers_payload(
+ session, frame->headers.nva, frame->headers.nvlen,
+ NGHTTP2_PRIORITY_SPECLEN);
+
+ if (estimated_payloadlen > session->max_send_header_block_length) {
+ return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+ }
+
+ rv = nghttp2_frame_pack_headers(&session->aob.framebufs, &frame->headers,
+ &session->hd_deflater);
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ DEBUGF("send: before padding, HEADERS serialized in %zd bytes\n",
+ nghttp2_bufs_len(&session->aob.framebufs));
+
+ rv = session_headers_add_pad(session, frame);
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ DEBUGF("send: HEADERS finally serialized in %zd bytes\n",
+ nghttp2_bufs_len(&session->aob.framebufs));
+
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ assert(session->last_sent_stream_id < frame->hd.stream_id);
+ session->last_sent_stream_id = frame->hd.stream_id;
+ }
+
+ return 0;
+ }
+ 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. */
+ nghttp2_frame_pack_priority(&session->aob.framebufs, &frame->priority);
+
+ /* 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. */
+ return 0;
+ }
+ case NGHTTP2_RST_STREAM:
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+ nghttp2_frame_pack_rst_stream(&session->aob.framebufs, &frame->rst_stream);
+ return 0;
+ case NGHTTP2_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ assert(session->obq_flood_counter_ > 0);
+ --session->obq_flood_counter_;
+ /* When session is about to close, don't send SETTINGS ACK.
+ We are required to send SETTINGS without ACK though; for
+ example, we have to send SETTINGS as a part of connection
+ preface. */
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+ }
+
+ rv = nghttp2_frame_pack_settings(&session->aob.framebufs, &frame->settings);
+ if (rv != 0) {
+ return rv;
+ }
+ return 0;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ nghttp2_stream *stream;
+ size_t estimated_payloadlen;
+
+ /* stream could be NULL if associated stream was already
+ closed. */
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+ /* predicate should fail if stream is NULL. */
+ rv = session_predicate_push_promise_send(session, stream);
+ if (rv != 0) {
+ return rv;
+ }
+
+ assert(stream);
+
+ estimated_payloadlen = session_estimate_headers_payload(
+ session, frame->push_promise.nva, frame->push_promise.nvlen, 0);
+
+ if (estimated_payloadlen > session->max_send_header_block_length) {
+ return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+ }
+
+ rv = nghttp2_frame_pack_push_promise(
+ &session->aob.framebufs, &frame->push_promise, &session->hd_deflater);
+ if (rv != 0) {
+ return rv;
+ }
+ rv = session_headers_add_pad(session, frame);
+ if (rv != 0) {
+ return rv;
+ }
+
+ assert(session->last_sent_stream_id + 2 <=
+ frame->push_promise.promised_stream_id);
+ session->last_sent_stream_id = frame->push_promise.promised_stream_id;
+
+ return 0;
+ }
+ case NGHTTP2_PING:
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ assert(session->obq_flood_counter_ > 0);
+ --session->obq_flood_counter_;
+ }
+ /* PING frame is allowed to be sent unless termination GOAWAY is
+ sent */
+ if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+ nghttp2_frame_pack_ping(&session->aob.framebufs, &frame->ping);
+ return 0;
+ case NGHTTP2_GOAWAY:
+ rv = nghttp2_frame_pack_goaway(&session->aob.framebufs, &frame->goaway);
+ if (rv != 0) {
+ return rv;
+ }
+ session->local_last_stream_id = frame->goaway.last_stream_id;
+
+ return 0;
+ case NGHTTP2_WINDOW_UPDATE:
+ rv = session_predicate_window_update_send(session, frame->hd.stream_id);
+ if (rv != 0) {
+ return rv;
+ }
+ nghttp2_frame_pack_window_update(&session->aob.framebufs,
+ &frame->window_update);
+ return 0;
+ case NGHTTP2_CONTINUATION:
+ /* We never handle CONTINUATION here. */
+ assert(0);
+ return 0;
+ default: {
+ nghttp2_ext_aux_data *aux_data;
+
+ /* extension frame */
+
+ aux_data = &item->aux_data.ext;
+
+ if (aux_data->builtin == 0) {
+ if (session_is_closing(session)) {
+ return NGHTTP2_ERR_SESSION_CLOSING;
+ }
+
+ return session_pack_extension(session, &session->aob.framebufs, frame);
+ }
+
+ switch (frame->hd.type) {
+ case NGHTTP2_ALTSVC:
+ rv = session_predicate_altsvc_send(session, frame->hd.stream_id);
+ if (rv != 0) {
+ return rv;
+ }
+
+ nghttp2_frame_pack_altsvc(&session->aob.framebufs, &frame->ext);
+
+ return 0;
+ case NGHTTP2_ORIGIN:
+ rv = session_predicate_origin_send(session);
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = nghttp2_frame_pack_origin(&session->aob.framebufs, &frame->ext);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+ case NGHTTP2_PRIORITY_UPDATE: {
+ nghttp2_ext_priority_update *priority_update = frame->ext.payload;
+ rv = session_predicate_priority_update_send(session,
+ priority_update->stream_id);
+ if (rv != 0) {
+ return rv;
+ }
+
+ nghttp2_frame_pack_priority_update(&session->aob.framebufs, &frame->ext);
+
+ return 0;
+ }
+ default:
+ /* Unreachable here */
+ assert(0);
+ return 0;
+ }
+ }
+ }
+}
+
+nghttp2_outbound_item *
+nghttp2_session_get_next_ob_item(nghttp2_session *session) {
+ nghttp2_outbound_item *item;
+
+ if (nghttp2_outbound_queue_top(&session->ob_urgent)) {
+ return nghttp2_outbound_queue_top(&session->ob_urgent);
+ }
+
+ if (nghttp2_outbound_queue_top(&session->ob_reg)) {
+ return nghttp2_outbound_queue_top(&session->ob_reg);
+ }
+
+ if (!session_is_outgoing_concurrent_streams_max(session)) {
+ if (nghttp2_outbound_queue_top(&session->ob_syn)) {
+ return nghttp2_outbound_queue_top(&session->ob_syn);
+ }
+ }
+
+ if (session->remote_window_size > 0) {
+ item = nghttp2_stream_next_outbound_item(&session->root);
+ if (item) {
+ return item;
+ }
+
+ return session_sched_get_next_outbound_item(session);
+ }
+
+ return NULL;
+}
+
+nghttp2_outbound_item *
+nghttp2_session_pop_next_ob_item(nghttp2_session *session) {
+ nghttp2_outbound_item *item;
+
+ item = nghttp2_outbound_queue_top(&session->ob_urgent);
+ if (item) {
+ nghttp2_outbound_queue_pop(&session->ob_urgent);
+ item->queued = 0;
+ return item;
+ }
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+ if (item) {
+ nghttp2_outbound_queue_pop(&session->ob_reg);
+ item->queued = 0;
+ return item;
+ }
+
+ if (!session_is_outgoing_concurrent_streams_max(session)) {
+ item = nghttp2_outbound_queue_top(&session->ob_syn);
+ if (item) {
+ nghttp2_outbound_queue_pop(&session->ob_syn);
+ item->queued = 0;
+ return item;
+ }
+ }
+
+ if (session->remote_window_size > 0) {
+ item = nghttp2_stream_next_outbound_item(&session->root);
+ if (item) {
+ return item;
+ }
+
+ return session_sched_get_next_outbound_item(session);
+ }
+
+ return NULL;
+}
+
+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 == NGHTTP2_ERR_CANCEL) {
+ return rv;
+ }
+
+ 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(void *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_reschedule_stream(nghttp2_session *session,
+ nghttp2_stream *stream) {
+ stream->last_writelen = stream->item->frame.hd.length;
+
+ if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) {
+ nghttp2_stream_reschedule(stream);
+ return;
+ }
+
+ if (!session->server) {
+ return;
+ }
+
+ session_sched_reschedule_stream(session, stream);
+}
+
+static int session_update_stream_consumed_size(nghttp2_session *session,
+ nghttp2_stream *stream,
+ size_t delta_size);
+
+static int session_update_connection_consumed_size(nghttp2_session *session,
+ size_t delta_size);
+
+/*
+ * 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;
+ nghttp2_stream *stream;
+
+ frame = &item->frame;
+
+ if (frame->hd.type == NGHTTP2_DATA) {
+ 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 -= (int32_t)frame->hd.length;
+ if (stream) {
+ stream->remote_window_size -= (int32_t)frame->hd.length;
+ }
+
+ if (stream && aux_data->eof) {
+ session_detach_stream_item(session, stream);
+
+ /* 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;
+ }
+
+ /* non-DATA frame */
+
+ if (frame->hd.type == NGHTTP2_HEADERS ||
+ frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ if (nghttp2_bufs_next_present(framebufs)) {
+ DEBUGF("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;
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ 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. */
+ }
+ return 0;
+ }
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_PUSH);
+ ++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. */
+ }
+ return 0;
+ default:
+ /* Unreachable */
+ assert(0);
+ return 0;
+ }
+ }
+ case NGHTTP2_PRIORITY:
+ if (session->server || session->pending_no_rfc7540_priorities == 1) {
+ return 0;
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
+
+ if (!stream) {
+ if (!session_detect_idle_stream(session, frame->hd.stream_id)) {
+ return 0;
+ }
+
+ stream = nghttp2_session_open_stream(
+ session, frame->hd.stream_id, NGHTTP2_FLAG_NONE,
+ &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL);
+ if (!stream) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+ } else {
+ rv = nghttp2_session_reprioritize_stream(session, stream,
+ &frame->priority.pri_spec);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ }
+
+ rv = nghttp2_session_adjust_idle_stream(session);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return 0;
+ 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;
+ }
+ return 0;
+ 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;
+ }
+ }
+
+ return 0;
+ }
+ case NGHTTP2_WINDOW_UPDATE:
+ if (frame->hd.stream_id == 0) {
+ session->window_update_queued = 0;
+ if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
+ rv = session_update_connection_consumed_size(session, 0);
+ } else {
+ rv = nghttp2_session_update_recv_connection_window_size(session, 0);
+ }
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return 0;
+ }
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ stream->window_update_queued = 0;
+
+ /* We don't have to send WINDOW_UPDATE if END_STREAM from peer
+ is seen. */
+ if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+ return 0;
+ }
+
+ if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
+ rv = session_update_stream_consumed_size(session, stream, 0);
+ } else {
+ rv =
+ nghttp2_session_update_recv_stream_window_size(session, stream, 0, 1);
+ }
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Called after a frame is sent and session_after_frame_sent1. This
+ * function is responsible to reset session->aob.
+ */
+static void session_after_frame_sent2(nghttp2_session *session) {
+ nghttp2_active_outbound_item *aob = &session->aob;
+ nghttp2_outbound_item *item = aob->item;
+ nghttp2_bufs *framebufs = &aob->framebufs;
+ nghttp2_frame *frame;
+ nghttp2_mem *mem;
+ nghttp2_stream *stream;
+ nghttp2_data_aux_data *aux_data;
+
+ 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("send: next CONTINUATION frame, %zu bytes\n",
+ nghttp2_buf_len(&framebufs->cur->buf));
+
+ return;
+ }
+ }
+
+ active_outbound_item_reset(&session->aob, mem);
+
+ return;
+ }
+
+ /* DATA frame */
+
+ 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;
+ }
+
+ /* Reset no_copy here because next write may not use this. */
+ aux_data->no_copy = 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) {
+ session_detach_stream_item(session, stream);
+ }
+
+ active_outbound_item_reset(aob, mem);
+
+ return;
+ }
+
+ aob->item = NULL;
+ active_outbound_item_reset(&session->aob, mem);
+
+ return;
+}
+
+static int session_call_send_data(nghttp2_session *session,
+ nghttp2_outbound_item *item,
+ nghttp2_bufs *framebufs) {
+ int rv;
+ nghttp2_buf *buf;
+ size_t length;
+ nghttp2_frame *frame;
+ nghttp2_data_aux_data *aux_data;
+
+ buf = &framebufs->cur->buf;
+ frame = &item->frame;
+ length = frame->hd.length - frame->data.padlen;
+ aux_data = &item->aux_data.data;
+
+ rv = session->callbacks.send_data_callback(session, frame, buf->pos, length,
+ &aux_data->data_prd.source,
+ session->user_data);
+
+ switch (rv) {
+ case 0:
+ case NGHTTP2_ERR_WOULDBLOCK:
+ case NGHTTP2_ERR_PAUSE:
+ case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE:
+ return rv;
+ default:
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+}
+
+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;
+
+ /* We may have idle streams more than we expect (e.g.,
+ nghttp2_session_change_stream_priority() or
+ nghttp2_session_create_idle_stream()). Adjust them here. */
+ rv = nghttp2_session_adjust_idle_stream(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ 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;
+ }
+
+ rv = session_prep_frame(session, item);
+ if (rv == NGHTTP2_ERR_PAUSE) {
+ return 0;
+ }
+ if (rv == NGHTTP2_ERR_DEFERRED) {
+ DEBUGF("send: frame transmission deferred\n");
+ break;
+ }
+ if (rv < 0) {
+ int32_t opened_stream_id = 0;
+ uint32_t error_code = NGHTTP2_INTERNAL_ERROR;
+ int rv2 = 0;
+
+ DEBUGF("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 &&
+ 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;
+ }
+ }
+ /* We have to close stream opened by failed request HEADERS
+ or PUSH_PROMISE. */
+ switch (item->frame.hd.type) {
+ case NGHTTP2_HEADERS:
+ if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) {
+ opened_stream_id = item->frame.hd.stream_id;
+ if (item->aux_data.headers.canceled) {
+ error_code = item->aux_data.headers.error_code;
+ } else {
+ /* Set error_code to REFUSED_STREAM so that application
+ can send request again. */
+ error_code = NGHTTP2_REFUSED_STREAM;
+ }
+ }
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ opened_stream_id = item->frame.push_promise.promised_stream_id;
+ break;
+ }
+ if (opened_stream_id) {
+ /* careful not to override rv */
+ rv2 = nghttp2_session_close_stream(session, opened_stream_id,
+ error_code);
+ }
+
+ nghttp2_outbound_item_free(item, mem);
+ nghttp2_mem_free(mem, item);
+ active_outbound_item_reset(aob, mem);
+
+ if (nghttp2_is_fatal(rv2)) {
+ return rv2;
+ }
+
+ if (rv == NGHTTP2_ERR_HEADER_COMP) {
+ /* If header compression error occurred, should terminate
+ 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("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;
+ }
+
+ if (rv == NGHTTP2_ERR_CANCEL) {
+ int32_t opened_stream_id = 0;
+ uint32_t error_code = NGHTTP2_INTERNAL_ERROR;
+
+ if (session->callbacks.on_frame_not_send_callback) {
+ if (session->callbacks.on_frame_not_send_callback(
+ session, frame, rv, session->user_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ /* We have to close stream opened by canceled request
+ HEADERS or PUSH_PROMISE. */
+ switch (item->frame.hd.type) {
+ case NGHTTP2_HEADERS:
+ if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) {
+ opened_stream_id = item->frame.hd.stream_id;
+ /* We don't have to check
+ item->aux_data.headers.canceled since it has already
+ been checked. */
+ /* Set error_code to REFUSED_STREAM so that application
+ can send request again. */
+ error_code = NGHTTP2_REFUSED_STREAM;
+ }
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ opened_stream_id = item->frame.push_promise.promised_stream_id;
+ break;
+ }
+ if (opened_stream_id) {
+ /* careful not to override rv */
+ int rv2;
+ rv2 = nghttp2_session_close_stream(session, opened_stream_id,
+ error_code);
+
+ if (nghttp2_is_fatal(rv2)) {
+ return rv2;
+ }
+ }
+
+ active_outbound_item_reset(aob, mem);
+
+ break;
+ }
+ } else {
+ DEBUGF("send: next frame: DATA\n");
+
+ if (item->aux_data.data.no_copy) {
+ aob->state = NGHTTP2_OB_SEND_NO_COPY;
+ break;
+ }
+ }
+
+ DEBUGF("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("send: end transmission of a frame\n");
+
+ /* Frame has completely sent */
+ if (fast_cb) {
+ session_after_frame_sent2(session);
+ } else {
+ rv = session_after_frame_sent1(session);
+ if (rv < 0) {
+ /* FATAL */
+ assert(nghttp2_is_fatal(rv));
+ return rv;
+ }
+ session_after_frame_sent2(session);
+ }
+ /* 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 (ssize_t)datalen;
+ }
+ case NGHTTP2_OB_SEND_NO_COPY: {
+ nghttp2_stream *stream;
+ nghttp2_frame *frame;
+ int pause;
+
+ DEBUGF("send: no copy DATA\n");
+
+ frame = &aob->item->frame;
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (stream == NULL) {
+ DEBUGF("send: no copy DATA cancelled because stream was closed\n");
+
+ active_outbound_item_reset(aob, mem);
+
+ break;
+ }
+
+ rv = session_call_send_data(session, aob->item, framebufs);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ session_detach_stream_item(session, stream);
+
+ rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ active_outbound_item_reset(aob, mem);
+
+ break;
+ }
+
+ if (rv == NGHTTP2_ERR_WOULDBLOCK) {
+ return 0;
+ }
+
+ pause = (rv == NGHTTP2_ERR_PAUSE);
+
+ rv = session_after_frame_sent1(session);
+ if (rv < 0) {
+ assert(nghttp2_is_fatal(rv));
+ return rv;
+ }
+ session_after_frame_sent2(session);
+
+ /* We have already adjusted the next state */
+
+ if (pause) {
+ return 0;
+ }
+
+ break;
+ }
+ case NGHTTP2_OB_SEND_CLIENT_MAGIC: {
+ size_t datalen;
+ nghttp2_buf *buf;
+
+ buf = &framebufs->cur->buf;
+
+ if (buf->pos == buf->last) {
+ DEBUGF("send: end transmission of client magic\n");
+ active_outbound_item_reset(aob, mem);
+ break;
+ }
+
+ *data_ptr = buf->pos;
+ datalen = nghttp2_buf_len(buf);
+
+ buf->pos += datalen;
+
+ return (ssize_t)datalen;
+ }
+ }
+ }
+}
+
+ssize_t nghttp2_session_mem_send(nghttp2_session *session,
+ const uint8_t **data_ptr) {
+ int rv;
+ ssize_t len;
+
+ *data_ptr = NULL;
+
+ len = nghttp2_session_mem_send_internal(session, data_ptr, 1);
+ if (len <= 0) {
+ return len;
+ }
+
+ if (session->aob.item) {
+ /* 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 = NULL;
+ 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, (size_t)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("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 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ return rv;
+ }
+ 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_hd_nv *nv) {
+ int rv = 0;
+ if (session->callbacks.on_header_callback2) {
+ rv = session->callbacks.on_header_callback2(
+ session, frame, nv->name, nv->value, nv->flags, session->user_data);
+ } else if (session->callbacks.on_header_callback) {
+ rv = session->callbacks.on_header_callback(
+ session, frame, nv->name->base, nv->name->len, nv->value->base,
+ nv->value->len, 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;
+}
+
+static int session_call_on_invalid_header(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ const nghttp2_hd_nv *nv) {
+ int rv;
+ if (session->callbacks.on_invalid_header_callback2) {
+ rv = session->callbacks.on_invalid_header_callback2(
+ session, frame, nv->name, nv->value, nv->flags, session->user_data);
+ } else if (session->callbacks.on_invalid_header_callback) {
+ rv = session->callbacks.on_invalid_header_callback(
+ session, frame, nv->name->base, nv->name->len, nv->value->base,
+ nv->value->len, nv->flags, session->user_data);
+ } else {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ return rv;
+ }
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+session_call_on_extension_chunk_recv_callback(nghttp2_session *session,
+ const uint8_t *data, size_t len) {
+ int rv;
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+
+ if (session->callbacks.on_extension_chunk_recv_callback) {
+ rv = session->callbacks.on_extension_chunk_recv_callback(
+ session, &frame->hd, data, len, session->user_data);
+ if (rv == NGHTTP2_ERR_CANCEL) {
+ return rv;
+ }
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ return 0;
+}
+
+static int session_call_unpack_extension_callback(nghttp2_session *session) {
+ int rv;
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+ void *payload = NULL;
+
+ rv = session->callbacks.unpack_extension_callback(
+ session, &payload, &frame->hd, session->user_data);
+ if (rv == NGHTTP2_ERR_CANCEL) {
+ return rv;
+ }
+ if (rv != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ frame->ext.payload = payload;
+
+ 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) {
+ /* 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 uint32_t get_error_code_from_lib_error_code(int lib_error_code) {
+ switch (lib_error_code) {
+ case NGHTTP2_ERR_STREAM_CLOSED:
+ return NGHTTP2_STREAM_CLOSED;
+ case NGHTTP2_ERR_HEADER_COMP:
+ return NGHTTP2_COMPRESSION_ERROR;
+ case NGHTTP2_ERR_FRAME_SIZE_ERROR:
+ return NGHTTP2_FRAME_SIZE_ERROR;
+ case NGHTTP2_ERR_FLOW_CONTROL:
+ return NGHTTP2_FLOW_CONTROL_ERROR;
+ case NGHTTP2_ERR_REFUSED_STREAM:
+ return NGHTTP2_REFUSED_STREAM;
+ case NGHTTP2_ERR_PROTO:
+ case NGHTTP2_ERR_HTTP_HEADER:
+ case NGHTTP2_ERR_HTTP_MESSAGING:
+ return NGHTTP2_PROTOCOL_ERROR;
+ default:
+ return NGHTTP2_INTERNAL_ERROR;
+ }
+}
+
+/*
+ * Calls on_invalid_frame_recv_callback if it is set to |session|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ * User defined callback function fails.
+ */
+static int session_call_on_invalid_frame_recv_callback(nghttp2_session *session,
+ nghttp2_frame *frame,
+ int lib_error_code) {
+ if (session->callbacks.on_invalid_frame_recv_callback) {
+ if (session->callbacks.on_invalid_frame_recv_callback(
+ session, frame, lib_error_code, session->user_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return 0;
+}
+
+static int session_handle_invalid_stream2(nghttp2_session *session,
+ int32_t stream_id,
+ nghttp2_frame *frame,
+ int lib_error_code) {
+ int rv;
+ rv = nghttp2_session_add_rst_stream(
+ session, stream_id, get_error_code_from_lib_error_code(lib_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, lib_error_code, session->user_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return 0;
+}
+
+static int session_handle_invalid_stream(nghttp2_session *session,
+ nghttp2_frame *frame,
+ int lib_error_code) {
+ return session_handle_invalid_stream2(session, frame->hd.stream_id, frame,
+ lib_error_code);
+}
+
+static int session_inflate_handle_invalid_stream(nghttp2_session *session,
+ nghttp2_frame *frame,
+ int lib_error_code) {
+ int rv;
+ rv = session_handle_invalid_stream(session, frame, lib_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,
+ int lib_error_code,
+ const char *reason) {
+ if (session->callbacks.on_invalid_frame_recv_callback) {
+ if (session->callbacks.on_invalid_frame_recv_callback(
+ session, frame, lib_error_code, session->user_data) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ return nghttp2_session_terminate_session_with_reason(
+ session, get_error_code_from_lib_error_code(lib_error_code), reason);
+}
+
+static int session_inflate_handle_invalid_connection(nghttp2_session *session,
+ nghttp2_frame *frame,
+ int lib_error_code,
+ const char *reason) {
+ int rv;
+ rv =
+ session_handle_invalid_connection(session, frame, lib_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_hd_nv nv;
+ nghttp2_stream *stream;
+ nghttp2_stream *subject_stream;
+ int trailer = 0;
+
+ *readlen_ptr = 0;
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ subject_stream = nghttp2_session_get_stream(
+ session, frame->push_promise.promised_stream_id);
+ } else {
+ subject_stream = stream;
+ trailer = session_trailer_headers(session, stream, frame);
+ }
+
+ DEBUGF("recv: decoding header block %zu bytes\n", inlen);
+ for (;;) {
+ inflate_flags = 0;
+ proclen = nghttp2_hd_inflate_hd_nv(&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) {
+ if (subject_stream && subject_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, subject_stream->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 -= (size_t)proclen;
+ *readlen_ptr += (size_t)proclen;
+
+ DEBUGF("recv: proclen=%zd\n", proclen);
+
+ if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
+ rv = 0;
+ if (subject_stream) {
+ if (session_enforce_http_messaging(session)) {
+ rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
+ trailer);
+
+ if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) {
+ /* Don't overwrite rv here */
+ int rv2;
+
+ rv2 = session_call_on_invalid_header(session, frame, &nv);
+ if (rv2 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ rv = NGHTTP2_ERR_HTTP_HEADER;
+ } else {
+ if (rv2 != 0) {
+ return rv2;
+ }
+
+ /* header is ignored */
+ DEBUGF("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",
+ frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
+ nv.name->base, (int)nv.value->len, nv.value->base);
+
+ rv2 = session_call_error_callback(
+ session, NGHTTP2_ERR_HTTP_HEADER,
+ "Ignoring received invalid HTTP header field: frame type: "
+ "%u, stream: %d, name: [%.*s], value: [%.*s]",
+ frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
+ nv.name->base, (int)nv.value->len, nv.value->base);
+
+ if (nghttp2_is_fatal(rv2)) {
+ return rv2;
+ }
+ }
+ }
+
+ if (rv == NGHTTP2_ERR_HTTP_HEADER) {
+ DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n",
+ frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
+ nv.name->base, (int)nv.value->len, nv.value->base);
+
+ rv = session_call_error_callback(
+ session, NGHTTP2_ERR_HTTP_HEADER,
+ "Invalid HTTP header field was received: frame type: "
+ "%u, stream: %d, name: [%.*s], value: [%.*s]",
+ frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
+ nv.name->base, (int)nv.value->len, nv.value->base);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ rv = session_handle_invalid_stream2(session,
+ subject_stream->stream_id,
+ frame, NGHTTP2_ERR_HTTP_HEADER);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ }
+ if (rv == 0) {
+ 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;
+}
+
+/*
+ * Call this function when HEADERS frame was completely received.
+ *
+ * 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.
+ */
+static int session_end_stream_headers_received(nghttp2_session *session,
+ nghttp2_frame *frame,
+ nghttp2_stream *stream) {
+ int rv;
+
+ assert(frame->hd.type == NGHTTP2_HEADERS);
+
+ if (session->server && session_enforce_http_messaging(session) &&
+ frame->headers.cat == NGHTTP2_HCAT_REQUEST &&
+ (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) &&
+ !(stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) &&
+ (stream->http_flags & NGHTTP2_HTTP_FLAG_PRIORITY)) {
+ rv = session_update_stream_priority(session, stream, stream->http_extpri);
+ if (rv != 0) {
+ assert(nghttp2_is_fatal(rv));
+ return rv;
+ }
+ }
+
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ return 0;
+ }
+
+ 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 = 0;
+ 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;
+ }
+
+ if (session_enforce_http_messaging(session)) {
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ nghttp2_stream *subject_stream;
+
+ subject_stream = nghttp2_session_get_stream(
+ session, frame->push_promise.promised_stream_id);
+ if (subject_stream) {
+ rv = nghttp2_http_on_request_headers(subject_stream, frame);
+ }
+ } else {
+ assert(frame->hd.type == NGHTTP2_HEADERS);
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_REQUEST:
+ rv = nghttp2_http_on_request_headers(stream, frame);
+ break;
+ case NGHTTP2_HCAT_RESPONSE:
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ rv = nghttp2_http_on_response_headers(stream);
+ break;
+ case NGHTTP2_HCAT_HEADERS:
+ if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
+ assert(!session->server);
+ rv = nghttp2_http_on_response_headers(stream);
+ } else {
+ rv = nghttp2_http_on_trailer_headers(stream, frame);
+ }
+ break;
+ default:
+ assert(0);
+ }
+ if (rv == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+ rv = nghttp2_http_on_remote_end_stream(stream);
+ }
+ }
+ if (rv != 0) {
+ int32_t stream_id;
+
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ stream_id = frame->push_promise.promised_stream_id;
+ } else {
+ stream_id = frame->hd.stream_id;
+ }
+
+ rv = session_handle_invalid_stream2(session, stream_id, frame,
+ NGHTTP2_ERR_HTTP_MESSAGING);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (frame->hd.type == NGHTTP2_HEADERS &&
+ (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ /* Don't call nghttp2_session_close_stream_if_shut_rdwr
+ because RST_STREAM has been submitted. */
+ }
+ 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;
+ }
+
+ return session_end_stream_headers_received(session, frame, stream);
+}
+
+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_ERR_PROTO, "request HEADERS: stream_id == 0");
+ }
+
+ /* If client receives 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_ERR_PROTO,
+ "request HEADERS: client received request");
+ }
+
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+
+ assert(session->server);
+
+ if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
+ if (frame->hd.stream_id == 0 ||
+ nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "request HEADERS: invalid stream_id");
+ }
+
+ /* RFC 7540 says if an endpoint receives a HEADERS with invalid
+ * stream ID (e.g, numerically smaller than previous), it MUST
+ * issue connection error with error code PROTOCOL_ERROR. It is a
+ * bit hard to detect this, since we cannot remember all streams
+ * we observed so far.
+ *
+ * You might imagine this is really easy. But no. HTTP/2 is
+ * asynchronous protocol, and usually client and server do not
+ * share the complete picture of open/closed stream status. For
+ * example, after server sends RST_STREAM for a stream, client may
+ * send trailer HEADERS for that stream. If naive server detects
+ * that, and issued connection error, then it is a bug of server
+ * implementation since client is not wrong if it did not get
+ * RST_STREAM when it issued trailer HEADERS.
+ *
+ * At the moment, we are very conservative here. We only use
+ * connection error if stream ID refers idle stream, or we are
+ * sure that stream is half-closed(remote) or closed. Otherwise
+ * we just ignore HEADERS for now.
+ */
+ stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
+ if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
+ }
+
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+ session->last_recv_stream_id = frame->hd.stream_id;
+
+ if (session_is_incoming_concurrent_streams_max(session)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "request HEADERS: max concurrent streams exceeded");
+ }
+
+ if (!session_allow_incoming_new_stream(session)) {
+ /* We just ignore stream after GOAWAY was sent */
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+
+ if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself");
+ }
+
+ if (session_is_incoming_concurrent_streams_pending_max(session)) {
+ return session_inflate_handle_invalid_stream(session, frame,
+ NGHTTP2_ERR_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;
+ }
+
+ rv = nghttp2_session_adjust_closed_stream(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ 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_ERR_PROTO, "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.
+
+ We go further, and make it connection error.
+ */
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: 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_ERR_PROTO,
+ "push response HEADERS: stream_id == 0");
+ }
+
+ if (session->server) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "HEADERS: no HEADERS allowed from client in reserved state");
+ }
+
+ if (session_is_incoming_concurrent_streams_max(session)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "push response HEADERS: max concurrent streams exceeded");
+ }
+
+ if (!session_allow_incoming_new_stream(session)) {
+ /* We don't accept new stream after GOAWAY was sent. */
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+
+ if (session_is_incoming_concurrent_streams_pending_max(session)) {
+ return session_inflate_handle_invalid_stream(session, frame,
+ NGHTTP2_ERR_REFUSED_STREAM);
+ }
+
+ nghttp2_stream_promise_fulfilled(stream);
+ if (!nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+ --session->num_incoming_reserved_streams;
+ }
+ ++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_ERR_PROTO, "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.
+
+ we go further, and make it connection error.
+ */
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: 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;
+ }
+
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+ /* 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) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+ nghttp2_stream *stream;
+
+ nghttp2_frame_unpack_headers_payload(&frame->headers, iframe->sbuf.pos);
+
+ 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 (stream->state == NGHTTP2_STREAM_RESERVED) {
+ frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
+ return nghttp2_session_on_push_response_headers_received(session, frame,
+ stream);
+ }
+
+ if (stream->state == NGHTTP2_STREAM_OPENING &&
+ nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+ 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);
+}
+
+int nghttp2_session_on_priority_received(nghttp2_session *session,
+ nghttp2_frame *frame) {
+ int rv;
+ nghttp2_stream *stream;
+
+ assert(!session_no_rfc7540_pri_no_fallback(session));
+
+ if (frame->hd.stream_id == 0) {
+ return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
+ "PRIORITY: stream_id == 0");
+ }
+
+ if (frame->priority.pri_spec.stream_id == frame->hd.stream_id) {
+ return nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR, "depend on itself");
+ }
+
+ 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;
+ }
+
+ rv = nghttp2_session_adjust_idle_stream(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ } else {
+ rv = nghttp2_session_reprioritize_stream(session, stream,
+ &frame->priority.pri_spec);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ rv = nghttp2_session_adjust_idle_stream(session);
+ 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;
+
+ assert(!session_no_rfc7540_pri_no_fallback(session));
+
+ nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos);
+
+ return nghttp2_session_on_priority_received(session, frame);
+}
+
+static int session_update_stream_reset_ratelim(nghttp2_session *session) {
+ if (!session->server || (session->goaway_flags & NGHTTP2_GOAWAY_SUBMITTED)) {
+ return 0;
+ }
+
+ nghttp2_ratelim_update(&session->stream_reset_ratelim,
+ nghttp2_time_now_sec());
+
+ if (nghttp2_ratelim_drain(&session->stream_reset_ratelim, 1) == 0) {
+ return 0;
+ }
+
+ return nghttp2_session_add_goaway(session, session->last_recv_stream_id,
+ NGHTTP2_INTERNAL_ERROR, NULL, 0,
+ NGHTTP2_GOAWAY_AUX_NONE);
+}
+
+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_ERR_PROTO,
+ "RST_STREAM: stream_id == 0");
+ }
+
+ if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+ return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
+ "RST_STREAM: stream in idle");
+ }
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (stream) {
+ /* We may use stream->shut_flags for strict error checking. */
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ }
+
+ 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 session_update_stream_reset_ratelim(session);
+}
+
+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);
+
+ return nghttp2_session_on_rst_stream_received(session, frame);
+}
+
+static int update_remote_initial_window_size_func(void *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_add_rst_stream(arg->session, stream->stream_id,
+ 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 = session_resume_deferred_stream_item(
+ arg->session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
+
+ 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 = (int32_t)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(void *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_add_rst_stream(arg->session, stream->stream_id,
+ NGHTTP2_FLOW_CONTROL_ERROR);
+ }
+
+ if (stream->window_update_queued) {
+ return 0;
+ }
+
+ if (arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
+ return session_update_stream_consumed_size(arg->session, stream, 0);
+ }
+
+ 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;
+ uint32_t header_table_size = 0;
+ uint32_t min_header_table_size = UINT32_MAX;
+ uint8_t header_table_size_seen = 0;
+ /* For NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, use the value last
+ seen. For NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, use both minimum
+ value and last seen value. */
+ 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;
+ min_header_table_size = nghttp2_min(min_header_table_size, iv[i].value);
+ break;
+ case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+ new_initial_window_size = (int32_t)iv[i].value;
+ break;
+ }
+ }
+ if (header_table_size_seen) {
+ if (min_header_table_size < header_table_size) {
+ rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater,
+ min_header_table_size);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ 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,
+ (int32_t)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;
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ session->local_settings.enable_connect_protocol = iv[i].value;
+ break;
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ session->local_settings.no_rfc7540_priorities = iv[i].value;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int nghttp2_session_on_settings_received(nghttp2_session *session,
+ nghttp2_frame *frame, int noack) {
+ int rv;
+ size_t i;
+ nghttp2_mem *mem;
+ nghttp2_inflight_settings *settings;
+
+ mem = &session->mem;
+
+ if (frame->hd.stream_id != 0) {
+ return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
+ "SETTINGS: stream_id != 0");
+ }
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ if (frame->settings.niv != 0) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_FRAME_SIZE_ERROR,
+ "SETTINGS: ACK and payload != 0");
+ }
+
+ settings = session->inflight_settings_head;
+
+ if (!settings) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "SETTINGS: unexpected ACK");
+ }
+
+ rv = nghttp2_session_update_local_settings(session, settings->iv,
+ settings->niv);
+
+ session->inflight_settings_head = settings->next;
+
+ inflight_settings_del(settings, mem);
+
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return session_handle_invalid_connection(session, frame, rv, NULL);
+ }
+ return session_call_on_frame_received(session, frame);
+ }
+
+ if (!session->remote_settings_received) {
+ session->remote_settings.max_concurrent_streams =
+ NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
+ session->remote_settings_received = 1;
+ }
+
+ 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:
+
+ 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_ERR_HEADER_COMP, 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_ERR_PROTO,
+ "SETTINGS: invalid SETTINGS_ENBLE_PUSH");
+ }
+
+ if (!session->server && entry->value != 0) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "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_ERR_FLOW_CONTROL,
+ "SETTINGS: too large SETTINGS_INITIAL_WINDOW_SIZE");
+ }
+
+ rv = session_update_remote_initial_window_size(session,
+ (int32_t)entry->value);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (rv != 0) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_FLOW_CONTROL, 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_ERR_PROTO,
+ "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;
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+
+ if (entry->value != 0 && entry->value != 1) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "SETTINGS: invalid SETTINGS_ENABLE_CONNECT_PROTOCOL");
+ }
+
+ if (!session->server &&
+ session->remote_settings.enable_connect_protocol &&
+ entry->value == 0) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "SETTINGS: server attempted to disable "
+ "SETTINGS_ENABLE_CONNECT_PROTOCOL");
+ }
+
+ session->remote_settings.enable_connect_protocol = entry->value;
+
+ break;
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+
+ if (entry->value != 0 && entry->value != 1) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "SETTINGS: invalid SETTINGS_NO_RFC7540_PRIORITIES");
+ }
+
+ if (session->remote_settings.no_rfc7540_priorities != UINT32_MAX &&
+ session->remote_settings.no_rfc7540_priorities != entry->value) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "SETTINGS: SETTINGS_NO_RFC7540_PRIORITIES cannot be changed");
+ }
+
+ session->remote_settings.no_rfc7540_priorities = entry->value;
+
+ break;
+ }
+ }
+
+ if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) {
+ session->remote_settings.no_rfc7540_priorities = 0;
+
+ if (session->server && session->pending_no_rfc7540_priorities &&
+ (session->opt_flags &
+ NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) {
+ session->fallback_rfc7540_priorities = 1;
+ }
+ }
+
+ 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_ERR_INTERNAL, NULL);
+ }
+ }
+
+ return session_call_on_frame_received(session, frame);
+}
+
+static int session_process_settings_frame(nghttp2_session *session) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+ size_t i;
+ nghttp2_settings_entry min_header_size_entry;
+
+ if (iframe->max_niv) {
+ min_header_size_entry = iframe->iv[iframe->max_niv - 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;
+ }
+ }
+ }
+
+ nghttp2_frame_unpack_settings_payload(&frame->settings, iframe->iv,
+ iframe->niv);
+
+ iframe->iv = NULL;
+ iframe->niv = 0;
+ iframe->max_niv = 0;
+
+ 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_ERR_PROTO, "PUSH_PROMISE: stream_id == 0");
+ }
+ if (session->server || session->local_settings.enable_push == 0) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled");
+ }
+
+ if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id");
+ }
+
+ if (!session_allow_incoming_new_stream(session)) {
+ /* We just discard PUSH_PROMISE after GOAWAY was sent */
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+
+ 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_ERR_PROTO,
+ "PUSH_PROMISE: invalid promised_stream_id");
+ }
+
+ if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
+ }
+
+ 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 ||
+ !session->pending_enable_push ||
+ session->num_incoming_reserved_streams >=
+ session->max_incoming_reserved_streams) {
+ /* Currently, client does not retain closed stream, so we don't
+ check NGHTTP2_SHUT_RD condition here. */
+
+ rv = nghttp2_session_add_rst_stream(
+ session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
+ if (rv != 0) {
+ return rv;
+ }
+ return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+ }
+
+ if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+ return session_inflate_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_STREAM_CLOSED,
+ "PUSH_PROMISE: stream closed");
+ }
+
+ 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_NONE,
+ &pri_spec, NGHTTP2_STREAM_RESERVED, NULL);
+
+ if (!promised_stream) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ /* We don't call nghttp2_session_adjust_closed_stream(), since we
+ don't keep closed stream in client side */
+
+ 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) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+
+ nghttp2_frame_unpack_push_promise_payload(&frame->push_promise,
+ iframe->sbuf.pos);
+
+ 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_ERR_PROTO,
+ "PING: stream_id != 0");
+ }
+ if ((session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK) == 0 &&
+ (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);
+
+ 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_ERR_PROTO,
+ "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_ERR_PROTO,
+ "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,
+ 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_ERR_PROTO,
+ "WINDOW_UPDATE: window_size_increment == 0");
+ }
+
+ if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
+ session->remote_window_size) {
+ return session_handle_invalid_connection(session, frame,
+ NGHTTP2_ERR_FLOW_CONTROL, 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;
+
+ if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+ return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
+ "WINDOW_UPDATE to idle stream");
+ }
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+ if (state_reserved_remote(session, stream)) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream");
+ }
+ if (frame->window_update.window_size_increment == 0) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "WINDOW_UPDATE: window_size_increment == 0");
+ }
+ if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
+ stream->remote_window_size) {
+ return session_handle_invalid_stream(session, frame,
+ NGHTTP2_ERR_FLOW_CONTROL);
+ }
+ 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 = session_resume_deferred_stream_item(
+ session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
+
+ 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);
+
+ return nghttp2_session_on_window_update_received(session, frame);
+}
+
+int nghttp2_session_on_altsvc_received(nghttp2_session *session,
+ nghttp2_frame *frame) {
+ nghttp2_ext_altsvc *altsvc;
+ nghttp2_stream *stream;
+
+ altsvc = frame->ext.payload;
+
+ /* session->server case has been excluded */
+
+ if (frame->hd.stream_id == 0) {
+ if (altsvc->origin_len == 0) {
+ return session_call_on_invalid_frame_recv_callback(session, frame,
+ NGHTTP2_ERR_PROTO);
+ }
+ } else {
+ if (altsvc->origin_len > 0) {
+ return session_call_on_invalid_frame_recv_callback(session, frame,
+ NGHTTP2_ERR_PROTO);
+ }
+
+ stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+ if (!stream) {
+ return 0;
+ }
+
+ if (stream->state == NGHTTP2_STREAM_CLOSING) {
+ return 0;
+ }
+ }
+
+ if (altsvc->field_value_len == 0) {
+ return session_call_on_invalid_frame_recv_callback(session, frame,
+ NGHTTP2_ERR_PROTO);
+ }
+
+ return session_call_on_frame_received(session, frame);
+}
+
+int nghttp2_session_on_origin_received(nghttp2_session *session,
+ nghttp2_frame *frame) {
+ return session_call_on_frame_received(session, frame);
+}
+
+int nghttp2_session_on_priority_update_received(nghttp2_session *session,
+ nghttp2_frame *frame) {
+ nghttp2_ext_priority_update *priority_update;
+ nghttp2_stream *stream;
+ nghttp2_priority_spec pri_spec;
+ nghttp2_extpri extpri;
+ int rv;
+
+ assert(session->server);
+
+ priority_update = frame->ext.payload;
+
+ if (frame->hd.stream_id != 0) {
+ return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
+ "PRIORITY_UPDATE: stream_id == 0");
+ }
+
+ if (nghttp2_session_is_my_stream_id(session, priority_update->stream_id)) {
+ if (session_detect_idle_stream(session, priority_update->stream_id)) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "PRIORITY_UPDATE: prioritizing idle push is not allowed");
+ }
+
+ /* TODO Ignore priority signal to a push stream for now */
+ return session_call_on_frame_received(session, frame);
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, priority_update->stream_id);
+ if (stream) {
+ /* Stream already exists. */
+ if (stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) {
+ return session_call_on_frame_received(session, frame);
+ }
+ } else if (session_detect_idle_stream(session, priority_update->stream_id)) {
+ if (session->num_idle_streams + session->num_incoming_streams >=
+ session->local_settings.max_concurrent_streams) {
+ return session_handle_invalid_connection(
+ session, frame, NGHTTP2_ERR_PROTO,
+ "PRIORITY_UPDATE: max concurrent streams exceeded");
+ }
+
+ nghttp2_priority_spec_default_init(&pri_spec);
+ stream = nghttp2_session_open_stream(session, priority_update->stream_id,
+ NGHTTP2_FLAG_NONE, &pri_spec,
+ NGHTTP2_STREAM_IDLE, NULL);
+ if (!stream) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+ } else {
+ return session_call_on_frame_received(session, frame);
+ }
+
+ extpri.urgency = NGHTTP2_EXTPRI_DEFAULT_URGENCY;
+ extpri.inc = 0;
+
+ rv = nghttp2_http_parse_priority(&extpri, priority_update->field_value,
+ priority_update->field_value_len);
+ if (rv != 0) {
+ /* Just ignore field_value if it cannot be parsed. */
+ return session_call_on_frame_received(session, frame);
+ }
+
+ rv = session_update_stream_priority(session, stream,
+ nghttp2_extpri_to_uint8(&extpri));
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ }
+
+ return session_call_on_frame_received(session, frame);
+}
+
+static int session_process_altsvc_frame(nghttp2_session *session) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+
+ nghttp2_frame_unpack_altsvc_payload(
+ &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos,
+ nghttp2_buf_len(&iframe->lbuf));
+
+ /* nghttp2_frame_unpack_altsvc_payload steals buffer from
+ iframe->lbuf */
+ nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
+
+ return nghttp2_session_on_altsvc_received(session, frame);
+}
+
+static int session_process_origin_frame(nghttp2_session *session) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+ nghttp2_mem *mem = &session->mem;
+ int rv;
+
+ rv = nghttp2_frame_unpack_origin_payload(&frame->ext, iframe->lbuf.pos,
+ nghttp2_buf_len(&iframe->lbuf), mem);
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ /* Ignore ORIGIN frame which cannot be parsed. */
+ return 0;
+ }
+
+ return nghttp2_session_on_origin_received(session, frame);
+}
+
+static int session_process_priority_update_frame(nghttp2_session *session) {
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+
+ nghttp2_frame_unpack_priority_update_payload(&frame->ext, iframe->sbuf.pos,
+ nghttp2_buf_len(&iframe->sbuf));
+
+ return nghttp2_session_on_priority_update_received(session, frame);
+}
+
+static int session_process_extension_frame(nghttp2_session *session) {
+ int rv;
+ nghttp2_inbound_frame *iframe = &session->iframe;
+ nghttp2_frame *frame = &iframe->frame;
+
+ rv = session_call_unpack_extension_callback(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ /* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */
+ if (rv != 0) {
+ return 0;
+ }
+
+ return session_call_on_frame_received(session, frame);
+}
+
+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;
+ }
+
+ if (session_enforce_http_messaging(session) &&
+ (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+ if (nghttp2_http_on_remote_end_stream(stream) != 0) {
+ rv = nghttp2_session_add_rst_stream(session, stream->stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ /* Don't call nghttp2_session_close_stream_if_shut_rdwr because
+ RST_STREAM has been submitted. */
+ 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 += (int32_t)delta;
+ return 0;
+}
+
+int nghttp2_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. */
+ /* We have to use local_settings here because it is the constraint
+ the remote endpoint should honor. */
+ if (send_window_update &&
+ !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) &&
+ stream->window_update_queued == 0 &&
+ 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) {
+ return rv;
+ }
+
+ stream->recv_window_size = 0;
+ }
+ return 0;
+}
+
+int nghttp2_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) &&
+ session->window_update_queued == 0 &&
+ 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,
+ uint8_t window_update_queued,
+ 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 += (int32_t)delta_size;
+
+ if (window_update_queued == 0) {
+ /* 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->window_update_queued, 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,
+ session->window_update_queued, 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;
+ }
+
+ if (session_detect_idle_stream(session, stream_id)) {
+ failure_reason = "DATA: stream in idle";
+ error_code = NGHTTP2_PROTOCOL_ERROR;
+ goto fail;
+ }
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+ if (!stream) {
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+ if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
+ failure_reason = "DATA: stream closed";
+ 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((size_t)(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;
+ nghttp2_settings_entry *min_header_table_size_entry;
+ 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:
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ break;
+ default:
+ DEBUGF("recv: unknown settings id=0x%02x\n", iv.settings_id);
+
+ iframe->iv[iframe->niv++] = iv;
+
+ 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) {
+ /* Keep track of minimum value of SETTINGS_HEADER_TABLE_SIZE */
+ min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1];
+
+ if (iv.value < min_header_table_size_entry->value) {
+ min_header_table_size_entry->value = iv.value;
+ }
+ }
+}
+
+/*
+ * 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("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 = (size_t)(iframe->sbuf.pos[0] + 1);
+
+ DEBUGF("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 (ssize_t)padlen;
+}
+
+/*
+ * This function returns the effective payload length in the data of
+ * length |readlen| when the remaining 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;
+ }
+ return (ssize_t)(readlen - padlen);
+ }
+ return (ssize_t)(readlen);
+}
+
+static const uint8_t static_in[] = {0};
+
+ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
+ size_t inlen) {
+ const uint8_t *first, *last;
+ 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;
+
+ if (in == NULL) {
+ assert(inlen == 0);
+ in = static_in;
+ }
+
+ first = in;
+ last = in + inlen;
+
+ DEBUGF("recv: connection recv_window_size=%d, local_window=%d\n",
+ session->recv_window_size, session->local_window_size);
+
+ mem = &session->mem;
+
+ /* We may have idle streams more than we expect (e.g.,
+ nghttp2_session_change_stream_priority() or
+ nghttp2_session_create_idle_stream()). Adjust them here. */
+ rv = nghttp2_session_adjust_idle_stream(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (!nghttp2_session_want_read(session)) {
+ return (ssize_t)inlen;
+ }
+
+ for (;;) {
+ switch (iframe->state) {
+ case NGHTTP2_IB_READ_CLIENT_MAGIC:
+ readlen = nghttp2_min(inlen, iframe->payloadleft);
+
+ if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN -
+ iframe->payloadleft],
+ in, readlen) != 0) {
+ return NGHTTP2_ERR_BAD_CLIENT_MAGIC;
+ }
+
+ 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("recv: [IB_READ_FIRST_SETTINGS]\n");
+
+ readlen = inbound_frame_buf_read(iframe, in, last);
+ in += readlen;
+
+ if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+ return (ssize_t)(in - first);
+ }
+
+ if (iframe->sbuf.pos[3] != NGHTTP2_SETTINGS ||
+ (iframe->sbuf.pos[4] & NGHTTP2_FLAG_ACK)) {
+ rv = session_call_error_callback(
+ session, NGHTTP2_ERR_SETTINGS_EXPECTED,
+ "Remote peer returned unexpected data while we expected "
+ "SETTINGS frame. Perhaps, peer does not support HTTP/2 "
+ "properly.");
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR, "SETTINGS expected");
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return (ssize_t)inlen;
+ }
+
+ iframe->state = NGHTTP2_IB_READ_HEAD;
+
+ /* Fall through */
+ case NGHTTP2_IB_READ_HEAD: {
+ int on_begin_frame_called = 0;
+
+ DEBUGF("recv: [IB_READ_HEAD]\n");
+
+ readlen = inbound_frame_buf_read(iframe, in, last);
+ in += readlen;
+
+ if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+ return (ssize_t)(in - first);
+ }
+
+ nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos);
+ iframe->payloadleft = iframe->frame.hd.length;
+
+ DEBUGF("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("recv: length is too large %zu > %u\n", iframe->frame.hd.length,
+ session->local_settings.max_frame_size);
+
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_FRAME_SIZE_ERROR, "too large frame size");
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return (ssize_t)inlen;
+ }
+
+ switch (iframe->frame.hd.type) {
+ case NGHTTP2_DATA: {
+ DEBUGF("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 (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+ if (rv == NGHTTP2_ERR_IGN_PAYLOAD) {
+ DEBUGF("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) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR,
+ "DATA: insufficient padding space");
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ if (rv == 1) {
+ iframe->state = NGHTTP2_IB_READ_PAD_DATA;
+ break;
+ }
+
+ iframe->state = NGHTTP2_IB_READ_DATA;
+ break;
+ }
+ case NGHTTP2_HEADERS:
+
+ DEBUGF("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) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR,
+ "HEADERS: insufficient padding space");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ 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 (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ rv = nghttp2_session_add_rst_stream(
+ session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+ break;
+ }
+
+ 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("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("recv: RST_STREAM\n");
+ break;
+ case NGHTTP2_WINDOW_UPDATE:
+ DEBUGF("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("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;
+ }
+
+ /* Check the settings flood counter early to be safe */
+ if (session->obq_flood_counter_ >= session->max_outbound_ack &&
+ !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) {
+ return NGHTTP2_ERR_FLOODED;
+ }
+
+ iframe->state = NGHTTP2_IB_READ_SETTINGS;
+
+ if (iframe->payloadleft) {
+ nghttp2_settings_entry *min_header_table_size_entry;
+
+ /* We allocate iv with additional one entry, to store the
+ minimum header table size. */
+ iframe->max_niv =
+ iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1;
+
+ if (iframe->max_niv - 1 > session->max_settings) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_ENHANCE_YOUR_CALM,
+ "SETTINGS: too many setting entries");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) *
+ iframe->max_niv);
+
+ if (!iframe->iv) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1];
+ min_header_table_size_entry->settings_id =
+ NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ min_header_table_size_entry->value = UINT32_MAX;
+
+ 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("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) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR,
+ "PUSH_PROMISE: insufficient padding space");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ 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("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("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("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;
+ }
+
+ return (ssize_t)inlen;
+ default:
+ DEBUGF("recv: extension frame\n");
+
+ if (check_ext_type_set(session->user_recv_ext_types,
+ iframe->frame.hd.type)) {
+ if (!session->callbacks.unpack_extension_callback) {
+ /* Silently ignore unknown frame type. */
+
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+ break;
+ }
+
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
+
+ break;
+ } else {
+ switch (iframe->frame.hd.type) {
+ case NGHTTP2_ALTSVC:
+ if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) ==
+ 0) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ DEBUGF("recv: ALTSVC\n");
+
+ iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+ iframe->frame.ext.payload = &iframe->ext_frame_payload.altsvc;
+
+ if (session->server) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ if (iframe->payloadleft < 2) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+ break;
+ }
+
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_READ_NBYTE;
+ inbound_frame_set_mark(iframe, 2);
+
+ break;
+ case NGHTTP2_ORIGIN:
+ if (!(session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN)) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ DEBUGF("recv: ORIGIN\n");
+
+ iframe->frame.ext.payload = &iframe->ext_frame_payload.origin;
+
+ if (session->server || iframe->frame.hd.stream_id ||
+ (iframe->frame.hd.flags & 0xf0)) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+
+ if (iframe->payloadleft) {
+ iframe->raw_lbuf = nghttp2_mem_malloc(mem, iframe->payloadleft);
+
+ if (iframe->raw_lbuf == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf,
+ iframe->payloadleft);
+ } else {
+ busy = 1;
+ }
+
+ iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD;
+
+ break;
+ case NGHTTP2_PRIORITY_UPDATE:
+ if ((session->builtin_recv_ext_types &
+ NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ DEBUGF("recv: PRIORITY_UPDATE\n");
+
+ iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+ iframe->frame.ext.payload =
+ &iframe->ext_frame_payload.priority_update;
+
+ if (!session->server) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR,
+ "PRIORITY_UPDATE is received from server");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ if (iframe->payloadleft < 4) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+ break;
+ }
+
+ if (!session_no_rfc7540_pri_no_fallback(session) ||
+ iframe->payloadleft > sizeof(iframe->raw_sbuf)) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+ break;
+ }
+
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_READ_NBYTE;
+ inbound_frame_set_mark(iframe, iframe->payloadleft);
+
+ break;
+ default:
+ 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:
+ case NGHTTP2_IB_IGN_ALL:
+ 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("recv: [IB_READ_NBYTE]\n");
+
+ readlen = inbound_frame_buf_read(iframe, in, last);
+ in += readlen;
+ iframe->payloadleft -= readlen;
+
+ DEBUGF("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 (ssize_t)(in - first);
+ }
+
+ switch (iframe->frame.hd.type) {
+ case NGHTTP2_HEADERS:
+ if (iframe->padlen == 0 &&
+ (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) {
+ pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags);
+ padlen = inbound_frame_compute_pad(iframe);
+ if (padlen < 0 ||
+ (size_t)padlen + pri_fieldlen > 1 + iframe->payloadleft) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: invalid padding");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+ iframe->frame.headers.padlen = (size_t)padlen;
+
+ 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 (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ rv = nghttp2_session_add_rst_stream(
+ session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+ break;
+ }
+
+ 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:
+ if (!session_no_rfc7540_pri_no_fallback(session) &&
+ session->remote_settings.no_rfc7540_priorities != 1) {
+ rv = session_process_priority_frame(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_RST_STREAM:
+ rv = session_process_rst_stream_frame(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ 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 || (size_t)padlen + 4 /* promised stream id */
+ > 1 + iframe->payloadleft) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_PROTOCOL_ERROR,
+ "PUSH_PROMISE: invalid padding");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
+ iframe->frame.push_promise.padlen = (size_t)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 (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+ rv = nghttp2_session_add_rst_stream(
+ session, iframe->frame.push_promise.promised_stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+ break;
+ }
+
+ 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;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ 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;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_ALTSVC: {
+ size_t origin_len;
+
+ origin_len = nghttp2_get_uint16(iframe->sbuf.pos);
+
+ DEBUGF("recv: origin_len=%zu\n", origin_len);
+
+ if (origin_len > iframe->payloadleft) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+ break;
+ }
+
+ if (iframe->frame.hd.length > 2) {
+ iframe->raw_lbuf =
+ nghttp2_mem_malloc(mem, iframe->frame.hd.length - 2);
+
+ if (iframe->raw_lbuf == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf,
+ iframe->frame.hd.length);
+ }
+
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD;
+
+ break;
+ case NGHTTP2_PRIORITY_UPDATE:
+ DEBUGF("recv: prioritized_stream_id=%d\n",
+ nghttp2_get_uint32(iframe->sbuf.pos) & NGHTTP2_STREAM_ID_MASK);
+
+ rv = session_process_priority_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;
+ size_t trail_padlen;
+ int final;
+#ifdef DEBUGBUILD
+ if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) {
+ DEBUGF("recv: [IB_READ_HEADER_BLOCK]\n");
+ } else {
+ DEBUGF("recv: [IB_IGN_HEADER_BLOCK]\n");
+ }
+#endif /* DEBUGBUILD */
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft - readlen);
+
+ data_readlen = inbound_frame_effective_readlen(
+ iframe, iframe->payloadleft - readlen, readlen);
+
+ if (data_readlen == -1) {
+ /* everything is padding */
+ data_readlen = 0;
+ }
+
+ trail_padlen = nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen);
+
+ final = (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) &&
+ iframe->payloadleft - (size_t)data_readlen == trail_padlen;
+
+ if (data_readlen > 0 || (data_readlen == 0 && final)) {
+ size_t hd_proclen = 0;
+
+ DEBUGF("recv: block final=%d\n", final);
+
+ rv =
+ inflate_header_block(session, &iframe->frame, &hd_proclen,
+ (uint8_t *)in, (size_t)data_readlen, final,
+ iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ if (rv == NGHTTP2_ERR_PAUSE) {
+ in += hd_proclen;
+ iframe->payloadleft -= hd_proclen;
+
+ return (ssize_t)(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;
+
+ /* Use promised stream ID for PUSH_PROMISE */
+ rv = nghttp2_session_add_rst_stream(
+ session,
+ iframe->frame.hd.type == NGHTTP2_PUSH_PROMISE
+ ? iframe->frame.push_promise.promised_stream_id
+ : 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("recv: [IB_IGN_PAYLOAD]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+ iframe->payloadleft -= readlen;
+ in += readlen;
+
+ DEBUGF("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("recv: [IB_FRAME_SIZE_ERROR]\n");
+
+ rv = session_handle_frame_size_error(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ assert(iframe->state == NGHTTP2_IB_IGN_ALL);
+
+ return (ssize_t)inlen;
+ case NGHTTP2_IB_READ_SETTINGS:
+ DEBUGF("recv: [IB_READ_SETTINGS]\n");
+
+ readlen = inbound_frame_buf_read(iframe, in, last);
+ iframe->payloadleft -= readlen;
+ in += readlen;
+
+ DEBUGF("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;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_IB_READ_GOAWAY_DEBUG:
+ DEBUGF("recv: [IB_READ_GOAWAY_DEBUG]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+
+ if (readlen > 0) {
+ iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen);
+
+ iframe->payloadleft -= readlen;
+ in += readlen;
+ }
+
+ DEBUGF("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;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ 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 (ssize_t)(in - first);
+ }
+
+ nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->sbuf.pos);
+ iframe->payloadleft = cont_hd.length;
+
+ DEBUGF("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("recv: expected stream_id=%d, type=%d, but got stream_id=%d, "
+ "type=%u\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;
+ }
+
+ return (ssize_t)inlen;
+ }
+
+ /* CONTINUATION won't bear NGHTTP2_PADDED flag */
+
+ iframe->frame.hd.flags =
+ (uint8_t)(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("recv: [IB_READ_PAD_DATA]\n");
+
+ readlen = inbound_frame_buf_read(iframe, in, last);
+ in += readlen;
+ iframe->payloadleft -= readlen;
+
+ DEBUGF("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 (ssize_t)(in - first);
+ }
+
+ /* Pad Length field is subject to flow control */
+ rv = nghttp2_session_update_recv_connection_window_size(session, readlen);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ /* Pad Length field is consumed immediately */
+ rv =
+ nghttp2_session_consume(session, iframe->frame.hd.stream_id, readlen);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
+ if (stream) {
+ rv = nghttp2_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;
+ }
+ return (ssize_t)inlen;
+ }
+
+ iframe->frame.data.padlen = (size_t)padlen;
+
+ iframe->state = NGHTTP2_IB_READ_DATA;
+
+ break;
+ case NGHTTP2_IB_READ_DATA:
+ stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
+
+ if (!stream) {
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_DATA;
+ break;
+ }
+
+ DEBUGF("recv: [IB_READ_DATA]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+ iframe->payloadleft -= readlen;
+ in += readlen;
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft);
+
+ if (readlen > 0) {
+ ssize_t data_readlen;
+
+ rv = nghttp2_session_update_recv_connection_window_size(session,
+ readlen);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ rv = nghttp2_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);
+
+ if (data_readlen == -1) {
+ /* everything is padding */
+ data_readlen = 0;
+ }
+
+ padlen = (ssize_t)readlen - data_readlen;
+
+ if (padlen > 0) {
+ /* Padding is considered as "consumed" immediately */
+ rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
+ (size_t)padlen);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+ }
+
+ DEBUGF("recv: data_readlen=%zd\n", data_readlen);
+
+ if (data_readlen > 0) {
+ if (session_enforce_http_messaging(session)) {
+ if (nghttp2_http_on_data_chunk(stream, (size_t)data_readlen) != 0) {
+ if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
+ /* Consume all data for connection immediately here */
+ rv = session_update_connection_consumed_size(
+ session, (size_t)data_readlen);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_DATA) {
+ return (ssize_t)inlen;
+ }
+ }
+
+ rv = nghttp2_session_add_rst_stream(
+ session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ busy = 1;
+ iframe->state = NGHTTP2_IB_IGN_DATA;
+ break;
+ }
+ }
+ if (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, (size_t)data_readlen, session->user_data);
+ if (rv == NGHTTP2_ERR_PAUSE) {
+ return (ssize_t)(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("recv: [IB_IGN_DATA]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+ iframe->payloadleft -= readlen;
+ in += readlen;
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft);
+
+ if (readlen > 0) {
+ /* Update connection-level flow control window for ignored
+ DATA frame too */
+ rv = nghttp2_session_update_recv_connection_window_size(session,
+ readlen);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ 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->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+ }
+ }
+
+ if (iframe->payloadleft) {
+ break;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_IB_IGN_ALL:
+ return (ssize_t)inlen;
+ case NGHTTP2_IB_READ_EXTENSION_PAYLOAD:
+ DEBUGF("recv: [IB_READ_EXTENSION_PAYLOAD]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+ iframe->payloadleft -= readlen;
+ in += readlen;
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft);
+
+ if (readlen > 0) {
+ rv = session_call_on_extension_chunk_recv_callback(
+ session, in - readlen, readlen);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (rv != 0) {
+ busy = 1;
+
+ iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+ break;
+ }
+ }
+
+ if (iframe->payloadleft > 0) {
+ break;
+ }
+
+ rv = session_process_extension_frame(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_IB_READ_ALTSVC_PAYLOAD:
+ DEBUGF("recv: [IB_READ_ALTSVC_PAYLOAD]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+ if (readlen > 0) {
+ iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen);
+
+ iframe->payloadleft -= readlen;
+ in += readlen;
+ }
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft);
+
+ if (iframe->payloadleft) {
+ assert(nghttp2_buf_avail(&iframe->lbuf) > 0);
+
+ break;
+ }
+
+ rv = session_process_altsvc_frame(session);
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ case NGHTTP2_IB_READ_ORIGIN_PAYLOAD:
+ DEBUGF("recv: [IB_READ_ORIGIN_PAYLOAD]\n");
+
+ readlen = inbound_frame_payload_readlen(iframe, in, last);
+
+ if (readlen > 0) {
+ iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen);
+
+ iframe->payloadleft -= readlen;
+ in += readlen;
+ }
+
+ DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen,
+ iframe->payloadleft);
+
+ if (iframe->payloadleft) {
+ assert(nghttp2_buf_avail(&iframe->lbuf) > 0);
+
+ break;
+ }
+
+ rv = session_process_origin_frame(session);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ if (iframe->state == NGHTTP2_IB_IGN_ALL) {
+ return (ssize_t)inlen;
+ }
+
+ session_inbound_frame_reset(session);
+
+ break;
+ }
+
+ if (!busy && in == last) {
+ break;
+ }
+
+ busy = 0;
+ }
+
+ assert(in == last);
+
+ return (ssize_t)(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, (size_t)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 -
+ session->num_idle_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) {
+ /* 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;
+ }
+
+ /*
+ * 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.
+ */
+ return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) ||
+ nghttp2_outbound_queue_top(&session->ob_reg) ||
+ ((!nghttp2_pq_empty(&session->root.obq) ||
+ !session_sched_empty(session)) &&
+ session->remote_window_size > 0) ||
+ (nghttp2_outbound_queue_top(&session->ob_syn) &&
+ !session_is_outgoing_concurrent_streams_max(session));
+}
+
+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;
+
+ if ((flags & NGHTTP2_FLAG_ACK) &&
+ session->obq_flood_counter_ >= session->max_outbound_ack) {
+ return NGHTTP2_ERR_FLOODED;
+ }
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_outbound_item_init(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;
+ }
+
+ if (flags & NGHTTP2_FLAG_ACK) {
+ ++session->obq_flood_counter_;
+ }
+
+ 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_outbound_item_init(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;
+ }
+
+ session->goaway_flags |= NGHTTP2_GOAWAY_SUBMITTED;
+
+ 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_outbound_item_init(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;
+}
+
+static void
+session_append_inflight_settings(nghttp2_session *session,
+ nghttp2_inflight_settings *settings) {
+ nghttp2_inflight_settings **i;
+
+ for (i = &session->inflight_settings_head; *i; i = &(*i)->next)
+ ;
+
+ *i = settings;
+}
+
+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;
+ nghttp2_inflight_settings *inflight_settings = NULL;
+ uint8_t no_rfc7540_pri = session->pending_no_rfc7540_priorities;
+
+ mem = &session->mem;
+
+ if (flags & NGHTTP2_FLAG_ACK) {
+ if (niv != 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (session->obq_flood_counter_ >= session->max_outbound_ack) {
+ return NGHTTP2_ERR_FLOODED;
+ }
+ }
+
+ if (!nghttp2_iv_check(iv, niv)) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ for (i = 0; i < niv; ++i) {
+ if (iv[i].settings_id != NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES) {
+ continue;
+ }
+
+ if (no_rfc7540_pri == UINT8_MAX) {
+ no_rfc7540_pri = (uint8_t)iv[i].value;
+ continue;
+ }
+
+ if (iv[i].value != (uint32_t)no_rfc7540_pri) {
+ 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) {
+ rv = inflight_settings_new(&inflight_settings, iv, niv, mem);
+ if (rv != 0) {
+ assert(nghttp2_is_fatal(rv));
+ nghttp2_mem_free(mem, iv_copy);
+ nghttp2_mem_free(mem, item);
+ return rv;
+ }
+ }
+
+ nghttp2_outbound_item_init(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));
+
+ inflight_settings_del(inflight_settings, mem);
+
+ nghttp2_frame_settings_free(&frame->settings, mem);
+ nghttp2_mem_free(mem, item);
+
+ return rv;
+ }
+
+ if (flags & NGHTTP2_FLAG_ACK) {
+ ++session->obq_flood_counter_;
+ } else {
+ session_append_inflight_settings(session, inflight_settings);
+ }
+
+ /* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS and ENABLE_PUSH
+ here. We use it to refuse the incoming stream and PUSH_PROMISE
+ 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;
+ }
+ }
+
+ for (i = niv; i > 0; --i) {
+ if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_PUSH) {
+ session->pending_enable_push = (uint8_t)iv[i - 1].value;
+ break;
+ }
+ }
+
+ for (i = niv; i > 0; --i) {
+ if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) {
+ session->pending_enable_connect_protocol = (uint8_t)iv[i - 1].value;
+ break;
+ }
+ }
+
+ if (no_rfc7540_pri == UINT8_MAX) {
+ session->pending_no_rfc7540_priorities = 0;
+ } else {
+ session->pending_no_rfc7540_priorities = no_rfc7540_pri;
+ }
+
+ 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,
+ nghttp2_stream *stream) {
+ 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) {
+
+ 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("send: read_length_callback=%zd\n", payloadlen);
+
+ payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream,
+ payloadlen);
+
+ DEBUGF("send: read_length_callback after flow control=%zd\n", payloadlen);
+
+ if (payloadlen <= 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if ((size_t)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,
+ (size_t)(NGHTTP2_FRAME_HDLEN + 1 + payloadlen));
+
+ if (rv != 0) {
+ DEBUGF("send: realloc buffer failed rv=%d", rv);
+ /* If reallocation failed, old buffers are still in tact. So
+ use safe limit. */
+ payloadlen = (ssize_t)datamax;
+
+ DEBUGF("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) >= 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 ||
+ payloadlen == NGHTTP2_ERR_PAUSE) {
+ DEBUGF("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 NGHTTP2_DATA_FLAG_NO_END_STREAM is set, don't set
+ NGHTTP2_FLAG_END_STREAM */
+ if ((aux_data->flags & NGHTTP2_FLAG_END_STREAM) &&
+ (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) == 0) {
+ frame->hd.flags |= NGHTTP2_FLAG_END_STREAM;
+ }
+ }
+
+ if (data_flags & NGHTTP2_DATA_FLAG_NO_COPY) {
+ if (session->callbacks.send_data_callback == NULL) {
+ DEBUGF("NGHTTP2_DATA_FLAG_NO_COPY requires send_data_callback set\n");
+
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ aux_data->no_copy = 1;
+ }
+
+ frame->hd.length = (size_t)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 = (size_t)(padded_payloadlen - payloadlen);
+
+ nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+ nghttp2_frame_add_pad(bufs, &frame->hd, frame->data.padlen,
+ aux_data->no_copy);
+
+ session_reschedule_stream(session, stream);
+
+ if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) &&
+ (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) {
+ /* DATA payload length is 0, and DATA frame does not bear
+ END_STREAM. In this case, there is no point to send 0 length
+ DATA frame. */
+ return NGHTTP2_ERR_CANCEL;
+ }
+
+ 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;
+ nghttp2_frame *frame;
+ nghttp2_outbound_item *item;
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+ if (stream) {
+ stream->stream_user_data = stream_user_data;
+ return 0;
+ }
+
+ if (session->server || !nghttp2_session_is_my_stream_id(session, stream_id) ||
+ !nghttp2_outbound_queue_top(&session->ob_syn)) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame;
+ assert(frame->hd.type == NGHTTP2_HEADERS);
+
+ if (frame->hd.stream_id > stream_id ||
+ (uint32_t)stream_id >= session->next_stream_id) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ for (item = session->ob_syn.head; item; item = item->qnext) {
+ if (item->frame.hd.stream_id < stream_id) {
+ continue;
+ }
+
+ if (item->frame.hd.stream_id > stream_id) {
+ break;
+ }
+
+ item->aux_data.headers.stream_user_data = stream_user_data;
+ return 0;
+ }
+
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+}
+
+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 = session_resume_deferred_stream_item(session, stream,
+ NGHTTP2_STREAM_FLAG_DEFERRED_USER);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return 0;
+}
+
+size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session) {
+ return nghttp2_outbound_queue_size(&session->ob_urgent) +
+ nghttp2_outbound_queue_size(&session->ob_reg) +
+ nghttp2_outbound_queue_size(&session->ob_syn);
+ /* TODO account for item attached to stream */
+}
+
+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_stream_local_window_size(nghttp2_session *session,
+ int32_t stream_id) {
+ nghttp2_stream *stream;
+ int32_t size;
+ stream = nghttp2_session_get_stream(session, stream_id);
+ if (stream == NULL) {
+ return -1;
+ }
+
+ size = stream->local_window_size - stream->recv_window_size;
+
+ /* size could be negative if local endpoint reduced
+ SETTINGS_INITIAL_WINDOW_SIZE */
+ if (size < 0) {
+ return 0;
+ }
+
+ return 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_local_window_size(nghttp2_session *session) {
+ return session->local_window_size - session->recv_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;
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ return session->remote_settings.enable_connect_protocol;
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ return session->remote_settings.no_rfc7540_priorities;
+ }
+
+ assert(0);
+ abort(); /* if NDEBUG is set */
+}
+
+uint32_t nghttp2_session_get_local_settings(nghttp2_session *session,
+ nghttp2_settings_id id) {
+ switch (id) {
+ case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+ return session->local_settings.header_table_size;
+ case NGHTTP2_SETTINGS_ENABLE_PUSH:
+ return session->local_settings.enable_push;
+ case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+ return session->local_settings.max_concurrent_streams;
+ case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+ return session->local_settings.initial_window_size;
+ case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+ return session->local_settings.max_frame_size;
+ case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+ return session->local_settings.max_header_list_size;
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ return session->local_settings.enable_connect_protocol;
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ return session->local_settings.no_rfc7540_priorities;
+ }
+
+ assert(0);
+ abort(); /* if NDEBUG is set */
+}
+
+static int nghttp2_session_upgrade_internal(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;
+ }
+ /* SETTINGS frame contains too many settings */
+ if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH >
+ session->max_settings) {
+ return NGHTTP2_ERR_TOO_MANY_SETTINGS;
+ }
+ 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;
+ }
+
+ /* We don't call nghttp2_session_adjust_closed_stream(), since this
+ should be the first stream open. */
+
+ 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->last_sent_stream_id = 1;
+ session->next_stream_id += 2;
+ }
+ return 0;
+}
+
+int nghttp2_session_upgrade(nghttp2_session *session,
+ const uint8_t *settings_payload,
+ size_t settings_payloadlen,
+ void *stream_user_data) {
+ int rv;
+ nghttp2_stream *stream;
+
+ rv = nghttp2_session_upgrade_internal(session, settings_payload,
+ settings_payloadlen, stream_user_data);
+ if (rv != 0) {
+ return rv;
+ }
+
+ stream = nghttp2_session_get_stream(session, 1);
+ assert(stream);
+
+ /* We have no information about request header fields when Upgrade
+ was happened. So we don't know the request method here. If
+ request method is HEAD, we have a trouble because we may have
+ nonzero content-length header field in response headers, and we
+ will going to check it against the actual DATA frames, but we may
+ get mismatch because HEAD response body must be empty. Because
+ of this reason, nghttp2_session_upgrade() was deprecated in favor
+ of nghttp2_session_upgrade2(), which has |head_request| parameter
+ to indicate that request method is HEAD or not. */
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND;
+ return 0;
+}
+
+int nghttp2_session_upgrade2(nghttp2_session *session,
+ const uint8_t *settings_payload,
+ size_t settings_payloadlen, int head_request,
+ void *stream_user_data) {
+ int rv;
+ nghttp2_stream *stream;
+
+ rv = nghttp2_session_upgrade_internal(session, settings_payload,
+ settings_payloadlen, stream_user_data);
+ if (rv != 0) {
+ return rv;
+ }
+
+ stream = nghttp2_session_get_stream(session, 1);
+ assert(stream);
+
+ if (head_request) {
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
+ }
+
+ 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) {
+ return 0;
+ }
+
+ rv = session_update_stream_consumed_size(session, stream, size);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ return 0;
+}
+
+int nghttp2_session_consume_connection(nghttp2_session *session, size_t size) {
+ int rv;
+
+ 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;
+ }
+
+ return 0;
+}
+
+int nghttp2_session_consume_stream(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;
+ }
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+
+ if (!stream) {
+ return 0;
+ }
+
+ 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;
+ }
+
+ if (session->server) {
+ if (next_stream_id % 2) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+ } else if (next_stream_id % 2 == 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ session->next_stream_id = (uint32_t)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;
+}
+
+nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session,
+ int32_t stream_id) {
+ if (stream_id == 0) {
+ return &session->root;
+ }
+
+ return nghttp2_session_get_stream_raw(session, stream_id);
+}
+
+nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) {
+ return &session->root;
+}
+
+int nghttp2_session_check_server_session(nghttp2_session *session) {
+ return session->server;
+}
+
+int nghttp2_session_change_stream_priority(
+ nghttp2_session *session, int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec) {
+ int rv;
+ nghttp2_stream *stream;
+ nghttp2_priority_spec pri_spec_copy;
+
+ if (session->pending_no_rfc7540_priorities == 1) {
+ return 0;
+ }
+
+ if (stream_id == 0 || stream_id == pri_spec->stream_id) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+ if (!stream) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ pri_spec_copy = *pri_spec;
+ nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
+
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+
+ /* We don't intentionally call nghttp2_session_adjust_idle_stream()
+ so that idle stream created by this function, and existing ones
+ are kept for application. We will adjust number of idle stream
+ in nghttp2_session_mem_send or nghttp2_session_mem_recv is
+ called. */
+ return 0;
+}
+
+int nghttp2_session_create_idle_stream(nghttp2_session *session,
+ int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec) {
+ nghttp2_stream *stream;
+ nghttp2_priority_spec pri_spec_copy;
+
+ if (session->pending_no_rfc7540_priorities == 1) {
+ return 0;
+ }
+
+ if (stream_id == 0 || stream_id == pri_spec->stream_id ||
+ !session_detect_idle_stream(session, stream_id)) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+ if (stream) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ pri_spec_copy = *pri_spec;
+ nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
+
+ stream =
+ nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE,
+ &pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL);
+ if (!stream) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ /* We don't intentionally call nghttp2_session_adjust_idle_stream()
+ so that idle stream created by this function, and existing ones
+ are kept for application. We will adjust number of idle stream
+ in nghttp2_session_mem_send or nghttp2_session_mem_recv is
+ called. */
+ return 0;
+}
+
+size_t
+nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session) {
+ return nghttp2_hd_inflate_get_dynamic_table_size(&session->hd_inflater);
+}
+
+size_t
+nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session) {
+ return nghttp2_hd_deflate_get_dynamic_table_size(&session->hd_deflater);
+}
+
+void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data) {
+ session->user_data = user_data;
+}
+
+int nghttp2_session_change_extpri_stream_priority(
+ nghttp2_session *session, int32_t stream_id,
+ const nghttp2_extpri *extpri_in, int ignore_client_signal) {
+ nghttp2_stream *stream;
+ nghttp2_extpri extpri = *extpri_in;
+
+ if (!session->server) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ if (session->pending_no_rfc7540_priorities != 1) {
+ return 0;
+ }
+
+ if (stream_id == 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+ if (!stream) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (extpri.urgency > NGHTTP2_EXTPRI_URGENCY_LOW) {
+ extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW;
+ }
+
+ if (ignore_client_signal) {
+ stream->flags |= NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES;
+ }
+
+ return session_update_stream_priority(session, stream,
+ nghttp2_extpri_to_uint8(&extpri));
+}
+
+int nghttp2_session_get_extpri_stream_priority(nghttp2_session *session,
+ nghttp2_extpri *extpri,
+ int32_t stream_id) {
+ nghttp2_stream *stream;
+
+ if (!session->server) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ if (session->pending_no_rfc7540_priorities != 1) {
+ return 0;
+ }
+
+ if (stream_id == 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+ if (!stream) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ nghttp2_extpri_from_uint8(extpri, stream->extpri);
+
+ return 0;
+}
diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h
new file mode 100644
index 0000000..b119329
--- /dev/null
+++ b/lib/nghttp2_session.h
@@ -0,0 +1,974 @@
+/*
+ * 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_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"
+#include "nghttp2_ratelim.h"
+
+/* The global variable for tests where we want to disable strict
+ preface handling. */
+extern int nghttp2_enable_strict_preface;
+
+/*
+ * Option flags.
+ */
+typedef enum {
+ NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
+ NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1,
+ NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2,
+ NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3,
+ NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4,
+ NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5,
+ NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 6,
+} nghttp2_optmask;
+
+/*
+ * bitmask for built-in type to enable the default handling for that
+ * type of the frame.
+ */
+typedef enum {
+ NGHTTP2_TYPEMASK_NONE = 0,
+ NGHTTP2_TYPEMASK_ALTSVC = 1 << 0,
+ NGHTTP2_TYPEMASK_ORIGIN = 1 << 1,
+ NGHTTP2_TYPEMASK_PRIORITY_UPDATE = 1 << 2
+} nghttp2_typemask;
+
+typedef enum {
+ NGHTTP2_OB_POP_ITEM,
+ NGHTTP2_OB_SEND_DATA,
+ NGHTTP2_OB_SEND_NO_COPY,
+ NGHTTP2_OB_SEND_CLIENT_MAGIC
+} 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
+
+/* The default maximum number of incoming reserved streams */
+#define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200
+
+/* Even if we have less SETTINGS_MAX_CONCURRENT_STREAMS than this
+ number, we keep NGHTTP2_MIN_IDLE_STREAMS streams in idle state */
+#define NGHTTP2_MIN_IDLE_STREAMS 16
+
+/* The maximum number of items in outbound queue, which is considered
+ as flooding caused by peer. All frames are not considered here.
+ We only consider PING + ACK and SETTINGS + ACK. This is because
+ they both are response to the frame initiated by peer and peer can
+ send as many of them as they want. If peer does not read network,
+ response frames are stacked up, which leads to memory exhaustion.
+ The value selected here is arbitrary, but safe value and if we have
+ these frames in this number, it is considered suspicious. */
+#define NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM 1000
+
+/* The default value of maximum number of concurrent streams. */
+#define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu
+
+/* The default values for stream reset rate limiter. */
+#define NGHTTP2_DEFAULT_STREAM_RESET_BURST 1000
+#define NGHTTP2_DEFAULT_STREAM_RESET_RATE 33
+
+/* Internal state when receiving incoming frame */
+typedef enum {
+ /* Receiving frame header */
+ NGHTTP2_IB_READ_CLIENT_MAGIC,
+ 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_IB_IGN_ALL,
+ NGHTTP2_IB_READ_ALTSVC_PAYLOAD,
+ NGHTTP2_IB_READ_ORIGIN_PAYLOAD,
+ NGHTTP2_IB_READ_EXTENSION_PAYLOAD
+} nghttp2_inbound_state;
+
+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. For the standard settings entries,
+ we only keep the last seen value. For
+ SETTINGS_HEADER_TABLE_SIZE, we also keep minimum value in the
+ last index. */
+ nghttp2_settings_entry *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;
+ /* The number of entries |iv| can store. */
+ size_t max_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 fixed sized buffer. */
+ uint8_t raw_sbuf[32];
+} 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;
+ uint32_t enable_connect_protocol;
+ uint32_t no_rfc7540_priorities;
+} 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,
+ /* Flag means GOAWAY has been submitted at least once */
+ NGHTTP2_GOAWAY_SUBMITTED = 0x10
+} nghttp2_goaway_flag;
+
+/* nghttp2_inflight_settings stores the SETTINGS entries which local
+ endpoint has sent to the remote endpoint, and has not received ACK
+ yet. */
+struct nghttp2_inflight_settings {
+ struct nghttp2_inflight_settings *next;
+ nghttp2_settings_entry *iv;
+ size_t niv;
+};
+
+typedef struct nghttp2_inflight_settings nghttp2_inflight_settings;
+
+struct nghttp2_session {
+ nghttp2_map /* <nghttp2_stream*> */ streams;
+ /* root of dependency tree*/
+ nghttp2_stream root;
+ /* Queue for outbound urgent frames (PING and SETTINGS) */
+ nghttp2_outbound_queue ob_urgent;
+ /* Queue for non-DATA frames */
+ nghttp2_outbound_queue ob_reg;
+ /* Queue for outbound stream-creating HEADERS (request or push
+ response) frame, which are subject to
+ SETTINGS_MAX_CONCURRENT_STREAMS limit. */
+ nghttp2_outbound_queue ob_syn;
+ /* Queues for DATA frames which is used when
+ SETTINGS_NO_RFC7540_PRIORITIES is enabled. This implements RFC
+ 9218 extensible prioritization scheme. */
+ struct {
+ nghttp2_pq ob_data;
+ } sched[NGHTTP2_EXTPRI_URGENCY_LEVELS];
+ 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;
+ void *user_data;
+ /* Points to the latest incoming 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 incoming 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;
+ /* Queue of In-flight SETTINGS values. SETTINGS bearing ACK is not
+ considered as in-flight. */
+ nghttp2_inflight_settings *inflight_settings_head;
+ /* Stream reset rate limiter. If receiving excessive amount of
+ stream resets, GOAWAY will be sent. */
+ nghttp2_ratelim stream_reset_ratelim;
+ /* Sequential number across all streams to process streams in
+ FIFO. */
+ uint64_t stream_seq;
+ /* 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 incoming reserved streams. This is the number of
+ streams in reserved (remote) state. RFC 7540 does not limit this
+ number. nghttp2 offers
+ nghttp2_option_set_max_reserved_remote_streams() to achieve this.
+ If it is used, num_incoming_streams is capped by
+ max_incoming_reserved_streams. Client application should
+ consider to set this because without that server can send
+ arbitrary number of PUSH_PROMISE, and exhaust client's memory. */
+ size_t num_incoming_reserved_streams;
+ /* The maximum number of incoming reserved streams (reserved
+ (remote) state). RST_STREAM will be sent for the pushed stream
+ which exceeds this limit. */
+ size_t max_incoming_reserved_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;
+ /* Counter for detecting flooding in outbound queue. If it exceeds
+ max_outbound_ack, session will be closed. */
+ size_t obq_flood_counter_;
+ /* The maximum number of outgoing SETTINGS ACK and PING ACK in
+ outbound queue. */
+ size_t max_outbound_ack;
+ /* The maximum length of header block to send. Calculated by the
+ same way as nghttp2_hd_deflate_bound() does. */
+ size_t max_send_header_block_length;
+ /* The maximum number of settings accepted per SETTINGS frame. */
+ size_t max_settings;
+ /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
+ uint32_t next_stream_id;
+ /* The last stream ID this session initiated. For client session,
+ this is the last stream ID it has sent. For server session, it
+ is the last promised stream ID sent in PUSH_PROMISE. */
+ int32_t last_sent_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;
+ /* This flag is used to indicate that the local endpoint received initial
+ SETTINGS frame from the remote endpoint. */
+ uint8_t remote_settings_received;
+ /* Settings value received from the remote endpoint. */
+ 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;
+ /* The bitwise OR of zero or more of nghttp2_typemask to indicate
+ that the default handling of extension frame is enabled. */
+ uint32_t builtin_recv_ext_types;
+ /* Unacked local ENABLE_PUSH value. We use this to refuse
+ PUSH_PROMISE before SETTINGS ACK is received. */
+ uint8_t pending_enable_push;
+ /* Unacked local ENABLE_CONNECT_PROTOCOL value. We use this to
+ accept :protocol header field before SETTINGS_ACK is received. */
+ uint8_t pending_enable_connect_protocol;
+ /* Unacked local SETTINGS_NO_RFC7540_PRIORITIES value, which is
+ effective before it is acknowledged. */
+ uint8_t pending_no_rfc7540_priorities;
+ /* Turn on fallback to RFC 7540 priorities; for server use only. */
+ uint8_t fallback_rfc7540_priorities;
+ /* Nonzero if the session is server side. */
+ uint8_t server;
+ /* Flags indicating GOAWAY is sent and/or received. The flags are
+ composed by bitwise OR-ing nghttp2_goaway_flag. */
+ uint8_t goaway_flags;
+ /* This flag is used to reduce excessive queuing of WINDOW_UPDATE to
+ this session. The nonzero does not necessarily mean
+ WINDOW_UPDATE is not queued. */
+ uint8_t window_update_queued;
+ /* Bitfield of extension frame types that application is willing to
+ receive. To designate the bit of given frame type i, use
+ user_recv_ext_types[i / 8] & (1 << (i & 0x7)). First 10 frame
+ types are standard frame types and not used in this bitfield. If
+ bit is set, it indicates that incoming frame with that type is
+ passed to user defined callbacks, otherwise they are ignored. */
+ uint8_t user_recv_ext_types[32];
+};
+
+/* 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);
+
+/*
+ * 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 and PUSH_PROMISE 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 function 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.
+ * NGHTTP2_ERR_FLOODED
+ * There are too many items in outbound queue; this only happens
+ * if NGHTTP2_FLAG_ACK is set in |flags|
+ */
+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.
+ * NGHTTP2_ERR_FLOODED
+ * There are too many items in outbound queue; this only happens
+ * if NGHTTP2_FLAG_ACK is set in |flags|
+ */
+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.
+ *
+ * If |initial_state| is NGHTTP2_STREAM_RESERVED, this function sets
+ * NGHTTP2_STREAM_FLAG_PUSH flag set.
+ *
+ * This function returns a pointer to created new stream object, or
+ * NULL.
+ *
+ * This function adjusts neither the number of closed streams or idle
+ * streams. The caller should manually call
+ * nghttp2_session_adjust_closed_stream() or
+ * nghttp2_session_adjust_idle_stream() respectively.
+ */
+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.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory
+ */
+int 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.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory
+ */
+int nghttp2_session_adjust_closed_stream(nghttp2_session *session);
+
+/*
+ * Deletes idle stream to ensure that number of idle streams is in
+ * certain limit.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory
+ */
+int 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_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
+ * NGHTTP2_ERR_FLOODED
+ * There are too many items in outbound queue, and this is most
+ * likely caused by misbehaviour of peer.
+ */
+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.
+ * NGHTTP2_ERR_FLOODED
+ * There are too many items in outbound queue, and this is most
+ * likely caused by misbehaviour of peer.
+ */
+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 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_window_update_received(nghttp2_session *session,
+ nghttp2_frame *frame);
+
+/*
+ * Called when ALTSVC is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ * The callback function failed.
+ */
+int nghttp2_session_on_altsvc_received(nghttp2_session *session,
+ nghttp2_frame *frame);
+
+/*
+ * Called when ORIGIN is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ * The callback function failed.
+ */
+int nghttp2_session_on_origin_received(nghttp2_session *session,
+ nghttp2_frame *frame);
+
+/*
+ * Called when PRIORITY_UPDATE is received, assuming |frame| is
+ * properly initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ * The callback function failed.
+ */
+int nghttp2_session_on_priority_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,
+ nghttp2_stream *stream);
+
+/*
+ * 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_syn has item and max concurrent
+ * streams is reached, the even if other queues contain items, 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_syn has item and max concurrent
+ * streams is reached, the even if other queues contain items, 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|. Caller must ensure that stream->hd.stream_id !=
+ * pri_spec->stream_id.
+ *
+ * This function does not adjust the number of idle streams. The
+ * caller should call nghttp2_session_adjust_idle_stream() later.
+ *
+ * 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);
+
+/*
+ * 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.
+ */
+int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session,
+ size_t delta_size);
+
+/*
+ * 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.
+ */
+int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session,
+ nghttp2_stream *stream,
+ size_t delta_size,
+ int send_window_update);
+
+#endif /* NGHTTP2_SESSION_H */
diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c
new file mode 100644
index 0000000..f1951f8
--- /dev/null
+++ b/lib/nghttp2_stream.c
@@ -0,0 +1,1016 @@
+/*
+ * 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"
+#include "nghttp2_debug.h"
+#include "nghttp2_frame.h"
+
+/* Maximum distance between any two stream's cycle in the same
+ priority queue. Imagine stream A's cycle is A, and stream B's
+ cycle is B, and A < B. The cycle is unsigned 32 bit integer, it
+ may get overflow. Because of how we calculate the next cycle
+ value, if B - A is less than or equals to
+ NGHTTP2_MAX_CYCLE_DISTANCE, A and B are in the same scale, in other
+ words, B is really greater than or equal to A. Otherwise, A is a
+ result of overflow, and it is actually A > B if we consider that
+ fact. */
+#define NGHTTP2_MAX_CYCLE_DISTANCE \
+ ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX * 256 + 255)
+
+static int stream_less(const void *lhsx, const void *rhsx) {
+ const nghttp2_stream *lhs, *rhs;
+
+ lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
+ rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
+
+ if (lhs->cycle == rhs->cycle) {
+ return lhs->seq < rhs->seq;
+ }
+
+ return rhs->cycle - lhs->cycle <= NGHTTP2_MAX_CYCLE_DISTANCE;
+}
+
+void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
+ uint8_t flags, nghttp2_stream_state initial_state,
+ int32_t weight, int32_t remote_initial_window_size,
+ int32_t local_initial_window_size,
+ void *stream_user_data, nghttp2_mem *mem) {
+ nghttp2_pq_init(&stream->obq, stream_less, mem);
+
+ 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->window_update_queued = 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->weight = weight;
+ stream->sum_dep_weight = 0;
+
+ stream->http_flags = NGHTTP2_HTTP_FLAG_NONE;
+ stream->content_length = -1;
+ stream->recv_content_length = 0;
+ stream->status_code = -1;
+
+ stream->queued = 0;
+ stream->descendant_last_cycle = 0;
+ stream->cycle = 0;
+ stream->pending_penalty = 0;
+ stream->descendant_next_seq = 0;
+ stream->seq = 0;
+ stream->last_writelen = 0;
+
+ stream->extpri = stream->http_extpri = NGHTTP2_EXTPRI_DEFAULT_URGENCY;
+}
+
+void nghttp2_stream_free(nghttp2_stream *stream) {
+ nghttp2_pq_free(&stream->obq);
+ /* We don't free stream->item. If it is assigned to aob, then
+ active_outbound_item_reset() will delete it. Otherwise,
+ nghttp2_stream_close() or session_del() will delete it. */
+}
+
+void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) {
+ stream->shut_flags = (uint8_t)(stream->shut_flags | flag);
+}
+
+/*
+ * Returns nonzero if |stream| is active. This function does not take
+ * into account its descendants.
+ */
+static int stream_active(nghttp2_stream *stream) {
+ return stream->item &&
+ (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0;
+}
+
+/*
+ * Returns nonzero if |stream| or one of its descendants is active
+ */
+static int stream_subtree_active(nghttp2_stream *stream) {
+ return stream_active(stream) || !nghttp2_pq_empty(&stream->obq);
+}
+
+/*
+ * Returns next cycle for |stream|.
+ */
+static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
+ uint64_t penalty;
+
+ penalty = (uint64_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT +
+ stream->pending_penalty;
+
+ stream->cycle = last_cycle + penalty / (uint32_t)stream->weight;
+ stream->pending_penalty = (uint32_t)(penalty % (uint32_t)stream->weight);
+}
+
+static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
+ int rv;
+
+ for (; dep_stream && !stream->queued;
+ stream = dep_stream, dep_stream = dep_stream->dep_prev) {
+ stream_next_cycle(stream, dep_stream->descendant_last_cycle);
+ stream->seq = dep_stream->descendant_next_seq++;
+
+ DEBUGF("stream: stream=%d obq push cycle=%lu\n", stream->stream_id,
+ stream->cycle);
+
+ DEBUGF("stream: push stream %d to stream %d\n", stream->stream_id,
+ dep_stream->stream_id);
+
+ rv = nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->queued = 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Removes |stream| from parent's obq. If removal of |stream| makes
+ * parent's obq empty, and parent is not active, then parent is also
+ * removed. This process is repeated recursively.
+ */
+static void stream_obq_remove(nghttp2_stream *stream) {
+ nghttp2_stream *dep_stream;
+
+ dep_stream = stream->dep_prev;
+
+ if (!stream->queued) {
+ return;
+ }
+
+ for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) {
+ DEBUGF("stream: remove stream %d from stream %d\n", stream->stream_id,
+ dep_stream->stream_id);
+
+ nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
+
+ assert(stream->queued);
+
+ stream->queued = 0;
+ stream->cycle = 0;
+ stream->pending_penalty = 0;
+ stream->descendant_last_cycle = 0;
+ stream->last_writelen = 0;
+
+ if (stream_subtree_active(dep_stream)) {
+ return;
+ }
+ }
+}
+
+/*
+ * Moves |stream| from |src|'s obq to |dest|'s obq. Removal from
+ * |src|'s obq is just done calling nghttp2_pq_remove(), so it does
+ * not recursively remove |src| and ancestors, like
+ * stream_obq_remove().
+ */
+static int stream_obq_move(nghttp2_stream *dest, nghttp2_stream *src,
+ nghttp2_stream *stream) {
+ if (!stream->queued) {
+ return 0;
+ }
+
+ DEBUGF("stream: remove stream %d from stream %d (move)\n", stream->stream_id,
+ src->stream_id);
+
+ nghttp2_pq_remove(&src->obq, &stream->pq_entry);
+ stream->queued = 0;
+
+ return stream_obq_push(dest, stream);
+}
+
+void nghttp2_stream_reschedule(nghttp2_stream *stream) {
+ nghttp2_stream *dep_stream;
+
+ assert(stream->queued);
+
+ dep_stream = stream->dep_prev;
+
+ for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) {
+ nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
+
+ stream_next_cycle(stream, dep_stream->descendant_last_cycle);
+ stream->seq = dep_stream->descendant_next_seq++;
+
+ nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
+
+ DEBUGF("stream: stream=%d obq resched cycle=%lu\n", stream->stream_id,
+ stream->cycle);
+
+ dep_stream->last_writelen = stream->last_writelen;
+ }
+}
+
+void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
+ nghttp2_stream *dep_stream;
+ uint64_t last_cycle;
+ int32_t old_weight;
+ uint64_t wlen_penalty;
+
+ if (stream->weight == weight) {
+ return;
+ }
+
+ old_weight = stream->weight;
+ stream->weight = weight;
+
+ dep_stream = stream->dep_prev;
+
+ if (!dep_stream) {
+ return;
+ }
+
+ dep_stream->sum_dep_weight += weight - old_weight;
+
+ if (!stream->queued) {
+ return;
+ }
+
+ nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
+
+ wlen_penalty = (uint64_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT;
+
+ /* Compute old stream->pending_penalty we used to calculate
+ stream->cycle */
+ stream->pending_penalty =
+ (uint32_t)((stream->pending_penalty + (uint32_t)old_weight -
+ (wlen_penalty % (uint32_t)old_weight)) %
+ (uint32_t)old_weight);
+
+ last_cycle = stream->cycle -
+ (wlen_penalty + stream->pending_penalty) / (uint32_t)old_weight;
+
+ /* Now we have old stream->pending_penalty and new stream->weight in
+ place */
+ stream_next_cycle(stream, last_cycle);
+
+ if (dep_stream->descendant_last_cycle - stream->cycle <=
+ NGHTTP2_MAX_CYCLE_DISTANCE) {
+ stream->cycle = dep_stream->descendant_last_cycle;
+ }
+
+ /* Continue to use same stream->seq */
+
+ nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
+
+ DEBUGF("stream: stream=%d obq resched cycle=%lu\n", stream->stream_id,
+ stream->cycle);
+}
+
+static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
+ for (; stream->sib_next; stream = stream->sib_next)
+ ;
+
+ 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);
+}
+
+#ifdef STREAM_DEP_DEBUG
+
+static void ensure_inactive(nghttp2_stream *stream) {
+ nghttp2_stream *si;
+
+ if (stream->queued) {
+ fprintf(stderr, "stream(%p)=%d, stream->queued = 1; want 0\n", stream,
+ stream->stream_id);
+ assert(0);
+ }
+
+ if (stream_active(stream)) {
+ fprintf(stderr, "stream(%p)=%d, stream_active(stream) = 1; want 0\n",
+ stream, stream->stream_id);
+ assert(0);
+ }
+
+ if (!nghttp2_pq_empty(&stream->obq)) {
+ fprintf(stderr, "stream(%p)=%d, nghttp2_pq_size() = %zu; want 0\n", stream,
+ stream->stream_id, nghttp2_pq_size(&stream->obq));
+ assert(0);
+ }
+
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ ensure_inactive(si);
+ }
+}
+
+static void check_queued(nghttp2_stream *stream) {
+ nghttp2_stream *si;
+ int queued;
+
+ if (stream->queued) {
+ if (!stream_subtree_active(stream)) {
+ fprintf(stderr,
+ "stream(%p)=%d, stream->queued == 1, but "
+ "stream_active() == %d and nghttp2_pq_size(&stream->obq) = %zu\n",
+ stream, stream->stream_id, stream_active(stream),
+ nghttp2_pq_size(&stream->obq));
+ assert(0);
+ }
+ if (!stream_active(stream)) {
+ queued = 0;
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ if (si->queued) {
+ ++queued;
+ }
+ }
+ if (queued == 0) {
+ fprintf(stderr,
+ "stream(%p)=%d, stream->queued == 1, and "
+ "!stream_active(), but no descendants is queued\n",
+ stream, stream->stream_id);
+ assert(0);
+ }
+ }
+
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ check_queued(si);
+ }
+ } else {
+ if (stream_active(stream) || !nghttp2_pq_empty(&stream->obq)) {
+ fprintf(stderr,
+ "stream(%p) = %d, stream->queued == 0, but "
+ "stream_active(stream) == %d and "
+ "nghttp2_pq_size(&stream->obq) = %zu\n",
+ stream, stream->stream_id, stream_active(stream),
+ nghttp2_pq_size(&stream->obq));
+ assert(0);
+ }
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ ensure_inactive(si);
+ }
+ }
+}
+
+static void check_sum_dep(nghttp2_stream *stream) {
+ nghttp2_stream *si;
+ int32_t n = 0;
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ n += si->weight;
+ }
+ if (n != stream->sum_dep_weight) {
+ fprintf(stderr, "stream(%p)=%d, sum_dep_weight = %d; want %d\n", stream,
+ stream->stream_id, n, stream->sum_dep_weight);
+ assert(0);
+ }
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ check_sum_dep(si);
+ }
+}
+
+static void check_dep_prev(nghttp2_stream *stream) {
+ nghttp2_stream *si;
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ if (si->dep_prev != stream) {
+ fprintf(stderr, "si->dep_prev = %p; want %p\n", si->dep_prev, stream);
+ assert(0);
+ }
+ check_dep_prev(si);
+ }
+}
+
+#endif /* STREAM_DEP_DEBUG */
+
+#ifdef STREAM_DEP_DEBUG
+static void validate_tree(nghttp2_stream *stream) {
+ nghttp2_stream *si;
+
+ if (!stream) {
+ return;
+ }
+
+ for (; stream->dep_prev; stream = stream->dep_prev)
+ ;
+
+ assert(stream->stream_id == 0);
+ assert(!stream->queued);
+
+ fprintf(stderr, "checking...\n");
+ if (nghttp2_pq_empty(&stream->obq)) {
+ fprintf(stderr, "root obq empty\n");
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ ensure_inactive(si);
+ }
+ } else {
+ for (si = stream->dep_next; si; si = si->sib_next) {
+ check_queued(si);
+ }
+ }
+
+ check_sum_dep(stream);
+ check_dep_prev(stream);
+}
+#else /* !STREAM_DEP_DEBUG */
+static void validate_tree(nghttp2_stream *stream) { (void)stream; }
+#endif /* !STREAM_DEP_DEBUG*/
+
+static int stream_update_dep_on_attach_item(nghttp2_stream *stream) {
+ int rv;
+
+ rv = stream_obq_push(stream->dep_prev, stream);
+ if (rv != 0) {
+ return rv;
+ }
+
+ validate_tree(stream);
+ return 0;
+}
+
+static void stream_update_dep_on_detach_item(nghttp2_stream *stream) {
+ if (nghttp2_pq_empty(&stream->obq)) {
+ stream_obq_remove(stream);
+ }
+
+ validate_tree(stream);
+}
+
+int nghttp2_stream_attach_item(nghttp2_stream *stream,
+ nghttp2_outbound_item *item) {
+ int rv;
+
+ assert((stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0);
+ assert(stream->item == NULL);
+
+ DEBUGF("stream: stream=%d attach item=%p\n", stream->stream_id, item);
+
+ stream->item = item;
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
+ return 0;
+ }
+
+ rv = stream_update_dep_on_attach_item(stream);
+ if (rv != 0) {
+ /* This may relave stream->queued == 1, but stream->item == NULL.
+ But only consequence of this error is fatal one, and session
+ destruction. In that execution path, these inconsistency does
+ not matter. */
+ stream->item = NULL;
+ return rv;
+ }
+
+ return 0;
+}
+
+void nghttp2_stream_detach_item(nghttp2_stream *stream) {
+ DEBUGF("stream: stream=%d detach item=%p\n", stream->stream_id, stream->item);
+
+ stream->item = NULL;
+ stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_DEFERRED_ALL);
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
+ return;
+ }
+
+ stream_update_dep_on_detach_item(stream);
+}
+
+void nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags) {
+ assert(stream->item);
+
+ DEBUGF("stream: stream=%d defer item=%p cause=%02x\n", stream->stream_id,
+ stream->item, flags);
+
+ stream->flags |= flags;
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
+ return;
+ }
+
+ stream_update_dep_on_detach_item(stream);
+}
+
+int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags) {
+ assert(stream->item);
+
+ DEBUGF("stream: stream=%d resume item=%p flags=%02x\n", stream->stream_id,
+ stream->item, flags);
+
+ stream->flags = (uint8_t)(stream->flags & ~flags);
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) {
+ return 0;
+ }
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
+ return 0;
+ }
+
+ return stream_update_dep_on_attach_item(stream);
+}
+
+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;
+ stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_PUSH);
+}
+
+int nghttp2_stream_dep_find_ancestor(nghttp2_stream *stream,
+ nghttp2_stream *target) {
+ for (; stream; stream = stream->dep_prev) {
+ if (stream == target) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int nghttp2_stream_dep_insert(nghttp2_stream *dep_stream,
+ nghttp2_stream *stream) {
+ nghttp2_stream *si;
+ int rv;
+
+ DEBUGF("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) {
+ si->dep_prev = stream;
+ if (si->queued) {
+ rv = stream_obq_move(stream, dep_stream, si);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+
+ if (stream_subtree_active(stream)) {
+ rv = stream_obq_push(dep_stream, stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ stream->dep_next = dep_stream->dep_next;
+ }
+
+ dep_stream->dep_next = stream;
+ stream->dep_prev = dep_stream;
+
+ validate_tree(stream);
+
+ return 0;
+}
+
+static void set_dep_prev(nghttp2_stream *stream, nghttp2_stream *dep) {
+ for (; stream; stream = stream->sib_next) {
+ stream->dep_prev = dep;
+ }
+}
+
+static void link_dep(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
+ dep_stream->dep_next = stream;
+ if (stream) {
+ stream->dep_prev = dep_stream;
+ }
+}
+
+static void link_sib(nghttp2_stream *a, nghttp2_stream *b) {
+ a->sib_next = b;
+ if (b) {
+ b->sib_prev = a;
+ }
+}
+
+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);
+
+ 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
+ */
+
+ link_sib(prev, dep_next);
+
+ set_dep_prev(dep_next, stream->dep_prev);
+
+ 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);
+
+ set_dep_prev(dep_next, stream->dep_prev);
+
+ 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) {
+ DEBUGF("stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n", dep_stream,
+ dep_stream->stream_id, stream, stream->stream_id);
+
+ 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);
+ }
+
+ validate_tree(stream);
+}
+
+int nghttp2_stream_dep_remove(nghttp2_stream *stream) {
+ nghttp2_stream *dep_prev, *si;
+ int32_t sum_dep_weight_delta;
+ int rv;
+
+ DEBUGF("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;
+
+ if (si->queued) {
+ rv = stream_obq_move(stream->dep_prev, stream, si);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+
+ assert(stream->dep_prev);
+
+ dep_prev = stream->dep_prev;
+
+ dep_prev->sum_dep_weight += sum_dep_weight_delta;
+
+ if (stream->queued) {
+ stream_obq_remove(stream);
+ }
+
+ if (stream->sib_prev) {
+ unlink_sib(stream);
+ } else {
+ unlink_dep(stream);
+ }
+
+ stream->sum_dep_weight = 0;
+
+ stream->dep_prev = NULL;
+ stream->dep_next = NULL;
+ stream->sib_prev = NULL;
+ stream->sib_next = NULL;
+
+ validate_tree(dep_prev);
+
+ return 0;
+}
+
+int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream,
+ nghttp2_stream *stream) {
+ nghttp2_stream *last_sib;
+ nghttp2_stream *dep_next;
+ nghttp2_stream *si;
+ int rv;
+
+ DEBUGF("stream: dep_insert_subtree 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) {
+ dep_next = dep_stream->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);
+ } else {
+ link_dep(stream, dep_next);
+ }
+
+ for (si = dep_next; si; si = si->sib_next) {
+ si->dep_prev = stream;
+ if (si->queued) {
+ rv = stream_obq_move(stream, dep_stream, si);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+ } else {
+ link_dep(dep_stream, stream);
+ }
+
+ if (stream_subtree_active(stream)) {
+ rv = stream_obq_push(dep_stream, stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ validate_tree(dep_stream);
+
+ return 0;
+}
+
+int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream,
+ nghttp2_stream *stream) {
+ int rv;
+
+ DEBUGF("stream: dep_add_subtree dep_stream(%p)=%d stream(%p)=%d\n",
+ dep_stream, dep_stream->stream_id, stream, stream->stream_id);
+
+ dep_stream->sum_dep_weight += stream->weight;
+
+ if (dep_stream->dep_next) {
+ insert_link_dep(dep_stream, stream);
+ } else {
+ link_dep(dep_stream, stream);
+ }
+
+ if (stream_subtree_active(stream)) {
+ rv = stream_obq_push(dep_stream, stream);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ validate_tree(dep_stream);
+
+ return 0;
+}
+
+void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) {
+ nghttp2_stream *next, *dep_prev;
+
+ DEBUGF("stream: dep_remove_subtree stream(%p)=%d\n", stream,
+ stream->stream_id);
+
+ assert(stream->dep_prev);
+
+ dep_prev = stream->dep_prev;
+
+ if (stream->sib_prev) {
+ link_sib(stream->sib_prev, stream->sib_next);
+ } else {
+ next = stream->sib_next;
+
+ link_dep(dep_prev, next);
+
+ if (next) {
+ next->sib_prev = NULL;
+ }
+ }
+
+ dep_prev->sum_dep_weight -= stream->weight;
+
+ if (stream->queued) {
+ stream_obq_remove(stream);
+ }
+
+ validate_tree(dep_prev);
+
+ stream->sib_prev = NULL;
+ stream->sib_next = NULL;
+ stream->dep_prev = NULL;
+}
+
+int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) {
+ return stream->dep_prev || stream->dep_next || stream->sib_prev ||
+ stream->sib_next;
+}
+
+nghttp2_outbound_item *
+nghttp2_stream_next_outbound_item(nghttp2_stream *stream) {
+ nghttp2_pq_entry *ent;
+ nghttp2_stream *si;
+
+ for (;;) {
+ if (stream_active(stream)) {
+ /* Update ascendant's descendant_last_cycle here, so that we can
+ assure that new stream is scheduled based on it. */
+ for (si = stream; si->dep_prev; si = si->dep_prev) {
+ si->dep_prev->descendant_last_cycle = si->cycle;
+ }
+ return stream->item;
+ }
+ ent = nghttp2_pq_top(&stream->obq);
+ if (!ent) {
+ return NULL;
+ }
+ stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry);
+ }
+}
+
+nghttp2_stream_proto_state nghttp2_stream_get_state(nghttp2_stream *stream) {
+ if (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) {
+ return NGHTTP2_STREAM_STATE_CLOSED;
+ }
+
+ if (stream->flags & NGHTTP2_STREAM_FLAG_PUSH) {
+ if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+ return NGHTTP2_STREAM_STATE_RESERVED_LOCAL;
+ }
+
+ if (stream->shut_flags & NGHTTP2_SHUT_WR) {
+ return NGHTTP2_STREAM_STATE_RESERVED_REMOTE;
+ }
+ }
+
+ if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+ return NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE;
+ }
+
+ if (stream->shut_flags & NGHTTP2_SHUT_WR) {
+ return NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL;
+ }
+
+ if (stream->state == NGHTTP2_STREAM_IDLE) {
+ return NGHTTP2_STREAM_STATE_IDLE;
+ }
+
+ return NGHTTP2_STREAM_STATE_OPEN;
+}
+
+nghttp2_stream *nghttp2_stream_get_parent(nghttp2_stream *stream) {
+ return stream->dep_prev;
+}
+
+nghttp2_stream *nghttp2_stream_get_next_sibling(nghttp2_stream *stream) {
+ return stream->sib_next;
+}
+
+nghttp2_stream *nghttp2_stream_get_previous_sibling(nghttp2_stream *stream) {
+ return stream->sib_prev;
+}
+
+nghttp2_stream *nghttp2_stream_get_first_child(nghttp2_stream *stream) {
+ return stream->dep_next;
+}
+
+int32_t nghttp2_stream_get_weight(nghttp2_stream *stream) {
+ return stream->weight;
+}
+
+int32_t nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream) {
+ return stream->sum_dep_weight;
+}
+
+int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream) {
+ return stream->stream_id;
+}
diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h
new file mode 100644
index 0000000..71b9fb1
--- /dev/null
+++ b/lib/nghttp2_stream.h
@@ -0,0 +1,441 @@
+/*
+ * 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"
+
+/*
+ * 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 and not opened
+ yet. */
+ 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,
+ /* Indicates that this stream is not subject to RFC7540
+ priorities scheme. */
+ NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10,
+ /* Ignore client RFC 9218 priority signal. */
+ NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES = 0x20,
+ /* Indicates that RFC 9113 leading and trailing white spaces
+ validation against a field value is not performed. */
+ NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 0x40,
+} nghttp2_stream_flag;
+
+/* HTTP related flags to enforce HTTP semantics */
+typedef enum {
+ NGHTTP2_HTTP_FLAG_NONE = 0,
+ /* header field seen so far */
+ NGHTTP2_HTTP_FLAG__AUTHORITY = 1,
+ NGHTTP2_HTTP_FLAG__PATH = 1 << 1,
+ NGHTTP2_HTTP_FLAG__METHOD = 1 << 2,
+ NGHTTP2_HTTP_FLAG__SCHEME = 1 << 3,
+ /* host is not pseudo header, but we require either host or
+ :authority */
+ NGHTTP2_HTTP_FLAG_HOST = 1 << 4,
+ NGHTTP2_HTTP_FLAG__STATUS = 1 << 5,
+ /* required header fields for HTTP request except for CONNECT
+ method. */
+ NGHTTP2_HTTP_FLAG_REQ_HEADERS = NGHTTP2_HTTP_FLAG__METHOD |
+ NGHTTP2_HTTP_FLAG__PATH |
+ NGHTTP2_HTTP_FLAG__SCHEME,
+ NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED = 1 << 6,
+ /* HTTP method flags */
+ NGHTTP2_HTTP_FLAG_METH_CONNECT = 1 << 7,
+ NGHTTP2_HTTP_FLAG_METH_HEAD = 1 << 8,
+ NGHTTP2_HTTP_FLAG_METH_OPTIONS = 1 << 9,
+ NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND = 1 << 10,
+ NGHTTP2_HTTP_FLAG_METH_ALL = NGHTTP2_HTTP_FLAG_METH_CONNECT |
+ NGHTTP2_HTTP_FLAG_METH_HEAD |
+ NGHTTP2_HTTP_FLAG_METH_OPTIONS |
+ NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND,
+ /* :path category */
+ /* path starts with "/" */
+ NGHTTP2_HTTP_FLAG_PATH_REGULAR = 1 << 11,
+ /* path "*" */
+ NGHTTP2_HTTP_FLAG_PATH_ASTERISK = 1 << 12,
+ /* scheme */
+ /* "http" or "https" scheme */
+ NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 13,
+ /* set if final response is expected */
+ NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 14,
+ NGHTTP2_HTTP_FLAG__PROTOCOL = 1 << 15,
+ /* set if priority header field is received */
+ NGHTTP2_HTTP_FLAG_PRIORITY = 1 << 16,
+ /* set if an error is encountered while parsing priority header
+ field */
+ NGHTTP2_HTTP_FLAG_BAD_PRIORITY = 1 << 17,
+} nghttp2_http_flag;
+
+struct nghttp2_stream {
+ /* Entry for dep_prev->obq */
+ nghttp2_pq_entry pq_entry;
+ /* Priority Queue storing direct descendant (nghttp2_stream). Only
+ streams which itself has some data to send, or has a descendant
+ which has some data to sent. */
+ nghttp2_pq obq;
+ /* Content-Length of request/response body. -1 if unknown. */
+ int64_t content_length;
+ /* Received body so far */
+ int64_t recv_content_length;
+ /* Base last_cycle for direct descendent streams. */
+ uint64_t descendant_last_cycle;
+ /* Next scheduled time to sent item */
+ uint64_t cycle;
+ /* Next seq used for direct descendant streams */
+ uint64_t descendant_next_seq;
+ /* Secondary key for prioritization to break a tie for cycle. This
+ value is monotonically increased for single parent stream. */
+ uint64_t seq;
+ /* 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;
+ /* 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;
+ /* The arbitrary data provided by user for this stream. */
+ void *stream_user_data;
+ /* Item to send */
+ nghttp2_outbound_item *item;
+ /* Last written length of frame payload */
+ size_t last_writelen;
+ /* stream ID */
+ int32_t stream_id;
+ /* 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;
+ /* This is unpaid penalty (offset) when calculating cycle. */
+ uint32_t pending_penalty;
+ /* sum of weight of direct descendants */
+ int32_t sum_dep_weight;
+ nghttp2_stream_state state;
+ /* status code from remote server */
+ int16_t status_code;
+ /* Bitwise OR of zero or more nghttp2_http_flag values */
+ uint32_t http_flags;
+ /* 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;
+ /* Nonzero if this stream has been queued to stream pointed by
+ dep_prev. We maintain the invariant that if a stream is queued,
+ then its ancestors, except for root, are also queued. This
+ invariant may break in fatal error condition. */
+ uint8_t queued;
+ /* This flag is used to reduce excessive queuing of WINDOW_UPDATE to
+ this stream. The nonzero does not necessarily mean WINDOW_UPDATE
+ is not queued. */
+ uint8_t window_update_queued;
+ /* extpri is a stream priority produced by nghttp2_extpri_to_uint8
+ used by RFC 9218 extensible priorities. */
+ uint8_t extpri;
+ /* http_extpri is a stream priority received in HTTP request header
+ fields and produced by nghttp2_extpri_to_uint8. */
+ uint8_t http_extpri;
+};
+
+void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
+ uint8_t flags, nghttp2_stream_state initial_state,
+ int32_t weight, int32_t remote_initial_window_size,
+ int32_t local_initial_window_size,
+ void *stream_user_data, nghttp2_mem *mem);
+
+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.
+ */
+void nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags);
+
+/*
+ * 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.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory
+ */
+int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags);
+
+/*
+ * 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 nonzero if |target| is an ancestor of |stream|.
+ */
+int nghttp2_stream_dep_find_ancestor(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.
+ */
+int32_t nghttp2_stream_dep_distributed_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->item| is NULL.
+ *
+ * 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(nghttp2_stream *dep_stream,
+ nghttp2_stream *stream);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|. This dependency is
+ * not exclusive. This function assumes |stream->item| is NULL.
+ */
+void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, nghttp2_stream *stream);
+
+/*
+ * Removes the |stream| from the current dependency tree. This
+ * function assumes |stream->item| is NULL.
+ */
+int nghttp2_stream_dep_remove(nghttp2_stream *stream);
+
+/*
+ * Attaches |item| to |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_attach_item(nghttp2_stream *stream,
+ nghttp2_outbound_item *item);
+
+/*
+ * Detaches |stream->item|. This function does not free
+ * |stream->item|. The caller must free it.
+ */
+void nghttp2_stream_detach_item(nghttp2_stream *stream);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|. This dependency is
+ * exclusive.
+ *
+ * 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);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|. This dependency is
+ * not exclusive.
+ *
+ * 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);
+
+/*
+ * Removes subtree whose root stream is |stream|. 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);
+
+/*
+ * Returns nonzero if |stream| is in any dependency tree.
+ */
+int nghttp2_stream_in_dep_tree(nghttp2_stream *stream);
+
+/*
+ * Schedules transmission of |stream|'s item, assuming stream->item is
+ * attached, and stream->last_writelen was updated.
+ */
+void nghttp2_stream_reschedule(nghttp2_stream *stream);
+
+/*
+ * Changes |stream|'s weight to |weight|. If |stream| is queued, it
+ * will be rescheduled based on new weight.
+ */
+void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight);
+
+/*
+ * Returns a stream which has highest priority, updating
+ * descendant_last_cycle of selected stream's ancestors.
+ */
+nghttp2_outbound_item *
+nghttp2_stream_next_outbound_item(nghttp2_stream *stream);
+
+#endif /* NGHTTP2_STREAM */
diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c
new file mode 100644
index 0000000..f5554eb
--- /dev/null
+++ b/lib/nghttp2_submit.c
@@ -0,0 +1,900 @@
+/*
+ * 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"
+
+/*
+ * Detects the dependency error, that is stream attempted to depend on
+ * itself. If |stream_id| is -1, we use session->next_stream_id as
+ * stream ID.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * error codes:
+ *
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ * Stream attempted to depend on itself.
+ */
+static int detect_self_dependency(nghttp2_session *session, int32_t stream_id,
+ const nghttp2_priority_spec *pri_spec) {
+ assert(pri_spec);
+
+ if (stream_id == -1) {
+ if ((int32_t)session->next_stream_id == pri_spec->stream_id) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+ return 0;
+ }
+
+ if (stream_id == pri_spec->stream_id) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ return 0;
+}
+
+/* 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) {
+ 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;
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ rv = NGHTTP2_ERR_NOMEM;
+ goto fail;
+ }
+
+ nghttp2_outbound_item_init(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;
+
+ flags_copy =
+ (uint8_t)((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 = (int32_t)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 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) {
+ 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;
+ nghttp2_priority_spec_normalize_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);
+}
+
+int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
+ const nghttp2_nv *nva, size_t nvlen) {
+ if (stream_id <= 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
+ stream_id, NULL, nva, nvlen, NULL,
+ NULL);
+}
+
+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) {
+ int rv;
+
+ if (stream_id == -1) {
+ if (session->server) {
+ return NGHTTP2_ERR_PROTO;
+ }
+ } else if (stream_id <= 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ flags &= NGHTTP2_FLAG_END_STREAM;
+
+ if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) &&
+ session->remote_settings.no_rfc7540_priorities != 1) {
+ rv = detect_self_dependency(session, stream_id, pri_spec);
+ if (rv != 0) {
+ return rv;
+ }
+
+ 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);
+}
+
+int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags,
+ const uint8_t *opaque_data) {
+ flags &= NGHTTP2_FLAG_ACK;
+ return nghttp2_session_add_ping(session, flags, opaque_data);
+}
+
+int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags,
+ 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;
+ (void)flags;
+
+ mem = &session->mem;
+
+ if (session->remote_settings.no_rfc7540_priorities == 1) {
+ return 0;
+ }
+
+ 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;
+
+ nghttp2_priority_spec_normalize_weight(&copy_pri_spec);
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+
+ if (item == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_outbound_item_init(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,
+ int32_t stream_id, uint32_t error_code) {
+ (void)flags;
+
+ 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,
+ int32_t last_stream_id, uint32_t error_code,
+ const uint8_t *opaque_data, size_t opaque_data_len) {
+ (void)flags;
+
+ 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,
+ const nghttp2_settings_entry *iv, size_t niv) {
+ (void)flags;
+ return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
+}
+
+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) {
+ 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;
+ (void)flags;
+
+ 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_outbound_item_init(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 = (int32_t)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;
+ (void)flags;
+
+ if (window_size_increment == 0) {
+ return 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, 0, stream_id,
+ window_size_increment);
+ }
+ return 0;
+}
+
+int nghttp2_session_set_local_window_size(nghttp2_session *session,
+ uint8_t flags, int32_t stream_id,
+ int32_t window_size) {
+ int32_t window_size_increment;
+ nghttp2_stream *stream;
+ int rv;
+ (void)flags;
+
+ if (window_size < 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (stream_id == 0) {
+ window_size_increment = window_size - session->local_window_size;
+
+ if (window_size_increment == 0) {
+ return 0;
+ }
+
+ if (window_size_increment < 0) {
+ return nghttp2_adjust_local_window_size(
+ &session->local_window_size, &session->recv_window_size,
+ &session->recv_reduction, &window_size_increment);
+ }
+
+ rv = nghttp2_increase_local_window_size(
+ &session->local_window_size, &session->recv_window_size,
+ &session->recv_reduction, &window_size_increment);
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (window_size_increment > 0) {
+ return nghttp2_session_add_window_update(session, 0, stream_id,
+ window_size_increment);
+ }
+
+ return nghttp2_session_update_recv_connection_window_size(session, 0);
+ } else {
+ stream = nghttp2_session_get_stream(session, stream_id);
+
+ if (stream == NULL) {
+ return 0;
+ }
+
+ window_size_increment = window_size - stream->local_window_size;
+
+ if (window_size_increment == 0) {
+ return 0;
+ }
+
+ if (window_size_increment < 0) {
+ return nghttp2_adjust_local_window_size(
+ &stream->local_window_size, &stream->recv_window_size,
+ &stream->recv_reduction, &window_size_increment);
+ }
+
+ rv = nghttp2_increase_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) {
+ return nghttp2_session_add_window_update(session, 0, stream_id,
+ window_size_increment);
+ }
+
+ return nghttp2_session_update_recv_stream_window_size(session, stream, 0,
+ 1);
+ }
+}
+
+int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *origin,
+ size_t origin_len, const uint8_t *field_value,
+ size_t field_value_len) {
+ nghttp2_mem *mem;
+ uint8_t *buf, *p;
+ uint8_t *origin_copy;
+ uint8_t *field_value_copy;
+ nghttp2_outbound_item *item;
+ nghttp2_frame *frame;
+ nghttp2_ext_altsvc *altsvc;
+ int rv;
+ (void)flags;
+
+ mem = &session->mem;
+
+ if (!session->server) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ if (2 + origin_len + field_value_len > NGHTTP2_MAX_PAYLOADLEN) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (stream_id == 0) {
+ if (origin_len == 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+ } else if (origin_len != 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ buf = nghttp2_mem_malloc(mem, origin_len + field_value_len + 2);
+ if (buf == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ p = buf;
+
+ origin_copy = p;
+ if (origin_len) {
+ p = nghttp2_cpymem(p, origin, origin_len);
+ }
+ *p++ = '\0';
+
+ field_value_copy = p;
+ if (field_value_len) {
+ p = nghttp2_cpymem(p, field_value, field_value_len);
+ }
+ *p++ = '\0';
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ rv = NGHTTP2_ERR_NOMEM;
+ goto fail_item_malloc;
+ }
+
+ nghttp2_outbound_item_init(item);
+
+ item->aux_data.ext.builtin = 1;
+
+ altsvc = &item->ext_frame_payload.altsvc;
+
+ frame = &item->frame;
+ frame->ext.payload = altsvc;
+
+ nghttp2_frame_altsvc_init(&frame->ext, stream_id, origin_copy, origin_len,
+ field_value_copy, field_value_len);
+
+ rv = nghttp2_session_add_item(session, item);
+ if (rv != 0) {
+ nghttp2_frame_altsvc_free(&frame->ext, mem);
+ nghttp2_mem_free(mem, item);
+
+ return rv;
+ }
+
+ return 0;
+
+fail_item_malloc:
+ free(buf);
+
+ return rv;
+}
+
+int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags,
+ const nghttp2_origin_entry *ov, size_t nov) {
+ nghttp2_mem *mem;
+ uint8_t *p;
+ nghttp2_outbound_item *item;
+ nghttp2_frame *frame;
+ nghttp2_ext_origin *origin;
+ nghttp2_origin_entry *ov_copy;
+ size_t len = 0;
+ size_t i;
+ int rv;
+ (void)flags;
+
+ mem = &session->mem;
+
+ if (!session->server) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ if (nov) {
+ for (i = 0; i < nov; ++i) {
+ len += ov[i].origin_len;
+ }
+
+ if (2 * nov + len > NGHTTP2_MAX_PAYLOADLEN) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ /* The last nov is added for terminal NULL character. */
+ ov_copy =
+ nghttp2_mem_malloc(mem, nov * sizeof(nghttp2_origin_entry) + len + nov);
+ if (ov_copy == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ p = (uint8_t *)ov_copy + nov * sizeof(nghttp2_origin_entry);
+
+ for (i = 0; i < nov; ++i) {
+ ov_copy[i].origin = p;
+ ov_copy[i].origin_len = ov[i].origin_len;
+ p = nghttp2_cpymem(p, ov[i].origin, ov[i].origin_len);
+ *p++ = '\0';
+ }
+
+ assert((size_t)(p - (uint8_t *)ov_copy) ==
+ nov * sizeof(nghttp2_origin_entry) + len + nov);
+ } else {
+ ov_copy = NULL;
+ }
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ rv = NGHTTP2_ERR_NOMEM;
+ goto fail_item_malloc;
+ }
+
+ nghttp2_outbound_item_init(item);
+
+ item->aux_data.ext.builtin = 1;
+
+ origin = &item->ext_frame_payload.origin;
+
+ frame = &item->frame;
+ frame->ext.payload = origin;
+
+ nghttp2_frame_origin_init(&frame->ext, ov_copy, nov);
+
+ rv = nghttp2_session_add_item(session, item);
+ if (rv != 0) {
+ nghttp2_frame_origin_free(&frame->ext, mem);
+ nghttp2_mem_free(mem, item);
+
+ return rv;
+ }
+
+ return 0;
+
+fail_item_malloc:
+ free(ov_copy);
+
+ return rv;
+}
+
+int nghttp2_submit_priority_update(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *field_value,
+ size_t field_value_len) {
+ nghttp2_mem *mem;
+ uint8_t *buf, *p;
+ nghttp2_outbound_item *item;
+ nghttp2_frame *frame;
+ nghttp2_ext_priority_update *priority_update;
+ int rv;
+ (void)flags;
+
+ mem = &session->mem;
+
+ if (session->server) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ if (session->remote_settings.no_rfc7540_priorities == 0) {
+ return 0;
+ }
+
+ if (stream_id == 0 || 4 + field_value_len > NGHTTP2_MAX_PAYLOADLEN) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (field_value_len) {
+ buf = nghttp2_mem_malloc(mem, field_value_len + 1);
+ if (buf == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ p = nghttp2_cpymem(buf, field_value, field_value_len);
+ *p = '\0';
+ } else {
+ buf = NULL;
+ }
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ rv = NGHTTP2_ERR_NOMEM;
+ goto fail_item_malloc;
+ }
+
+ nghttp2_outbound_item_init(item);
+
+ item->aux_data.ext.builtin = 1;
+
+ priority_update = &item->ext_frame_payload.priority_update;
+
+ frame = &item->frame;
+ frame->ext.payload = priority_update;
+
+ nghttp2_frame_priority_update_init(&frame->ext, stream_id, buf,
+ field_value_len);
+
+ rv = nghttp2_session_add_item(session, item);
+ if (rv != 0) {
+ nghttp2_frame_priority_update_free(&frame->ext, mem);
+ nghttp2_mem_free(mem, item);
+
+ return rv;
+ }
+
+ return 0;
+
+fail_item_malloc:
+ free(buf);
+
+ return rv;
+}
+
+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;
+ int rv;
+
+ if (session->server) {
+ return NGHTTP2_ERR_PROTO;
+ }
+
+ if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) &&
+ session->remote_settings.no_rfc7540_priorities != 1) {
+ rv = detect_self_dependency(session, -1, pri_spec);
+ if (rv != 0) {
+ return rv;
+ }
+ } else {
+ 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);
+}
+
+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;
+
+ if (stream_id <= 0) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (!session->server) {
+ return NGHTTP2_ERR_PROTO;
+ }
+
+ flags = set_response_flags(data_prd);
+ return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
+ data_prd, NULL);
+}
+
+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_outbound_item_init(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 (ssize_t)nghttp2_frame_pack_settings_payload(buf, iv, niv);
+}
+
+int nghttp2_submit_extension(nghttp2_session *session, uint8_t type,
+ uint8_t flags, int32_t stream_id, void *payload) {
+ int rv;
+ nghttp2_outbound_item *item;
+ nghttp2_frame *frame;
+ nghttp2_mem *mem;
+
+ mem = &session->mem;
+
+ if (type <= NGHTTP2_CONTINUATION) {
+ return NGHTTP2_ERR_INVALID_ARGUMENT;
+ }
+
+ if (!session->callbacks.pack_extension_callback) {
+ return NGHTTP2_ERR_INVALID_STATE;
+ }
+
+ item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+ if (item == NULL) {
+ return NGHTTP2_ERR_NOMEM;
+ }
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+ nghttp2_frame_extension_init(&frame->ext, type, flags, stream_id, payload);
+
+ rv = nghttp2_session_add_item(session, item);
+ if (rv != 0) {
+ nghttp2_frame_extension_free(&frame->ext);
+ nghttp2_mem_free(mem, item);
+ return rv;
+ }
+
+ return 0;
+}
diff --git a/lib/nghttp2_submit.h b/lib/nghttp2_submit.h
new file mode 100644
index 0000000..74d702f
--- /dev/null
+++ b/lib/nghttp2_submit.h
@@ -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_time.c b/lib/nghttp2_time.c
new file mode 100644
index 0000000..947b544
--- /dev/null
+++ b/lib/nghttp2_time.c
@@ -0,0 +1,63 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_time.h"
+
+#ifdef HAVE_WINDOWS_H
+# include <windows.h>
+#endif /* HAVE_WINDOWS_H */
+
+#include <time.h>
+
+#if !defined(HAVE_GETTICKCOUNT64) || defined(__CYGWIN__)
+static uint64_t time_now_sec(void) {
+ time_t t = time(NULL);
+
+ if (t == -1) {
+ return 0;
+ }
+
+ return (uint64_t)t;
+}
+#endif /* !HAVE_GETTICKCOUNT64 || __CYGWIN__ */
+
+#if defined(HAVE_GETTICKCOUNT64) && !defined(__CYGWIN__)
+uint64_t nghttp2_time_now_sec(void) { return GetTickCount64() / 1000; }
+#elif defined(HAVE_CLOCK_GETTIME) && defined(HAVE_DECL_CLOCK_MONOTONIC) && \
+ HAVE_DECL_CLOCK_MONOTONIC
+uint64_t nghttp2_time_now_sec(void) {
+ struct timespec tp;
+ int rv = clock_gettime(CLOCK_MONOTONIC, &tp);
+
+ if (rv == -1) {
+ return time_now_sec();
+ }
+
+ return (uint64_t)tp.tv_sec;
+}
+#else /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \
+ (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */
+uint64_t nghttp2_time_now_sec(void) { return time_now_sec(); }
+#endif /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \
+ (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */
diff --git a/lib/nghttp2_time.h b/lib/nghttp2_time.h
new file mode 100644
index 0000000..03c0bbe
--- /dev/null
+++ b/lib/nghttp2_time.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_TIME_H
+#define NGHTTP2_TIME_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+/* nghttp2_time_now_sec returns seconds from implementation-specific
+ timepoint. If it is unable to get seconds, it returns 0. */
+uint64_t nghttp2_time_now_sec(void);
+
+#endif /* NGHTTP2_TIME_H */
diff --git a/lib/nghttp2_version.c b/lib/nghttp2_version.c
new file mode 100644
index 0000000..4211f2c
--- /dev/null
+++ b/lib/nghttp2_version.c
@@ -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/lib/sfparse.c b/lib/sfparse.c
new file mode 100644
index 0000000..efa2850
--- /dev/null
+++ b/lib/sfparse.c
@@ -0,0 +1,1146 @@
+/*
+ * sfparse
+ *
+ * Copyright (c) 2023 sfparse contributors
+ * Copyright (c) 2019 nghttp3 contributors
+ * Copyright (c) 2015 nghttp2 contributors
+ *
+ * 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 "sfparse.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#define SF_STATE_DICT 0x08u
+#define SF_STATE_LIST 0x10u
+#define SF_STATE_ITEM 0x18u
+
+#define SF_STATE_INNER_LIST 0x04u
+
+#define SF_STATE_BEFORE 0x00u
+#define SF_STATE_BEFORE_PARAMS 0x01u
+#define SF_STATE_PARAMS 0x02u
+#define SF_STATE_AFTER 0x03u
+
+#define SF_STATE_OP_MASK 0x03u
+
+#define SF_SET_STATE_AFTER(NAME) (SF_STATE_##NAME | SF_STATE_AFTER)
+#define SF_SET_STATE_BEFORE_PARAMS(NAME) \
+ (SF_STATE_##NAME | SF_STATE_BEFORE_PARAMS)
+#define SF_SET_STATE_INNER_LIST_BEFORE(NAME) \
+ (SF_STATE_##NAME | SF_STATE_INNER_LIST | SF_STATE_BEFORE)
+
+#define SF_STATE_DICT_AFTER SF_SET_STATE_AFTER(DICT)
+#define SF_STATE_DICT_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(DICT)
+#define SF_STATE_DICT_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(DICT)
+
+#define SF_STATE_LIST_AFTER SF_SET_STATE_AFTER(LIST)
+#define SF_STATE_LIST_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(LIST)
+#define SF_STATE_LIST_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(LIST)
+
+#define SF_STATE_ITEM_AFTER SF_SET_STATE_AFTER(ITEM)
+#define SF_STATE_ITEM_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(ITEM)
+#define SF_STATE_ITEM_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(ITEM)
+
+#define SF_STATE_INITIAL 0x00u
+
+#define DIGIT_CASES \
+ case '0': \
+ case '1': \
+ case '2': \
+ case '3': \
+ case '4': \
+ case '5': \
+ case '6': \
+ case '7': \
+ case '8': \
+ case '9'
+
+#define LCALPHA_CASES \
+ case 'a': \
+ case 'b': \
+ case 'c': \
+ case 'd': \
+ case 'e': \
+ case 'f': \
+ case 'g': \
+ case 'h': \
+ case 'i': \
+ case 'j': \
+ case 'k': \
+ case 'l': \
+ case 'm': \
+ case 'n': \
+ case 'o': \
+ case 'p': \
+ case 'q': \
+ case 'r': \
+ case 's': \
+ case 't': \
+ case 'u': \
+ case 'v': \
+ case 'w': \
+ case 'x': \
+ case 'y': \
+ case 'z'
+
+#define UCALPHA_CASES \
+ case 'A': \
+ case 'B': \
+ case 'C': \
+ case 'D': \
+ case 'E': \
+ case 'F': \
+ case 'G': \
+ case 'H': \
+ case 'I': \
+ case 'J': \
+ case 'K': \
+ case 'L': \
+ case 'M': \
+ case 'N': \
+ case 'O': \
+ case 'P': \
+ case 'Q': \
+ case 'R': \
+ case 'S': \
+ case 'T': \
+ case 'U': \
+ case 'V': \
+ case 'W': \
+ case 'X': \
+ case 'Y': \
+ case 'Z'
+
+#define ALPHA_CASES \
+ UCALPHA_CASES: \
+ LCALPHA_CASES
+
+#define X20_21_CASES \
+ case ' ': \
+ case '!'
+
+#define X23_5B_CASES \
+ case '#': \
+ case '$': \
+ case '%': \
+ case '&': \
+ case '\'': \
+ case '(': \
+ case ')': \
+ case '*': \
+ case '+': \
+ case ',': \
+ case '-': \
+ case '.': \
+ case '/': \
+ DIGIT_CASES: \
+ case ':': \
+ case ';': \
+ case '<': \
+ case '=': \
+ case '>': \
+ case '?': \
+ case '@': \
+ UCALPHA_CASES: \
+ case '['
+
+#define X5D_7E_CASES \
+ case ']': \
+ case '^': \
+ case '_': \
+ case '`': \
+ LCALPHA_CASES: \
+ case '{': \
+ case '|': \
+ case '}': \
+ case '~'
+
+static int is_ws(uint8_t c) {
+ switch (c) {
+ case ' ':
+ case '\t':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int parser_eof(sf_parser *sfp) { return sfp->pos == sfp->end; }
+
+static void parser_discard_ows(sf_parser *sfp) {
+ for (; !parser_eof(sfp) && is_ws(*sfp->pos); ++sfp->pos)
+ ;
+}
+
+static void parser_discard_sp(sf_parser *sfp) {
+ for (; !parser_eof(sfp) && *sfp->pos == ' '; ++sfp->pos)
+ ;
+}
+
+static void parser_set_op_state(sf_parser *sfp, uint32_t op) {
+ sfp->state &= ~SF_STATE_OP_MASK;
+ sfp->state |= op;
+}
+
+static void parser_unset_inner_list_state(sf_parser *sfp) {
+ sfp->state &= ~SF_STATE_INNER_LIST;
+}
+
+static int parser_key(sf_parser *sfp, sf_vec *dest) {
+ const uint8_t *base;
+
+ switch (*sfp->pos) {
+ case '*':
+ LCALPHA_CASES:
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ base = sfp->pos++;
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ case '_':
+ case '-':
+ case '.':
+ case '*':
+ DIGIT_CASES:
+ LCALPHA_CASES:
+ continue;
+ }
+
+ break;
+ }
+
+ if (dest) {
+ dest->base = (uint8_t *)base;
+ dest->len = (size_t)(sfp->pos - dest->base);
+ }
+
+ return 0;
+}
+
+static int parser_number(sf_parser *sfp, sf_value *dest) {
+ int sign = 1;
+ int64_t value = 0;
+ size_t len = 0;
+ size_t fpos = 0;
+
+ if (*sfp->pos == '-') {
+ ++sfp->pos;
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ sign = -1;
+ }
+
+ assert(!parser_eof(sfp));
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ DIGIT_CASES:
+ if (++len > 15) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ value *= 10;
+ value += *sfp->pos - '0';
+
+ continue;
+ }
+
+ break;
+ }
+
+ if (len == 0) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ if (parser_eof(sfp) || *sfp->pos != '.') {
+ if (dest) {
+ dest->type = SF_TYPE_INTEGER;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->integer = value * sign;
+ }
+
+ return 0;
+ }
+
+ /* decimal */
+
+ if (len > 12) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ fpos = len;
+
+ ++sfp->pos;
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ DIGIT_CASES:
+ if (++len > 15) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ value *= 10;
+ value += *sfp->pos - '0';
+
+ continue;
+ }
+
+ break;
+ }
+
+ if (fpos == len || len - fpos > 3) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ if (dest) {
+ dest->type = SF_TYPE_DECIMAL;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->decimal.numer = value * sign;
+
+ switch (len - fpos) {
+ case 1:
+ dest->decimal.denom = 10;
+
+ break;
+ case 2:
+ dest->decimal.denom = 100;
+
+ break;
+ case 3:
+ dest->decimal.denom = 1000;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int parser_date(sf_parser *sfp, sf_value *dest) {
+ int rv;
+ sf_value val;
+
+ /* The first byte has already been validated by the caller. */
+ assert('@' == *sfp->pos);
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ rv = parser_number(sfp, &val);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (val.type != SF_TYPE_INTEGER) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ if (dest) {
+ *dest = val;
+ dest->type = SF_TYPE_DATE;
+ }
+
+ return 0;
+}
+
+static int parser_string(sf_parser *sfp, sf_value *dest) {
+ const uint8_t *base;
+ uint32_t flags = SF_VALUE_FLAG_NONE;
+
+ /* The first byte has already been validated by the caller. */
+ assert('"' == *sfp->pos);
+
+ base = ++sfp->pos;
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ X20_21_CASES:
+ X23_5B_CASES:
+ X5D_7E_CASES:
+ break;
+ case '\\':
+ ++sfp->pos;
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ switch (*sfp->pos) {
+ case '"':
+ case '\\':
+ flags = SF_VALUE_FLAG_ESCAPED_STRING;
+
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ case '"':
+ if (dest) {
+ dest->type = SF_TYPE_STRING;
+ dest->flags = flags;
+ dest->vec.len = (size_t)(sfp->pos - base);
+ dest->vec.base = dest->vec.len == 0 ? NULL : (uint8_t *)base;
+ }
+
+ ++sfp->pos;
+
+ return 0;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+ }
+
+ return SF_ERR_PARSE_ERROR;
+}
+
+static int parser_token(sf_parser *sfp, sf_value *dest) {
+ const uint8_t *base;
+
+ /* The first byte has already been validated by the caller. */
+ base = sfp->pos++;
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ case ':':
+ case '/':
+ DIGIT_CASES:
+ ALPHA_CASES:
+ continue;
+ }
+
+ break;
+ }
+
+ if (dest) {
+ dest->type = SF_TYPE_TOKEN;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->vec.base = (uint8_t *)base;
+ dest->vec.len = (size_t)(sfp->pos - base);
+ }
+
+ return 0;
+}
+
+static int parser_byteseq(sf_parser *sfp, sf_value *dest) {
+ const uint8_t *base;
+
+ /* The first byte has already been validated by the caller. */
+ assert(':' == *sfp->pos);
+
+ base = ++sfp->pos;
+
+ for (; !parser_eof(sfp); ++sfp->pos) {
+ switch (*sfp->pos) {
+ case '+':
+ case '/':
+ DIGIT_CASES:
+ ALPHA_CASES:
+ continue;
+ case '=':
+ switch ((sfp->pos - base) & 0x3) {
+ case 0:
+ case 1:
+ return SF_ERR_PARSE_ERROR;
+ case 2:
+ switch (*(sfp->pos - 1)) {
+ case 'A':
+ case 'Q':
+ case 'g':
+ case 'w':
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp) || *sfp->pos != '=') {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ case 3:
+ switch (*(sfp->pos - 1)) {
+ case 'A':
+ case 'E':
+ case 'I':
+ case 'M':
+ case 'Q':
+ case 'U':
+ case 'Y':
+ case 'c':
+ case 'g':
+ case 'k':
+ case 'o':
+ case 's':
+ case 'w':
+ case '0':
+ case '4':
+ case '8':
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ }
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp) || *sfp->pos != ':') {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ goto fin;
+ case ':':
+ if ((sfp->pos - base) & 0x3) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ goto fin;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+ }
+
+ return SF_ERR_PARSE_ERROR;
+
+fin:
+ if (dest) {
+ dest->type = SF_TYPE_BYTESEQ;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->vec.len = (size_t)(sfp->pos - base);
+ dest->vec.base = dest->vec.len == 0 ? NULL : (uint8_t *)base;
+ }
+
+ ++sfp->pos;
+
+ return 0;
+}
+
+static int parser_boolean(sf_parser *sfp, sf_value *dest) {
+ int b;
+
+ /* The first byte has already been validated by the caller. */
+ assert('?' == *sfp->pos);
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ switch (*sfp->pos) {
+ case '0':
+ b = 0;
+
+ break;
+ case '1':
+ b = 1;
+
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ ++sfp->pos;
+
+ if (dest) {
+ dest->type = SF_TYPE_BOOLEAN;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->boolean = b;
+ }
+
+ return 0;
+}
+
+static int parser_bare_item(sf_parser *sfp, sf_value *dest) {
+ switch (*sfp->pos) {
+ case '"':
+ return parser_string(sfp, dest);
+ case '-':
+ DIGIT_CASES:
+ return parser_number(sfp, dest);
+ case '@':
+ return parser_date(sfp, dest);
+ case ':':
+ return parser_byteseq(sfp, dest);
+ case '?':
+ return parser_boolean(sfp, dest);
+ case '*':
+ ALPHA_CASES:
+ return parser_token(sfp, dest);
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+}
+
+static int parser_skip_inner_list(sf_parser *sfp);
+
+int sf_parser_param(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value) {
+ int rv;
+
+ switch (sfp->state & SF_STATE_OP_MASK) {
+ case SF_STATE_BEFORE:
+ rv = parser_skip_inner_list(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_BEFORE_PARAMS:
+ parser_set_op_state(sfp, SF_STATE_PARAMS);
+
+ break;
+ case SF_STATE_PARAMS:
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ if (parser_eof(sfp) || *sfp->pos != ';') {
+ parser_set_op_state(sfp, SF_STATE_AFTER);
+
+ return SF_ERR_EOF;
+ }
+
+ ++sfp->pos;
+
+ parser_discard_sp(sfp);
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ rv = parser_key(sfp, dest_key);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (parser_eof(sfp) || *sfp->pos != '=') {
+ if (dest_value) {
+ dest_value->type = SF_TYPE_BOOLEAN;
+ dest_value->flags = SF_VALUE_FLAG_NONE;
+ dest_value->boolean = 1;
+ }
+
+ return 0;
+ }
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ return parser_bare_item(sfp, dest_value);
+}
+
+static int parser_skip_params(sf_parser *sfp) {
+ int rv;
+
+ for (;;) {
+ rv = sf_parser_param(sfp, NULL, NULL);
+ switch (rv) {
+ case 0:
+ break;
+ case SF_ERR_EOF:
+ return 0;
+ case SF_ERR_PARSE_ERROR:
+ return rv;
+ default:
+ assert(0);
+ abort();
+ }
+ }
+}
+
+int sf_parser_inner_list(sf_parser *sfp, sf_value *dest) {
+ int rv;
+
+ switch (sfp->state & SF_STATE_OP_MASK) {
+ case SF_STATE_BEFORE:
+ parser_discard_sp(sfp);
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ case SF_STATE_BEFORE_PARAMS:
+ rv = parser_skip_params(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* Technically, we are entering SF_STATE_AFTER, but we will set
+ another state without reading the state. */
+ /* parser_set_op_state(sfp, SF_STATE_AFTER); */
+
+ /* fall through */
+ case SF_STATE_AFTER:
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ switch (*sfp->pos) {
+ case ' ':
+ parser_discard_sp(sfp);
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ case ')':
+ break;
+ default:
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ if (*sfp->pos == ')') {
+ ++sfp->pos;
+
+ parser_unset_inner_list_state(sfp);
+ parser_set_op_state(sfp, SF_STATE_BEFORE_PARAMS);
+
+ return SF_ERR_EOF;
+ }
+
+ rv = parser_bare_item(sfp, dest);
+ if (rv != 0) {
+ return rv;
+ }
+
+ parser_set_op_state(sfp, SF_STATE_BEFORE_PARAMS);
+
+ return 0;
+}
+
+static int parser_skip_inner_list(sf_parser *sfp) {
+ int rv;
+
+ for (;;) {
+ rv = sf_parser_inner_list(sfp, NULL);
+ switch (rv) {
+ case 0:
+ break;
+ case SF_ERR_EOF:
+ return 0;
+ case SF_ERR_PARSE_ERROR:
+ return rv;
+ default:
+ assert(0);
+ abort();
+ }
+ }
+}
+
+static int parser_next_key_or_item(sf_parser *sfp) {
+ parser_discard_ows(sfp);
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_EOF;
+ }
+
+ if (*sfp->pos != ',') {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ ++sfp->pos;
+
+ parser_discard_ows(sfp);
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ return 0;
+}
+
+static int parser_dict_value(sf_parser *sfp, sf_value *dest) {
+ int rv;
+
+ if (parser_eof(sfp) || *(sfp->pos) != '=') {
+ /* Boolean true */
+ if (dest) {
+ dest->type = SF_TYPE_BOOLEAN;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ dest->boolean = 1;
+ }
+
+ sfp->state = SF_STATE_DICT_BEFORE_PARAMS;
+
+ return 0;
+ }
+
+ ++sfp->pos;
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ if (*sfp->pos == '(') {
+ if (dest) {
+ dest->type = SF_TYPE_INNER_LIST;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ }
+
+ ++sfp->pos;
+
+ sfp->state = SF_STATE_DICT_INNER_LIST_BEFORE;
+
+ return 0;
+ }
+
+ rv = parser_bare_item(sfp, dest);
+ if (rv != 0) {
+ return rv;
+ }
+
+ sfp->state = SF_STATE_DICT_BEFORE_PARAMS;
+
+ return 0;
+}
+
+int sf_parser_dict(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value) {
+ int rv;
+
+ switch (sfp->state) {
+ case SF_STATE_DICT_INNER_LIST_BEFORE:
+ rv = parser_skip_inner_list(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_DICT_BEFORE_PARAMS:
+ rv = parser_skip_params(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_DICT_AFTER:
+ rv = parser_next_key_or_item(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ break;
+ case SF_STATE_INITIAL:
+ parser_discard_sp(sfp);
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_EOF;
+ }
+
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ rv = parser_key(sfp, dest_key);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return parser_dict_value(sfp, dest_value);
+}
+
+int sf_parser_list(sf_parser *sfp, sf_value *dest) {
+ int rv;
+
+ switch (sfp->state) {
+ case SF_STATE_LIST_INNER_LIST_BEFORE:
+ rv = parser_skip_inner_list(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_LIST_BEFORE_PARAMS:
+ rv = parser_skip_params(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_LIST_AFTER:
+ rv = parser_next_key_or_item(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ break;
+ case SF_STATE_INITIAL:
+ parser_discard_sp(sfp);
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_EOF;
+ }
+
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ if (*sfp->pos == '(') {
+ if (dest) {
+ dest->type = SF_TYPE_INNER_LIST;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ }
+
+ ++sfp->pos;
+
+ sfp->state = SF_STATE_LIST_INNER_LIST_BEFORE;
+
+ return 0;
+ }
+
+ rv = parser_bare_item(sfp, dest);
+ if (rv != 0) {
+ return rv;
+ }
+
+ sfp->state = SF_STATE_LIST_BEFORE_PARAMS;
+
+ return 0;
+}
+
+int sf_parser_item(sf_parser *sfp, sf_value *dest) {
+ int rv;
+
+ switch (sfp->state) {
+ case SF_STATE_INITIAL:
+ parser_discard_sp(sfp);
+
+ if (parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ break;
+ case SF_STATE_ITEM_INNER_LIST_BEFORE:
+ rv = parser_skip_inner_list(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_ITEM_BEFORE_PARAMS:
+ rv = parser_skip_params(sfp);
+ if (rv != 0) {
+ return rv;
+ }
+
+ /* fall through */
+ case SF_STATE_ITEM_AFTER:
+ parser_discard_sp(sfp);
+
+ if (!parser_eof(sfp)) {
+ return SF_ERR_PARSE_ERROR;
+ }
+
+ return SF_ERR_EOF;
+ default:
+ assert(0);
+ abort();
+ }
+
+ if (*sfp->pos == '(') {
+ if (dest) {
+ dest->type = SF_TYPE_INNER_LIST;
+ dest->flags = SF_VALUE_FLAG_NONE;
+ }
+
+ ++sfp->pos;
+
+ sfp->state = SF_STATE_ITEM_INNER_LIST_BEFORE;
+
+ return 0;
+ }
+
+ rv = parser_bare_item(sfp, dest);
+ if (rv != 0) {
+ return rv;
+ }
+
+ sfp->state = SF_STATE_ITEM_BEFORE_PARAMS;
+
+ return 0;
+}
+
+void sf_parser_init(sf_parser *sfp, const uint8_t *data, size_t datalen) {
+ if (datalen == 0) {
+ sfp->pos = sfp->end = NULL;
+ } else {
+ sfp->pos = data;
+ sfp->end = data + datalen;
+ }
+
+ sfp->state = SF_STATE_INITIAL;
+}
+
+void sf_unescape(sf_vec *dest, const sf_vec *src) {
+ const uint8_t *p, *q;
+ uint8_t *o;
+ size_t len, slen;
+
+ if (src->len == 0) {
+ *dest = *src;
+
+ return;
+ }
+
+ o = dest->base;
+ p = src->base;
+ len = src->len;
+
+ for (;;) {
+ q = memchr(p, '\\', len);
+ if (q == NULL) {
+ if (len == src->len) {
+ *dest = *src;
+
+ return;
+ }
+
+ memcpy(o, p, len);
+ o += len;
+
+ break;
+ }
+
+ slen = (size_t)(q - p);
+ memcpy(o, p, slen);
+ o += slen;
+
+ p = q + 1;
+ *o++ = *p++;
+ len -= slen + 2;
+ }
+
+ dest->len = (size_t)(o - dest->base);
+}
+
+void sf_base64decode(sf_vec *dest, const sf_vec *src) {
+ static const int index_tbl[] = {
+ -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};
+ uint8_t *o;
+ const uint8_t *p, *end;
+ uint32_t n;
+ size_t i;
+ int idx;
+
+ assert((src->len & 0x3) == 0);
+
+ if (src->len == 0) {
+ *dest = *src;
+
+ return;
+ }
+
+ o = dest->base;
+ p = src->base;
+ end = src->base + src->len;
+
+ for (; p != end;) {
+ n = 0;
+
+ for (i = 1; i <= 4; ++i, ++p) {
+ idx = index_tbl[*p];
+
+ if (idx == -1) {
+ assert(i > 2);
+
+ if (i == 3) {
+ assert(*p == '=' && *(p + 1) == '=' && p + 2 == end);
+
+ *o++ = (uint8_t)(n >> 16);
+
+ goto fin;
+ }
+
+ assert(*p == '=' && p + 1 == end);
+
+ *o++ = (uint8_t)(n >> 16);
+ *o++ = (n >> 8) & 0xffu;
+
+ goto fin;
+ }
+
+ n += (uint32_t)(idx << (24 - i * 6));
+ }
+
+ *o++ = (uint8_t)(n >> 16);
+ *o++ = (n >> 8) & 0xffu;
+ *o++ = n & 0xffu;
+ }
+
+fin:
+ dest->len = (size_t)(o - dest->base);
+}
diff --git a/lib/sfparse.h b/lib/sfparse.h
new file mode 100644
index 0000000..1474db1
--- /dev/null
+++ b/lib/sfparse.h
@@ -0,0 +1,409 @@
+/*
+ * sfparse
+ *
+ * Copyright (c) 2023 sfparse contributors
+ * Copyright (c) 2019 nghttp3 contributors
+ * Copyright (c) 2015 nghttp2 contributors
+ *
+ * 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 SFPARSE_H
+#define SFPARSE_H
+
+/* Define WIN32 when build target is Win32 API (borrowed from
+ libcurl) */
+#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
+# define WIN32
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+/* MSVC < 2013 does not have inttypes.h because it is not C99
+ compliant. See compiler macros and version number in
+ https://sourceforge.net/p/predef/wiki/Compilers/ */
+# include <stdint.h>
+#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */
+# include <inttypes.h>
+#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */
+#include <sys/types.h>
+#include <stddef.h>
+
+/**
+ * @enum
+ *
+ * :type:`sf_type` defines value type.
+ */
+typedef enum sf_type {
+ /**
+ * :enum:`SF_TYPE_BOOLEAN` indicates boolean type.
+ */
+ SF_TYPE_BOOLEAN,
+ /**
+ * :enum:`SF_TYPE_INTEGER` indicates integer type.
+ */
+ SF_TYPE_INTEGER,
+ /**
+ * :enum:`SF_TYPE_DECIMAL` indicates decimal type.
+ */
+ SF_TYPE_DECIMAL,
+ /**
+ * :enum:`SF_TYPE_STRING` indicates string type.
+ */
+ SF_TYPE_STRING,
+ /**
+ * :enum:`SF_TYPE_TOKEN` indicates token type.
+ */
+ SF_TYPE_TOKEN,
+ /**
+ * :enum:`SF_TYPE_BYTESEQ` indicates byte sequence type.
+ */
+ SF_TYPE_BYTESEQ,
+ /**
+ * :enum:`SF_TYPE_INNER_LIST` indicates inner list type.
+ */
+ SF_TYPE_INNER_LIST,
+ /**
+ * :enum:`SF_TYPE_DATE` indicates date type.
+ */
+ SF_TYPE_DATE
+} sf_type;
+
+/**
+ * @macro
+ *
+ * :macro:`SF_ERR_PARSE_ERROR` indicates fatal parse error has
+ * occurred, and it is not possible to continue the processing.
+ */
+#define SF_ERR_PARSE_ERROR -1
+
+/**
+ * @macro
+ *
+ * :macro:`SF_ERR_EOF` indicates that there is nothing left to read.
+ * The context of this error varies depending on the function that
+ * returns this error code.
+ */
+#define SF_ERR_EOF -2
+
+/**
+ * @struct
+ *
+ * :type:`sf_vec` stores sequence of bytes.
+ */
+typedef struct sf_vec {
+ /**
+ * :member:`base` points to the beginning of the sequence of bytes.
+ */
+ uint8_t *base;
+ /**
+ * :member:`len` is the number of bytes contained in this sequence.
+ */
+ size_t len;
+} sf_vec;
+
+/**
+ * @macro
+ *
+ * :macro:`SF_VALUE_FLAG_NONE` indicates no flag set.
+ */
+#define SF_VALUE_FLAG_NONE 0x0u
+
+/**
+ * @macro
+ *
+ * :macro:`SF_VALUE_FLAG_ESCAPED_STRING` indicates that a string
+ * contains escaped character(s).
+ */
+#define SF_VALUE_FLAG_ESCAPED_STRING 0x1u
+
+/**
+ * @struct
+ *
+ * :type:`sf_decimal` contains decimal value.
+ */
+typedef struct sf_decimal {
+ /**
+ * :member:`numer` contains numerator of the decimal value.
+ */
+ int64_t numer;
+ /**
+ * :member:`denom` contains denominator of the decimal value.
+ */
+ int64_t denom;
+} sf_decimal;
+
+/**
+ * @struct
+ *
+ * :type:`sf_value` stores a Structured Field item. For Inner List,
+ * only type is set to :enum:`sf_type.SF_TYPE_INNER_LIST`. In order
+ * to read the items contained in an inner list, call
+ * `sf_parser_inner_list`.
+ */
+typedef struct sf_value {
+ /**
+ * :member:`type` is the type of the value contained in this
+ * particular object.
+ */
+ sf_type type;
+ /**
+ * :member:`flags` is bitwise OR of one or more of
+ * :macro:`SF_VALUE_FLAG_* <SF_VALUE_FLAG_NONE>`.
+ */
+ uint32_t flags;
+ /**
+ * @anonunion_start
+ *
+ * @sf_value_value
+ */
+ union {
+ /**
+ * :member:`boolean` contains boolean value if :member:`type` ==
+ * :enum:`sf_type.SF_TYPE_BOOLEAN`. 1 indicates true, and 0
+ * indicates false.
+ */
+ int boolean;
+ /**
+ * :member:`integer` contains integer value if :member:`type` is
+ * either :enum:`sf_type.SF_TYPE_INTEGER` or
+ * :enum:`sf_type.SF_TYPE_DATE`.
+ */
+ int64_t integer;
+ /**
+ * :member:`decimal` contains decimal value if :member:`type` ==
+ * :enum:`sf_type.SF_TYPE_DECIMAL`.
+ */
+ sf_decimal decimal;
+ /**
+ * :member:`vec` contains sequence of bytes if :member:`type` is
+ * either :enum:`sf_type.SF_TYPE_STRING`,
+ * :enum:`sf_type.SF_TYPE_TOKEN`, or
+ * :enum:`sf_type.SF_TYPE_BYTESEQ`.
+ *
+ * For :enum:`sf_type.SF_TYPE_STRING`, this field contains one or
+ * more escaped characters if :member:`flags` has
+ * :macro:`SF_VALUE_FLAG_ESCAPED_STRING` set. To unescape the
+ * string, use `sf_unescape`.
+ *
+ * For :enum:`sf_type.SF_TYPE_BYTESEQ`, this field contains base64
+ * encoded string. To decode this byte string, use
+ * `sf_base64decode`.
+ *
+ * If :member:`vec.len <sf_vec.len>` == 0, :member:`vec.base
+ * <sf_vec.base>` is guaranteed to be NULL.
+ */
+ sf_vec vec;
+ /**
+ * @anonunion_end
+ */
+ };
+} sf_value;
+
+/**
+ * @struct
+ *
+ * :type:`sf_parser` is the Structured Field Values parser. Use
+ * `sf_parser_init` to initialize it.
+ */
+typedef struct sf_parser {
+ /* all fields are private */
+ const uint8_t *pos;
+ const uint8_t *end;
+ uint32_t state;
+} sf_parser;
+
+/**
+ * @function
+ *
+ * `sf_parser_init` initializes |sfp| with the given buffer pointed by
+ * |data| of length |datalen|.
+ */
+void sf_parser_init(sf_parser *sfp, const uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `sf_parser_param` reads a parameter. If this function returns 0,
+ * it stores parameter key and value in |dest_key| and |dest_value|
+ * respectively, if they are not NULL.
+ *
+ * This function does no effort to find duplicated keys. Same key may
+ * be reported more than once.
+ *
+ * Caller should keep calling this function until it returns negative
+ * error code. If it returns :macro:`SF_ERR_EOF`, all parameters have
+ * read, and caller can continue to read rest of the values. If it
+ * returns :macro:`SF_ERR_PARSE_ERROR`, it encountered fatal error
+ * while parsing field value.
+ */
+int sf_parser_param(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value);
+
+/**
+ * @function
+ *
+ * `sf_parser_dict` reads the next dictionary key and value pair. If
+ * this function returns 0, it stores the key and value in |dest_key|
+ * and |dest_value| respectively, if they are not NULL.
+ *
+ * Caller can optionally read parameters attached to the pair by
+ * calling `sf_parser_param`.
+ *
+ * This function does no effort to find duplicated keys. Same key may
+ * be reported more than once.
+ *
+ * Caller should keep calling this function until it returns negative
+ * error code. If it returns :macro:`SF_ERR_EOF`, all key and value
+ * pairs have been read, and there is nothing left to read.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`SF_ERR_EOF`
+ * All values in the dictionary have read.
+ * :macro:`SF_ERR_PARSE_ERROR`
+ * It encountered fatal error while parsing field value.
+ */
+int sf_parser_dict(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value);
+
+/**
+ * @function
+ *
+ * `sf_parser_list` reads the next list item. If this function
+ * returns 0, it stores the item in |dest| if it is not NULL.
+ *
+ * Caller can optionally read parameters attached to the item by
+ * calling `sf_parser_param`.
+ *
+ * Caller should keep calling this function until it returns negative
+ * error code. If it returns :macro:`SF_ERR_EOF`, all values in the
+ * list have been read, and there is nothing left to read.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`SF_ERR_EOF`
+ * All values in the list have read.
+ * :macro:`SF_ERR_PARSE_ERROR`
+ * It encountered fatal error while parsing field value.
+ */
+int sf_parser_list(sf_parser *sfp, sf_value *dest);
+
+/**
+ * @function
+ *
+ * `sf_parser_item` reads a single item. If this function returns 0,
+ * it stores the item in |dest| if it is not NULL.
+ *
+ * This function is only used for the field value that consists of a
+ * single item.
+ *
+ * Caller can optionally read parameters attached to the item by
+ * calling `sf_parser_param`.
+ *
+ * Caller should call this function again to make sure that there is
+ * nothing left to read. If this 2nd function call returns
+ * :macro:`SF_ERR_EOF`, all data have been processed successfully.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`SF_ERR_EOF`
+ * There is nothing left to read.
+ * :macro:`SF_ERR_PARSE_ERROR`
+ * It encountered fatal error while parsing field value.
+ */
+int sf_parser_item(sf_parser *sfp, sf_value *dest);
+
+/**
+ * @function
+ *
+ * `sf_parser_inner_list` reads the next inner list item. If this
+ * function returns 0, it stores the item in |dest| if it is not NULL.
+ *
+ * Caller can optionally read parameters attached to the item by
+ * calling `sf_parser_param`.
+ *
+ * Caller should keep calling this function until it returns negative
+ * error code. If it returns :macro:`SF_ERR_EOF`, all values in this
+ * inner list have been read, and caller can optionally read
+ * parameters attached to this inner list by calling
+ * `sf_parser_param`. Then caller can continue to read rest of the
+ * values.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`SF_ERR_EOF`
+ * All values in the inner list have read.
+ * :macro:`SF_ERR_PARSE_ERROR`
+ * It encountered fatal error while parsing field value.
+ */
+int sf_parser_inner_list(sf_parser *sfp, sf_value *dest);
+
+/**
+ * @function
+ *
+ * `sf_unescape` copies |src| to |dest| by removing escapes (``\``).
+ * |src| should be the pointer to :member:`sf_value.vec` of type
+ * :enum:`sf_type.SF_TYPE_STRING` produced by either `sf_parser_dict`,
+ * `sf_parser_list`, `sf_parser_inner_list`, `sf_parser_item`, or
+ * `sf_parser_param`, otherwise the behavior is undefined.
+ *
+ * :member:`dest->base <sf_vec.base>` must point to the buffer that
+ * has sufficient space to store the unescaped string.
+ *
+ * If there is no escape character in |src|, |*src| is assigned to
+ * |*dest|. This includes the case that :member:`src->len
+ * <sf_vec.len>` == 0.
+ *
+ * This function sets the length of unescaped string to
+ * :member:`dest->len <sf_vec.len>`.
+ */
+void sf_unescape(sf_vec *dest, const sf_vec *src);
+
+/**
+ * @function
+ *
+ * `sf_base64decode` decodes Base64 encoded string |src| and writes
+ * the result into |dest|. |src| should be the pointer to
+ * :member:`sf_value.vec` of type :enum:`sf_type.SF_TYPE_BYTESEQ`
+ * produced by either `sf_parser_dict`, `sf_parser_list`,
+ * `sf_parser_inner_list`, `sf_parser_item`, or `sf_parser_param`,
+ * otherwise the behavior is undefined.
+ *
+ * :member:`dest->base <sf_vec.base>` must point to the buffer that
+ * has sufficient space to store the decoded byte string.
+ *
+ * If :member:`src->len <sf_vec.len>` == 0, |*src| is assigned to
+ * |*dest|.
+ *
+ * This function sets the length of decoded byte string to
+ * :member:`dest->len <sf_vec.len>`.
+ */
+void sf_base64decode(sf_vec *dest, const sf_vec *src);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SFPARSE_H */
diff --git a/lib/version.rc.in b/lib/version.rc.in
new file mode 100644
index 0000000..4edfa7a
--- /dev/null
+++ b/lib/version.rc.in
@@ -0,0 +1,40 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+
+FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0
+PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0
+FILEFLAGSMASK 0x3fL
+FILEOS 0x40004L
+FILETYPE 0x2L
+FILESUBTYPE 0x0L
+#ifdef _DEBUG
+ #define VER_STR "@PROJECT_VERSION@.0 (MSVC debug)"
+ #define DBG "d"
+ FILEFLAGS 0x1L
+#else
+ #define VER_STR "@PROJECT_VERSION@.0 (MSVC release)"
+ #define DBG ""
+ FILEFLAGS 0x0L
+#endif
+BEGIN
+BLOCK "StringFileInfo"
+BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "https://nghttp2.org/"
+ VALUE "FileDescription", "nghttp2; HTTP/2 C library"
+ VALUE "FileVersion", VER_STR
+ VALUE "InternalName", "nghttp2" DBG
+ VALUE "LegalCopyright", "The MIT License"
+ VALUE "LegalTrademarks", ""
+ VALUE "OriginalFilename", "nghttp2" DBG ".dll"
+ VALUE "ProductName", "NGHTTP2."
+ VALUE "ProductVersion", VER_STR
+ END
+END
+BLOCK "VarFileInfo"
+BEGIN
+VALUE "Translation", 0x409, 1200
+END
+END
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..ca36397
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -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 4
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+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_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4
new file mode 100644
index 0000000..8edf515
--- /dev/null
+++ b/m4/ax_cxx_compile_stdcxx.m4
@@ -0,0 +1,1018 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional])
+#
+# DESCRIPTION
+#
+# Check for baseline language coverage in the compiler for the specified
+# version of the C++ standard. If necessary, add switches to CXX and
+# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for
+# the respective C++ standard version.
+#
+# The second 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 no added switch, and then for an extended mode.
+#
+# The third argument, if specified 'mandatory' or if left unspecified,
+# indicates that baseline support for the specified C++ standard 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_CXX${VERSION} 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>
+# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+# Copyright (c) 2015 Paul Norman <penorman@mac.com>
+# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
+# Copyright (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com>
+# Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com>
+# Copyright (c) 2020 Jason Merrill <jason@redhat.com>
+# Copyright (c) 2021 Jörn Heusipp <osmanx@problemloesungsmaschine.de>
+#
+# 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 18
+
+dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro
+dnl (serial version number 13).
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
+ m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"],
+ [$1], [14], [ax_cxx_compile_alternatives="14 1y"],
+ [$1], [17], [ax_cxx_compile_alternatives="17 1z"],
+ [$1], [20], [ax_cxx_compile_alternatives="20"],
+ [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl
+ m4_if([$2], [], [],
+ [$2], [ext], [],
+ [$2], [noext], [],
+ [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl
+ m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true],
+ [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true],
+ [$3], [optional], [ax_cxx_compile_cxx$1_required=false],
+ [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])])
+ AC_LANG_PUSH([C++])dnl
+ ac_success=no
+
+ m4_if([$2], [], [dnl
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features by default,
+ ax_cv_cxx_compile_cxx$1,
+ [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [ax_cv_cxx_compile_cxx$1=yes],
+ [ax_cv_cxx_compile_cxx$1=no])])
+ if test x$ax_cv_cxx_compile_cxx$1 = xyes; then
+ ac_success=yes
+ fi])
+
+ m4_if([$2], [noext], [], [dnl
+ if test x$ac_success = xno; then
+ for alternative in ${ax_cxx_compile_alternatives}; do
+ switch="-std=gnu++${alternative}"
+ cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+ $cachevar,
+ [ac_save_CXX="$CXX"
+ CXX="$CXX $switch"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [eval $cachevar=yes],
+ [eval $cachevar=no])
+ CXX="$ac_save_CXX"])
+ if eval test x\$$cachevar = xyes; then
+ CXX="$CXX $switch"
+ if test -n "$CXXCPP" ; then
+ CXXCPP="$CXXCPP $switch"
+ fi
+ ac_success=yes
+ break
+ fi
+ done
+ fi])
+
+ m4_if([$2], [ext], [], [dnl
+ if test x$ac_success = xno; then
+ dnl HP's aCC needs +std=c++11 according to:
+ dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf
+ dnl Cray's crayCC needs "-h std=c++11"
+ dnl MSVC needs -std:c++NN for C++17 and later (default is C++14)
+ for alternative in ${ax_cxx_compile_alternatives}; do
+ for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do
+ if test x"$switch" = xMSVC; then
+ dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide
+ dnl with -std=c++17. We suffix the cache variable name with _MSVC to
+ dnl avoid this.
+ switch=-std:c++${alternative}
+ cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC])
+ else
+ cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+ fi
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+ $cachevar,
+ [ac_save_CXX="$CXX"
+ CXX="$CXX $switch"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [eval $cachevar=yes],
+ [eval $cachevar=no])
+ CXX="$ac_save_CXX"])
+ if eval test x\$$cachevar = xyes; then
+ CXX="$CXX $switch"
+ if test -n "$CXXCPP" ; then
+ CXXCPP="$CXXCPP $switch"
+ fi
+ ac_success=yes
+ break
+ fi
+ done
+ if test x$ac_success = xyes; then
+ break
+ fi
+ done
+ fi])
+ AC_LANG_POP([C++])
+ if test x$ax_cxx_compile_cxx$1_required = xtrue; then
+ if test x$ac_success = xno; then
+ AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.])
+ fi
+ fi
+ if test x$ac_success = xno; then
+ HAVE_CXX$1=0
+ AC_MSG_NOTICE([No compiler with C++$1 support was found])
+ else
+ HAVE_CXX$1=1
+ AC_DEFINE(HAVE_CXX$1,1,
+ [define if the compiler supports basic C++$1 syntax])
+ fi
+ AC_SUBST(HAVE_CXX$1)
+])
+
+
+dnl Test body for checking C++11 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+)
+
+dnl Test body for checking C++14 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+)
+
+dnl Test body for checking C++17 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_17
+)
+
+dnl Test body for checking C++20 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_17
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_20
+)
+
+
+dnl Tests for new features in C++11
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[
+
+// If the compiler admits that it is not ready for C++11, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+// MSVC always sets __cplusplus to 199711L in older versions; newer versions
+// only set it correctly if /Zc:__cplusplus is specified as well as a
+// /std:c++NN switch:
+// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
+#elif __cplusplus < 201103L && !defined _MSC_VER
+
+#error "This is not a C++11 compiler"
+
+#else
+
+namespace cxx11
+{
+
+ namespace test_static_assert
+ {
+
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ }
+
+ namespace test_final_override
+ {
+
+ struct Base
+ {
+ virtual ~Base() {}
+ virtual void f() {}
+ };
+
+ struct Derived : public Base
+ {
+ virtual ~Derived() override {}
+ virtual void f() override {}
+ };
+
+ }
+
+ namespace test_double_right_angle_brackets
+ {
+
+ template < typename T >
+ struct check {};
+
+ typedef check<void> single_type;
+ typedef check<check<void>> double_type;
+ typedef check<check<check<void>>> triple_type;
+ typedef check<check<check<check<void>>>> quadruple_type;
+
+ }
+
+ namespace test_decltype
+ {
+
+ int
+ f()
+ {
+ int a = 1;
+ decltype(a) b = 2;
+ return a + b;
+ }
+
+ }
+
+ namespace test_type_deduction
+ {
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static const bool value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static const bool value = true;
+ };
+
+ template < typename T1, typename T2 >
+ auto
+ add(T1 a1, T2 a2) -> decltype(a1 + a2)
+ {
+ return a1 + a2;
+ }
+
+ int
+ test(const int c, volatile int v)
+ {
+ static_assert(is_same<int, decltype(0)>::value == true, "");
+ static_assert(is_same<int, decltype(c)>::value == false, "");
+ static_assert(is_same<int, decltype(v)>::value == false, "");
+ auto ac = c;
+ auto av = v;
+ auto sumi = ac + av + 'x';
+ auto sumf = ac + av + 1.0;
+ static_assert(is_same<int, decltype(ac)>::value == true, "");
+ static_assert(is_same<int, decltype(av)>::value == true, "");
+ static_assert(is_same<int, decltype(sumi)>::value == true, "");
+ static_assert(is_same<int, decltype(sumf)>::value == false, "");
+ static_assert(is_same<int, decltype(add(c, v))>::value == true, "");
+ return (sumf > 0.0) ? sumi : add(c, v);
+ }
+
+ }
+
+ namespace test_noexcept
+ {
+
+ int f() { return 0; }
+ int g() noexcept { return 0; }
+
+ static_assert(noexcept(f()) == false, "");
+ static_assert(noexcept(g()) == true, "");
+
+ }
+
+ namespace test_constexpr
+ {
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c_r(const CharT *const s, const unsigned long acc) noexcept
+ {
+ return *s ? strlen_c_r(s + 1, acc + 1) : acc;
+ }
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c(const CharT *const s) noexcept
+ {
+ return strlen_c_r(s, 0UL);
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("1") == 1UL, "");
+ static_assert(strlen_c("example") == 7UL, "");
+ static_assert(strlen_c("another\0example") == 7UL, "");
+
+ }
+
+ namespace test_rvalue_references
+ {
+
+ template < int N >
+ struct answer
+ {
+ static constexpr int value = N;
+ };
+
+ answer<1> f(int&) { return answer<1>(); }
+ answer<2> f(const int&) { return answer<2>(); }
+ answer<3> f(int&&) { return answer<3>(); }
+
+ void
+ test()
+ {
+ int i = 0;
+ const int c = 0;
+ static_assert(decltype(f(i))::value == 1, "");
+ static_assert(decltype(f(c))::value == 2, "");
+ static_assert(decltype(f(0))::value == 3, "");
+ }
+
+ }
+
+ namespace test_uniform_initialization
+ {
+
+ struct test
+ {
+ static const int zero {};
+ static const int one {1};
+ };
+
+ static_assert(test::zero == 0, "");
+ static_assert(test::one == 1, "");
+
+ }
+
+ namespace test_lambdas
+ {
+
+ void
+ test1()
+ {
+ auto lambda1 = [](){};
+ auto lambda2 = lambda1;
+ lambda1();
+ lambda2();
+ }
+
+ int
+ test2()
+ {
+ auto a = [](int i, int j){ return i + j; }(1, 2);
+ auto b = []() -> int { return '0'; }();
+ auto c = [=](){ return a + b; }();
+ auto d = [&](){ return c; }();
+ auto e = [a, &b](int x) mutable {
+ const auto identity = [](int y){ return y; };
+ for (auto i = 0; i < a; ++i)
+ a += b--;
+ return x + identity(a + b);
+ }(0);
+ return a + b + c + d + e;
+ }
+
+ int
+ test3()
+ {
+ const auto nullary = [](){ return 0; };
+ const auto unary = [](int x){ return x; };
+ using nullary_t = decltype(nullary);
+ using unary_t = decltype(unary);
+ const auto higher1st = [](nullary_t f){ return f(); };
+ const auto higher2nd = [unary](nullary_t f1){
+ return [unary, f1](unary_t f2){ return f2(unary(f1())); };
+ };
+ return higher1st(nullary) + higher2nd(nullary)(unary);
+ }
+
+ }
+
+ namespace test_variadic_templates
+ {
+
+ template <int...>
+ struct sum;
+
+ template <int N0, int... N1toN>
+ struct sum<N0, N1toN...>
+ {
+ static constexpr auto value = N0 + sum<N1toN...>::value;
+ };
+
+ template <>
+ struct sum<>
+ {
+ static constexpr auto value = 0;
+ };
+
+ static_assert(sum<>::value == 0, "");
+ static_assert(sum<1>::value == 1, "");
+ static_assert(sum<23>::value == 23, "");
+ static_assert(sum<1, 2>::value == 3, "");
+ static_assert(sum<5, 5, 11>::value == 21, "");
+ static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, "");
+
+ }
+
+ // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae
+ // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function
+ // because of this.
+ namespace test_template_alias_sfinae
+ {
+
+ struct foo {};
+
+ template<typename T>
+ using member = typename T::member_type;
+
+ template<typename T>
+ void func(...) {}
+
+ template<typename T>
+ void func(member<T>*) {}
+
+ void test();
+
+ void test() { func<foo>(0); }
+
+ }
+
+} // namespace cxx11
+
+#endif // __cplusplus >= 201103L
+
+]])
+
+
+dnl Tests for new features in C++14
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[
+
+// If the compiler admits that it is not ready for C++14, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201402L && !defined _MSC_VER
+
+#error "This is not a C++14 compiler"
+
+#else
+
+namespace cxx14
+{
+
+ namespace test_polymorphic_lambdas
+ {
+
+ int
+ test()
+ {
+ const auto lambda = [](auto&&... args){
+ const auto istiny = [](auto x){
+ return (sizeof(x) == 1UL) ? 1 : 0;
+ };
+ const int aretiny[] = { istiny(args)... };
+ return aretiny[0];
+ };
+ return lambda(1, 1L, 1.0f, '1');
+ }
+
+ }
+
+ namespace test_binary_literals
+ {
+
+ constexpr auto ivii = 0b0000000000101010;
+ static_assert(ivii == 42, "wrong value");
+
+ }
+
+ namespace test_generalized_constexpr
+ {
+
+ template < typename CharT >
+ constexpr unsigned long
+ strlen_c(const CharT *const s) noexcept
+ {
+ auto length = 0UL;
+ for (auto p = s; *p; ++p)
+ ++length;
+ return length;
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("x") == 1UL, "");
+ static_assert(strlen_c("test") == 4UL, "");
+ static_assert(strlen_c("another\0test") == 7UL, "");
+
+ }
+
+ namespace test_lambda_init_capture
+ {
+
+ int
+ test()
+ {
+ auto x = 0;
+ const auto lambda1 = [a = x](int b){ return a + b; };
+ const auto lambda2 = [a = lambda1(x)](){ return a; };
+ return lambda2();
+ }
+
+ }
+
+ namespace test_digit_separators
+ {
+
+ constexpr auto ten_million = 100'000'000;
+ static_assert(ten_million == 100000000, "");
+
+ }
+
+ namespace test_return_type_deduction
+ {
+
+ auto f(int& x) { return x; }
+ decltype(auto) g(int& x) { return x; }
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static constexpr auto value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static constexpr auto value = true;
+ };
+
+ int
+ test()
+ {
+ auto x = 0;
+ static_assert(is_same<int, decltype(f(x))>::value, "");
+ static_assert(is_same<int&, decltype(g(x))>::value, "");
+ return x;
+ }
+
+ }
+
+} // namespace cxx14
+
+#endif // __cplusplus >= 201402L
+
+]])
+
+
+dnl Tests for new features in C++17
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[
+
+// If the compiler admits that it is not ready for C++17, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201703L && !defined _MSC_VER
+
+#error "This is not a C++17 compiler"
+
+#else
+
+#include <initializer_list>
+#include <utility>
+#include <type_traits>
+
+namespace cxx17
+{
+
+ namespace test_constexpr_lambdas
+ {
+
+ constexpr int foo = [](){return 42;}();
+
+ }
+
+ namespace test::nested_namespace::definitions
+ {
+
+ }
+
+ namespace test_fold_expression
+ {
+
+ template<typename... Args>
+ int multiply(Args... args)
+ {
+ return (args * ... * 1);
+ }
+
+ template<typename... Args>
+ bool all(Args... args)
+ {
+ return (args && ...);
+ }
+
+ }
+
+ namespace test_extended_static_assert
+ {
+
+ static_assert (true);
+
+ }
+
+ namespace test_auto_brace_init_list
+ {
+
+ auto foo = {5};
+ auto bar {5};
+
+ static_assert(std::is_same<std::initializer_list<int>, decltype(foo)>::value);
+ static_assert(std::is_same<int, decltype(bar)>::value);
+ }
+
+ namespace test_typename_in_template_template_parameter
+ {
+
+ template<template<typename> typename X> struct D;
+
+ }
+
+ namespace test_fallthrough_nodiscard_maybe_unused_attributes
+ {
+
+ int f1()
+ {
+ return 42;
+ }
+
+ [[nodiscard]] int f2()
+ {
+ [[maybe_unused]] auto unused = f1();
+
+ switch (f1())
+ {
+ case 17:
+ f1();
+ [[fallthrough]];
+ case 42:
+ f1();
+ }
+ return f1();
+ }
+
+ }
+
+ namespace test_extended_aggregate_initialization
+ {
+
+ struct base1
+ {
+ int b1, b2 = 42;
+ };
+
+ struct base2
+ {
+ base2() {
+ b3 = 42;
+ }
+ int b3;
+ };
+
+ struct derived : base1, base2
+ {
+ int d;
+ };
+
+ derived d1 {{1, 2}, {}, 4}; // full initialization
+ derived d2 {{}, {}, 4}; // value-initialized bases
+
+ }
+
+ namespace test_general_range_based_for_loop
+ {
+
+ struct iter
+ {
+ int i;
+
+ int& operator* ()
+ {
+ return i;
+ }
+
+ const int& operator* () const
+ {
+ return i;
+ }
+
+ iter& operator++()
+ {
+ ++i;
+ return *this;
+ }
+ };
+
+ struct sentinel
+ {
+ int i;
+ };
+
+ bool operator== (const iter& i, const sentinel& s)
+ {
+ return i.i == s.i;
+ }
+
+ bool operator!= (const iter& i, const sentinel& s)
+ {
+ return !(i == s);
+ }
+
+ struct range
+ {
+ iter begin() const
+ {
+ return {0};
+ }
+
+ sentinel end() const
+ {
+ return {5};
+ }
+ };
+
+ void f()
+ {
+ range r {};
+
+ for (auto i : r)
+ {
+ [[maybe_unused]] auto v = i;
+ }
+ }
+
+ }
+
+ namespace test_lambda_capture_asterisk_this_by_value
+ {
+
+ struct t
+ {
+ int i;
+ int foo()
+ {
+ return [*this]()
+ {
+ return i;
+ }();
+ }
+ };
+
+ }
+
+ namespace test_enum_class_construction
+ {
+
+ enum class byte : unsigned char
+ {};
+
+ byte foo {42};
+
+ }
+
+ namespace test_constexpr_if
+ {
+
+ template <bool cond>
+ int f ()
+ {
+ if constexpr(cond)
+ {
+ return 13;
+ }
+ else
+ {
+ return 42;
+ }
+ }
+
+ }
+
+ namespace test_selection_statement_with_initializer
+ {
+
+ int f()
+ {
+ return 13;
+ }
+
+ int f2()
+ {
+ if (auto i = f(); i > 0)
+ {
+ return 3;
+ }
+
+ switch (auto i = f(); i + 4)
+ {
+ case 17:
+ return 2;
+
+ default:
+ return 1;
+ }
+ }
+
+ }
+
+ namespace test_template_argument_deduction_for_class_templates
+ {
+
+ template <typename T1, typename T2>
+ struct pair
+ {
+ pair (T1 p1, T2 p2)
+ : m1 {p1},
+ m2 {p2}
+ {}
+
+ T1 m1;
+ T2 m2;
+ };
+
+ void f()
+ {
+ [[maybe_unused]] auto p = pair{13, 42u};
+ }
+
+ }
+
+ namespace test_non_type_auto_template_parameters
+ {
+
+ template <auto n>
+ struct B
+ {};
+
+ B<5> b1;
+ B<'a'> b2;
+
+ }
+
+ namespace test_structured_bindings
+ {
+
+ int arr[2] = { 1, 2 };
+ std::pair<int, int> pr = { 1, 2 };
+
+ auto f1() -> int(&)[2]
+ {
+ return arr;
+ }
+
+ auto f2() -> std::pair<int, int>&
+ {
+ return pr;
+ }
+
+ struct S
+ {
+ int x1 : 2;
+ volatile double y1;
+ };
+
+ S f3()
+ {
+ return {};
+ }
+
+ auto [ x1, y1 ] = f1();
+ auto& [ xr1, yr1 ] = f1();
+ auto [ x2, y2 ] = f2();
+ auto& [ xr2, yr2 ] = f2();
+ const auto [ x3, y3 ] = f3();
+
+ }
+
+ namespace test_exception_spec_type_system
+ {
+
+ struct Good {};
+ struct Bad {};
+
+ void g1() noexcept;
+ void g2();
+
+ template<typename T>
+ Bad
+ f(T*, T*);
+
+ template<typename T1, typename T2>
+ Good
+ f(T1*, T2*);
+
+ static_assert (std::is_same_v<Good, decltype(f(g1, g2))>);
+
+ }
+
+ namespace test_inline_variables
+ {
+
+ template<class T> void f(T)
+ {}
+
+ template<class T> inline T g(T)
+ {
+ return T{};
+ }
+
+ template<> inline void f<>(int)
+ {}
+
+ template<> int g<>(int)
+ {
+ return 5;
+ }
+
+ }
+
+} // namespace cxx17
+
+#endif // __cplusplus < 201703L && !defined _MSC_VER
+
+]])
+
+
+dnl Tests for new features in C++20
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 202002L && !defined _MSC_VER
+
+#error "This is not a C++20 compiler"
+
+#else
+
+#include <version>
+
+namespace cxx20
+{
+
+// As C++20 supports feature test macros in the standard, there is no
+// immediate need to actually test for feature availability on the
+// Autoconf side.
+
+} // namespace cxx20
+
+#endif // __cplusplus < 202002L && !defined _MSC_VER
+
+]])
diff --git a/m4/libxml2.m4 b/m4/libxml2.m4
new file mode 100644
index 0000000..68cd824
--- /dev/null
+++ b/m4/libxml2.m4
@@ -0,0 +1,188 @@
+# Configure paths for LIBXML2
+# Mike Hommey 2004-06-19
+# use CPPFLAGS instead of CFLAGS
+# Toshio Kuratomi 2001-04-21
+# Adapted from:
+# Configure paths for GLIB
+# Owen Taylor 97-11-3
+
+dnl AM_PATH_XML2([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]])
+dnl Test for XML, and define XML_CPPFLAGS and XML_LIBS
+dnl
+AC_DEFUN([AM_PATH_XML2],[
+AC_ARG_WITH(xml-prefix,
+ [ --with-xml-prefix=PFX Prefix where libxml is installed (optional)],
+ xml_config_prefix="$withval", xml_config_prefix="")
+AC_ARG_WITH(xml-exec-prefix,
+ [ --with-xml-exec-prefix=PFX Exec prefix where libxml is installed (optional)],
+ xml_config_exec_prefix="$withval", xml_config_exec_prefix="")
+AC_ARG_ENABLE(xmltest,
+ [ --disable-xmltest Do not try to compile and run a test LIBXML program],,
+ enable_xmltest=yes)
+
+ if test x$xml_config_exec_prefix != x ; then
+ xml_config_args="$xml_config_args"
+ if test x${XML2_CONFIG+set} != xset ; then
+ XML2_CONFIG=$xml_config_exec_prefix/bin/xml2-config
+ fi
+ fi
+ if test x$xml_config_prefix != x ; then
+ xml_config_args="$xml_config_args --prefix=$xml_config_prefix"
+ if test x${XML2_CONFIG+set} != xset ; then
+ XML2_CONFIG=$xml_config_prefix/bin/xml2-config
+ fi
+ fi
+
+ AC_PATH_PROG(XML2_CONFIG, xml2-config, no)
+ min_xml_version=ifelse([$1], ,2.0.0,[$1])
+ AC_MSG_CHECKING(for libxml - version >= $min_xml_version)
+ no_xml=""
+ if test "$XML2_CONFIG" = "no" ; then
+ no_xml=yes
+ else
+ XML_CPPFLAGS=`$XML2_CONFIG $xml_config_args --cflags`
+ XML_LIBS=`$XML2_CONFIG $xml_config_args --libs`
+ xml_config_major_version=`$XML2_CONFIG $xml_config_args --version | \
+ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+ xml_config_minor_version=`$XML2_CONFIG $xml_config_args --version | \
+ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+ xml_config_micro_version=`$XML2_CONFIG $xml_config_args --version | \
+ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+ if test "x$enable_xmltest" = "xyes" ; then
+ ac_save_CPPFLAGS="$CPPFLAGS"
+ ac_save_LIBS="$LIBS"
+ CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS"
+ LIBS="$XML_LIBS $LIBS"
+dnl
+dnl Now check if the installed libxml is sufficiently new.
+dnl (Also sanity checks the results of xml2-config to some extent)
+dnl
+ rm -f conf.xmltest
+ AC_TRY_RUN([
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <libxml/xmlversion.h>
+
+int
+main()
+{
+ int xml_major_version, xml_minor_version, xml_micro_version;
+ int major, minor, micro;
+ char *tmp_version;
+
+ system("touch conf.xmltest");
+
+ /* Capture xml2-config output via autoconf/configure variables */
+ /* HP/UX 9 (%@#!) writes to sscanf strings */
+ tmp_version = (char *)strdup("$min_xml_version");
+ if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, &micro) != 3) {
+ printf("%s, bad version string from xml2-config\n", "$min_xml_version");
+ exit(1);
+ }
+ free(tmp_version);
+
+ /* Capture the version information from the header files */
+ tmp_version = (char *)strdup(LIBXML_DOTTED_VERSION);
+ if (sscanf(tmp_version, "%d.%d.%d", &xml_major_version, &xml_minor_version, &xml_micro_version) != 3) {
+ printf("%s, bad version string from libxml includes\n", "LIBXML_DOTTED_VERSION");
+ exit(1);
+ }
+ free(tmp_version);
+
+ /* Compare xml2-config output to the libxml headers */
+ if ((xml_major_version != $xml_config_major_version) ||
+ (xml_minor_version != $xml_config_minor_version) ||
+ (xml_micro_version != $xml_config_micro_version))
+ {
+ printf("*** libxml header files (version %d.%d.%d) do not match\n",
+ xml_major_version, xml_minor_version, xml_micro_version);
+ printf("*** xml2-config (version %d.%d.%d)\n",
+ $xml_config_major_version, $xml_config_minor_version, $xml_config_micro_version);
+ return 1;
+ }
+/* Compare the headers to the library to make sure we match */
+ /* Less than ideal -- doesn't provide us with return value feedback,
+ * only exits if there's a serious mismatch between header and library.
+ */
+ LIBXML_TEST_VERSION;
+
+ /* Test that the library is greater than our minimum version */
+ if ((xml_major_version > major) ||
+ ((xml_major_version == major) && (xml_minor_version > minor)) ||
+ ((xml_major_version == major) && (xml_minor_version == minor) &&
+ (xml_micro_version >= micro)))
+ {
+ return 0;
+ }
+ else
+ {
+ printf("\n*** An old version of libxml (%d.%d.%d) was found.\n",
+ xml_major_version, xml_minor_version, xml_micro_version);
+ printf("*** You need a version of libxml newer than %d.%d.%d. The latest version of\n",
+ major, minor, micro);
+ printf("*** libxml is always available from ftp://ftp.xmlsoft.org.\n");
+ printf("***\n");
+ printf("*** If you have already installed a sufficiently new version, this error\n");
+ printf("*** probably means that the wrong copy of the xml2-config shell script is\n");
+ printf("*** being found. The easiest way to fix this is to remove the old version\n");
+ printf("*** of LIBXML, but you can also set the XML2_CONFIG environment to point to the\n");
+ printf("*** correct copy of xml2-config. (In this case, you will have to\n");
+ printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+ printf("*** so that the correct libraries are found at run-time))\n");
+ }
+ return 1;
+}
+],, no_xml=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIBS="$ac_save_LIBS"
+ fi
+ fi
+
+ if test "x$no_xml" = x ; then
+ AC_MSG_RESULT(yes (version $xml_config_major_version.$xml_config_minor_version.$xml_config_micro_version))
+ ifelse([$2], , :, [$2])
+ else
+ AC_MSG_RESULT(no)
+ if test "$XML2_CONFIG" = "no" ; then
+ echo "*** The xml2-config script installed by LIBXML could not be found"
+ echo "*** If libxml was installed in PREFIX, make sure PREFIX/bin is in"
+ echo "*** your path, or set the XML2_CONFIG environment variable to the"
+ echo "*** full path to xml2-config."
+ else
+ if test -f conf.xmltest ; then
+ :
+ else
+ echo "*** Could not run libxml test program, checking why..."
+ CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS"
+ LIBS="$LIBS $XML_LIBS"
+ AC_TRY_LINK([
+#include <libxml/xmlversion.h>
+#include <stdio.h>
+], [ LIBXML_TEST_VERSION; return 0;],
+ [ echo "*** The test program compiled, but did not run. This usually means"
+ echo "*** that the run-time linker is not finding LIBXML or finding the wrong"
+ echo "*** version of LIBXML. If it is not finding LIBXML, you'll need to set your"
+ echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+ echo "*** to the installed location Also, make sure you have run ldconfig if that"
+ echo "*** is required on your system"
+ echo "***"
+ echo "*** If you have an old version installed, it is best to remove it, although"
+ echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ],
+ [ echo "*** The test program failed to compile or link. See the file config.log for the"
+ echo "*** exact error that occured. This usually means LIBXML was incorrectly installed"
+ echo "*** or that you have moved LIBXML since it was installed. In the latter case, you"
+ echo "*** may want to edit the xml2-config script: $XML2_CONFIG" ])
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIBS="$ac_save_LIBS"
+ fi
+ fi
+
+ XML_CPPFLAGS=""
+ XML_LIBS=""
+ ifelse([$3], , :, [$3])
+ fi
+ AC_SUBST(XML_CPPFLAGS)
+ AC_SUBST(XML_LIBS)
+ rm -f conf.xmltest
+])
diff --git a/makebashcompletion b/makebashcompletion
new file mode 100755
index 0000000..9e88d9d
--- /dev/null
+++ b/makebashcompletion
@@ -0,0 +1,7 @@
+#!/bin/sh -e
+
+BCPATH=doc/bash_completion
+
+for prog in nghttp nghttpd nghttpx h2load; do
+ $BCPATH/make_bash_completion.py src/$prog > $BCPATH/$prog
+done
diff --git a/makemanpages b/makemanpages
new file mode 100755
index 0000000..0b7c54e
--- /dev/null
+++ b/makemanpages
@@ -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
index 0000000..b65e08f
--- /dev/null
+++ b/makerelease.sh
@@ -0,0 +1,23 @@
+#!/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
+
+git submodule update --init
+
+autoreconf -i
+./configure --with-mruby && \
+ make dist-bzip2 && make dist-gzip && make dist-xz || echo "error"
+
+rm -f checksums.txt
+
+VERSION=`echo -n $TAG | sed -E 's|^v([0-9]+\.[0-9]+\.[0-9]+)(-DEV)?$|\1|'`
+for f in nghttp2-$VERSION.tar.bz2 nghttp2-$VERSION.tar.gz nghttp2-$VERSION.tar.xz; do
+ sha256sum $f >> checksums.txt
+ gpg --armor --detach-sign $f
+done
+
+make distclean
diff --git a/mkcipherlist.py b/mkcipherlist.py
new file mode 100755
index 0000000..e366f5e
--- /dev/null
+++ b/mkcipherlist.py
@@ -0,0 +1,325 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# This script read cipher suite list csv file [1] and prints out id
+# and name of black listed cipher suites. The output is used by
+# src/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
+
+import re
+import sys
+import csv
+
+# From RFC 7540
+blacklist = [
+ 'TLS_NULL_WITH_NULL_NULL',
+ 'TLS_RSA_WITH_NULL_MD5',
+ 'TLS_RSA_WITH_NULL_SHA',
+ 'TLS_RSA_EXPORT_WITH_RC4_40_MD5',
+ 'TLS_RSA_WITH_RC4_128_MD5',
+ 'TLS_RSA_WITH_RC4_128_SHA',
+ 'TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5',
+ 'TLS_RSA_WITH_IDEA_CBC_SHA',
+ 'TLS_RSA_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_RSA_WITH_DES_CBC_SHA',
+ 'TLS_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_DH_DSS_WITH_DES_CBC_SHA',
+ 'TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_DH_RSA_WITH_DES_CBC_SHA',
+ 'TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_DES_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_DES_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DH_anon_EXPORT_WITH_RC4_40_MD5',
+ 'TLS_DH_anon_WITH_RC4_128_MD5',
+ 'TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA',
+ 'TLS_DH_anon_WITH_DES_CBC_SHA',
+ 'TLS_DH_anon_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_KRB5_WITH_DES_CBC_SHA',
+ 'TLS_KRB5_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_KRB5_WITH_RC4_128_SHA',
+ 'TLS_KRB5_WITH_IDEA_CBC_SHA',
+ 'TLS_KRB5_WITH_DES_CBC_MD5',
+ 'TLS_KRB5_WITH_3DES_EDE_CBC_MD5',
+ 'TLS_KRB5_WITH_RC4_128_MD5',
+ 'TLS_KRB5_WITH_IDEA_CBC_MD5',
+ 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA',
+ 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA',
+ 'TLS_KRB5_EXPORT_WITH_RC4_40_SHA',
+ 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5',
+ 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5',
+ 'TLS_KRB5_EXPORT_WITH_RC4_40_MD5',
+ 'TLS_PSK_WITH_NULL_SHA',
+ 'TLS_DHE_PSK_WITH_NULL_SHA',
+ 'TLS_RSA_PSK_WITH_NULL_SHA',
+ 'TLS_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_DH_DSS_WITH_AES_128_CBC_SHA',
+ 'TLS_DH_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_DH_anon_WITH_AES_128_CBC_SHA',
+ 'TLS_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_DH_DSS_WITH_AES_256_CBC_SHA',
+ 'TLS_DH_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_DH_anon_WITH_AES_256_CBC_SHA',
+ 'TLS_RSA_WITH_NULL_SHA256',
+ 'TLS_RSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_RSA_WITH_AES_256_CBC_SHA256',
+ 'TLS_DH_DSS_WITH_AES_128_CBC_SHA256',
+ 'TLS_DH_RSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256',
+ 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_DH_DSS_WITH_AES_256_CBC_SHA256',
+ 'TLS_DH_RSA_WITH_AES_256_CBC_SHA256',
+ 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256',
+ 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256',
+ 'TLS_DH_anon_WITH_AES_128_CBC_SHA256',
+ 'TLS_DH_anon_WITH_AES_256_CBC_SHA256',
+ 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA',
+ 'TLS_PSK_WITH_RC4_128_SHA',
+ 'TLS_PSK_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_PSK_WITH_AES_128_CBC_SHA',
+ 'TLS_PSK_WITH_AES_256_CBC_SHA',
+ 'TLS_DHE_PSK_WITH_RC4_128_SHA',
+ 'TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA',
+ 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA',
+ 'TLS_RSA_PSK_WITH_RC4_128_SHA',
+ 'TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA',
+ 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA',
+ 'TLS_RSA_WITH_SEED_CBC_SHA',
+ 'TLS_DH_DSS_WITH_SEED_CBC_SHA',
+ 'TLS_DH_RSA_WITH_SEED_CBC_SHA',
+ 'TLS_DHE_DSS_WITH_SEED_CBC_SHA',
+ 'TLS_DHE_RSA_WITH_SEED_CBC_SHA',
+ 'TLS_DH_anon_WITH_SEED_CBC_SHA',
+ 'TLS_RSA_WITH_AES_128_GCM_SHA256',
+ 'TLS_RSA_WITH_AES_256_GCM_SHA384',
+ 'TLS_DH_RSA_WITH_AES_128_GCM_SHA256',
+ 'TLS_DH_RSA_WITH_AES_256_GCM_SHA384',
+ 'TLS_DH_DSS_WITH_AES_128_GCM_SHA256',
+ 'TLS_DH_DSS_WITH_AES_256_GCM_SHA384',
+ 'TLS_DH_anon_WITH_AES_128_GCM_SHA256',
+ 'TLS_DH_anon_WITH_AES_256_GCM_SHA384',
+ 'TLS_PSK_WITH_AES_128_GCM_SHA256',
+ 'TLS_PSK_WITH_AES_256_GCM_SHA384',
+ 'TLS_RSA_PSK_WITH_AES_128_GCM_SHA256',
+ 'TLS_RSA_PSK_WITH_AES_256_GCM_SHA384',
+ 'TLS_PSK_WITH_AES_128_CBC_SHA256',
+ 'TLS_PSK_WITH_AES_256_CBC_SHA384',
+ 'TLS_PSK_WITH_NULL_SHA256',
+ 'TLS_PSK_WITH_NULL_SHA384',
+ 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA256',
+ 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA384',
+ 'TLS_DHE_PSK_WITH_NULL_SHA256',
+ 'TLS_DHE_PSK_WITH_NULL_SHA384',
+ 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA256',
+ 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA384',
+ 'TLS_RSA_PSK_WITH_NULL_SHA256',
+ 'TLS_RSA_PSK_WITH_NULL_SHA384',
+ 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256',
+ 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV',
+ 'TLS_ECDH_ECDSA_WITH_NULL_SHA',
+ 'TLS_ECDH_ECDSA_WITH_RC4_128_SHA',
+ 'TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_NULL_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_RC4_128_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDH_RSA_WITH_NULL_SHA',
+ 'TLS_ECDH_RSA_WITH_RC4_128_SHA',
+ 'TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDHE_RSA_WITH_NULL_SHA',
+ 'TLS_ECDHE_RSA_WITH_RC4_128_SHA',
+ 'TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDH_anon_WITH_NULL_SHA',
+ 'TLS_ECDH_anon_WITH_RC4_128_SHA',
+ 'TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDH_anon_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDH_anon_WITH_AES_256_CBC_SHA',
+ 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA',
+ 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA',
+ 'TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA',
+ 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA',
+ 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA',
+ 'TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384',
+ 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384',
+ 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256',
+ 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384',
+ 'TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256',
+ 'TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384',
+ 'TLS_ECDHE_PSK_WITH_RC4_128_SHA',
+ 'TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA',
+ 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA',
+ 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA',
+ 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256',
+ 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384',
+ 'TLS_ECDHE_PSK_WITH_NULL_SHA',
+ 'TLS_ECDHE_PSK_WITH_NULL_SHA256',
+ 'TLS_ECDHE_PSK_WITH_NULL_SHA384',
+ 'TLS_RSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_RSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DH_anon_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DH_anon_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_RSA_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_RSA_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_DH_anon_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_DH_anon_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_PSK_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_PSK_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_PSK_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_PSK_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256',
+ 'TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384',
+ 'TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256',
+ 'TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384',
+ 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256',
+ 'TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384',
+ 'TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256',
+ 'TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384',
+ 'TLS_RSA_WITH_AES_128_CCM',
+ 'TLS_RSA_WITH_AES_256_CCM',
+ 'TLS_RSA_WITH_AES_128_CCM_8',
+ 'TLS_RSA_WITH_AES_256_CCM_8',
+ 'TLS_PSK_WITH_AES_128_CCM',
+ 'TLS_PSK_WITH_AES_256_CCM',
+ 'TLS_PSK_WITH_AES_128_CCM_8',
+ 'TLS_PSK_WITH_AES_256_CCM_8',
+]
+
+ciphers = []
+found = set()
+for hl, name, _, _, _ in csv.reader(sys.stdin):
+ if name not in blacklist:
+ continue
+
+ found.add(name)
+
+ 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))
+
+if len(found) != len(blacklist):
+ print('{} found out of {}; not all cipher was found: {}'.format(
+ len(found), len(blacklist),
+ found.symmetric_difference(blacklist)))
diff --git a/mkhufftbl.py b/mkhufftbl.py
new file mode 100755
index 0000000..a9f10d7
--- /dev/null
+++ b/mkhufftbl.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python3
+# -*- 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] https://httpwg.org/specs/rfc7541.html
+
+import re
+import sys
+from io import StringIO
+
+# From [1]
+HUFFMAN_CODE_TABLE = """\
+ ( 0) |11111111|11000 1ff8 [13]
+ ( 1) |11111111|11111111|1011000 7fffd8 [23]
+ ( 2) |11111111|11111111|11111110|0010 fffffe2 [28]
+ ( 3) |11111111|11111111|11111110|0011 fffffe3 [28]
+ ( 4) |11111111|11111111|11111110|0100 fffffe4 [28]
+ ( 5) |11111111|11111111|11111110|0101 fffffe5 [28]
+ ( 6) |11111111|11111111|11111110|0110 fffffe6 [28]
+ ( 7) |11111111|11111111|11111110|0111 fffffe7 [28]
+ ( 8) |11111111|11111111|11111110|1000 fffffe8 [28]
+ ( 9) |11111111|11111111|11101010 ffffea [24]
+ ( 10) |11111111|11111111|11111111|111100 3ffffffc [30]
+ ( 11) |11111111|11111111|11111110|1001 fffffe9 [28]
+ ( 12) |11111111|11111111|11111110|1010 fffffea [28]
+ ( 13) |11111111|11111111|11111111|111101 3ffffffd [30]
+ ( 14) |11111111|11111111|11111110|1011 fffffeb [28]
+ ( 15) |11111111|11111111|11111110|1100 fffffec [28]
+ ( 16) |11111111|11111111|11111110|1101 fffffed [28]
+ ( 17) |11111111|11111111|11111110|1110 fffffee [28]
+ ( 18) |11111111|11111111|11111110|1111 fffffef [28]
+ ( 19) |11111111|11111111|11111111|0000 ffffff0 [28]
+ ( 20) |11111111|11111111|11111111|0001 ffffff1 [28]
+ ( 21) |11111111|11111111|11111111|0010 ffffff2 [28]
+ ( 22) |11111111|11111111|11111111|111110 3ffffffe [30]
+ ( 23) |11111111|11111111|11111111|0011 ffffff3 [28]
+ ( 24) |11111111|11111111|11111111|0100 ffffff4 [28]
+ ( 25) |11111111|11111111|11111111|0101 ffffff5 [28]
+ ( 26) |11111111|11111111|11111111|0110 ffffff6 [28]
+ ( 27) |11111111|11111111|11111111|0111 ffffff7 [28]
+ ( 28) |11111111|11111111|11111111|1000 ffffff8 [28]
+ ( 29) |11111111|11111111|11111111|1001 ffffff9 [28]
+ ( 30) |11111111|11111111|11111111|1010 ffffffa [28]
+ ( 31) |11111111|11111111|11111111|1011 ffffffb [28]
+' ' ( 32) |010100 14 [ 6]
+'!' ( 33) |11111110|00 3f8 [10]
+'"' ( 34) |11111110|01 3f9 [10]
+'#' ( 35) |11111111|1010 ffa [12]
+'$' ( 36) |11111111|11001 1ff9 [13]
+'%' ( 37) |010101 15 [ 6]
+'&' ( 38) |11111000 f8 [ 8]
+''' ( 39) |11111111|010 7fa [11]
+'(' ( 40) |11111110|10 3fa [10]
+')' ( 41) |11111110|11 3fb [10]
+'*' ( 42) |11111001 f9 [ 8]
+'+' ( 43) |11111111|011 7fb [11]
+',' ( 44) |11111010 fa [ 8]
+'-' ( 45) |010110 16 [ 6]
+'.' ( 46) |010111 17 [ 6]
+'/' ( 47) |011000 18 [ 6]
+'0' ( 48) |00000 0 [ 5]
+'1' ( 49) |00001 1 [ 5]
+'2' ( 50) |00010 2 [ 5]
+'3' ( 51) |011001 19 [ 6]
+'4' ( 52) |011010 1a [ 6]
+'5' ( 53) |011011 1b [ 6]
+'6' ( 54) |011100 1c [ 6]
+'7' ( 55) |011101 1d [ 6]
+'8' ( 56) |011110 1e [ 6]
+'9' ( 57) |011111 1f [ 6]
+':' ( 58) |1011100 5c [ 7]
+';' ( 59) |11111011 fb [ 8]
+'<' ( 60) |11111111|1111100 7ffc [15]
+'=' ( 61) |100000 20 [ 6]
+'>' ( 62) |11111111|1011 ffb [12]
+'?' ( 63) |11111111|00 3fc [10]
+'@' ( 64) |11111111|11010 1ffa [13]
+'A' ( 65) |100001 21 [ 6]
+'B' ( 66) |1011101 5d [ 7]
+'C' ( 67) |1011110 5e [ 7]
+'D' ( 68) |1011111 5f [ 7]
+'E' ( 69) |1100000 60 [ 7]
+'F' ( 70) |1100001 61 [ 7]
+'G' ( 71) |1100010 62 [ 7]
+'H' ( 72) |1100011 63 [ 7]
+'I' ( 73) |1100100 64 [ 7]
+'J' ( 74) |1100101 65 [ 7]
+'K' ( 75) |1100110 66 [ 7]
+'L' ( 76) |1100111 67 [ 7]
+'M' ( 77) |1101000 68 [ 7]
+'N' ( 78) |1101001 69 [ 7]
+'O' ( 79) |1101010 6a [ 7]
+'P' ( 80) |1101011 6b [ 7]
+'Q' ( 81) |1101100 6c [ 7]
+'R' ( 82) |1101101 6d [ 7]
+'S' ( 83) |1101110 6e [ 7]
+'T' ( 84) |1101111 6f [ 7]
+'U' ( 85) |1110000 70 [ 7]
+'V' ( 86) |1110001 71 [ 7]
+'W' ( 87) |1110010 72 [ 7]
+'X' ( 88) |11111100 fc [ 8]
+'Y' ( 89) |1110011 73 [ 7]
+'Z' ( 90) |11111101 fd [ 8]
+'[' ( 91) |11111111|11011 1ffb [13]
+'\' ( 92) |11111111|11111110|000 7fff0 [19]
+']' ( 93) |11111111|11100 1ffc [13]
+'^' ( 94) |11111111|111100 3ffc [14]
+'_' ( 95) |100010 22 [ 6]
+'`' ( 96) |11111111|1111101 7ffd [15]
+'a' ( 97) |00011 3 [ 5]
+'b' ( 98) |100011 23 [ 6]
+'c' ( 99) |00100 4 [ 5]
+'d' (100) |100100 24 [ 6]
+'e' (101) |00101 5 [ 5]
+'f' (102) |100101 25 [ 6]
+'g' (103) |100110 26 [ 6]
+'h' (104) |100111 27 [ 6]
+'i' (105) |00110 6 [ 5]
+'j' (106) |1110100 74 [ 7]
+'k' (107) |1110101 75 [ 7]
+'l' (108) |101000 28 [ 6]
+'m' (109) |101001 29 [ 6]
+'n' (110) |101010 2a [ 6]
+'o' (111) |00111 7 [ 5]
+'p' (112) |101011 2b [ 6]
+'q' (113) |1110110 76 [ 7]
+'r' (114) |101100 2c [ 6]
+'s' (115) |01000 8 [ 5]
+'t' (116) |01001 9 [ 5]
+'u' (117) |101101 2d [ 6]
+'v' (118) |1110111 77 [ 7]
+'w' (119) |1111000 78 [ 7]
+'x' (120) |1111001 79 [ 7]
+'y' (121) |1111010 7a [ 7]
+'z' (122) |1111011 7b [ 7]
+'{' (123) |11111111|1111110 7ffe [15]
+'|' (124) |11111111|100 7fc [11]
+'}' (125) |11111111|111101 3ffd [14]
+'~' (126) |11111111|11101 1ffd [13]
+ (127) |11111111|11111111|11111111|1100 ffffffc [28]
+ (128) |11111111|11111110|0110 fffe6 [20]
+ (129) |11111111|11111111|010010 3fffd2 [22]
+ (130) |11111111|11111110|0111 fffe7 [20]
+ (131) |11111111|11111110|1000 fffe8 [20]
+ (132) |11111111|11111111|010011 3fffd3 [22]
+ (133) |11111111|11111111|010100 3fffd4 [22]
+ (134) |11111111|11111111|010101 3fffd5 [22]
+ (135) |11111111|11111111|1011001 7fffd9 [23]
+ (136) |11111111|11111111|010110 3fffd6 [22]
+ (137) |11111111|11111111|1011010 7fffda [23]
+ (138) |11111111|11111111|1011011 7fffdb [23]
+ (139) |11111111|11111111|1011100 7fffdc [23]
+ (140) |11111111|11111111|1011101 7fffdd [23]
+ (141) |11111111|11111111|1011110 7fffde [23]
+ (142) |11111111|11111111|11101011 ffffeb [24]
+ (143) |11111111|11111111|1011111 7fffdf [23]
+ (144) |11111111|11111111|11101100 ffffec [24]
+ (145) |11111111|11111111|11101101 ffffed [24]
+ (146) |11111111|11111111|010111 3fffd7 [22]
+ (147) |11111111|11111111|1100000 7fffe0 [23]
+ (148) |11111111|11111111|11101110 ffffee [24]
+ (149) |11111111|11111111|1100001 7fffe1 [23]
+ (150) |11111111|11111111|1100010 7fffe2 [23]
+ (151) |11111111|11111111|1100011 7fffe3 [23]
+ (152) |11111111|11111111|1100100 7fffe4 [23]
+ (153) |11111111|11111110|11100 1fffdc [21]
+ (154) |11111111|11111111|011000 3fffd8 [22]
+ (155) |11111111|11111111|1100101 7fffe5 [23]
+ (156) |11111111|11111111|011001 3fffd9 [22]
+ (157) |11111111|11111111|1100110 7fffe6 [23]
+ (158) |11111111|11111111|1100111 7fffe7 [23]
+ (159) |11111111|11111111|11101111 ffffef [24]
+ (160) |11111111|11111111|011010 3fffda [22]
+ (161) |11111111|11111110|11101 1fffdd [21]
+ (162) |11111111|11111110|1001 fffe9 [20]
+ (163) |11111111|11111111|011011 3fffdb [22]
+ (164) |11111111|11111111|011100 3fffdc [22]
+ (165) |11111111|11111111|1101000 7fffe8 [23]
+ (166) |11111111|11111111|1101001 7fffe9 [23]
+ (167) |11111111|11111110|11110 1fffde [21]
+ (168) |11111111|11111111|1101010 7fffea [23]
+ (169) |11111111|11111111|011101 3fffdd [22]
+ (170) |11111111|11111111|011110 3fffde [22]
+ (171) |11111111|11111111|11110000 fffff0 [24]
+ (172) |11111111|11111110|11111 1fffdf [21]
+ (173) |11111111|11111111|011111 3fffdf [22]
+ (174) |11111111|11111111|1101011 7fffeb [23]
+ (175) |11111111|11111111|1101100 7fffec [23]
+ (176) |11111111|11111111|00000 1fffe0 [21]
+ (177) |11111111|11111111|00001 1fffe1 [21]
+ (178) |11111111|11111111|100000 3fffe0 [22]
+ (179) |11111111|11111111|00010 1fffe2 [21]
+ (180) |11111111|11111111|1101101 7fffed [23]
+ (181) |11111111|11111111|100001 3fffe1 [22]
+ (182) |11111111|11111111|1101110 7fffee [23]
+ (183) |11111111|11111111|1101111 7fffef [23]
+ (184) |11111111|11111110|1010 fffea [20]
+ (185) |11111111|11111111|100010 3fffe2 [22]
+ (186) |11111111|11111111|100011 3fffe3 [22]
+ (187) |11111111|11111111|100100 3fffe4 [22]
+ (188) |11111111|11111111|1110000 7ffff0 [23]
+ (189) |11111111|11111111|100101 3fffe5 [22]
+ (190) |11111111|11111111|100110 3fffe6 [22]
+ (191) |11111111|11111111|1110001 7ffff1 [23]
+ (192) |11111111|11111111|11111000|00 3ffffe0 [26]
+ (193) |11111111|11111111|11111000|01 3ffffe1 [26]
+ (194) |11111111|11111110|1011 fffeb [20]
+ (195) |11111111|11111110|001 7fff1 [19]
+ (196) |11111111|11111111|100111 3fffe7 [22]
+ (197) |11111111|11111111|1110010 7ffff2 [23]
+ (198) |11111111|11111111|101000 3fffe8 [22]
+ (199) |11111111|11111111|11110110|0 1ffffec [25]
+ (200) |11111111|11111111|11111000|10 3ffffe2 [26]
+ (201) |11111111|11111111|11111000|11 3ffffe3 [26]
+ (202) |11111111|11111111|11111001|00 3ffffe4 [26]
+ (203) |11111111|11111111|11111011|110 7ffffde [27]
+ (204) |11111111|11111111|11111011|111 7ffffdf [27]
+ (205) |11111111|11111111|11111001|01 3ffffe5 [26]
+ (206) |11111111|11111111|11110001 fffff1 [24]
+ (207) |11111111|11111111|11110110|1 1ffffed [25]
+ (208) |11111111|11111110|010 7fff2 [19]
+ (209) |11111111|11111111|00011 1fffe3 [21]
+ (210) |11111111|11111111|11111001|10 3ffffe6 [26]
+ (211) |11111111|11111111|11111100|000 7ffffe0 [27]
+ (212) |11111111|11111111|11111100|001 7ffffe1 [27]
+ (213) |11111111|11111111|11111001|11 3ffffe7 [26]
+ (214) |11111111|11111111|11111100|010 7ffffe2 [27]
+ (215) |11111111|11111111|11110010 fffff2 [24]
+ (216) |11111111|11111111|00100 1fffe4 [21]
+ (217) |11111111|11111111|00101 1fffe5 [21]
+ (218) |11111111|11111111|11111010|00 3ffffe8 [26]
+ (219) |11111111|11111111|11111010|01 3ffffe9 [26]
+ (220) |11111111|11111111|11111111|1101 ffffffd [28]
+ (221) |11111111|11111111|11111100|011 7ffffe3 [27]
+ (222) |11111111|11111111|11111100|100 7ffffe4 [27]
+ (223) |11111111|11111111|11111100|101 7ffffe5 [27]
+ (224) |11111111|11111110|1100 fffec [20]
+ (225) |11111111|11111111|11110011 fffff3 [24]
+ (226) |11111111|11111110|1101 fffed [20]
+ (227) |11111111|11111111|00110 1fffe6 [21]
+ (228) |11111111|11111111|101001 3fffe9 [22]
+ (229) |11111111|11111111|00111 1fffe7 [21]
+ (230) |11111111|11111111|01000 1fffe8 [21]
+ (231) |11111111|11111111|1110011 7ffff3 [23]
+ (232) |11111111|11111111|101010 3fffea [22]
+ (233) |11111111|11111111|101011 3fffeb [22]
+ (234) |11111111|11111111|11110111|0 1ffffee [25]
+ (235) |11111111|11111111|11110111|1 1ffffef [25]
+ (236) |11111111|11111111|11110100 fffff4 [24]
+ (237) |11111111|11111111|11110101 fffff5 [24]
+ (238) |11111111|11111111|11111010|10 3ffffea [26]
+ (239) |11111111|11111111|1110100 7ffff4 [23]
+ (240) |11111111|11111111|11111010|11 3ffffeb [26]
+ (241) |11111111|11111111|11111100|110 7ffffe6 [27]
+ (242) |11111111|11111111|11111011|00 3ffffec [26]
+ (243) |11111111|11111111|11111011|01 3ffffed [26]
+ (244) |11111111|11111111|11111100|111 7ffffe7 [27]
+ (245) |11111111|11111111|11111101|000 7ffffe8 [27]
+ (246) |11111111|11111111|11111101|001 7ffffe9 [27]
+ (247) |11111111|11111111|11111101|010 7ffffea [27]
+ (248) |11111111|11111111|11111101|011 7ffffeb [27]
+ (249) |11111111|11111111|11111111|1110 ffffffe [28]
+ (250) |11111111|11111111|11111101|100 7ffffec [27]
+ (251) |11111111|11111111|11111101|101 7ffffed [27]
+ (252) |11111111|11111111|11111101|110 7ffffee [27]
+ (253) |11111111|11111111|11111101|111 7ffffef [27]
+ (254) |11111111|11111111|11111110|000 7fffff0 [27]
+ (255) |11111111|11111111|11111011|10 3ffffee [26]
+EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
+"""
+
+class Node:
+
+ def __init__(self, term = None):
+ self.term = term
+ self.left = None
+ self.right = None
+ self.trans = []
+ self.id = None
+ self.accept = False
+
+class Context:
+
+ def __init__(self):
+ self.next_id_ = 0
+ self.root = Node()
+
+ def next_id(self):
+ id = self.next_id_
+ self.next_id_ += 1
+ return id
+
+def _add(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
+ _add(child, sym, bits[1:])
+
+def huffman_tree_add(ctx, sym, bits):
+ _add(ctx.root, sym, bits)
+
+def _set_node_id(ctx, node, prefix):
+ if node.term is not None:
+ return
+ if len(prefix) <= 7 and [1] * len(prefix) == prefix:
+ node.accept = True
+ node.id = ctx.next_id()
+ _set_node_id(ctx, node.left, prefix + [0])
+ _set_node_id(ctx, node.right, prefix + [1])
+
+def huffman_tree_set_node_id(ctx):
+ _set_node_id(ctx, ctx.root, [])
+
+def _traverse(node, sym, start_node, root, left):
+ if left == 0:
+ if sym == 256:
+ sym = None
+ node = None
+ start_node.trans.append((node, sym))
+ return
+
+ if node.term is not None:
+ node = root
+
+ def go(node):
+ if node.term is not None:
+ assert sym is None
+ nsym = node.term
+ else:
+ nsym = sym
+
+ _traverse(node, nsym, start_node, root, left - 1)
+
+ go(node.left)
+ go(node.right)
+
+def _build_transition_table(ctx, node):
+ if node is None:
+ return
+ _traverse(node, None, node, ctx.root, 4)
+ _build_transition_table(ctx, node.left)
+ _build_transition_table(ctx, node.right)
+
+def huffman_tree_build_transition_table(ctx):
+ _build_transition_table(ctx, ctx.root)
+
+NGHTTP2_HUFF_ACCEPTED = 1 << 14
+NGHTTP2_HUFF_SYM = 1 << 15
+
+def _print_transition_table(node):
+ if node.term is not None:
+ return
+ print('/* {} */'.format(node.id))
+ print('{')
+ for nd, sym in node.trans:
+ flags = 0
+ if sym is None:
+ out = 0
+ else:
+ out = sym
+ flags |= NGHTTP2_HUFF_SYM
+ if nd is None:
+ id = 256
+ 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('},')
+ _print_transition_table(node.left)
+ _print_transition_table(node.right)
+
+def huffman_tree_print_transition_table(ctx):
+ _print_transition_table(ctx.root)
+ print('/* 256 */')
+ print('{')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print(' {0x100, 0},')
+ print('},')
+
+if __name__ == '__main__':
+ ctx = Context()
+ symbol_tbl = [(None, 0) for i in range(257)]
+
+ for line in StringIO(HUFFMAN_CODE_TABLE):
+ m = re.match(
+ r'.*\(\s*(\d+)\)\s+([|01]+)\s+(\S+)\s+\[\s*(\d+)\].*', line)
+ if m:
+ sym = int(m.group(1))
+ bits = re.sub(r'\|', '', m.group(2))
+ code = m.group(3)
+ nbits = int(m.group(4))
+ if len(code) > 8:
+ raise Error('Code is more than 4 bytes long')
+ assert(len(bits) == nbits)
+ symbol_tbl[sym] = (nbits, code)
+ huffman_tree_add(ctx, sym, bits)
+
+ huffman_tree_set_node_id(ctx)
+ huffman_tree_build_transition_table(ctx)
+
+ 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):
+ nbits = symbol_tbl[i][0]
+ k = int(symbol_tbl[i][1], 16)
+ k = k << (32 - nbits)
+ print('''\
+ {{ {}, 0x{}u }}{}\
+'''.format(symbol_tbl[i][0], hex(k)[2:], ',' if i < 256 else ''))
+ print('};')
+ print()
+
+ print('''\
+enum {{
+ NGHTTP2_HUFF_ACCEPTED = {},
+ NGHTTP2_HUFF_SYM = {},
+}} nghttp2_huff_decode_flag;
+'''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM))
+
+ print('''\
+typedef struct {
+ uint16_t fstate;
+ uint8_t sym;
+} nghttp2_huff_decode;
+''')
+
+ print('''\
+const nghttp2_huff_decode huff_decode_table[][16] = {''')
+ huffman_tree_print_transition_table(ctx)
+ print('};')
diff --git a/mkstatichdtbl.py b/mkstatichdtbl.py
new file mode 100755
index 0000000..20f0c15
--- /dev/null
+++ b/mkstatichdtbl.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# -*- 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] https://httpwg.org/specs/rfc7541.html
+
+import re, sys
+
+def hd_map_hash(name):
+ h = 2166136261
+
+ # FNV hash variant: http://isthe.com/chongo/tech/comp/fnv/
+ for c in name:
+ h ^= ord(c)
+ h *= 16777619
+ h &= 0xffffffff
+
+ return h
+
+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((int(m.group(1)), m.group(2), val))
+
+print('static nghttp2_hd_entry static_table[] = {')
+idx = 0
+for i, ent in enumerate(entries):
+ if entries[idx][1] != ent[1]:
+ idx = i
+ print('MAKE_STATIC_ENT("{}", "{}", {}, {}u),'\
+ .format(ent[1], ent[2], entries[idx][0] - 1, hd_map_hash(ent[1])))
+print('};')
diff --git a/nghttpx.conf.sample b/nghttpx.conf.sample
new file mode 100644
index 0000000..97d7954
--- /dev/null
+++ b/nghttpx.conf.sample
@@ -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., http2-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
+# http2-proxy=no
+# workers=1
diff --git a/pre-commit b/pre-commit
new file mode 100755
index 0000000..1dfe1f5
--- /dev/null
+++ b/pre-commit
@@ -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 lib src examples tests | $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
index 0000000..9283920
--- /dev/null
+++ b/proxy.pac.sample
@@ -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/releasechk b/releasechk
new file mode 100755
index 0000000..0c05cca
--- /dev/null
+++ b/releasechk
@@ -0,0 +1,6 @@
+#!/bin/sh -e
+
+autoreconf -i
+git submodule update --init
+./configure --with-mruby --with-neverbleed
+make -j8 distcheck DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror"
diff --git a/script/CMakeLists.txt b/script/CMakeLists.txt
new file mode 100644
index 0000000..f898858
--- /dev/null
+++ b/script/CMakeLists.txt
@@ -0,0 +1,5 @@
+# EXTRA_DIST = README.rst
+install(
+ PROGRAMS fetch-ocsp-response
+ DESTINATION "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}"
+)
diff --git a/script/Makefile.am b/script/Makefile.am
new file mode 100644
index 0000000..387f33c
--- /dev/null
+++ b/script/Makefile.am
@@ -0,0 +1,25 @@
+# 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 = README.rst CMakeLists.txt
+dist_pkgdata_SCRIPTS = fetch-ocsp-response
diff --git a/script/README.rst b/script/README.rst
new file mode 100644
index 0000000..a9f7737
--- /dev/null
+++ b/script/README.rst
@@ -0,0 +1,10 @@
+fetch-ocsp-response is a Python script which performs OCSP query and
+get response. It uses openssl command under the hood. nghttpx uses
+it to enable OCSP stapling feature.
+
+fetch-ocsp-response is a translation from original fetch-ocsp-response
+written in Perl and which has been developed as part of h2o project
+(https://github.com/h2o/h2o).
+
+fetch-ocsp-response is usually installed under $(pkgdatadir), which is
+$(prefix)/share/nghttp2.
diff --git a/script/fetch-ocsp-response b/script/fetch-ocsp-response
new file mode 100755
index 0000000..7c4785b
--- /dev/null
+++ b/script/fetch-ocsp-response
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# 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.
+
+# This program was translated from the program originally developed by
+# h2o project (https://github.com/h2o/h2o), written in Perl. It had
+# the following copyright notice:
+
+# Copyright (c) 2015 DeNA Co., Ltd.
+#
+# 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 __future__ import unicode_literals
+import argparse
+import io
+import os
+import os.path
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+
+# make this program work for both Python 3 and Python 2.
+try:
+ from urllib.parse import urlparse
+ stdout_bwrite = sys.stdout.buffer.write
+except ImportError:
+ from urlparse import urlparse
+ stdout_bwrite = sys.stdout.write
+
+
+def die(msg):
+ sys.stderr.write(msg)
+ sys.stderr.write('\n')
+ sys.exit(255)
+
+
+def tempfail(msg):
+ sys.stderr.write(msg)
+ sys.stderr.write('\n')
+ sys.exit(os.EX_TEMPFAIL)
+
+
+def run_openssl(args, allow_tempfail=False):
+ buf = io.BytesIO()
+ try:
+ p = subprocess.Popen(args, stdout=subprocess.PIPE)
+ except Exception as e:
+ die('failed to invoke {}:{}'.format(args, e))
+ try:
+ while True:
+ data = p.stdout.read()
+ if len(data) == 0:
+ break
+ buf.write(data)
+ if p.wait() != 0:
+ raise Exception('nonzero return code {}'.format(p.returncode))
+ return buf.getvalue()
+ except Exception as e:
+ msg = 'OpenSSL exited abnormally: {}:{}'.format(args, e)
+ tempfail(msg) if allow_tempfail else die(msg)
+
+
+def read_file(path):
+ with open(path, 'rb') as f:
+ return f.read()
+
+
+def write_file(path, data):
+ with open(path, 'wb') as f:
+ f.write(data)
+
+
+def detect_openssl_version(cmd):
+ return run_openssl([cmd, 'version']).decode('utf-8').strip()
+
+
+def extract_ocsp_uri(cmd, cert_fn):
+ # obtain ocsp uri
+ ocsp_uri = run_openssl(
+ [cmd, 'x509', '-in', cert_fn, '-noout',
+ '-ocsp_uri']).decode('utf-8').strip()
+
+ if not re.match(r'^https?://', ocsp_uri):
+ die('failed to extract ocsp URI from {}'.format(cert_fn))
+
+ return ocsp_uri
+
+
+def save_issuer_certificate(issuer_fn, cert_fn):
+ # save issuer certificate
+ chain = read_file(cert_fn).decode('utf-8')
+ m = re.match(
+ r'.*?-----END CERTIFICATE-----.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)',
+ chain, re.DOTALL)
+ if not m:
+ die('--issuer option was not used, and failed to extract issuer certificate from the certificate')
+ write_file(issuer_fn, (m.group(1) + '\n').encode('utf-8'))
+
+
+def send_and_receive_ocsp(respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri,
+ ocsp_host, openssl_version):
+ # obtain response (without verification)
+ sys.stderr.write('sending OCSP request to {}\n'.format(ocsp_uri))
+ args = [
+ cmd, 'ocsp', '-issuer', issuer_fn, '-cert', cert_fn, '-url', ocsp_uri,
+ '-noverify', '-respout', respder_fn
+ ]
+ ver = openssl_version.lower()
+ if ver.startswith('openssl 1.0.') or ver.startswith('libressl '):
+ args.extend(['-header', 'Host', ocsp_host])
+ resp = run_openssl(args, allow_tempfail=True)
+ return resp.decode('utf-8')
+
+
+def verify_response(cmd, tempdir, issuer_fn, respder_fn):
+ # verify the response
+ sys.stderr.write('verifying the response signature\n')
+
+ verify_fn = os.path.join(tempdir, 'verify.out')
+
+ # try from exotic options
+ allextra = [
+ # for comodo
+ ['-VAfile', issuer_fn],
+ # these options are only available in OpenSSL >= 1.0.2
+ ['-partial_chain', '-trusted_first', '-CAfile', issuer_fn],
+ # for OpenSSL <= 1.0.1
+ ['-CAfile', issuer_fn],
+ ]
+
+ for extra in allextra:
+ with open(verify_fn, 'w+b') as f:
+ args = [cmd, 'ocsp', '-respin', respder_fn]
+ args.extend(extra)
+ p = subprocess.Popen(args, stdout=f, stderr=f)
+ if p.wait() == 0:
+ # OpenSSL <= 1.0.1, openssl ocsp still returns exit
+ # code 0 even if verification was failed. So check
+ # the error message in stderr output.
+ f.seek(0)
+ if f.read().decode('utf-8').find(
+ 'Response Verify Failure') != -1:
+ continue
+ sys.stderr.write('verify OK (used: {})\n'.format(extra))
+ return True
+
+ sys.stderr.write(read_file(verify_fn).decode('utf-8'))
+ return False
+
+
+def fetch_ocsp_response(cmd, cert_fn, tempdir, issuer_fn=None):
+ openssl_version = detect_openssl_version(cmd)
+
+ sys.stderr.write(
+ 'fetch-ocsp-response (using {})\n'.format(openssl_version))
+
+ ocsp_uri = extract_ocsp_uri(cmd, cert_fn)
+ ocsp_host = urlparse(ocsp_uri).netloc
+
+ if not issuer_fn:
+ issuer_fn = os.path.join(tempdir, 'issuer.crt')
+ save_issuer_certificate(issuer_fn, cert_fn)
+
+ respder_fn = os.path.join(tempdir, 'resp.der')
+ resp = send_and_receive_ocsp(
+ respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, ocsp_host,
+ openssl_version)
+
+ sys.stderr.write('{}\n'.format(resp))
+
+ # OpenSSL 1.0.2 still returns exit code 0 even if ocsp responder
+ # returned error status (e.g., trylater(3))
+ if resp.find('Responder Error:') != -1:
+ raise Exception('responder returned error')
+
+ if not verify_response(cmd, tempdir, issuer_fn, respder_fn):
+ tempfail('failed to verify the response')
+
+ # success
+ res = read_file(respder_fn)
+ stdout_bwrite(res)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ description=
+ '''The command issues an OCSP request for given server certificate, verifies the response and prints the resulting DER.''',
+ epilog=
+ '''The command exits 0 if successful, or 75 (EX_TEMPFAIL) on temporary error. Other exit codes may be returned in case of hard errors.''')
+ parser.add_argument(
+ '--issuer',
+ metavar='FILE',
+ help=
+ 'issuer certificate (if omitted, is extracted from the certificate chain)')
+ parser.add_argument('--openssl',
+ metavar='CMD',
+ help='openssl command to use (default: "openssl")',
+ default='openssl')
+ parser.add_argument('certificate',
+ help='path to certificate file to validate')
+ args = parser.parse_args()
+
+ tempdir = None
+ try:
+ # Python3.2 has tempfile.TemporaryDirectory, which has nice
+ # feature to delete its tree by cleanup() function. We have
+ # to support Python2.7, so we have to do this manually.
+ tempdir = tempfile.mkdtemp()
+ fetch_ocsp_response(args.openssl, args.certificate, tempdir,
+ args.issuer)
+ finally:
+ if tempdir:
+ shutil.rmtree(tempdir)
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..27631a7
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,13 @@
+# programs
+deflatehd
+h2load
+inflatehd
+nghttp
+nghttpd
+nghttpx
+
+# build
+libnghttpx.a
+
+# tests
+nghttpx-unittest
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..201c5a2
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,243 @@
+file(GLOB c_sources *.c)
+set_source_files_properties(${c_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCFLAGS}")
+file(GLOB cxx_sources *.cc)
+set_source_files_properties(${cxx_sources} PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}")
+
+include_directories(
+ "${CMAKE_CURRENT_SOURCE_DIR}/includes"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include"
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${LIBXML2_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ ${LIBNGTCP2_INCLUDE_DIRS}
+ ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ ${LIBCARES_INCLUDE_DIRS}
+ ${JANSSON_INCLUDE_DIRS}
+ ${ZLIB_INCLUDE_DIRS}
+ ${LIBBPF_INCLUDE_DIRS}
+)
+
+# XXX per-target?
+link_libraries(
+ nghttp2
+ ${JEMALLOC_LIBRARIES}
+ ${LIBXML2_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ ${LIBNGTCP2_LIBRARIES}
+ ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${LIBCARES_LIBRARIES}
+ ${JANSSON_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ ${APP_LIBRARIES}
+ ${LIBBPF_LIBRARIES}
+)
+
+if(ENABLE_APP)
+ set(HELPER_OBJECTS
+ util.cc
+ http2.cc timegm.c app_helper.cc nghttp2_gzip.c
+ )
+
+ # nghttp client
+ set(NGHTTP_SOURCES
+ ${HELPER_OBJECTS}
+ nghttp.cc
+ tls.cc
+ )
+ if(HAVE_LIBXML2)
+ list(APPEND NGHTTP_SOURCES HtmlParser.cc)
+ endif()
+
+ # nghttpd
+ set(NGHTTPD_SOURCES
+ ${HELPER_OBJECTS}
+ nghttpd.cc
+ tls.cc
+ HttpServer.cc
+ )
+
+ # h2load
+ set(H2LOAD_SOURCES
+ util.cc
+ http2.cc h2load.cc
+ timegm.c
+ tls.cc
+ h2load_http2_session.cc
+ h2load_http1_session.cc
+ )
+ if(ENABLE_HTTP3)
+ list(APPEND H2LOAD_SOURCES
+ h2load_http3_session.cc
+ h2load_quic.cc
+ quic.cc
+ )
+ endif()
+
+ # Common libnhttpx sources (used for nghttpx and unit tests)
+ set(NGHTTPX_SRCS
+ util.cc http2.cc timegm.c
+ app_helper.cc
+ tls.cc
+ shrpx_config.cc
+ shrpx_accept_handler.cc
+ shrpx_connection_handler.cc
+ shrpx_client_handler.cc
+ shrpx_http2_upstream.cc
+ shrpx_https_upstream.cc
+ shrpx_downstream.cc
+ shrpx_downstream_connection.cc
+ shrpx_http_downstream_connection.cc
+ shrpx_http2_downstream_connection.cc
+ shrpx_http2_session.cc
+ shrpx_downstream_queue.cc
+ shrpx_log.cc
+ shrpx_http.cc
+ shrpx_io_control.cc
+ shrpx_tls.cc
+ shrpx_worker.cc
+ shrpx_log_config.cc
+ shrpx_connect_blocker.cc
+ shrpx_live_check.cc
+ shrpx_downstream_connection_pool.cc
+ shrpx_rate_limit.cc
+ shrpx_connection.cc
+ shrpx_memcached_dispatcher.cc
+ shrpx_memcached_connection.cc
+ shrpx_worker_process.cc
+ shrpx_signal.cc
+ shrpx_router.cc
+ shrpx_api_downstream_connection.cc
+ shrpx_health_monitor_downstream_connection.cc
+ shrpx_null_downstream_connection.cc
+ shrpx_exec.cc
+ shrpx_dns_resolver.cc
+ shrpx_dual_dns_resolver.cc
+ shrpx_dns_tracker.cc
+ xsi_strerror.c
+ )
+ if(HAVE_MRUBY)
+ list(APPEND NGHTTPX_SRCS
+ shrpx_mruby.cc
+ shrpx_mruby_module.cc
+ shrpx_mruby_module_env.cc
+ shrpx_mruby_module_request.cc
+ shrpx_mruby_module_response.cc
+ )
+ endif()
+ if(ENABLE_HTTP3)
+ list(APPEND NGHTTPX_SRCS
+ shrpx_quic.cc
+ shrpx_quic_listener.cc
+ shrpx_quic_connection_handler.cc
+ shrpx_http3_upstream.cc
+ http3.cc
+ quic.cc
+ )
+ endif()
+ add_library(nghttpx_static STATIC ${NGHTTPX_SRCS})
+ set_target_properties(nghttpx_static PROPERTIES ARCHIVE_OUTPUT_NAME nghttpx)
+
+ set(NGHTTPX-bin_SOURCES
+ shrpx.cc
+ )
+
+ if(HAVE_SYSTEMD)
+ target_link_libraries(nghttpx_static ${SYSTEMD_LIBRARIES})
+ target_compile_definitions(nghttpx_static PUBLIC HAVE_LIBSYSTEMD)
+ target_include_directories(nghttpx_static PUBLIC ${SYSTEMD_INCLUDE_DIRS})
+ endif()
+
+ if(HAVE_MRUBY)
+ target_link_libraries(nghttpx_static mruby-lib)
+ endif()
+
+ if(HAVE_NEVERBLEED)
+ target_link_libraries(nghttpx_static neverbleed)
+ endif()
+
+
+ if(HAVE_CUNIT)
+ set(NGHTTPX_UNITTEST_SOURCES
+ shrpx-unittest.cc
+ shrpx_tls_test.cc
+ shrpx_downstream_test.cc
+ shrpx_config_test.cc
+ shrpx_worker_test.cc
+ shrpx_http_test.cc
+ shrpx_router_test.cc
+ http2_test.cc
+ util_test.cc
+ nghttp2_gzip_test.c
+ nghttp2_gzip.c
+ buffer_test.cc
+ memchunk_test.cc
+ template_test.cc
+ base64_test.cc
+ )
+ add_executable(nghttpx-unittest EXCLUDE_FROM_ALL
+ ${NGHTTPX_UNITTEST_SOURCES}
+ $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ target_include_directories(nghttpx-unittest PRIVATE ${CUNIT_INCLUDE_DIRS})
+ target_compile_definitions(nghttpx-unittest
+ PRIVATE "-DNGHTTP2_SRC_DIR=\"${CMAKE_SOURCE_DIR}/src\""
+ )
+ target_link_libraries(nghttpx-unittest nghttpx_static ${CUNIT_LIBRARIES})
+ if(HAVE_MRUBY)
+ target_link_libraries(nghttpx-unittest mruby-lib)
+ endif()
+ if(HAVE_NEVERBLEED)
+ target_link_libraries(nghttpx-unittest neverbleed)
+ endif()
+
+ add_test(nghttpx-unittest nghttpx-unittest)
+ add_dependencies(check nghttpx-unittest)
+ endif()
+
+ add_executable(nghttp ${NGHTTP_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(nghttpd ${NGHTTPD_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ add_executable(nghttpx ${NGHTTPX-bin_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+ target_compile_definitions(nghttpx PRIVATE
+ "-DPKGDATADIR=\"${PKGDATADIR}\""
+ "-DPKGLIBDIR=\"${PKGLIBDIR}\""
+ )
+ target_link_libraries(nghttpx nghttpx_static)
+ add_executable(h2load ${H2LOAD_SOURCES} $<TARGET_OBJECTS:llhttp>
+ $<TARGET_OBJECTS:url-parser>
+ )
+
+ install(TARGETS nghttp nghttpd nghttpx h2load
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_HPACK_TOOLS)
+ set(inflatehd_SOURCES
+ inflatehd.cc
+ comp_helper.c
+ )
+ set(deflatehd_SOURCES
+ deflatehd.cc
+ comp_helper.c
+ util.cc
+ timegm.c
+ )
+ add_executable(inflatehd ${inflatehd_SOURCES})
+ add_executable(deflatehd ${deflatehd_SOURCES})
+ install(TARGETS inflatehd deflatehd
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
diff --git a/src/HtmlParser.cc b/src/HtmlParser.cc
new file mode 100644
index 0000000..591c4c7
--- /dev/null
+++ b/src/HtmlParser.cc
@@ -0,0 +1,217 @@
+/*
+ * 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), inside_head(0) {}
+
+HtmlParser::HtmlParser(const std::string &base_uri)
+ : base_uri_(base_uri), parser_ctx_(nullptr), parser_data_(base_uri) {}
+
+HtmlParser::~HtmlParser() { htmlFreeParserCtxt(parser_ctx_); }
+
+namespace {
+StringRef get_attr(const xmlChar **attrs, const StringRef &name) {
+ if (attrs == nullptr) {
+ return StringRef{};
+ }
+ for (; *attrs; attrs += 2) {
+ if (util::strieq(StringRef{attrs[0], strlen(reinterpret_cast<const char *>(
+ attrs[0]))},
+ name)) {
+ return StringRef{attrs[1],
+ strlen(reinterpret_cast<const char *>(attrs[1]))};
+ }
+ }
+ return StringRef{};
+}
+} // namespace
+
+namespace {
+ResourceType
+get_resource_type_for_preload_as(const StringRef &attribute_value) {
+ if (util::strieq_l("image", attribute_value)) {
+ return REQ_IMG;
+ } else if (util::strieq_l("style", attribute_value)) {
+ return REQ_CSS;
+ } else if (util::strieq_l("script", attribute_value)) {
+ return REQ_UNBLOCK_JS;
+ } else {
+ return REQ_OTHERS;
+ }
+}
+} // namespace
+
+namespace {
+void add_link(ParserData *parser_data, const StringRef &uri,
+ ResourceType res_type) {
+ auto u = xmlBuildURI(
+ reinterpret_cast<const xmlChar *>(uri.c_str()),
+ reinterpret_cast<const xmlChar *>(parser_data->base_uri.c_str()));
+ if (u) {
+ parser_data->links.push_back(
+ std::make_pair(reinterpret_cast<char *>(u), res_type));
+ free(u);
+ }
+}
+} // namespace
+
+namespace {
+void start_element_func(void *user_data, const xmlChar *src_name,
+ const xmlChar **attrs) {
+ auto parser_data = static_cast<ParserData *>(user_data);
+ auto name =
+ StringRef{src_name, strlen(reinterpret_cast<const char *>(src_name))};
+ if (util::strieq_l("head", name)) {
+ ++parser_data->inside_head;
+ }
+ if (util::strieq_l("link", name)) {
+ auto rel_attr = get_attr(attrs, StringRef::from_lit("rel"));
+ auto href_attr = get_attr(attrs, StringRef::from_lit("href"));
+ if (rel_attr.empty() || href_attr.empty()) {
+ return;
+ }
+ if (util::strieq_l("shortcut icon", rel_attr)) {
+ add_link(parser_data, href_attr, REQ_OTHERS);
+ } else if (util::strieq_l("stylesheet", rel_attr)) {
+ add_link(parser_data, href_attr, REQ_CSS);
+ } else if (util::strieq_l("preload", rel_attr)) {
+ auto as_attr = get_attr(attrs, StringRef::from_lit("as"));
+ if (as_attr.empty()) {
+ return;
+ }
+ add_link(parser_data, href_attr,
+ get_resource_type_for_preload_as(as_attr));
+ }
+ } else if (util::strieq_l("img", name)) {
+ auto src_attr = get_attr(attrs, StringRef::from_lit("src"));
+ if (src_attr.empty()) {
+ return;
+ }
+ add_link(parser_data, src_attr, REQ_IMG);
+ } else if (util::strieq_l("script", name)) {
+ auto src_attr = get_attr(attrs, StringRef::from_lit("src"));
+ if (src_attr.empty()) {
+ return;
+ }
+ if (parser_data->inside_head) {
+ add_link(parser_data, src_attr, REQ_JS);
+ } else {
+ add_link(parser_data, src_attr, REQ_UNBLOCK_JS);
+ }
+ }
+}
+} // namespace
+
+namespace {
+void end_element_func(void *user_data, const xmlChar *name) {
+ auto parser_data = static_cast<ParserData *>(user_data);
+ if (util::strieq_l(
+ "head",
+ StringRef{name, strlen(reinterpret_cast<const char *>(name))})) {
+ --parser_data->inside_head;
+ }
+}
+} // 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
+ &end_element_func, // 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, ResourceType>> &
+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
index 0000000..1e84688
--- /dev/null
+++ b/src/HtmlParser.h
@@ -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 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 ResourceType {
+ REQ_CSS = 1,
+ REQ_JS,
+ REQ_UNBLOCK_JS,
+ REQ_IMG,
+ REQ_OTHERS,
+};
+
+struct ParserData {
+ std::string base_uri;
+ std::vector<std::pair<std::string, ResourceType>> links;
+ // > 0 if we are inside "head" element.
+ int inside_head;
+ 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, ResourceType>> &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, ResourceType>> &get_links() const {
+ return links_;
+ }
+ void clear_links() {}
+
+private:
+ std::vector<std::pair<std::string, ResourceType>> links_;
+};
+
+#endif // !HAVE_LIBXML2
+
+} // namespace nghttp2
+
+#endif // HTML_PARSER_H
diff --git a/src/HttpServer.cc b/src/HttpServer.cc
new file mode 100644
index 0000000..0385cd0
--- /dev/null
+++ b/src/HttpServer.cc
@@ -0,0 +1,2248 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+#include <cassert>
+#include <set>
+#include <iostream>
+#include <thread>
+#include <mutex>
+#include <deque>
+
+#include "ssl_compat.h"
+
+#include <openssl/err.h>
+#include <openssl/dh.h>
+#if OPENSSL_3_0_0_API
+# include <openssl/decoder.h>
+#endif // OPENSSL_3_0_0_API
+
+#include <zlib.h>
+
+#include "app_helper.h"
+#include "http2.h"
+#include "util.h"
+#include "tls.h"
+#include "template.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+using namespace std::chrono_literals;
+
+namespace nghttp2 {
+
+namespace {
+// TODO could be constexpr
+constexpr auto DEFAULT_HTML = StringRef::from_lit("index.html");
+constexpr auto NGHTTPD_SERVER =
+ StringRef::from_lit("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
+
+Config::Config()
+ : mime_types_file("/etc/mime.types"),
+ stream_read_timeout(1_min),
+ stream_write_timeout(1_min),
+ data_ptr(nullptr),
+ padding(0),
+ num_worker(1),
+ max_concurrent_streams(100),
+ header_table_size(-1),
+ encoder_header_table_size(-1),
+ window_bits(-1),
+ connection_window_bits(-1),
+ port(0),
+ verbose(false),
+ daemon(false),
+ verify_client(false),
+ no_tls(false),
+ error_gzip(false),
+ early_response(false),
+ hexdump(false),
+ echo_upload(false),
+ no_content_length(false),
+ ktls(false),
+ no_rfc7540_pri(false) {}
+
+Config::~Config() {}
+
+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
+
+namespace {
+constexpr ev_tstamp RELEASE_FD_TIMEOUT = 2.;
+} // namespace
+
+namespace {
+void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents);
+} // namespace
+
+namespace {
+constexpr auto FILE_ENTRY_MAX_AGE = 10s;
+} // namespace
+
+namespace {
+constexpr size_t FILE_ENTRY_EVICT_THRES = 2048;
+} // namespace
+
+namespace {
+bool need_validation_file_entry(
+ const FileEntry *ent, const std::chrono::steady_clock::time_point &now) {
+ return ent->last_valid + FILE_ENTRY_MAX_AGE < now;
+}
+} // namespace
+
+namespace {
+bool validate_file_entry(FileEntry *ent,
+ const std::chrono::steady_clock::time_point &now) {
+ struct stat stbuf;
+ int rv;
+
+ rv = fstat(ent->fd, &stbuf);
+ if (rv != 0) {
+ ent->stale = true;
+ return false;
+ }
+
+ if (stbuf.st_nlink == 0 || ent->mtime != stbuf.st_mtime) {
+ ent->stale = true;
+ return false;
+ }
+
+ ent->mtime = stbuf.st_mtime;
+ ent->last_valid = now;
+
+ return true;
+}
+} // namespace
+
+class Sessions {
+public:
+ Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
+ SSL_CTX *ssl_ctx)
+ : sv_(sv),
+ loop_(loop),
+ config_(config),
+ ssl_ctx_(ssl_ctx),
+ callbacks_(nullptr),
+ option_(nullptr),
+ next_session_id_(1),
+ tstamp_cached_(ev_now(loop)),
+ cached_date_(util::http_date(tstamp_cached_)) {
+ nghttp2_session_callbacks_new(&callbacks_);
+
+ fill_callback(callbacks_, config_);
+
+ nghttp2_option_new(&option_);
+
+ if (config_->encoder_header_table_size != -1) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ option_, config_->encoder_header_table_size);
+ }
+
+ ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT);
+ release_fd_timer_.data = this;
+ }
+ ~Sessions() {
+ ev_timer_stop(loop_, &release_fd_timer_);
+ for (auto handler : handlers_) {
+ delete handler;
+ }
+ nghttp2_option_del(option_);
+ nghttp2_session_callbacks_del(callbacks_);
+ }
+ void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
+ void remove_handler(Http2Handler *handler) {
+ handlers_.erase(handler);
+ if (handlers_.empty() && !fd_cache_.empty()) {
+ ev_timer_again(loop_, &release_fd_timer_);
+ }
+ }
+ 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_; }
+ const nghttp2_option *get_option() const { return option_; }
+ 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 =
+ std::make_unique<Http2Handler>(this, fd, ssl, get_next_session_id());
+ if (!ssl) {
+ if (handler->connection_made() != 0) {
+ return;
+ }
+ }
+ add_handler(handler.release());
+ }
+ void update_cached_date() { cached_date_ = util::http_date(tstamp_cached_); }
+ const std::string &get_cached_date() {
+ auto t = ev_now(loop_);
+ if (t != tstamp_cached_) {
+ tstamp_cached_ = t;
+ update_cached_date();
+ }
+ return cached_date_;
+ }
+ FileEntry *get_cached_fd(const std::string &path) {
+ auto range = fd_cache_.equal_range(path);
+ if (range.first == range.second) {
+ return nullptr;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ for (auto it = range.first; it != range.second;) {
+ auto &ent = (*it).second;
+ if (ent->stale) {
+ ++it;
+ continue;
+ }
+ if (need_validation_file_entry(ent.get(), now) &&
+ !validate_file_entry(ent.get(), now)) {
+ if (ent->usecount == 0) {
+ fd_cache_lru_.remove(ent.get());
+ close(ent->fd);
+ it = fd_cache_.erase(it);
+ continue;
+ }
+ ++it;
+ continue;
+ }
+
+ fd_cache_lru_.remove(ent.get());
+ fd_cache_lru_.append(ent.get());
+
+ ++ent->usecount;
+ return ent.get();
+ }
+ return nullptr;
+ }
+ FileEntry *cache_fd(const std::string &path, const FileEntry &ent) {
+#ifdef HAVE_STD_MAP_EMPLACE
+ auto rv = fd_cache_.emplace(path, std::make_unique<FileEntry>(ent));
+#else // !HAVE_STD_MAP_EMPLACE
+ // for gcc-4.7
+ auto rv = fd_cache_.insert(
+ std::make_pair(path, std::make_unique<FileEntry>(ent)));
+#endif // !HAVE_STD_MAP_EMPLACE
+ auto &res = (*rv).second;
+ res->it = rv;
+ fd_cache_lru_.append(res.get());
+
+ while (fd_cache_.size() > FILE_ENTRY_EVICT_THRES) {
+ auto ent = fd_cache_lru_.head;
+ if (ent->usecount) {
+ break;
+ }
+ fd_cache_lru_.remove(ent);
+ close(ent->fd);
+ fd_cache_.erase(ent->it);
+ }
+
+ return res.get();
+ }
+ void release_fd(FileEntry *target) {
+ --target->usecount;
+
+ if (target->usecount == 0 && target->stale) {
+ fd_cache_lru_.remove(target);
+ close(target->fd);
+ fd_cache_.erase(target->it);
+ return;
+ }
+
+ // We use timer to close file descriptor and delete the entry from
+ // cache. The timer will be started when there is no handler.
+ }
+ void release_unused_fd() {
+ for (auto i = std::begin(fd_cache_); i != std::end(fd_cache_);) {
+ auto &ent = (*i).second;
+ if (ent->usecount != 0) {
+ ++i;
+ continue;
+ }
+
+ fd_cache_lru_.remove(ent.get());
+ close(ent->fd);
+ i = fd_cache_.erase(i);
+ }
+ }
+ const HttpServer *get_server() const { return sv_; }
+ bool handlers_empty() const { return handlers_.empty(); }
+
+private:
+ std::set<Http2Handler *> handlers_;
+ // cache for file descriptors to read file.
+ std::multimap<std::string, std::unique_ptr<FileEntry>> fd_cache_;
+ DList<FileEntry> fd_cache_lru_;
+ HttpServer *sv_;
+ struct ev_loop *loop_;
+ const Config *config_;
+ SSL_CTX *ssl_ctx_;
+ nghttp2_session_callbacks *callbacks_;
+ nghttp2_option *option_;
+ ev_timer release_fd_timer_;
+ int64_t next_session_id_;
+ ev_tstamp tstamp_cached_;
+ std::string cached_date_;
+};
+
+namespace {
+void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto sessions = static_cast<Sessions *>(w->data);
+
+ ev_timer_stop(loop, w);
+
+ if (!sessions->handlers_empty()) {
+ return;
+ }
+
+ sessions->release_unused_fd();
+}
+} // namespace
+
+Stream::Stream(Http2Handler *handler, int32_t stream_id)
+ : balloc(1024, 1024),
+ header{},
+ handler(handler),
+ file_ent(nullptr),
+ body_length(0),
+ body_offset(0),
+ header_buffer_size(0),
+ stream_id(stream_id),
+ echo_upload(false) {
+ 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;
+}
+
+Stream::~Stream() {
+ if (file_ent != nullptr) {
+ auto sessions = handler->get_sessions();
+ sessions->release_fd(file_ent);
+ }
+
+ auto &rcbuf = header.rcbuf;
+ nghttp2_rcbuf_decref(rcbuf.method);
+ nghttp2_rcbuf_decref(rcbuf.scheme);
+ nghttp2_rcbuf_decref(rcbuf.authority);
+ nghttp2_rcbuf_decref(rcbuf.host);
+ nghttp2_rcbuf_decref(rcbuf.path);
+ nghttp2_rcbuf_decref(rcbuf.ims);
+ nghttp2_rcbuf_decref(rcbuf.expect);
+
+ 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) {
+ int rv;
+ auto hd = static_cast<Http2Handler *>(w->data);
+ hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT);
+ rv = hd->on_write();
+ if (rv == -1) {
+ delete_handler(hd);
+ }
+}
+} // 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_get_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(); }
+
+Http2Handler::WriteBuf *Http2Handler::get_wb() { return &wb_; }
+
+void Http2Handler::start_settings_timer() {
+ ev_timer_start(sessions_->get_loop(), &settings_timerev_);
+}
+
+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, 8_k> buf;
+
+ ssize_t nread;
+ while ((nread = read(fd_, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return write_(*this);
+ }
+ return -1;
+ }
+ if (nread == 0) {
+ return -1;
+ }
+
+ if (get_config()->hexdump) {
+ util::hexdump(stdout, buf.data(), nread);
+ }
+
+ rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ 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) {
+ 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_alpn_result() != 0) {
+ return -1;
+ }
+
+ read_ = &Http2Handler::read_tls;
+ write_ = &Http2Handler::write_tls;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ if (sessions_->get_config()->verbose) {
+ if (SSL_session_reused(ssl_)) {
+ std::cerr << "SSL/TLS session reused" << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+int Http2Handler::read_tls() {
+ std::array<uint8_t, 8_k> buf;
+
+ ERR_clear_error();
+
+ auto rv = SSL_read(ssl_, buf.data(), buf.size());
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(ssl_, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return write_(*this);
+ case SSL_ERROR_WANT_WRITE:
+ // renegotiation started
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
+ auto nread = rv;
+
+ if (get_config()->hexdump) {
+ util::hexdump(stdout, buf.data(), nread);
+ }
+
+ rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ std::cerr << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ }
+ return -1;
+ }
+
+ 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) {
+ 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::connection_made() {
+ int r;
+
+ r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this,
+ sessions_->get_option());
+
+ if (r != 0) {
+ return r;
+ }
+
+ auto config = sessions_->get_config();
+ std::array<nghttp2_settings_entry, 4> entry;
+ size_t niv = 1;
+
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = config->max_concurrent_streams;
+
+ if (config->header_table_size >= 0) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[niv].value = config->header_table_size;
+ ++niv;
+ }
+
+ if (config->window_bits != -1) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ entry[niv].value = (1 << config->window_bits) - 1;
+ ++niv;
+ }
+
+ if (config->no_rfc7540_pri) {
+ entry[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[niv].value = 1;
+ ++niv;
+ }
+
+ r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
+ if (r != 0) {
+ return r;
+ }
+
+ if (config->connection_window_bits != -1) {
+ r = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0,
+ (1 << config->connection_window_bits) - 1);
+ if (r != 0) {
+ return r;
+ }
+ }
+
+ if (ssl_ && !nghttp2::tls::check_http2_requirement(ssl_)) {
+ terminate_session(NGHTTP2_INADEQUATE_SECURITY);
+ }
+
+ return on_write();
+}
+
+int Http2Handler::verify_alpn_result() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+ // Check the negotiated protocol in ALPN
+ SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (sessions_->get_config()->verbose) {
+ std::cout << "The negotiated protocol: " << proto << std::endl;
+ }
+ if (util::check_h2_is_selected(proto)) {
+ return 0;
+ }
+ }
+ 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 StringRef &status, Stream *stream,
+ time_t last_modified, off_t file_length,
+ const std::string *content_type,
+ nghttp2_data_provider *data_prd) {
+ std::string last_modified_str;
+ auto nva = make_array(http2::make_nv_ls_nocopy(":status", status),
+ http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER),
+ http2::make_nv_ll("cache-control", "max-age=3600"),
+ http2::make_nv_ls("date", sessions_->get_cached_date()),
+ http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
+ http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
+ size_t nvlen = 4;
+ if (!get_config()->no_content_length) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy(
+ "content-length",
+ util::make_string_ref_uint(stream->balloc, file_length));
+ }
+ if (last_modified != 0) {
+ last_modified_str = util::http_date(last_modified);
+ nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
+ }
+ if (content_type) {
+ nva[nvlen++] = http2::make_nv_ls("content-type", *content_type);
+ }
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names);
+ }
+ return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
+ data_prd);
+}
+
+int Http2Handler::submit_response(const StringRef &status, int32_t stream_id,
+ const HeaderRefs &headers,
+ nghttp2_data_provider *data_prd) {
+ auto nva = std::vector<nghttp2_nv>();
+ nva.reserve(4 + headers.size());
+ nva.push_back(http2::make_nv_ls_nocopy(":status", status));
+ nva.push_back(http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER));
+ nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date()));
+
+ if (data_prd) {
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva.push_back(http2::make_nv_ls_nocopy("trailer", trailer_names));
+ }
+ }
+
+ for (auto &nv : headers) {
+ nva.push_back(http2::make_nv_nocopy(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 StringRef &status, int32_t stream_id,
+ nghttp2_data_provider *data_prd) {
+ auto nva = make_array(http2::make_nv_ls_nocopy(":status", status),
+ http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER),
+ http2::make_nv_ls("date", sessions_->get_cached_date()),
+ http2::make_nv_ll("", ""));
+ size_t nvlen = 3;
+
+ if (data_prd) {
+ auto &trailer_names = get_config()->trailer_names;
+ if (!trailer_names.empty()) {
+ nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names);
+ }
+ }
+
+ return nghttp2_submit_response(session_, stream_id, nva.data(), nvlen,
+ 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 StringRef &push_path) {
+ auto authority = stream->header.authority;
+
+ if (authority.empty()) {
+ authority = stream->header.host;
+ }
+
+ auto scheme = get_config()->no_tls ? StringRef::from_lit("http")
+ : StringRef::from_lit("https");
+
+ auto nva = make_array(http2::make_nv_ll(":method", "GET"),
+ http2::make_nv_ls_nocopy(":path", push_path),
+ http2::make_nv_ls_nocopy(":scheme", scheme),
+ http2::make_nv_ls_nocopy(":authority", authority));
+
+ 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 = std::make_unique<Stream>(this, promised_stream_id);
+
+ auto &promised_header = promised_stream->header;
+ promised_header.method = StringRef::from_lit("GET");
+ promised_header.path = push_path;
+ promised_header.scheme = scheme;
+ promised_header.authority =
+ make_string_ref(promised_stream->balloc, authority);
+
+ 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) {
+ int rv;
+ auto hd = static_cast<Http2Handler *>(user_data);
+ auto stream = hd->get_stream(stream_id);
+
+ auto nread = std::min(stream->body_length - stream->body_offset,
+ static_cast<int64_t>(length));
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (nread == 0 || stream->body_length == stream->body_offset + nread) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ auto config = hd->get_config();
+ if (!config->trailer.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(config->trailer.size());
+ for (auto &kv : config->trailer) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+
+ 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, int status) {
+ auto sessions = hd->get_sessions();
+ auto status_page = sessions->get_server()->get_status_page(status);
+ auto file_ent = &status_page->file_ent;
+
+ // we don't set stream->file_ent since we don't want to expire it.
+ stream->body_length = file_ent->length;
+ nghttp2_data_provider data_prd;
+ data_prd.source.fd = file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ HeaderRefs headers;
+ headers.reserve(2);
+ headers.emplace_back(StringRef::from_lit("content-type"),
+ StringRef::from_lit("text/html; charset=UTF-8"));
+ headers.emplace_back(
+ StringRef::from_lit("content-length"),
+ util::make_string_ref_uint(stream->balloc, file_ent->length));
+ hd->submit_response(StringRef{status_page->status}, stream->stream_id,
+ headers, &data_prd);
+}
+} // namespace
+
+namespace {
+void prepare_echo_response(Stream *stream, Http2Handler *hd) {
+ auto length = lseek(stream->file_ent->fd, 0, SEEK_END);
+ if (length == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return;
+ }
+ stream->body_length = length;
+ if (lseek(stream->file_ent->fd, 0, SEEK_SET) == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return;
+ }
+ nghttp2_data_provider data_prd;
+ data_prd.source.fd = stream->file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ HeaderRefs headers;
+ headers.emplace_back(StringRef::from_lit("nghttpd-response"),
+ StringRef::from_lit("echo"));
+ if (!hd->get_config()->no_content_length) {
+ headers.emplace_back(StringRef::from_lit("content-length"),
+ util::make_string_ref_uint(stream->balloc, length));
+ }
+
+ hd->submit_response(StringRef::from_lit("200"), stream->stream_id, headers,
+ &data_prd);
+}
+} // namespace
+
+namespace {
+bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) {
+ auto sessions = hd->get_sessions();
+
+ char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
+ auto fd = mkstemp(tempfn);
+ if (fd == -1) {
+ return false;
+ }
+ unlink(tempfn);
+ // Ordinary request never start with "echo:". The length is 0 for
+ // now. We will update it when we get whole request body.
+ auto path = std::string("echo:") + tempfn;
+ stream->file_ent =
+ sessions->cache_fd(path, FileEntry(path, 0, 0, fd, nullptr, {}, true));
+ stream->echo_upload = true;
+ return true;
+}
+} // namespace
+
+namespace {
+void prepare_redirect_response(Stream *stream, Http2Handler *hd,
+ const StringRef &path, int status) {
+ auto scheme = stream->header.scheme;
+
+ auto authority = stream->header.authority;
+ if (authority.empty()) {
+ authority = stream->header.host;
+ }
+
+ auto location = concat_string_ref(
+ stream->balloc, scheme, StringRef::from_lit("://"), authority, path);
+
+ auto headers = HeaderRefs{{StringRef::from_lit("location"), location}};
+
+ auto sessions = hd->get_sessions();
+ auto status_page = sessions->get_server()->get_status_page(status);
+
+ hd->submit_response(StringRef{status_page->status}, stream->stream_id,
+ headers, nullptr);
+}
+} // namespace
+
+namespace {
+void prepare_response(Stream *stream, Http2Handler *hd,
+ bool allow_push = true) {
+ int rv;
+ auto reqpath = stream->header.path;
+ if (reqpath.empty()) {
+ prepare_status_response(stream, hd, 405);
+ return;
+ }
+
+ auto ims = stream->header.ims;
+
+ time_t last_mod = 0;
+ bool last_mod_found = false;
+ if (!ims.empty()) {
+ last_mod_found = true;
+ last_mod = util::parse_http_date(ims);
+ }
+
+ StringRef raw_path, raw_query;
+ auto query_pos = std::find(std::begin(reqpath), std::end(reqpath), '?');
+ if (query_pos != std::end(reqpath)) {
+ // Do not response to this request to allow clients to test timeouts.
+ if (util::streq_l("nghttpd_do_not_respond_to_req=yes",
+ StringRef{query_pos, std::end(reqpath)})) {
+ return;
+ }
+ raw_path = StringRef{std::begin(reqpath), query_pos};
+ raw_query = StringRef{query_pos, std::end(reqpath)};
+ } else {
+ raw_path = reqpath;
+ }
+
+ auto sessions = hd->get_sessions();
+
+ StringRef path;
+ if (std::find(std::begin(raw_path), std::end(raw_path), '%') ==
+ std::end(raw_path)) {
+ path = raw_path;
+ } else {
+ path = util::percent_decode(stream->balloc, raw_path);
+ }
+
+ path = http2::path_join(stream->balloc, StringRef{}, StringRef{}, path,
+ StringRef{});
+
+ if (std::find(std::begin(path), std::end(path), '\\') != std::end(path)) {
+ if (stream->file_ent) {
+ sessions->release_fd(stream->file_ent);
+ stream->file_ent = nullptr;
+ }
+ prepare_status_response(stream, hd, 404);
+ return;
+ }
+
+ if (!hd->get_config()->push.empty()) {
+ auto push_itr = hd->get_config()->push.find(path.str());
+ if (allow_push && push_itr != std::end(hd->get_config()->push)) {
+ for (auto &push_path : (*push_itr).second) {
+ rv = hd->submit_push_promise(stream, StringRef{push_path});
+ if (rv != 0) {
+ std::cerr << "nghttp2_submit_push_promise() returned error: "
+ << nghttp2_strerror(rv) << std::endl;
+ }
+ }
+ }
+ }
+
+ std::string file_path;
+ {
+ auto len = hd->get_config()->htdocs.size() + path.size();
+
+ auto trailing_slash = path[path.size() - 1] == '/';
+ if (trailing_slash) {
+ len += DEFAULT_HTML.size();
+ }
+
+ file_path.resize(len);
+
+ auto p = &file_path[0];
+
+ auto &htdocs = hd->get_config()->htdocs;
+ p = std::copy(std::begin(htdocs), std::end(htdocs), p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ if (trailing_slash) {
+ std::copy(std::begin(DEFAULT_HTML), std::end(DEFAULT_HTML), p);
+ }
+ }
+
+ if (stream->echo_upload) {
+ assert(stream->file_ent);
+ prepare_echo_response(stream, hd);
+ return;
+ }
+
+ auto file_ent = sessions->get_cached_fd(file_path);
+
+ if (file_ent == nullptr) {
+ int file = open(file_path.c_str(), O_RDONLY | O_BINARY);
+ if (file == -1) {
+ prepare_status_response(stream, hd, 404);
+
+ return;
+ }
+
+ struct stat buf;
+
+ if (fstat(file, &buf) == -1) {
+ close(file);
+ prepare_status_response(stream, hd, 404);
+
+ return;
+ }
+
+ if (buf.st_mode & S_IFDIR) {
+ close(file);
+
+ auto reqpath = concat_string_ref(stream->balloc, raw_path,
+ StringRef::from_lit("/"), raw_query);
+
+ prepare_redirect_response(stream, hd, reqpath, 301);
+
+ return;
+ }
+
+ const std::string *content_type = nullptr;
+
+ auto ext = file_path.c_str() + file_path.size() - 1;
+ for (; file_path.c_str() < ext && *ext != '.' && *ext != '/'; --ext)
+ ;
+ if (*ext == '.') {
+ ++ext;
+
+ const auto &mime_types = hd->get_config()->mime_types;
+ auto content_type_itr = mime_types.find(ext);
+ if (content_type_itr != std::end(mime_types)) {
+ content_type = &(*content_type_itr).second;
+ }
+ }
+
+ file_ent = sessions->cache_fd(
+ file_path, FileEntry(file_path, buf.st_size, buf.st_mtime, file,
+ content_type, std::chrono::steady_clock::now()));
+ }
+
+ stream->file_ent = file_ent;
+
+ if (last_mod_found && file_ent->mtime <= last_mod) {
+ hd->submit_response(StringRef::from_lit("304"), stream->stream_id, nullptr);
+
+ return;
+ }
+
+ auto method = stream->header.method;
+ if (method == StringRef::from_lit("HEAD")) {
+ hd->submit_file_response(StringRef::from_lit("200"), stream,
+ file_ent->mtime, file_ent->length,
+ file_ent->content_type, nullptr);
+ return;
+ }
+
+ stream->body_length = file_ent->length;
+
+ nghttp2_data_provider data_prd;
+
+ data_prd.source.fd = file_ent->fd;
+ data_prd.read_callback = file_read_callback;
+
+ hd->submit_file_response(StringRef::from_lit("200"), stream, file_ent->mtime,
+ file_ent->length, file_ent->content_type, &data_prd);
+}
+} // namespace
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ if (hd->get_config()->verbose) {
+ print_session_id(hd->session_id());
+ verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
+ valuebuf.base, valuebuf.len, 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 (stream->header_buffer_size + namebuf.len + valuebuf.len > 64_k) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ stream->header_buffer_size += namebuf.len + valuebuf.len;
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+
+ auto &header = stream->header;
+
+ switch (token) {
+ case http2::HD__METHOD:
+ header.method = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.method = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__SCHEME:
+ header.scheme = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.scheme = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__AUTHORITY:
+ header.authority = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.authority = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_HOST:
+ header.host = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.host = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD__PATH:
+ header.path = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.path = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_IF_MODIFIED_SINCE:
+ header.ims = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.ims = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ case http2::HD_EXPECT:
+ header.expect = StringRef{valuebuf.base, valuebuf.len};
+ header.rcbuf.expect = value;
+ nghttp2_rcbuf_incref(value);
+ break;
+ }
+
+ 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 = std::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->echo_upload || !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) {
+
+ auto expect100 = stream->header.expect;
+
+ if (util::strieq_l("100-continue", expect100)) {
+ hd->submit_non_final_response("100", frame->hd.stream_id);
+ }
+
+ auto method = stream->header.method;
+ if (hd->get_config()->echo_upload &&
+ (method == StringRef::from_lit("POST") ||
+ method == StringRef::from_lit("PUT"))) {
+ if (!prepare_upload_temp_store(stream, hd)) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ } else 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->echo_upload || !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_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ return 0;
+ }
+
+ hd->start_settings_timer();
+
+ 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 {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, void *user_data) {
+ auto hd = static_cast<Http2Handler *>(user_data);
+ auto wb = hd->get_wb();
+ auto padlen = frame->data.padlen;
+ auto stream = hd->get_stream(frame->hd.stream_id);
+
+ if (wb->wleft() < 9 + length + padlen) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ int fd = source->fd;
+
+ auto p = wb->last;
+
+ p = std::copy_n(framehd, 9, p);
+
+ if (padlen) {
+ *p++ = padlen - 1;
+ }
+
+ while (length) {
+ ssize_t nread;
+ while ((nread = pread(fd, p, length, stream->body_offset)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ remove_stream_read_timeout(stream);
+ remove_stream_write_timeout(stream);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ stream->body_offset += nread;
+ length -= nread;
+ p += nread;
+ }
+
+ if (padlen) {
+ std::fill(p, p + padlen - 1, 0);
+ p += padlen - 1;
+ }
+
+ wb->last = p;
+
+ 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;
+ }
+
+ if (stream->echo_upload) {
+ assert(stream->file_ent);
+ while (len) {
+ ssize_t n;
+ while ((n = write(stream->file_ent->fd, data, len)) == -1 &&
+ errno == EINTR)
+ ;
+ if (n == -1) {
+ hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ len -= n;
+ data += n;
+ }
+ }
+ // TODO Handle POST
+
+ 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_error_callback2(callbacks,
+ verbose_error_callback);
+ }
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_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;
+ // protects 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 (const auto &c : q) {
+ sessions->accept_connection(c.fd);
+ }
+}
+} // namespace
+
+namespace {
+void run_worker(Worker *worker) {
+ auto loop = worker->sessions->get_loop();
+
+ ev_run(loop, 0);
+}
+} // namespace
+
+namespace {
+int get_ev_loop_flags() {
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ return ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ return 0;
+}
+} // namespace
+
+class AcceptHandler {
+public:
+ AcceptHandler(HttpServer *sv, 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 = std::make_unique<Worker>();
+ auto loop = ev_loop_new(get_ev_loop_flags());
+ worker->sessions = std::make_unique<Sessions>(sv, 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_(std::move(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) {
+ break;
+ }
+#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
+
+namespace {
+FileEntry make_status_body(int status, uint16_t port) {
+ BlockAllocator balloc(1024, 1024);
+
+ auto status_string = http2::stringify_status(balloc, status);
+ auto reason_pharase = http2::get_reason_phrase(status);
+
+ std::string body;
+ body = "<html><head><title>";
+ body += status_string;
+ body += ' ';
+ body += reason_pharase;
+ body += "</title></head><body><h1>";
+ body += status_string;
+ body += ' ';
+ body += reason_pharase;
+ body += "</h1><hr><address>";
+ body += NGHTTPD_SERVER;
+ body += " at port ";
+ body += util::utos(port);
+ body += "</address>";
+ body += "</body></html>";
+
+ char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
+ int fd = mkstemp(tempfn);
+ if (fd == -1) {
+ auto error = errno;
+ std::cerr << "Could not open status response body file: errno=" << error;
+ assert(0);
+ }
+ unlink(tempfn);
+ ssize_t nwrite;
+ while ((nwrite = write(fd, body.c_str(), body.size())) == -1 &&
+ errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ auto error = errno;
+ std::cerr << "Could not write status response body into file: errno="
+ << error;
+ assert(0);
+ }
+
+ return FileEntry(util::utos(status), nwrite, 0, fd, nullptr, {});
+}
+} // namespace
+
+// index into HttpServer::status_pages_
+enum {
+ IDX_200,
+ IDX_301,
+ IDX_400,
+ IDX_404,
+ IDX_405,
+};
+
+HttpServer::HttpServer(const Config *config) : config_(config) {
+ status_pages_ = std::vector<StatusPage>{
+ {"200", make_status_body(200, config_->port)},
+ {"301", make_status_body(301, config_->port)},
+ {"400", make_status_body(400, config_->port)},
+ {"404", make_status_body(404, config_->port)},
+ {"405", make_status_body(405, config_->port)},
+ };
+}
+
+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(HttpServer *sv, struct ev_loop *loop, Sessions *sessions,
+ const Config *config) {
+ int r;
+ bool ok = false;
+ const char *addr = nullptr;
+
+ std::shared_ptr<AcceptHandler> acceptor;
+ auto service = util::utos(config->port);
+
+ addrinfo 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
+
+ if (!config->address.empty()) {
+ addr = config->address.c_str();
+ }
+
+ addrinfo *res, *rp;
+ r = getaddrinfo(addr, 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) {
+ if (!acceptor) {
+ acceptor = std::make_shared<AcceptHandler>(sv, sessions, config);
+ }
+ new ListenEventHandler(sessions, fd, acceptor);
+
+ if (config->verbose) {
+ std::string s = util::numeric_name(rp->ai_addr, rp->ai_addrlen);
+ std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6") << ": listen "
+ << s << ":" << 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
+
+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
+
+int HttpServer::run() {
+ SSL_CTX *ssl_ctx = nullptr;
+ std::vector<unsigned char> next_proto;
+
+ if (!config_->no_tls) {
+ ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ 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;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config_->ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "Could not set TLS versions" << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tls::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
+ if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
+ std::cerr << "SSL_CTX_set1_curves_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+#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(), "rb");
+ if (bio == nullptr) {
+ std::cerr << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+#if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ std::cerr << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ std::cerr << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ 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);
+#endif // !OPENSSL_3_0_0_API
+ 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();
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this);
+ }
+
+ auto loop = EV_DEFAULT;
+
+ Sessions sessions(this, loop, config_, ssl_ctx);
+ if (start_listen(this, loop, &sessions, config_) != 0) {
+ std::cerr << "Could not listen" << std::endl;
+ if (ssl_ctx) {
+ SSL_CTX_free(ssl_ctx);
+ }
+ return -1;
+ }
+
+ ev_run(loop, 0);
+
+ SSL_CTX_free(ssl_ctx);
+
+ return 0;
+}
+
+const Config *HttpServer::get_config() const { return config_; }
+
+const StatusPage *HttpServer::get_status_page(int status) const {
+ switch (status) {
+ case 200:
+ return &status_pages_[IDX_200];
+ case 301:
+ return &status_pages_[IDX_301];
+ case 400:
+ return &status_pages_[IDX_400];
+ case 404:
+ return &status_pages_[IDX_404];
+ case 405:
+ return &status_pages_[IDX_405];
+ default:
+ assert(0);
+ }
+ return nullptr;
+}
+
+} // namespace nghttp2
diff --git a/src/HttpServer.h b/src/HttpServer.h
new file mode 100644
index 0000000..949bd1f
--- /dev/null
+++ b/src/HttpServer.h
@@ -0,0 +1,253 @@
+/*
+ * 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 <sys/types.h>
+
+#include <cinttypes>
+#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"
+#include "template.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+struct Config {
+ std::map<std::string, std::vector<std::string>> push;
+ std::map<std::string, std::string> mime_types;
+ Headers trailer;
+ std::string trailer_names;
+ std::string htdocs;
+ std::string host;
+ std::string private_key_file;
+ std::string cert_file;
+ std::string dh_param_file;
+ std::string address;
+ std::string mime_types_file;
+ ev_tstamp stream_read_timeout;
+ ev_tstamp stream_write_timeout;
+ void *data_ptr;
+ size_t padding;
+ size_t num_worker;
+ size_t max_concurrent_streams;
+ ssize_t header_table_size;
+ ssize_t encoder_header_table_size;
+ int window_bits;
+ int connection_window_bits;
+ uint16_t port;
+ bool verbose;
+ bool daemon;
+ bool verify_client;
+ bool no_tls;
+ bool error_gzip;
+ bool early_response;
+ bool hexdump;
+ bool echo_upload;
+ bool no_content_length;
+ bool ktls;
+ bool no_rfc7540_pri;
+ Config();
+ ~Config();
+};
+
+class Http2Handler;
+
+struct FileEntry {
+ FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
+ const std::string *content_type,
+ const std::chrono::steady_clock::time_point &last_valid,
+ bool stale = false)
+ : path(std::move(path)),
+ length(length),
+ mtime(mtime),
+ last_valid(last_valid),
+ content_type(content_type),
+ dlnext(nullptr),
+ dlprev(nullptr),
+ fd(fd),
+ usecount(1),
+ stale(stale) {}
+ std::string path;
+ std::multimap<std::string, std::unique_ptr<FileEntry>>::iterator it;
+ int64_t length;
+ int64_t mtime;
+ std::chrono::steady_clock::time_point last_valid;
+ const std::string *content_type;
+ FileEntry *dlnext, *dlprev;
+ int fd;
+ int usecount;
+ bool stale;
+};
+
+struct RequestHeader {
+ StringRef method;
+ StringRef scheme;
+ StringRef authority;
+ StringRef host;
+ StringRef path;
+ StringRef ims;
+ StringRef expect;
+
+ struct {
+ nghttp2_rcbuf *method;
+ nghttp2_rcbuf *scheme;
+ nghttp2_rcbuf *authority;
+ nghttp2_rcbuf *host;
+ nghttp2_rcbuf *path;
+ nghttp2_rcbuf *ims;
+ nghttp2_rcbuf *expect;
+ } rcbuf;
+};
+
+struct Stream {
+ BlockAllocator balloc;
+ RequestHeader header;
+ Http2Handler *handler;
+ FileEntry *file_ent;
+ ev_timer rtimer;
+ ev_timer wtimer;
+ int64_t body_length;
+ int64_t body_offset;
+ // Total amount of bytes (sum of name and value length) used in
+ // headers.
+ size_t header_buffer_size;
+ int32_t stream_id;
+ bool echo_upload;
+ 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();
+ void start_settings_timer();
+ int on_read();
+ int on_write();
+ int connection_made();
+ int verify_alpn_result();
+
+ int submit_file_response(const StringRef &status, Stream *stream,
+ time_t last_modified, off_t file_length,
+ const std::string *content_type,
+ nghttp2_data_provider *data_prd);
+
+ int submit_response(const StringRef &status, int32_t stream_id,
+ nghttp2_data_provider *data_prd);
+
+ int submit_response(const StringRef &status, int32_t stream_id,
+ const HeaderRefs &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 StringRef &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;
+
+ using WriteBuf = Buffer<64_k>;
+
+ WriteBuf *get_wb();
+
+private:
+ ev_io wev_;
+ ev_io rev_;
+ ev_timer settings_timerev_;
+ std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
+ WriteBuf 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_;
+};
+
+struct StatusPage {
+ std::string status;
+ FileEntry file_ent;
+};
+
+class HttpServer {
+public:
+ HttpServer(const Config *config);
+ int listen();
+ int run();
+ const Config *get_config() const;
+ const StatusPage *get_status_page(int status) const;
+
+private:
+ std::vector<StatusPage> status_pages_;
+ const Config *config_;
+};
+
+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);
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER_H
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..f112ac2
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,257 @@
+# 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
+
+EXTRA_DIST = \
+ CMakeLists.txt \
+ test.example.com.pem \
+ test.nghttp2.org.pem
+
+bin_PROGRAMS =
+check_PROGRAMS =
+TESTS =
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
+AM_CPPFLAGS = \
+ -DPKGDATADIR='"$(pkgdatadir)"' \
+ -DPKGLIBDIR='"$(pkglibdir)"' \
+ -I$(top_srcdir)/lib/includes \
+ -I$(top_builddir)/lib/includes \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/third-party \
+ -I$(top_srcdir)/third-party/llhttp/include \
+ @JEMALLOC_CFLAGS@ \
+ @LIBXML2_CFLAGS@ \
+ @LIBEV_CFLAGS@ \
+ @LIBNGHTTP3_CFLAGS@ \
+ @LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS@ \
+ @LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS@ \
+ @LIBNGTCP2_CFLAGS@ \
+ @OPENSSL_CFLAGS@ \
+ @LIBCARES_CFLAGS@ \
+ @JANSSON_CFLAGS@ \
+ @LIBBPF_CFLAGS@ \
+ @ZLIB_CFLAGS@ \
+ @EXTRA_DEFS@ \
+ @DEFS@
+AM_LDFLAGS = @LIBTOOL_LDFLAGS@
+
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+ $(top_builddir)/third-party/liburl-parser.la \
+ $(top_builddir)/third-party/libllhttp.la \
+ @JEMALLOC_LIBS@ \
+ @LIBXML2_LIBS@ \
+ @LIBEV_LIBS@ \
+ @LIBNGHTTP3_LIBS@ \
+ @LIBNGTCP2_CRYPTO_QUICTLS_LIBS@ \
+ @LIBNGTCP2_CRYPTO_BORINGSSL_LIBS@ \
+ @LIBNGTCP2_LIBS@ \
+ @OPENSSL_LIBS@ \
+ @LIBCARES_LIBS@ \
+ @SYSTEMD_LIBS@ \
+ @JANSSON_LIBS@ \
+ @LIBBPF_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 network.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} \
+ tls.cc tls.h ssl_compat.h
+
+nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
+ tls.cc tls.h ssl_compat.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 \
+ tls.cc tls.h ssl_compat.h \
+ h2load_session.h \
+ h2load_http2_session.cc h2load_http2_session.h \
+ h2load_http1_session.cc h2load_http1_session.h
+
+if ENABLE_HTTP3
+h2load_SOURCES += \
+ h2load_http3_session.cc h2load_http3_session.h \
+ h2load_quic.cc h2load_quic.h \
+ quic.cc quic.h
+endif # ENABLE_HTTP3
+
+NGHTTPX_SRCS = \
+ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
+ app_helper.cc app_helper.h \
+ tls.cc tls.h ssl_compat.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_tls.cc shrpx_tls.h \
+ shrpx_worker.cc shrpx_worker.h \
+ shrpx_log_config.cc shrpx_log_config.h \
+ shrpx_connect_blocker.cc shrpx_connect_blocker.h \
+ shrpx_live_check.cc shrpx_live_check.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 \
+ shrpx_memcached_dispatcher.cc shrpx_memcached_dispatcher.h \
+ shrpx_memcached_connection.cc shrpx_memcached_connection.h \
+ shrpx_memcached_request.h \
+ shrpx_memcached_result.h \
+ shrpx_worker_process.cc shrpx_worker_process.h \
+ shrpx_process.h \
+ shrpx_signal.cc shrpx_signal.h \
+ shrpx_router.cc shrpx_router.h \
+ shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \
+ shrpx_health_monitor_downstream_connection.cc \
+ shrpx_health_monitor_downstream_connection.h \
+ shrpx_null_downstream_connection.cc shrpx_null_downstream_connection.h \
+ shrpx_exec.cc shrpx_exec.h \
+ shrpx_dns_resolver.cc shrpx_dns_resolver.h \
+ shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
+ shrpx_dns_tracker.cc shrpx_dns_tracker.h \
+ buffer.h memchunk.h template.h allocator.h \
+ xsi_strerror.c xsi_strerror.h
+
+if HAVE_MRUBY
+NGHTTPX_SRCS += \
+ shrpx_mruby.cc shrpx_mruby.h \
+ shrpx_mruby_module.cc shrpx_mruby_module.h \
+ shrpx_mruby_module_env.cc shrpx_mruby_module_env.h \
+ shrpx_mruby_module_request.cc shrpx_mruby_module_request.h \
+ shrpx_mruby_module_response.cc shrpx_mruby_module_response.h
+endif # HAVE_MRUBY
+
+if ENABLE_HTTP3
+NGHTTPX_SRCS += \
+ shrpx_quic.cc shrpx_quic.h \
+ shrpx_quic_listener.cc shrpx_quic_listener.h \
+ shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \
+ shrpx_http3_upstream.cc shrpx_http3_upstream.h \
+ http3.cc http3.h \
+ quic.cc quic.h
+endif # ENABLE_HTTP3
+
+noinst_LIBRARIES = libnghttpx.a
+libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
+libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS}
+
+nghttpx_SOURCES = shrpx.cc shrpx.h
+nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS}
+nghttpx_LDADD = libnghttpx.a ${LDADD}
+
+if HAVE_MRUBY
+libnghttpx_a_CPPFLAGS += \
+ -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
+nghttpx_LDADD += -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
+endif # HAVE_MRUBY
+
+if HAVE_NEVERBLEED
+libnghttpx_a_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed
+nghttpx_LDADD += ${top_builddir}/third-party/libneverbleed.la
+endif # HAVE_NEVERBLEED
+
+if HAVE_CUNIT
+check_PROGRAMS += nghttpx-unittest
+nghttpx_unittest_SOURCES = shrpx-unittest.cc \
+ shrpx_tls_test.cc shrpx_tls_test.h \
+ shrpx_downstream_test.cc shrpx_downstream_test.h \
+ shrpx_config_test.cc shrpx_config_test.h \
+ shrpx_worker_test.cc shrpx_worker_test.h \
+ shrpx_http_test.cc shrpx_http_test.h \
+ shrpx_router_test.cc shrpx_router_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 \
+ template_test.cc template_test.h \
+ base64_test.cc base64_test.h
+nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
+ -DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
+nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
+
+if HAVE_MRUBY
+nghttpx_unittest_CPPFLAGS += \
+ -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
+nghttpx_unittest_LDADD += \
+ -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
+endif # HAVE_MRUBY
+
+if HAVE_NEVERBLEED
+nghttpx_unittest_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed
+nghttpx_unittest_LDADD += ${top_builddir}/third-party/libneverbleed.la
+endif # HAVE_NEVERBLEED
+
+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 \
+ util.cc util.h \
+ timegm.c timegm.h
+
+inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+endif # ENABLE_HPACK_TOOLS
diff --git a/src/allocator.h b/src/allocator.h
new file mode 100644
index 0000000..97b9a41
--- /dev/null
+++ b/src/allocator.h
@@ -0,0 +1,273 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 ALLOCATOR_H
+#define ALLOCATOR_H
+
+#include "nghttp2_config.h"
+
+#ifndef _WIN32
+# include <sys/uio.h>
+#endif // !_WIN32
+
+#include <cassert>
+#include <utility>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+struct MemBlock {
+ // The next MemBlock to chain them. This is for book keeping
+ // purpose to free them later.
+ MemBlock *next;
+ // begin is the pointer to the beginning of buffer. last is the
+ // location of next write. end is the one beyond of the end of the
+ // buffer.
+ uint8_t *begin, *last, *end;
+};
+
+// BlockAllocator allocates memory block with given size at once, and
+// cuts the region from it when allocation is requested. If the
+// requested size is larger than given threshold (plus small internal
+// overhead), it will be allocated in a distinct buffer on demand.
+// The |isolation_threshold| must be less than or equal to
+// |block_size|.
+struct BlockAllocator {
+ BlockAllocator(size_t block_size, size_t isolation_threshold)
+ : retain(nullptr),
+ head(nullptr),
+ block_size(block_size),
+ isolation_threshold(std::min(block_size, isolation_threshold)) {
+ assert(isolation_threshold <= block_size);
+ }
+
+ ~BlockAllocator() { reset(); }
+
+ BlockAllocator(BlockAllocator &&other) noexcept
+ : retain{std::exchange(other.retain, nullptr)},
+ head{std::exchange(other.head, nullptr)},
+ block_size(other.block_size),
+ isolation_threshold(other.isolation_threshold) {}
+
+ BlockAllocator &operator=(BlockAllocator &&other) noexcept {
+ reset();
+
+ retain = std::exchange(other.retain, nullptr);
+ head = std::exchange(other.head, nullptr);
+ block_size = other.block_size;
+ isolation_threshold = other.isolation_threshold;
+
+ return *this;
+ }
+
+ BlockAllocator(const BlockAllocator &) = delete;
+ BlockAllocator &operator=(const BlockAllocator &) = delete;
+
+ void reset() {
+ for (auto mb = retain; mb;) {
+ auto next = mb->next;
+ delete[] reinterpret_cast<uint8_t *>(mb);
+ mb = next;
+ }
+
+ retain = nullptr;
+ head = nullptr;
+ }
+
+ MemBlock *alloc_mem_block(size_t size) {
+ auto block = new uint8_t[sizeof(MemBlock) + size];
+ auto mb = reinterpret_cast<MemBlock *>(block);
+
+ mb->next = retain;
+ mb->begin = mb->last = block + sizeof(MemBlock);
+ mb->end = mb->begin + size;
+ retain = mb;
+ return mb;
+ }
+
+ void *alloc(size_t size) {
+ if (size + sizeof(size_t) >= isolation_threshold) {
+ auto len = std::max(static_cast<size_t>(16), size);
+ // We will store the allocated size in size_t field.
+ auto mb = alloc_mem_block(len + sizeof(size_t));
+ auto sp = reinterpret_cast<size_t *>(mb->begin);
+ *sp = len;
+ mb->last = mb->end;
+ return mb->begin + sizeof(size_t);
+ }
+
+ if (!head ||
+ head->end - head->last < static_cast<ssize_t>(size + sizeof(size_t))) {
+ head = alloc_mem_block(block_size);
+ }
+
+ // We will store the allocated size in size_t field.
+ auto res = head->last + sizeof(size_t);
+ auto sp = reinterpret_cast<size_t *>(head->last);
+ *sp = size;
+
+ head->last = reinterpret_cast<uint8_t *>(
+ (reinterpret_cast<intptr_t>(res + size) + 0xf) & ~0xf);
+
+ return res;
+ }
+
+ // Returns allocated size for memory pointed by |ptr|. We assume
+ // that |ptr| was returned from alloc() or realloc().
+ size_t get_alloc_length(void *ptr) {
+ return *reinterpret_cast<size_t *>(static_cast<uint8_t *>(ptr) -
+ sizeof(size_t));
+ }
+
+ // Allocates memory of at least |size| bytes. If |ptr| is nullptr,
+ // this is equivalent to alloc(size). If |ptr| is not nullptr,
+ // obtain the allocated size for |ptr|, assuming that |ptr| was
+ // returned from alloc() or realloc(). If the allocated size is
+ // greater than or equal to size, |ptr| is returned. Otherwise,
+ // allocates at least |size| bytes of memory, and the original
+ // content pointed by |ptr| is copied to the newly allocated memory.
+ void *realloc(void *ptr, size_t size) {
+ if (!ptr) {
+ return alloc(size);
+ }
+ auto alloclen = get_alloc_length(ptr);
+ auto p = reinterpret_cast<uint8_t *>(ptr);
+ if (size <= alloclen) {
+ return ptr;
+ }
+
+ auto nalloclen = std::max(size + 1, alloclen * 2);
+
+ auto res = alloc(nalloclen);
+ std::copy_n(p, alloclen, static_cast<uint8_t *>(res));
+
+ return res;
+ }
+
+ // This holds live memory block to free them in dtor.
+ MemBlock *retain;
+ // Current memory block to use.
+ MemBlock *head;
+ // size of single memory block
+ size_t block_size;
+ // if allocation greater or equal to isolation_threshold bytes is
+ // requested, allocate dedicated block.
+ size_t isolation_threshold;
+};
+
+// Makes a copy of |src|. The resulting string will be
+// NULL-terminated.
+template <typename BlockAllocator>
+StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
+ auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
+ auto p = dst;
+ p = std::copy(std::begin(src), std::end(src), p);
+ *p = '\0';
+ return StringRef{dst, src.size()};
+}
+
+// private function used in concat_string_ref. this is the base
+// function of concat_string_ref_count().
+inline constexpr size_t concat_string_ref_count(size_t acc) { return acc; }
+
+// private function used in concat_string_ref. This function counts
+// the sum of length of given arguments. The calculated length is
+// accumulated, and passed to the next function.
+template <typename... Args>
+constexpr size_t concat_string_ref_count(size_t acc, const StringRef &value,
+ Args &&...args) {
+ return concat_string_ref_count(acc + value.size(),
+ std::forward<Args>(args)...);
+}
+
+// private function used in concat_string_ref. this is the base
+// function of concat_string_ref_copy().
+inline uint8_t *concat_string_ref_copy(uint8_t *p) { return p; }
+
+// private function used in concat_string_ref. This function copies
+// given strings into |p|. |p| is incremented by the copied length,
+// and returned. In the end, return value points to the location one
+// beyond the last byte written.
+template <typename... Args>
+uint8_t *concat_string_ref_copy(uint8_t *p, const StringRef &value,
+ Args &&...args) {
+ p = std::copy(std::begin(value), std::end(value), p);
+ return concat_string_ref_copy(p, std::forward<Args>(args)...);
+}
+
+// Returns the string which is the concatenation of |args| in the
+// given order. The resulting string will be NULL-terminated.
+template <typename BlockAllocator, typename... Args>
+StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) {
+ size_t len = concat_string_ref_count(0, std::forward<Args>(args)...);
+ auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
+ auto p = dst;
+ p = concat_string_ref_copy(p, std::forward<Args>(args)...);
+ *p = '\0';
+ return StringRef{dst, len};
+}
+
+// Returns the string which is the concatenation of |value| and |args|
+// in the given order. The resulting string will be NULL-terminated.
+// This function assumes that the pointer value value.c_str() was
+// obtained from alloc.alloc() or alloc.realloc(), and attempts to use
+// unused memory region by using alloc.realloc(). If value is empty,
+// then just call concat_string_ref().
+template <typename BlockAllocator, typename... Args>
+StringRef realloc_concat_string_ref(BlockAllocator &alloc,
+ const StringRef &value, Args &&...args) {
+ if (value.empty()) {
+ return concat_string_ref(alloc, std::forward<Args>(args)...);
+ }
+
+ auto len =
+ value.size() + concat_string_ref_count(0, std::forward<Args>(args)...);
+ auto dst = static_cast<uint8_t *>(
+ alloc.realloc(const_cast<uint8_t *>(value.byte()), len + 1));
+ auto p = dst + value.size();
+ p = concat_string_ref_copy(p, std::forward<Args>(args)...);
+ *p = '\0';
+
+ return StringRef{dst, len};
+}
+
+struct ByteRef {
+ // The pointer to the beginning of the buffer.
+ uint8_t *base;
+ // The length of the buffer.
+ size_t len;
+};
+
+// Makes a buffer with given size. The resulting byte string might
+// not be NULL-terminated.
+template <typename BlockAllocator>
+ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) {
+ auto dst = static_cast<uint8_t *>(alloc.alloc(size));
+ return {dst, size};
+}
+
+} // namespace nghttp2
+
+#endif // ALLOCATOR_H
diff --git a/src/app_helper.cc b/src/app_helper.cc
new file mode 100644
index 0000000..ef92762
--- /dev/null
+++ b/src/app_helper.cc
@@ -0,0 +1,518 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_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"
+#include "template.h"
+
+namespace nghttp2 {
+
+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";
+ case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
+ case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
+ return "SETTINGS_NO_RFC7540_PRIORITIES";
+ default:
+ return "UNKNOWN";
+ }
+}
+} // namespace
+
+namespace {
+std::string 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_ALTSVC:
+ return "ALTSVC";
+ case NGHTTP2_ORIGIN:
+ return "ORIGIN";
+ case NGHTTP2_PRIORITY_UPDATE:
+ return "PRIORITY_UPDATE";
+ }
+
+ std::string s = "extension(0x";
+ s += util::format_hex(&type, 1);
+ s += ')';
+
+ return s;
+};
+} // 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%s%s: %s\n", ansi_esc("\033[1;34m"), nv->name,
+ ansi_escend(), nv->value);
+}
+} // 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);
+ }
+}
+} // namespace
+
+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).c_str(), 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",
+ nghttp2_http2_strerror(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,
+ nghttp2_http2_strerror(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_ALTSVC: {
+ auto altsvc = static_cast<nghttp2_ext_altsvc *>(frame->ext.payload);
+ print_frame_attr_indent();
+ fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n",
+ static_cast<int>(altsvc->origin_len), altsvc->origin,
+ static_cast<int>(altsvc->field_value_len), altsvc->field_value);
+ break;
+ }
+ case NGHTTP2_ORIGIN: {
+ auto origin = static_cast<nghttp2_ext_origin *>(frame->ext.payload);
+ for (size_t i = 0; i < origin->nov; ++i) {
+ auto ent = &origin->ov[i];
+ print_frame_attr_indent();
+ fprintf(outfile, "[%.*s]\n", (int)ent->origin_len, ent->origin);
+ }
+ break;
+ }
+ case NGHTTP2_PRIORITY_UPDATE: {
+ auto priority_update =
+ static_cast<nghttp2_ext_priority_update *>(frame->ext.payload);
+ print_frame_attr_indent();
+ fprintf(outfile,
+ "(prioritized_stream_id=%d, priority_field_value=[%.*s])\n",
+ priority_update->stream_id,
+ static_cast<int>(priority_update->field_value_len),
+ priority_update->field_value);
+ 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", frame->hd.stream_id);
+ if (flags & NGHTTP2_NV_FLAG_NO_INDEX) {
+ fprintf(outfile, ", sensitive");
+ }
+ fprintf(outfile, ") ");
+
+ 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,
+ int lib_error_code,
+ void *user_data) {
+ print_timer();
+ fprintf(outfile, " [INVALID; error=%s] recv ",
+ nghttp2_strerror(lib_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;
+}
+
+int verbose_error_callback(nghttp2_session *session, int lib_error_code,
+ const char *msg, size_t len, void *user_data) {
+ print_timer();
+ fprintf(outfile, " [ERROR] %.*s\n", (int)len, msg);
+ 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[8_k];
+ auto temp_outlen = sizeof(temp_out);
+
+ 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
index 0000000..5424054
--- /dev/null
+++ b/src/app_helper.h
@@ -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 APP_HELPER_H
+#define APP_HELPER_H
+
+#include "nghttp2_config.h"
+
+#include <cinttypes>
+#include <cstdlib>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif // HAVE_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,
+ int lib_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);
+
+int verbose_error_callback(nghttp2_session *session, int lib_error_code,
+ const char *msg, 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/base64.h b/src/base64.h
new file mode 100644
index 0000000..1bd51af
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,225 @@
+/*
+ * 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>
+
+#include "template.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+namespace base64 {
+
+namespace {
+constexpr char B64_CHARS[] = {
+ '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', '+', '/',
+};
+} // namespace
+
+template <typename InputIt> std::string encode(InputIt first, InputIt last) {
+ std::string res;
+ size_t len = last - first;
+ if (len == 0) {
+ return res;
+ }
+ size_t r = len % 3;
+ res.resize((len + 2) / 3 * 4);
+ auto j = last - r;
+ auto p = std::begin(res);
+ while (first != j) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ n += static_cast<uint8_t>(*first++);
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = B64_CHARS[n & 0x3fu];
+ }
+
+ if (r == 2) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = '=';
+ } else if (r == 1) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = '=';
+ *p++ = '=';
+ }
+ return res;
+}
+
+constexpr size_t encode_length(size_t n) { return (n + 2) / 3 * 4; }
+
+template <typename InputIt, typename OutputIt>
+OutputIt encode(InputIt first, InputIt last, OutputIt d_first) {
+ size_t len = last - first;
+ if (len == 0) {
+ return d_first;
+ }
+ auto r = len % 3;
+ auto j = last - r;
+ auto p = d_first;
+ while (first != j) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ n += static_cast<uint8_t>(*first++);
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = B64_CHARS[n & 0x3fu];
+ }
+
+ switch (r) {
+ case 2: {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = '=';
+ break;
+ }
+ case 1: {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = '=';
+ *p++ = '=';
+ break;
+ }
+ }
+ return p;
+}
+
+template <typename InputIt>
+InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) {
+ for (; first != last; ++first) {
+ if (tbl[static_cast<size_t>(*first)] != -1 || *first == '=') {
+ break;
+ }
+ }
+ return first;
+}
+
+template <typename InputIt, typename OutputIt>
+OutputIt decode(InputIt first, InputIt last, OutputIt d_first) {
+ static constexpr 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};
+ assert(std::distance(first, last) % 4 == 0);
+ auto p = d_first;
+ for (; first != last;) {
+ uint32_t n = 0;
+ for (int i = 1; i <= 4; ++i, ++first) {
+ auto idx = INDEX_TABLE[static_cast<size_t>(*first)];
+ if (idx == -1) {
+ if (i <= 2) {
+ return d_first;
+ }
+ if (i == 3) {
+ if (*first == '=' && *(first + 1) == '=' && first + 2 == last) {
+ *p++ = n >> 16;
+ return p;
+ }
+ return d_first;
+ }
+ if (*first == '=' && first + 1 == last) {
+ *p++ = n >> 16;
+ *p++ = n >> 8 & 0xffu;
+ return p;
+ }
+ return d_first;
+ }
+
+ n += idx << (24 - i * 6);
+ }
+
+ *p++ = n >> 16;
+ *p++ = n >> 8 & 0xffu;
+ *p++ = n & 0xffu;
+ }
+
+ return p;
+}
+
+template <typename InputIt> std::string decode(InputIt first, InputIt last) {
+ auto len = std::distance(first, last);
+ if (len % 4 != 0) {
+ return "";
+ }
+ std::string res;
+ res.resize(len / 4 * 3);
+
+ res.erase(decode(first, last, std::begin(res)), std::end(res));
+
+ return res;
+}
+
+template <typename InputIt>
+StringRef decode(BlockAllocator &balloc, InputIt first, InputIt last) {
+ auto len = std::distance(first, last);
+ if (len % 4 != 0) {
+ return StringRef::from_lit("");
+ }
+ auto iov = make_byte_ref(balloc, len / 4 * 3 + 1);
+ auto p = iov.base;
+
+ p = decode(first, last, p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+} // namespace base64
+
+} // namespace nghttp2
+
+#endif // BASE64_H
diff --git a/src/base64_test.cc b/src/base64_test.cc
new file mode 100644
index 0000000..4324bd7
--- /dev/null
+++ b/src/base64_test.cc
@@ -0,0 +1,121 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 "base64_test.h"
+
+#include <cstring>
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "base64.h"
+
+namespace nghttp2 {
+
+void test_base64_encode(void) {
+ {
+ std::string in = "\xff";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("/w==" == out);
+ }
+ {
+ std::string in = "\xff\xfe";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//4=" == out);
+ }
+ {
+ std::string in = "\xff\xfe\xfd";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//79" == out);
+ }
+ {
+ std::string in = "\xff\xfe\xfd\xfc";
+ auto out = base64::encode(std::begin(in), std::end(in));
+ CU_ASSERT("//79/A==" == out);
+ }
+}
+
+void test_base64_decode(void) {
+ BlockAllocator balloc(4096, 4096);
+ {
+ std::string in = "/w==";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff" == out);
+ CU_ASSERT("\xff" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//4=";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe" == out);
+ CU_ASSERT("\xff\xfe" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//79";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe\xfd" == out);
+ CU_ASSERT("\xff\xfe\xfd" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ std::string in = "//79/A==";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("\xff\xfe\xfd\xfc" == out);
+ CU_ASSERT("\xff\xfe\xfd\xfc" ==
+ base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // we check the number of valid input must be multiples of 4
+ std::string in = "//79=";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // ending invalid character at the boundary of multiples of 4 is
+ // bad
+ std::string in = "bmdodHRw\n";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // after seeing '=', subsequent input must be also '='.
+ std::string in = "//79/A=A";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+ {
+ // additional '=' at the end is bad
+ std::string in = "//79/A======";
+ auto out = base64::decode(std::begin(in), std::end(in));
+ CU_ASSERT("" == out);
+ CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in)));
+ }
+}
+
+} // namespace nghttp2
diff --git a/src/base64_test.h b/src/base64_test.h
new file mode 100644
index 0000000..8bdb84f
--- /dev/null
+++ b/src/base64_test.h
@@ -0,0 +1,39 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_TEST_H
+#define BASE64_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_base64_encode(void);
+void test_base64_decode(void);
+
+} // namespace nghttp2
+
+#endif // BASE64_TEST_H
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 0000000..1921edf
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,78 @@
+/*
+ * 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;
+ }
+ size_t drain_reset(size_t count) {
+ count = std::min(count, rleft());
+ std::copy(pos + count, last, std::begin(buf));
+ last = std::begin(buf) + (last - (pos + count));
+ pos = std::begin(buf);
+ return count;
+ }
+ void reset() { pos = last = std::begin(buf); }
+ uint8_t *begin() { return std::begin(buf); }
+ uint8_t &operator[](size_t n) { return buf[n]; }
+ const uint8_t &operator[](size_t n) const { return buf[n]; }
+ 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
index 0000000..38688ed
--- /dev/null
+++ b/src/buffer_test.cc
@@ -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
index 0000000..6789aa3
--- /dev/null
+++ b/src/buffer_test.h
@@ -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 BUFFER_TEST_H
+#define BUFFER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_buffer_write(void);
+
+} // namespace nghttp2
+
+#endif // BUFFER_TEST_H
diff --git a/src/ca-config.json b/src/ca-config.json
new file mode 100644
index 0000000..5788d07
--- /dev/null
+++ b/src/ca-config.json
@@ -0,0 +1,17 @@
+{
+ "signing": {
+ "default": {
+ "expiry": "87600h"
+ },
+ "profiles": {
+ "server": {
+ "expiry": "87600h",
+ "usages": [
+ "signing",
+ "key encipherment",
+ "server auth"
+ ]
+ }
+ }
+ }
+}
diff --git a/src/ca.nghttp2.org-key.pem b/src/ca.nghttp2.org-key.pem
new file mode 100644
index 0000000..6ce8707
--- /dev/null
+++ b/src/ca.nghttp2.org-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1kVkF8QSUwW/HV9EFRPSMoiOVmYwB8vqKDtT0d6MFiKAM8/Y
+JFUq2uKlUydgT4IPE7PATvVcIj3GtL9XzPhscqYO/S0Y7scyTE2VAPmtz+StPWf2
+wZ1IQR09HrnDTc44KvYGZpefBZkD9UjbmJ9a1ZmJjJiMr3hTnKE/sxZ2+dMsnMZX
+N822cfaHyTN+T0+Tyw5vBBboCDsZzxmf+9FFIDJNs3NL34cR8EZRhpfaegapH8bt
+OJ+D+RZ2kg7E/YYkGcS6NodvTjSUFCFHpWjHCfTFhn/owBIAooCdWorh6dc8Q72l
+AodwNLXS8uuPgPqM5s4Cz57m7Zgs4OilNmIdawIDAQABAoIBAQCwqLtygLye6KD+
+RXorapEmCsJX5553/x6Klwdvg+25ni5XCWjp47IWj0DBQzi7tL5bfxrxvod8z7QR
+d6SbIMLA77px8Ima7G7CzEAqcrBkM+TFOP8P+G4HCWVH/N5SOtDCUt9KHH4Grna9
+95jdx5yreRAX8/oh/bHp9GRBcicbpwYMVWOnjTE2seEUYQOpdpYdP4bOPUvAju0l
+mwmy2/dDGmbibktN3sdHEhDodKu+Znv7nFZo0jzhlyoXse653WcvaQeZZYuojvSe
+Sr92DvPp7UaYrb4KvT7ujXiPavSV2m/4EmGtyqevUf2dZ6sfMXZjmXsjWz9txhWp
+4BgbHyHRAoGBAPqyuNj2CDD3FE7N3Hxyba8d+ZtsVUNawjq2gwOvT9NLsMstOGyH
+OCc1v4W6Sq4w1wo4nIJyY8kNZwtReaTHOPZlDgBhVvk/x8eLBu+QTMRyocRt1LoD
+8HyKxWSAnYTtCh/GUEQ37amIqvOJ5GNL+25WDzevLa5kMYWG743uxEupAoGBANrN
+c/fVxepvP0GISlLpL3aZCFGAjMrq3xUYcf/w4wPoMq6AdpIPeRVBmJ1/Uqw1FkV8
+NRKJNPE2YcMuv8iMeQlacoPd34KT9ob80EYVlMwAkeC0NK+FfiM/UteR0wB49gmi
+ugX9YlJytOP9aUgPvEGT6l+XtgGC44W1TQWe62zzAoGBAKZenNU+0UjNb6isbToZ
+Jjkkh1Vhm2PLg0I7hM6ZNTxf6r+rDtrXEajTvnocmxrmRo796r+W8immv09/jl6P
+53l8rsIJ1xIqBYai+MNa29cyy6/zw0x++MVtwnlj8SUZubJEhVgAVbRAglKEnBBZ
+iE48xnSJyKMG0uZuGePzJEmhAoGBAIOHJcNBumum3DuklikpC+MbMyjrQbdpYRjp
+TP4x7AWZO34ysxQyQPNKL1feBfCHKRA0DiNKX4zwx+vw2lDQQKIiwNwMMCPqljOn
+HfxDVOMdJJQTP+iTMrQ1iLMVceXC0QQR0glvu/8b/SlgWD19WAmDxUwZgst9xw/F
+YLuUQKmJAoGAREeTugd4hc0U/YV/BQQjSCLhl11EtVry/oQMHj8KZpIJhP7tj8lw
+hSE0+z04oMhiTeq55PYKQkTo5l6V4PW0zfpEwlKEEm0erab1G9Ddh7us47XFcKLl
+Rmk192EVZ0lQuzftsYv7dzRLiAR7yDFXwD1ELIK/uPkwBtu7wtHlq+M=
+-----END RSA PRIVATE KEY-----
diff --git a/src/ca.nghttp2.org.csr b/src/ca.nghttp2.org.csr
new file mode 100644
index 0000000..37ee560
--- /dev/null
+++ b/src/ca.nghttp2.org.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICwjCCAaoCAQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2Eu
+bmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWRWQX
+xBJTBb8dX0QVE9IyiI5WZjAHy+ooO1PR3owWIoAzz9gkVSra4qVTJ2BPgg8Ts8BO
+9VwiPca0v1fM+Gxypg79LRjuxzJMTZUA+a3P5K09Z/bBnUhBHT0eucNNzjgq9gZm
+l58FmQP1SNuYn1rVmYmMmIyveFOcoT+zFnb50yycxlc3zbZx9ofJM35PT5PLDm8E
+FugIOxnPGZ/70UUgMk2zc0vfhxHwRlGGl9p6Bqkfxu04n4P5FnaSDsT9hiQZxLo2
+h29ONJQUIUelaMcJ9MWGf+jAEgCigJ1aiuHp1zxDvaUCh3A0tdLy64+A+ozmzgLP
+nubtmCzg6KU2Yh1rAgMBAAGgHzAdBgkqhkiG9w0BCQ4xEDAOMAwGA1UdEwQFMAMB
+Af8wDQYJKoZIhvcNAQELBQADggEBACI5v8GbOXKv38h9/tuGEwJ9uxpYEljgGt8h
+QL5lwfEifh/7A8b39b9JEzWk5hnMRCOb8J6Jc3/6nmVgtKkQ+Mceupqpwsp1gT/v
+uUoAkJE03Iuja9zLhHmy74oZ7LWOQrZ1T7Z0eGQ+5u+LBZiPKnKxmkLCQoUPTbc4
+NQ9BbKhr8OaoJ4DDvJnszcL7to6kih7SkdoNZsq4zB0/ai/cPhvoVgkYfbLH2++D
+Tcs7TqU2L7gKzqXUtHeAKM2y81ewL7QTrcYzgiW86s3NmquxZG5pq0mjD+P4BYLc
+MOdnCxKbBuE/1R29pa6+JKgc46jOa2yRgv5+8rXkkpu53Ke3FGc=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/ca.nghttp2.org.csr.json b/src/ca.nghttp2.org.csr.json
new file mode 100644
index 0000000..69d9c6a
--- /dev/null
+++ b/src/ca.nghttp2.org.csr.json
@@ -0,0 +1,17 @@
+{
+ "CN": "ca.nghttp2.org",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "ca": {
+ "expiry": "87600h"
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/ca.nghttp2.org.pem b/src/ca.nghttp2.org.pem
new file mode 100644
index 0000000..e50acfc
--- /dev/null
+++ b/src/ca.nghttp2.org.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrTCCApWgAwIBAgIUe4dvx8haIjsT3ZpNCMrl62Xk6E0wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBeMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMRcwFQYDVQQDEw5jYS5uZ2h0dHAyLm9yZzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANZFZBfEElMFvx1fRBUT0jKIjlZmMAfL6ig7U9He
+jBYigDPP2CRVKtripVMnYE+CDxOzwE71XCI9xrS/V8z4bHKmDv0tGO7HMkxNlQD5
+rc/krT1n9sGdSEEdPR65w03OOCr2BmaXnwWZA/VI25ifWtWZiYyYjK94U5yhP7MW
+dvnTLJzGVzfNtnH2h8kzfk9Pk8sObwQW6Ag7Gc8Zn/vRRSAyTbNzS9+HEfBGUYaX
+2noGqR/G7Tifg/kWdpIOxP2GJBnEujaHb040lBQhR6Voxwn0xYZ/6MASAKKAnVqK
+4enXPEO9pQKHcDS10vLrj4D6jObOAs+e5u2YLODopTZiHWsCAwEAAaNjMGEwDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNA5xVR1Zcax
+RJL9VC6pzuLmvduGMB8GA1UdIwQYMBaAFNA5xVR1ZcaxRJL9VC6pzuLmvduGMA0G
+CSqGSIb3DQEBCwUAA4IBAQCmdVfn/hUyEdvkKG7svg5d8o6BENOj8695KtWmzJjK
+zxH8J5Vy3mn89XrHQ+BOYXCDPyhs0aDS8aq3Z+HY0n9z1oAicyGzlVwZQQNX3YId
+Y2vcf7qu/2ATm/1S+mebE1/EXMUlWISKKUYXjggCwFgjDhH87Ai+A8MKScVdmqgL
+Hf+fRSzH3ToW7BCXlRl5bPAq2g+v1ALYc8wU9cT1MYm4dqAXh870LGFyUpaSWmFr
+TtX1DXBTgLp62syNlDthAvGigYFDtCa4cDM2vdTD9wpec2V9EKpfVqiRDDuYjUVX
+UXl27MvkNWnEBKCIoNv5abWXpZVG2zQdEMmUOkVuAXUC
+-----END CERTIFICATE-----
diff --git a/src/comp_helper.c b/src/comp_helper.c
new file mode 100644
index 0000000..98db08a
--- /dev/null
+++ b/src/comp_helper.c
@@ -0,0 +1,133 @@
+/*
+ * 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));
+}
+
+#define NGHTTP2_HD_ENTRY_OVERHEAD 32
+
+json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater) {
+ json_t *obj, *entries;
+ size_t i;
+ size_t len = nghttp2_hd_deflate_get_num_table_entries(deflater);
+
+ obj = json_object();
+ entries = json_array();
+ /* The first index of dynamic table is 62 */
+ for (i = 62; i <= len; ++i) {
+ const nghttp2_nv *nv = nghttp2_hd_deflate_get_table_entry(deflater, i);
+ json_t *outent = json_object();
+ json_object_set_new(outent, "index", json_integer((json_int_t)i));
+ dump_val(outent, "name", nv->name, nv->namelen);
+ dump_val(outent, "value", nv->value, nv->valuelen);
+ json_object_set_new(outent, "size",
+ json_integer((json_int_t)(nv->namelen + 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(
+ (json_int_t)nghttp2_hd_deflate_get_dynamic_table_size(deflater)));
+ json_object_set_new(
+ obj, "max_size",
+ json_integer(
+ (json_int_t)nghttp2_hd_deflate_get_max_dynamic_table_size(deflater)));
+
+ return obj;
+}
+
+json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater) {
+ json_t *obj, *entries;
+ size_t i;
+ size_t len = nghttp2_hd_inflate_get_num_table_entries(inflater);
+
+ obj = json_object();
+ entries = json_array();
+ /* The first index of dynamic table is 62 */
+ for (i = 62; i <= len; ++i) {
+ const nghttp2_nv *nv = nghttp2_hd_inflate_get_table_entry(inflater, i);
+ json_t *outent = json_object();
+ json_object_set_new(outent, "index", json_integer((json_int_t)i));
+ dump_val(outent, "name", nv->name, nv->namelen);
+ dump_val(outent, "value", nv->value, nv->valuelen);
+ json_object_set_new(outent, "size",
+ json_integer((json_int_t)(nv->namelen + 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(
+ (json_int_t)nghttp2_hd_inflate_get_dynamic_table_size(inflater)));
+ json_object_set_new(
+ obj, "max_size",
+ json_integer(
+ (json_int_t)nghttp2_hd_inflate_get_max_dynamic_table_size(inflater)));
+
+ 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);
+ if (cname == NULL) {
+ return NULL;
+ }
+ 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
index 0000000..131ed21
--- /dev/null
+++ b/src/comp_helper.h
@@ -0,0 +1,57 @@
+/*
+ * 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/nghttp2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater);
+
+json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater);
+
+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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_COMP_HELPER_H */
diff --git a/src/deflatehd.cc b/src/deflatehd.cc
new file mode 100644
index 0000000..7dcfccf
--- /dev/null
+++ b/src/deflatehd.cc
@@ -0,0 +1,450 @@
+/*
+ * 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
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "template.h"
+#include "comp_helper.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+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, const uint8_t *buf,
+ size_t buflen, size_t inputlen,
+ const std::vector<nghttp2_nv> &nva, int seq) {
+ auto hex = std::vector<char>(buflen * 2);
+ auto obj = json_object();
+ auto comp_ratio = inputlen == 0 ? 0.0 : (double)buflen / 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(buflen));
+ json_object_set_new(obj, "percentage_of_original_size",
+ json_real(comp_ratio));
+
+ if (buflen == 0) {
+ json_object_set_new(obj, "wire", json_string(""));
+ } else {
+ to_hex(hex.data(), buf, buflen);
+ 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_deflate_header_table(deflater));
+ }
+ 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;
+ std::array<uint8_t, 64_k> buf;
+
+ rv = nghttp2_hd_deflate_hd(deflater, buf.data(), buf.size(),
+ (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 += rv;
+
+ output_to_json(deflater, buf.data(), rv, inputlen, nva, seq);
+}
+
+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);
+ if (config.table_size != NGHTTP2_DEFAULT_HEADER_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;
+}
+
+constexpr 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) {
+ config.table_size = 4_k;
+ config.deflate_table_size = 4_k;
+ 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
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-s: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.table_size = n;
+ break;
+ }
+ case 'S': {
+ // --deflate-table-size
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-S: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.deflate_table_size = n;
+ 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;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/h2load.cc b/src/h2load.cc
new file mode 100644
index 0000000..0f07610
--- /dev/null
+++ b/src/h2load.cc
@@ -0,0 +1,3292 @@
+/*
+ * 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>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#include <sys/mman.h>
+#include <netinet/udp.h>
+
+#include <cstdio>
+#include <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <chrono>
+#include <thread>
+#include <future>
+#include <random>
+
+#include <openssl/err.h>
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+
+#include "url-parser/url_parser.h"
+
+#include "h2load_http1_session.h"
+#include "h2load_http2_session.h"
+#ifdef ENABLE_HTTP3
+# include "h2load_http3_session.h"
+# include "h2load_quic.h"
+#endif // ENABLE_HTTP3
+#include "tls.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+#include "ssl_compat.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+using namespace nghttp2;
+
+namespace h2load {
+
+namespace {
+bool recorded(const std::chrono::steady_clock::time_point &t) {
+ return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
+}
+} // namespace
+
+namespace {
+std::ofstream keylog_file;
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+Config::Config()
+ : ciphers(tls::DEFAULT_CIPHER_LIST),
+ tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_"
+ "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"),
+ groups("X25519:P-256:P-384:P-521"),
+ data_length(-1),
+ data(nullptr),
+ addrs(nullptr),
+ nreqs(1),
+ nclients(1),
+ nthreads(1),
+ max_concurrent_streams(1),
+ window_bits(30),
+ connection_window_bits(30),
+ max_frame_size(16_k),
+ rate(0),
+ rate_period(1.0),
+ duration(0.0),
+ warm_up_time(0.0),
+ conn_active_timeout(0.),
+ conn_inactivity_timeout(0.),
+ no_tls_proto(PROTO_HTTP2),
+ header_table_size(4_k),
+ encoder_header_table_size(4_k),
+ data_fd(-1),
+ log_fd(-1),
+ qlog_file_base(),
+ port(0),
+ default_port(0),
+ connect_to_port(0),
+ verbose(false),
+ timing_script(false),
+ base_uri_unix(false),
+ unix_addr{},
+ rps(0.),
+ no_udp_gso(false),
+ max_udp_payload_size(0),
+ ktls(false) {}
+
+Config::~Config() {
+ if (addrs) {
+ if (base_uri_unix) {
+ delete addrs;
+ } else {
+ freeaddrinfo(addrs);
+ }
+ }
+
+ if (data_fd != -1) {
+ close(data_fd);
+ }
+}
+
+bool Config::is_rate_mode() const { return (this->rate != 0); }
+bool Config::is_timing_based_mode() const { return (this->duration > 0); }
+bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
+bool Config::rps_enabled() const { return this->rps > 0.0; }
+bool Config::is_quic() const {
+#ifdef ENABLE_HTTP3
+ return !alpn_list.empty() &&
+ (alpn_list[0] == NGHTTP3_ALPN_H3 || alpn_list[0] == "\x5h3-29");
+#else // !ENABLE_HTTP3
+ return false;
+#endif // !ENABLE_HTTP3
+}
+Config config;
+
+namespace {
+constexpr size_t MAX_SAMPLES = 1000000;
+} // namespace
+
+Stats::Stats(size_t req_todo, size_t nclients)
+ : req_todo(req_todo),
+ req_started(0),
+ req_done(0),
+ req_success(0),
+ req_status_success(0),
+ req_failed(0),
+ req_error(0),
+ req_timedout(0),
+ bytes_total(0),
+ bytes_head(0),
+ bytes_head_decomp(0),
+ bytes_body(0),
+ status(),
+ udp_dgram_recv(0),
+ udp_dgram_sent(0) {}
+
+Stream::Stream() : req_stat{}, status_success(-1) {}
+
+namespace {
+std::random_device rd;
+} // namespace
+
+namespace {
+std::mt19937 gen(rd());
+} // namespace
+
+namespace {
+void sampling_init(Sampling &smp, size_t max_samples) {
+ smp.n = 0;
+ smp.max_samples = max_samples;
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ client->restart_timeout();
+ auto rv = client->do_write();
+ if (rv == Client::ERR_CONNECT_FAIL) {
+ client->disconnect();
+ // Try next address
+ client->current_addr = nullptr;
+ rv = client->connect();
+ if (rv != 0) {
+ client->fail();
+ client->worker->free_client(client);
+ delete client;
+ return;
+ }
+ return;
+ }
+ if (rv != 0) {
+ client->fail();
+ client->worker->free_client(client);
+ delete client;
+ }
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ client->restart_timeout();
+ if (client->do_read() != 0) {
+ if (client->try_again_or_fail() == 0) {
+ return;
+ }
+ client->worker->free_client(client);
+ delete client;
+ return;
+ }
+ client->signal_write();
+}
+} // namespace
+
+namespace {
+// Called every rate_period when rate mode is being used
+void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ auto nclients_per_second = worker->rate;
+ auto conns_remaining = worker->nclients - worker->nconns_made;
+ auto nclients = std::min(nclients_per_second, conns_remaining);
+
+ for (size_t i = 0; i < nclients; ++i) {
+ auto req_todo = worker->nreqs_per_client;
+ if (worker->nreqs_rem > 0) {
+ ++req_todo;
+ --worker->nreqs_rem;
+ }
+ auto client =
+ std::make_unique<Client>(worker->next_client_id++, worker, req_todo);
+
+ ++worker->nconns_made;
+
+ if (client->connect() != 0) {
+ std::cerr << "client could not connect to host" << std::endl;
+ client->fail();
+ } else {
+ if (worker->config->is_timing_based_mode()) {
+ worker->clients.push_back(client.release());
+ } else {
+ client.release();
+ }
+ }
+ worker->report_rate_progress();
+ }
+ if (!worker->config->is_timing_based_mode()) {
+ if (worker->nconns_made >= worker->nclients) {
+ ev_timer_stop(worker->loop, w);
+ }
+ } else {
+ // To check whether all created clients are pushed correctly
+ assert(worker->nclients == worker->clients.size());
+ }
+}
+} // namespace
+
+namespace {
+// Called when the duration for infinite number of requests are over
+void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+
+ worker->current_phase = Phase::DURATION_OVER;
+
+ std::cout << "Main benchmark duration is over for thread #" << worker->id
+ << ". Stopping all clients." << std::endl;
+ worker->stop_all_clients();
+ std::cout << "Stopped all clients for thread #" << worker->id << std::endl;
+}
+} // namespace
+
+namespace {
+// Called when the warmup duration for infinite number of requests are over
+void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+
+ std::cout << "Warm-up phase is over for thread #" << worker->id << "."
+ << std::endl;
+ std::cout << "Main benchmark duration is started for thread #" << worker->id
+ << "." << std::endl;
+ assert(worker->stats.req_started == 0);
+ assert(worker->stats.req_done == 0);
+
+ for (auto client : worker->clients) {
+ if (client) {
+ assert(client->req_todo == 0);
+ assert(client->req_left == 1);
+ assert(client->req_inflight == 0);
+ assert(client->req_started == 0);
+ assert(client->req_done == 0);
+
+ client->record_client_start_time();
+ client->clear_connect_times();
+ client->record_connect_start_time();
+ }
+ }
+
+ worker->current_phase = Phase::MAIN_DURATION;
+
+ ev_timer_start(worker->loop, &worker->duration_watcher);
+}
+} // namespace
+
+namespace {
+void rps_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+ auto &session = client->session;
+
+ assert(!config.timing_script);
+
+ if (client->req_left == 0) {
+ ev_timer_stop(loop, w);
+ return;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ auto d = now - client->rps_duration_started;
+ auto n = static_cast<size_t>(
+ round(std::chrono::duration<double>(d).count() * config.rps));
+ client->rps_req_pending += n;
+ client->rps_duration_started +=
+ util::duration_from(static_cast<double>(n) / config.rps);
+
+ if (client->rps_req_pending == 0) {
+ return;
+ }
+
+ auto nreq = session->max_concurrent_streams() - client->rps_req_inflight;
+ if (nreq == 0) {
+ return;
+ }
+
+ nreq = config.is_timing_based_mode() ? std::max(nreq, client->req_left)
+ : std::min(nreq, client->req_left);
+ nreq = std::min(nreq, client->rps_req_pending);
+
+ client->rps_req_inflight += nreq;
+ client->rps_req_pending -= nreq;
+
+ for (; nreq > 0; --nreq) {
+ if (client->submit_request() != 0) {
+ client->process_request_failure();
+ break;
+ }
+ }
+
+ client->signal_write();
+}
+} // namespace
+
+namespace {
+// Called when an a connection has been inactive for a set period of time
+// or a fixed amount of time after all requests have been made on a
+// connection
+void conn_timeout_cb(EV_P_ ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+
+ ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher);
+ ev_timer_stop(client->worker->loop, &client->conn_active_watcher);
+
+ if (util::check_socket_connected(client->fd)) {
+ client->timeout();
+ }
+}
+} // namespace
+
+namespace {
+bool check_stop_client_request_timeout(Client *client, ev_timer *w) {
+ if (client->req_left == 0) {
+ // no more requests to make, stop timer
+ ev_timer_stop(client->worker->loop, w);
+ return true;
+ }
+
+ return false;
+}
+} // namespace
+
+namespace {
+void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<Client *>(w->data);
+
+ if (client->streams.size() >= (size_t)config.max_concurrent_streams) {
+ ev_timer_stop(client->worker->loop, w);
+ return;
+ }
+
+ if (client->submit_request() != 0) {
+ ev_timer_stop(client->worker->loop, w);
+ client->process_request_failure();
+ return;
+ }
+ client->signal_write();
+
+ if (check_stop_client_request_timeout(client, w)) {
+ return;
+ }
+
+ auto duration =
+ config.timings[client->reqidx] - config.timings[client->reqidx - 1];
+
+ while (duration < std::chrono::duration<double>(1e-9)) {
+ if (client->submit_request() != 0) {
+ ev_timer_stop(client->worker->loop, w);
+ client->process_request_failure();
+ return;
+ }
+ client->signal_write();
+ if (check_stop_client_request_timeout(client, w)) {
+ return;
+ }
+
+ duration =
+ config.timings[client->reqidx] - config.timings[client->reqidx - 1];
+ }
+
+ client->request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
+ ev_timer_again(client->worker->loop, &client->request_timeout_watcher);
+}
+} // namespace
+
+Client::Client(uint32_t id, Worker *worker, size_t req_todo)
+ : wb(&worker->mcpool),
+ cstat{},
+ worker(worker),
+ ssl(nullptr),
+#ifdef ENABLE_HTTP3
+ quic{},
+#endif // ENABLE_HTTP3
+ next_addr(config.addrs),
+ current_addr(nullptr),
+ reqidx(0),
+ state(CLIENT_IDLE),
+ req_todo(req_todo),
+ req_left(req_todo),
+ req_inflight(0),
+ req_started(0),
+ req_done(0),
+ id(id),
+ fd(-1),
+ local_addr{},
+ new_connection_requested(false),
+ final(false),
+ rps_req_pending(0),
+ rps_req_inflight(0) {
+ if (req_todo == 0) { // this means infinite number of requests are to be made
+ // This ensures that number of requests are unbounded
+ // Just a positive number is fine, we chose the first positive number
+ req_left = 1;
+ }
+ 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(&conn_inactivity_watcher, conn_timeout_cb, 0.,
+ worker->config->conn_inactivity_timeout);
+ conn_inactivity_watcher.data = this;
+
+ ev_timer_init(&conn_active_watcher, conn_timeout_cb,
+ worker->config->conn_active_timeout, 0.);
+ conn_active_watcher.data = this;
+
+ ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.);
+ request_timeout_watcher.data = this;
+
+ ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
+ rps_watcher.data = this;
+
+#ifdef ENABLE_HTTP3
+ ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
+ quic.pkt_timer.data = this;
+
+ if (config.is_quic()) {
+ quic.tx.data = std::make_unique<uint8_t[]>(64_k);
+ }
+
+ ngtcp2_ccerr_default(&quic.last_error);
+#endif // ENABLE_HTTP3
+}
+
+Client::~Client() {
+ disconnect();
+
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic_free();
+ }
+#endif // ENABLE_HTTP3
+
+ if (ssl) {
+ SSL_free(ssl);
+ }
+
+ worker->sample_client_stat(&cstat);
+ ++worker->client_smp.n;
+}
+
+int Client::do_read() { return readfn(*this); }
+int Client::do_write() { return writefn(*this); }
+
+int Client::make_socket(addrinfo *addr) {
+ int rv;
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ fd = util::create_nonblock_udp_socket(addr->ai_family);
+ if (fd == -1) {
+ return -1;
+ }
+
+# ifdef UDP_GRO
+ int val = 1;
+ if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) != 0) {
+ std::cerr << "setsockopt UDP_GRO failed" << std::endl;
+ return -1;
+ }
+# endif // UDP_GRO
+
+ rv = util::bind_any_addr_udp(fd, addr->ai_family);
+ if (rv != 0) {
+ close(fd);
+ fd = -1;
+ return -1;
+ }
+
+ socklen_t addrlen = sizeof(local_addr.su.storage);
+ rv = getsockname(fd, &local_addr.su.sa, &addrlen);
+ if (rv == -1) {
+ return -1;
+ }
+ local_addr.len = addrlen;
+
+ if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
+ addr->ai_addrlen) != 0) {
+ std::cerr << "quic_init failed" << std::endl;
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+ } else {
+ fd = util::create_nonblock_socket(addr->ai_family);
+ if (fd == -1) {
+ return -1;
+ }
+ if (config.scheme == "https") {
+ if (!ssl) {
+ ssl = SSL_new(worker->ssl_ctx);
+ }
+
+ SSL_set_connect_state(ssl);
+ }
+ }
+
+ if (ssl && !util::numeric_host(config.host.c_str())) {
+ SSL_set_tlsext_host_name(ssl, config.host.c_str());
+ }
+
+ if (config.is_quic()) {
+ return 0;
+ }
+
+ 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;
+ return -1;
+ }
+ return 0;
+}
+
+int Client::connect() {
+ int rv;
+
+ if (!worker->config->is_timing_based_mode() ||
+ worker->current_phase == Phase::MAIN_DURATION) {
+ record_client_start_time();
+ clear_connect_times();
+ record_connect_start_time();
+ } else if (worker->current_phase == Phase::INITIAL_IDLE) {
+ worker->current_phase = Phase::WARM_UP;
+ std::cout << "Warm-up started for thread #" << worker->id << "."
+ << std::endl;
+ ev_timer_start(worker->loop, &worker->warmup_watcher);
+ }
+
+ if (worker->config->conn_inactivity_timeout > 0.) {
+ ev_timer_again(worker->loop, &conn_inactivity_watcher);
+ }
+
+ if (current_addr) {
+ rv = make_socket(current_addr);
+ if (rv == -1) {
+ return -1;
+ }
+ } else {
+ addrinfo *addr = nullptr;
+ while (next_addr) {
+ addr = next_addr;
+ next_addr = next_addr->ai_next;
+ rv = make_socket(addr);
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ assert(addr);
+
+ current_addr = addr;
+ }
+
+ ev_io_set(&rev, fd, EV_READ);
+ ev_io_set(&wev, fd, EV_WRITE);
+
+ ev_io_start(worker->loop, &wev);
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ ev_io_start(worker->loop, &rev);
+
+ readfn = &Client::read_quic;
+ writefn = &Client::write_quic;
+#endif // ENABLE_HTTP3
+ } else {
+ writefn = &Client::connected;
+ }
+
+ return 0;
+}
+
+void Client::timeout() {
+ process_timedout_streams();
+
+ disconnect();
+}
+
+void Client::restart_timeout() {
+ if (worker->config->conn_inactivity_timeout > 0.) {
+ ev_timer_again(worker->loop, &conn_inactivity_watcher);
+ }
+}
+
+int Client::try_again_or_fail() {
+ disconnect();
+
+ if (new_connection_requested) {
+ new_connection_requested = false;
+
+ if (req_left) {
+
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ // At the moment, we don't have a facility to re-start request
+ // already in in-flight. Make them fail.
+ worker->stats.req_failed += req_inflight;
+ worker->stats.req_error += req_inflight;
+
+ req_inflight = 0;
+ }
+
+ // Keep using current address
+ if (connect() == 0) {
+ return 0;
+ }
+ std::cerr << "client could not connect to host" << std::endl;
+ }
+ }
+
+ process_abandoned_streams();
+
+ return -1;
+}
+
+void Client::fail() {
+ disconnect();
+
+ process_abandoned_streams();
+}
+
+void Client::disconnect() {
+ record_client_end_time();
+
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic_close_connection();
+ }
+#endif // ENABLE_HTTP3
+
+#ifdef ENABLE_HTTP3
+ ev_timer_stop(worker->loop, &quic.pkt_timer);
+#endif // ENABLE_HTTP3
+ ev_timer_stop(worker->loop, &conn_inactivity_watcher);
+ ev_timer_stop(worker->loop, &conn_active_watcher);
+ ev_timer_stop(worker->loop, &rps_watcher);
+ ev_timer_stop(worker->loop, &request_timeout_watcher);
+ streams.clear();
+ session.reset();
+ wb.reset();
+ state = CLIENT_IDLE;
+ ev_io_stop(worker->loop, &wev);
+ ev_io_stop(worker->loop, &rev);
+ if (ssl) {
+ if (config.is_quic()) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ } else {
+ SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+
+ if (SSL_shutdown(ssl) != 1) {
+ SSL_free(ssl);
+ ssl = nullptr;
+ }
+ }
+ }
+ if (fd != -1) {
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ fd = -1;
+ }
+
+ final = false;
+}
+
+int Client::submit_request() {
+ if (session->submit_request() != 0) {
+ return -1;
+ }
+
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return 0;
+ }
+
+ ++worker->stats.req_started;
+ ++req_started;
+ ++req_inflight;
+ if (!worker->config->is_timing_based_mode()) {
+ --req_left;
+ }
+ // if an active timeout is set and this is the last request to be submitted
+ // on this connection, start the active timeout.
+ if (worker->config->conn_active_timeout > 0. && req_left == 0) {
+ ev_timer_start(worker->loop, &conn_active_watcher);
+ }
+
+ return 0;
+}
+
+void Client::process_timedout_streams() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ for (auto &p : streams) {
+ auto &req_stat = p.second.req_stat;
+ if (!req_stat.completed) {
+ req_stat.stream_close_time = std::chrono::steady_clock::now();
+ }
+ }
+
+ worker->stats.req_timedout += req_inflight;
+
+ process_abandoned_streams();
+}
+
+void Client::process_abandoned_streams() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ auto req_abandoned = req_inflight + req_left;
+
+ worker->stats.req_failed += req_abandoned;
+ worker->stats.req_error += req_abandoned;
+
+ req_inflight = 0;
+ req_left = 0;
+}
+
+void Client::process_request_failure() {
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ return;
+ }
+
+ worker->stats.req_failed += req_left;
+ worker->stats.req_error += req_left;
+
+ req_left = 0;
+
+ if (req_inflight == 0) {
+ terminate_session();
+ }
+ std::cout << "Process Request Failure:" << worker->stats.req_failed
+ << std::endl;
+}
+
+namespace {
+void print_server_tmp_key(SSL *ssl) {
+ 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: ";
+
+ auto pkey_id = EVP_PKEY_id(key);
+ switch (pkey_id) {
+ 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: {
+#if OPENSSL_3_0_0_API
+ std::array<char, 64> curve_name;
+ const char *cname;
+ if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(),
+ curve_name.size(), nullptr)) {
+ cname = "<unknown>";
+ } else {
+ cname = curve_name.data();
+ }
+#else // !OPENSSL_3_0_0_API
+ 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);
+ }
+#endif // !OPENSSL_3_0_0_API
+
+ std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
+ << std::endl;
+ break;
+ }
+ default:
+ std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits"
+ << std::endl;
+ break;
+ }
+}
+} // 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 << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n"
+ << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
+ print_server_tmp_key(ssl);
+ }
+}
+
+void Client::report_app_info() {
+ if (worker->id == 0 && !worker->app_info_report_done) {
+ worker->app_info_report_done = true;
+ std::cout << "Application protocol: " << selected_proto << std::endl;
+ }
+}
+
+void Client::terminate_session() {
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ quic.close_requested = true;
+ }
+#endif // ENABLE_HTTP3
+ if (session) {
+ session->terminate();
+ }
+ // http1 session needs writecb to tear down session.
+ signal_write();
+}
+
+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 (worker->current_phase != Phase::MAIN_DURATION) {
+ // If the stream is for warm-up phase, then mark as a success
+ // But we do not update the count for 2xx, 3xx, etc status codes
+ // Same has been done in on_status_code function
+ stream.status_success = 1;
+ return;
+ }
+
+ if (stream.status_success == -1 && namelen == 7 &&
+ util::streq_l(":status", 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) {
+ return;
+ }
+
+ stream.req_stat.status = status;
+ 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_status_code(int32_t stream_id, uint16_t status) {
+ auto itr = streams.find(stream_id);
+ if (itr == std::end(streams)) {
+ return;
+ }
+ auto &stream = (*itr).second;
+
+ if (worker->current_phase != Phase::MAIN_DURATION) {
+ stream.status_success = 1;
+ return;
+ }
+
+ stream.req_stat.status = status;
+ 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, bool final) {
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ if (req_inflight > 0) {
+ --req_inflight;
+ }
+ auto req_stat = get_req_stat(stream_id);
+ if (!req_stat) {
+ return;
+ }
+
+ req_stat->stream_close_time = std::chrono::steady_clock::now();
+ if (success) {
+ req_stat->completed = true;
+ ++worker->stats.req_success;
+ ++cstat.req_success;
+
+ if (streams[stream_id].status_success == 1) {
+ ++worker->stats.req_status_success;
+ } else {
+ ++worker->stats.req_failed;
+ }
+
+ worker->sample_req_stat(req_stat);
+
+ // Count up in successful cases only
+ ++worker->request_times_smp.n;
+ } else {
+ ++worker->stats.req_failed;
+ ++worker->stats.req_error;
+ }
+ ++worker->stats.req_done;
+ ++req_done;
+
+ if (worker->config->log_fd != -1) {
+ auto start = std::chrono::duration_cast<std::chrono::microseconds>(
+ req_stat->request_wall_time.time_since_epoch());
+ auto delta = std::chrono::duration_cast<std::chrono::microseconds>(
+ req_stat->stream_close_time - req_stat->request_time);
+
+ std::array<uint8_t, 256> buf;
+ auto p = std::begin(buf);
+ p = util::utos(p, start.count());
+ *p++ = '\t';
+ if (success) {
+ p = util::utos(p, req_stat->status);
+ } else {
+ *p++ = '-';
+ *p++ = '1';
+ }
+ *p++ = '\t';
+ p = util::utos(p, delta.count());
+ *p++ = '\n';
+
+ auto nwrite = static_cast<size_t>(std::distance(std::begin(buf), p));
+ assert(nwrite <= buf.size());
+ while (write(worker->config->log_fd, buf.data(), nwrite) == -1 &&
+ errno == EINTR)
+ ;
+ }
+ }
+
+ worker->report_progress();
+ streams.erase(stream_id);
+ if (req_left == 0 && req_inflight == 0) {
+ terminate_session();
+ return;
+ }
+
+ if (!final && req_left > 0) {
+ if (config.timing_script) {
+ if (!ev_is_active(&request_timeout_watcher)) {
+ ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER);
+ }
+ } else if (!config.rps_enabled()) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else if (rps_req_pending) {
+ --rps_req_pending;
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else {
+ assert(rps_req_inflight);
+ --rps_req_inflight;
+ }
+ }
+}
+
+RequestStat *Client::get_req_stat(int32_t stream_id) {
+ auto it = streams.find(stream_id);
+ if (it == std::end(streams)) {
+ return nullptr;
+ }
+
+ return &(*it).second.req_stat;
+}
+
+int Client::connection_made() {
+ if (ssl) {
+ report_tls_info();
+
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+ assert(session);
+ if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) &&
+ !util::streq_l("h3-29", proto)) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+ } else if (util::check_h2_is_selected(proto)) {
+ session = std::make_unique<Http2Session>(this);
+ } else if (util::streq(NGHTTP2_H1_1, proto)) {
+ session = std::make_unique<Http1Session>(this);
+ }
+
+ // Just assign next_proto to selected_proto anyway to show the
+ // negotiation result.
+ selected_proto = proto.str();
+ } else if (config.is_quic()) {
+ std::cerr << "QUIC requires ALPN negotiation" << std::endl;
+ return -1;
+ } else {
+ std::cout << "No protocol negotiated. Fallback behaviour may be activated"
+ << std::endl;
+
+ for (const auto &proto : config.alpn_list) {
+ if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) {
+ std::cout << "Server does not support ALPN. Falling back to HTTP/1.1."
+ << std::endl;
+ session = std::make_unique<Http1Session>(this);
+ selected_proto = NGHTTP2_H1_1.str();
+ break;
+ }
+ }
+ }
+
+ if (!selected_proto.empty()) {
+ report_app_info();
+ }
+
+ if (!session) {
+ std::cout
+ << "No supported protocol was negotiated. Supported protocols were:"
+ << std::endl;
+ for (const auto &proto : config.alpn_list) {
+ std::cout << proto.substr(1) << std::endl;
+ }
+ disconnect();
+ return -1;
+ }
+ } else {
+ switch (config.no_tls_proto) {
+ case Config::PROTO_HTTP2:
+ session = std::make_unique<Http2Session>(this);
+ selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
+ break;
+ case Config::PROTO_HTTP1_1:
+ session = std::make_unique<Http1Session>(this);
+ selected_proto = NGHTTP2_H1_1.str();
+ break;
+ default:
+ // unreachable
+ assert(0);
+ }
+
+ report_app_info();
+ }
+
+ state = CLIENT_CONNECTED;
+
+ session->on_connect();
+
+ record_connect_time();
+
+ if (config.rps_enabled()) {
+ rps_watcher.repeat = std::max(0.01, 1. / config.rps);
+ ev_timer_again(worker->loop, &rps_watcher);
+ rps_duration_started = std::chrono::steady_clock::now();
+ }
+
+ if (config.rps_enabled()) {
+ assert(req_left);
+
+ ++rps_req_inflight;
+
+ if (submit_request() != 0) {
+ process_request_failure();
+ }
+ } else if (!config.timing_script) {
+ auto nreq = config.is_timing_based_mode()
+ ? std::max(req_left, session->max_concurrent_streams())
+ : std::min(req_left, session->max_concurrent_streams());
+
+ for (; nreq > 0; --nreq) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ break;
+ }
+ }
+ } else {
+
+ auto duration = config.timings[reqidx];
+
+ while (duration < std::chrono::duration<double>(1e-9)) {
+ if (submit_request() != 0) {
+ process_request_failure();
+ break;
+ }
+ duration = config.timings[reqidx];
+ if (reqidx == 0) {
+ // if reqidx wraps around back to 0, we uses up all lines and
+ // should break
+ break;
+ }
+ }
+
+ if (duration >= std::chrono::duration<double>(1e-9)) {
+ // double check since we may have break due to reqidx wraps
+ // around back to 0
+ request_timeout_watcher.repeat = util::ev_tstamp_from(duration);
+ ev_timer_again(worker->loop, &request_timeout_watcher);
+ }
+ }
+ 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;
+ }
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ worker->stats.bytes_total += len;
+ }
+ signal_write();
+ return 0;
+}
+
+int Client::on_write() {
+ if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
+ return 0;
+ }
+
+ if (session->on_write() != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int Client::read_clear() {
+ uint8_t buf[8_k];
+
+ 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() {
+ std::array<struct iovec, 2> iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(iov.data(), iov.size());
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
+ ;
+
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ ev_io_start(worker->loop, &wev);
+ return 0;
+ }
+ return -1;
+ }
+
+ wb.drain(nwrite);
+ }
+
+ 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) {
+ SSL_set_fd(ssl, fd);
+
+ readfn = &Client::tls_handshake;
+ writefn = &Client::tls_handshake;
+
+ return do_write();
+ }
+
+ readfn = &Client::read_clear;
+ writefn = &Client::write_clear;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::tls_handshake() {
+ ERR_clear_error();
+
+ auto rv = SSL_do_handshake(ssl);
+
+ 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 (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::read_tls() {
+ uint8_t buf[8_k];
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto rv = SSL_read(ssl, buf, sizeof(buf));
+
+ 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();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(&iov, 1);
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
+
+ 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);
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ return 0;
+}
+
+#ifdef ENABLE_HTTP3
+// Returns 1 if sendmsg is blocked.
+int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(addr);
+ msg.msg_namelen = addrlen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+# ifdef UDP_SEGMENT
+ std::array<uint8_t, CMSG_SPACE(sizeof(uint16_t))> msg_ctrl{};
+ if (gso_size && datalen > gso_size) {
+ msg.msg_control = msg_ctrl.data();
+ msg.msg_controllen = msg_ctrl.size();
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ uint16_t n = gso_size;
+ memcpy(CMSG_DATA(cm), &n, sizeof(n));
+ }
+# endif // UDP_SEGMENT
+
+ auto nwrite = sendmsg(fd, &msg, 0);
+ if (nwrite < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 1;
+ }
+
+ std::cerr << "sendmsg: errno=" << errno << std::endl;
+ } else {
+ ++worker->stats.udp_dgram_sent;
+ }
+
+ ev_io_stop(worker->loop, &wev);
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+void Client::record_request_time(RequestStat *req_stat) {
+ req_stat->request_time = std::chrono::steady_clock::now();
+ req_stat->request_wall_time = std::chrono::system_clock::now();
+}
+
+void Client::record_connect_start_time() {
+ cstat.connect_start_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_connect_time() {
+ cstat.connect_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_ttfb() {
+ if (recorded(cstat.ttfb)) {
+ return;
+ }
+
+ cstat.ttfb = std::chrono::steady_clock::now();
+}
+
+void Client::clear_connect_times() {
+ cstat.connect_start_time = std::chrono::steady_clock::time_point();
+ cstat.connect_time = std::chrono::steady_clock::time_point();
+ cstat.ttfb = std::chrono::steady_clock::time_point();
+}
+
+void Client::record_client_start_time() {
+ // Record start time only once at the very first connection is going
+ // to be made.
+ if (recorded(cstat.client_start_time)) {
+ return;
+ }
+
+ cstat.client_start_time = std::chrono::steady_clock::now();
+}
+
+void Client::record_client_end_time() {
+ // Unlike client_start_time, we overwrite client_end_time. This
+ // handles multiple connect/disconnect for HTTP/1.1 benchmark.
+ cstat.client_end_time = std::chrono::steady_clock::now();
+}
+
+void Client::signal_write() { ev_io_start(worker->loop, &wev); }
+
+void Client::try_new_connection() { new_connection_requested = true; }
+
+namespace {
+int get_ev_loop_flags() {
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ return ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ return 0;
+}
+} // namespace
+
+Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
+ size_t rate, size_t max_samples, Config *config)
+ : randgen(util::make_mt19937()),
+ stats(req_todo, nclients),
+ loop(ev_loop_new(get_ev_loop_flags())),
+ ssl_ctx(ssl_ctx),
+ config(config),
+ id(id),
+ tls_info_report_done(false),
+ app_info_report_done(false),
+ nconns_made(0),
+ nclients(nclients),
+ nreqs_per_client(req_todo / nclients),
+ nreqs_rem(req_todo % nclients),
+ rate(rate),
+ max_samples(max_samples),
+ next_client_id(0) {
+ if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
+ progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
+ } else {
+ progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
+ }
+
+ // Below timeout is not needed in case of timing-based benchmarking
+ // create timer that will go off every rate_period
+ ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
+ config->rate_period);
+ timeout_watcher.data = this;
+
+ if (config->is_timing_based_mode()) {
+ stats.req_stats.reserve(std::max(req_todo, max_samples));
+ stats.client_stats.reserve(std::max(nclients, max_samples));
+ } else {
+ stats.req_stats.reserve(std::min(req_todo, max_samples));
+ stats.client_stats.reserve(std::min(nclients, max_samples));
+ }
+
+ sampling_init(request_times_smp, max_samples);
+ sampling_init(client_smp, max_samples);
+
+ ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.);
+ duration_watcher.data = this;
+
+ ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.);
+ warmup_watcher.data = this;
+
+ if (config->is_timing_based_mode()) {
+ current_phase = Phase::INITIAL_IDLE;
+ } else {
+ current_phase = Phase::MAIN_DURATION;
+ }
+}
+
+Worker::~Worker() {
+ ev_timer_stop(loop, &timeout_watcher);
+ ev_timer_stop(loop, &duration_watcher);
+ ev_timer_stop(loop, &warmup_watcher);
+ ev_loop_destroy(loop);
+}
+
+void Worker::stop_all_clients() {
+ for (auto client : clients) {
+ if (client) {
+ client->terminate_session();
+ }
+ }
+}
+
+void Worker::free_client(Client *deleted_client) {
+ for (auto &client : clients) {
+ if (client == deleted_client) {
+ client->req_todo = client->req_done;
+ stats.req_todo += client->req_todo;
+ auto index = &client - &clients[0];
+ clients[index] = nullptr;
+ return;
+ }
+ }
+}
+
+void Worker::run() {
+ if (!config->is_rate_mode() && !config->is_timing_based_mode()) {
+ for (size_t i = 0; i < nclients; ++i) {
+ auto req_todo = nreqs_per_client;
+ if (nreqs_rem > 0) {
+ ++req_todo;
+ --nreqs_rem;
+ }
+
+ auto client = std::make_unique<Client>(next_client_id++, this, req_todo);
+ if (client->connect() != 0) {
+ std::cerr << "client could not connect to host" << std::endl;
+ client->fail();
+ } else {
+ client.release();
+ }
+ }
+ } else if (config->is_rate_mode()) {
+ ev_timer_again(loop, &timeout_watcher);
+
+ // call callback so that we don't waste the first rate_period
+ rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
+ } else {
+ // call the callback to start for one single time
+ rate_period_timeout_w_cb(loop, &timeout_watcher, 0);
+ }
+ ev_run(loop, 0);
+}
+
+namespace {
+template <typename Stats, typename Stat>
+void sample(Sampling &smp, Stats &stats, Stat *s) {
+ ++smp.n;
+ if (stats.size() < smp.max_samples) {
+ stats.push_back(*s);
+ return;
+ }
+ auto d = std::uniform_int_distribution<unsigned long>(0, smp.n - 1);
+ auto i = d(gen);
+ if (i < smp.max_samples) {
+ stats[i] = *s;
+ }
+}
+} // namespace
+
+void Worker::sample_req_stat(RequestStat *req_stat) {
+ sample(request_times_smp, stats.req_stats, req_stat);
+}
+
+void Worker::sample_client_stat(ClientStat *cstat) {
+ sample(client_smp, stats.client_stats, cstat);
+}
+
+void Worker::report_progress() {
+ if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval ||
+ config->is_timing_based_mode()) {
+ return;
+ }
+
+ std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
+ << std::endl;
+}
+
+void Worker::report_rate_progress() {
+ if (id != 0 || nconns_made % progress_interval) {
+ return;
+ }
+
+ std::cout << "progress: " << nconns_made * 100 / nclients
+ << "% of clients started" << std::endl;
+}
+
+namespace {
+// Returns percentage of number of samples within mean +/- sd.
+double within_sd(const std::vector<double> &samples, double mean, double sd) {
+ if (samples.size() == 0) {
+ return 0.0;
+ }
+ auto lower = mean - sd;
+ auto upper = mean + sd;
+ auto m = std::count_if(
+ std::begin(samples), std::end(samples),
+ [&lower, &upper](double t) { return lower <= t && t <= upper; });
+ return (m / static_cast<double>(samples.size())) * 100;
+}
+} // namespace
+
+namespace {
+// Computes statistics using |samples|. The min, max, mean, sd, and
+// percentage of number of samples within mean +/- sd are computed.
+// If |sampling| is true, this computes sample variance. Otherwise,
+// population variance.
+SDStat compute_time_stat(const std::vector<double> &samples,
+ bool sampling = false) {
+ if (samples.empty()) {
+ return {0.0, 0.0, 0.0, 0.0, 0.0};
+ }
+ // standard deviation calculated using Rapid calculation method:
+ // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+ double a = 0, q = 0;
+ size_t n = 0;
+ double sum = 0;
+ auto res = SDStat{std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::min()};
+ for (const auto &t : samples) {
+ ++n;
+ res.min = std::min(res.min, t);
+ res.max = std::max(res.max, t);
+ sum += t;
+
+ auto na = a + (t - a) / n;
+ q += (t - a) * (t - na);
+ a = na;
+ }
+
+ assert(n > 0);
+ res.mean = sum / n;
+ res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
+ res.within_sd = within_sd(samples, res.mean, res.sd);
+
+ return res;
+}
+} // namespace
+
+namespace {
+SDStats
+process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
+ auto request_times_sampling = false;
+ auto client_times_sampling = false;
+ size_t nrequest_times = 0;
+ size_t nclient_times = 0;
+ for (const auto &w : workers) {
+ nrequest_times += w->stats.req_stats.size();
+ request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size();
+
+ nclient_times += w->stats.client_stats.size();
+ client_times_sampling = w->client_smp.n > w->stats.client_stats.size();
+ }
+
+ std::vector<double> request_times;
+ request_times.reserve(nrequest_times);
+
+ std::vector<double> connect_times, ttfb_times, rps_values;
+ connect_times.reserve(nclient_times);
+ ttfb_times.reserve(nclient_times);
+ rps_values.reserve(nclient_times);
+
+ for (const auto &w : workers) {
+ for (const auto &req_stat : w->stats.req_stats) {
+ if (!req_stat.completed) {
+ continue;
+ }
+ request_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ req_stat.stream_close_time - req_stat.request_time)
+ .count());
+ }
+
+ const auto &stat = w->stats;
+
+ for (const auto &cstat : stat.client_stats) {
+ if (recorded(cstat.client_start_time) &&
+ recorded(cstat.client_end_time)) {
+ auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.client_end_time - cstat.client_start_time)
+ .count();
+ if (t > 1e-9) {
+ rps_values.push_back(cstat.req_success / t);
+ }
+ }
+
+ // We will get connect event before FFTB.
+ if (!recorded(cstat.connect_start_time) ||
+ !recorded(cstat.connect_time)) {
+ continue;
+ }
+
+ connect_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.connect_time - cstat.connect_start_time)
+ .count());
+
+ if (!recorded(cstat.ttfb)) {
+ continue;
+ }
+
+ ttfb_times.push_back(
+ std::chrono::duration_cast<std::chrono::duration<double>>(
+ cstat.ttfb - cstat.connect_start_time)
+ .count());
+ }
+ }
+
+ return {compute_time_stat(request_times, request_times_sampling),
+ compute_time_stat(connect_times, client_times_sampling),
+ compute_time_stat(ttfb_times, client_times_sampling),
+ compute_time_stat(rps_values, client_times_sampling)};
+}
+} // namespace
+
+namespace {
+void resolve_host() {
+ if (config.base_uri_unix) {
+ auto res = std::make_unique<addrinfo>();
+ res->ai_family = config.unix_addr.sun_family;
+ res->ai_socktype = SOCK_STREAM;
+ res->ai_addrlen = sizeof(config.unix_addr);
+ res->ai_addr =
+ static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
+
+ config.addrs = res.release();
+ return;
+ };
+
+ int rv;
+ addrinfo hints{}, *res;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ const auto &resolve_host =
+ config.connect_to_host.empty() ? config.host : config.connect_to_host;
+ auto port =
+ config.connect_to_port == 0 ? config.port : config.connect_to_port;
+
+ rv =
+ getaddrinfo(resolve_host.c_str(), util::utos(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).str();
+ } else {
+ reqline = "/";
+ }
+
+ if (util::has_uri_field(u, UF_QUERY)) {
+ reqline += '?';
+ reqline += util::get_uri_field(uri, u, UF_QUERY);
+ }
+
+ return reqline;
+}
+} // namespace
+
+namespace {
+constexpr char UNIX_PATH_PREFIX[] = "unix:";
+} // namespace
+
+namespace {
+bool parse_base_uri(const StringRef &base_uri) {
+ http_parser_url u{};
+ if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 ||
+ !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
+ return false;
+ }
+
+ config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str();
+ config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str();
+ config.default_port = util::get_default_port(base_uri.c_str(), u);
+ if (util::has_uri_field(u, UF_PORT)) {
+ config.port = u.port;
+ } else {
+ config.port = config.default_port;
+ }
+
+ return true;
+}
+} // namespace
+namespace {
+// Use std::vector<std::string>::iterator explicitly, without that,
+// http_parser_url u{} fails with clang-3.4.
+std::vector<std::string> parse_uris(std::vector<std::string>::iterator first,
+ std::vector<std::string>::iterator last) {
+ std::vector<std::string> reqlines;
+
+ if (first == last) {
+ std::cerr << "no URI available" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.has_base_uri()) {
+
+ if (!parse_base_uri(StringRef{*first})) {
+ std::cerr << "invalid URI: " << *first << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri = *first;
+ }
+
+ for (; first != last; ++first) {
+ http_parser_url u{};
+
+ auto uri = (*first).c_str();
+
+ if (http_parser_parse_url(uri, (*first).size(), 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 read_script_from_file(
+ std::istream &infile,
+ std::vector<std::chrono::steady_clock::duration> &timings,
+ std::vector<std::string> &uris) {
+ std::string script_line;
+ int line_count = 0;
+ while (std::getline(infile, script_line)) {
+ line_count++;
+ if (script_line.empty()) {
+ std::cerr << "Empty line detected at line " << line_count
+ << ". Ignoring and continuing." << std::endl;
+ continue;
+ }
+
+ std::size_t pos = script_line.find("\t");
+ if (pos == std::string::npos) {
+ std::cerr << "Invalid line format detected, no tab character at line "
+ << line_count << ". \n\t" << script_line << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ const char *start = script_line.c_str();
+ char *end;
+ auto v = std::strtod(start, &end);
+
+ errno = 0;
+ if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
+ auto error = errno;
+ std::cerr << "Time value error at line " << line_count << ". \n\t"
+ << "value = " << script_line.substr(0, pos) << std::endl;
+ if (error != 0) {
+ std::cerr << "\t" << strerror(error) << std::endl;
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ timings.emplace_back(
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+ std::chrono::duration<double, std::milli>(v)));
+ uris.push_back(script_line.substr(pos + 1, script_line.size()));
+ }
+}
+} // namespace
+
+namespace {
+std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
+ size_t nreqs, size_t nclients,
+ size_t rate, size_t max_samples) {
+ std::stringstream rate_report;
+ if (config.is_rate_mode() && nclients > rate) {
+ rate_report << "Up to " << rate << " client(s) will be created every "
+ << util::duration_str(config.rate_period) << " ";
+ }
+
+ if (config.is_timing_based_mode()) {
+ std::cout << "spawning thread #" << id << ": " << nclients
+ << " total client(s). Timing-based test with "
+ << config.warm_up_time << "s of warm-up time and "
+ << config.duration << "s of main duration for measurements."
+ << std::endl;
+ } else {
+ std::cout << "spawning thread #" << id << ": " << nclients
+ << " total client(s). " << rate_report.str() << nreqs
+ << " total requests" << std::endl;
+ }
+
+ if (config.is_rate_mode()) {
+ return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate,
+ max_samples, &config);
+ } else {
+ // Here rate is same as client because the rate_timeout callback
+ // will be called only once
+ return std::make_unique<Worker>(id, ssl_ctx, nreqs, nclients, nclients,
+ max_samples, &config);
+ }
+}
+} // namespace
+
+namespace {
+int parse_header_table_size(uint32_t &dst, const char *opt,
+ const char *optarg) {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl;
+ return -1;
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--" << opt
+ << ": Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ return -1;
+ }
+
+ dst = n;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::string make_http_authority(const Config &config) {
+ std::string host;
+
+ if (util::numeric_host(config.host.c_str(), AF_INET6)) {
+ host += '[';
+ host += config.host;
+ host += ']';
+ } else {
+ host = config.host;
+ }
+
+ if (config.port != config.default_port) {
+ host += ':';
+ host += util::utos(config.port);
+ }
+
+ return host;
+}
+} // 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 server)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+constexpr char DEFAULT_ALPN_LIST[] = "h2,h2-16,h2-14,http/1.1";
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ print_usage(out);
+
+ auto config = Config();
+
+ 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. Definition of a base URI overrides all
+ scheme, host or port values.
+Options:
+ -n, --requests=<N>
+ Number of requests across all clients. If it is used
+ with --timing-script-file option, this option specifies
+ the number of requests each client performs rather than
+ the number of requests across all clients. This option
+ is ignored if timing-based benchmarking is enabled (see
+ --duration option).
+ Default: )"
+ << config.nreqs << R"(
+ -c, --clients=<N>
+ Number of concurrent clients. With -r option, this
+ specifies the maximum number of connections to be made.
+ Default: )"
+ << config.nclients << R"(
+ -t, --threads=<N>
+ Number of native threads.
+ Default: )"
+ << config.nthreads << R"(
+ -i, --input-file=<PATH>
+ Path of a file with multiple URIs are separated by EOLs.
+ This option will disable URIs getting from command-line.
+ If '-' is given as <PATH>, 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. Definition of a base URI overrides all
+ scheme, host or port values.
+ -m, --max-concurrent-streams=<N>
+ Max concurrent streams to issue per session. When
+ http/1.1 is used, this specifies the number of HTTP
+ pipelining requests in-flight.
+ Default: 1
+ -f, --max-frame-size=<SIZE>
+ Maximum frame size that the local endpoint is willing to
+ receive.
+ Default: )"
+ << util::utos_unit(config.max_frame_size) << R"(
+ -w, --window-bits=<N>
+ Sets the stream level initial window size to (2**<N>)-1.
+ For QUIC, <N> is capped to 26 (roughly 64MiB).
+ Default: )"
+ << config.window_bits << R"(
+ -W, --connection-window-bits=<N>
+ Sets the connection level initial window size to
+ (2**<N>)-1.
+ Default: )"
+ << config.connection_window_bits << R"(
+ -H, --header=<HEADER>
+ Add/Override a header to the requests.
+ --ciphers=<SUITE>
+ Set allowed cipher list for TLSv1.2 or earlier. The
+ format of the string is described in OpenSSL ciphers(1).
+ Default: )"
+ << config.ciphers << R"(
+ --tls13-ciphers=<SUITE>
+ Set allowed cipher list for TLSv1.3. The format of the
+ string is described in OpenSSL ciphers(1).
+ Default: )"
+ << config.tls13_ciphers << R"(
+ -p, --no-tls-proto=<PROTOID>
+ Specify ALPN identifier of the protocol to be used when
+ accessing http URI without SSL/TLS.
+ Available protocols: )"
+ << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"(
+ Default: )"
+ << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
+ -d, --data=<PATH>
+ Post FILE to server. The request method is changed to
+ POST. For http/1.1 connection, if -d is used, the
+ maximum number of in-flight pipelined requests is set to
+ 1.
+ -r, --rate=<N>
+ Specifies the fixed rate at which connections are
+ created. The rate must be a positive integer,
+ representing the number of connections to be made per
+ rate period. The maximum number of connections to be
+ made is given in -c option. This rate will be
+ distributed among threads as evenly as possible. For
+ example, with -t2 and -r4, each thread gets 2
+ connections per period. When the rate is 0, the program
+ will run as it normally does, creating connections at
+ whatever variable rate it wants. The default value for
+ this option is 0. -r and -D are mutually exclusive.
+ --rate-period=<DURATION>
+ Specifies the time period between creating connections.
+ The period must be a positive number, representing the
+ length of the period in time. This option is ignored if
+ the rate option is not used. The default value for this
+ option is 1s.
+ -D, --duration=<DURATION>
+ Specifies the main duration for the measurements in case
+ of timing-based benchmarking. -D and -r are mutually
+ exclusive.
+ --warm-up-time=<DURATION>
+ Specifies the time period before starting the actual
+ measurements, in case of timing-based benchmarking.
+ Needs to provided along with -D option.
+ -T, --connection-active-timeout=<DURATION>
+ Specifies the maximum time that h2load is willing to
+ keep a connection open, regardless of the activity on
+ said connection. <DURATION> must be a positive integer,
+ specifying the amount of time to wait. When no timeout
+ value is set (either active or inactive), h2load will
+ keep a connection open indefinitely, waiting for a
+ response.
+ -N, --connection-inactivity-timeout=<DURATION>
+ Specifies the amount of time that h2load is willing to
+ wait to see activity on a given connection. <DURATION>
+ must be a positive integer, specifying the amount of
+ time to wait. When no timeout value is set (either
+ active or inactive), h2load will keep a connection open
+ indefinitely, waiting for a response.
+ --timing-script-file=<PATH>
+ Path of a file containing one or more lines separated by
+ EOLs. Each script line is composed of two tab-separated
+ fields. The first field represents the time offset from
+ the start of execution, expressed as a positive value of
+ milliseconds with microsecond resolution. The second
+ field represents the URI. This option will disable URIs
+ getting from command-line. If '-' is given as <PATH>,
+ script lines will be read from stdin. Script lines are
+ used in order for each client. If -n is given, it must
+ be less than or equal to the number of script lines,
+ larger values are clamped to the number of script lines.
+ If -n is not given, the number of requests will default
+ to the number of script lines. The scheme, host and
+ port defined in the first URI are used solely. Values
+ contained in other URIs, if present, are ignored.
+ Definition of a base URI overrides all scheme, host or
+ port values. --timing-script-file and --rps are
+ mutually exclusive.
+ -B, --base-uri=(<URI>|unix:<PATH>)
+ Specify URI from which the scheme, host and port will be
+ used for all requests. The base URI overrides all
+ values defined either at the command line or inside
+ input files. If argument starts with "unix:", then the
+ rest of the argument will be treated as UNIX domain
+ socket path. The connection is made through that path
+ instead of TCP. In this case, scheme is inferred from
+ the first URI appeared in the command line or inside
+ input files as usual.
+ --alpn-list=<LIST>
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+ Default: )"
+ << DEFAULT_ALPN_LIST << R"(
+ --h1 Short hand for --alpn-list=http/1.1
+ --no-tls-proto=http/1.1, which effectively force
+ http/1.1 for both http and https URI.
+ --header-table-size=<SIZE>
+ Specify decoder header table size.
+ Default: )"
+ << util::utos_unit(config.header_table_size) << R"(
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+ Default: )"
+ << util::utos_unit(config.encoder_header_table_size) << R"(
+ --log-file=<PATH>
+ Write per-request information to a file as tab-separated
+ columns: start time as microseconds since epoch; HTTP
+ status code; microseconds until end of response. More
+ columns may be added later. Rows are ordered by end-of-
+ response time when using one worker thread, but may
+ appear slightly out of order with multiple threads due
+ to buffering. Status code is -1 for failed streams.
+ --qlog-file-base=<PATH>
+ Enable qlog output and specify base file name for qlogs.
+ Qlog is emitted for each connection. For a given base
+ name "base", each output file name becomes
+ "base.M.N.sqlog" where M is worker ID and N is client ID
+ (e.g. "base.0.3.sqlog"). Only effective in QUIC runs.
+ --connect-to=<HOST>[:<PORT>]
+ Host and port to connect instead of using the authority
+ in <URI>.
+ --rps=<N> Specify request per second for each client. --rps and
+ --timing-script-file are mutually exclusive.
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ --no-udp-gso
+ Disable UDP GSO.
+ --max-udp-payload-size=<SIZE>
+ Specify the maximum outgoing UDP datagram payload size.
+ --ktls Enable ktls.
+ -v, --verbose
+ Output debug information.
+ --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).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ std::string datafile;
+ std::string logfile;
+ bool nreqs_set_manually = false;
+ while (1) {
+ static int flag = 0;
+ constexpr static option long_options[] = {
+ {"requests", required_argument, nullptr, 'n'},
+ {"clients", required_argument, nullptr, 'c'},
+ {"data", required_argument, nullptr, 'd'},
+ {"threads", required_argument, nullptr, 't'},
+ {"max-concurrent-streams", required_argument, nullptr, 'm'},
+ {"window-bits", required_argument, nullptr, 'w'},
+ {"max-frame-size", required_argument, nullptr, 'f'},
+ {"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},
+ {"ciphers", required_argument, &flag, 2},
+ {"rate", required_argument, nullptr, 'r'},
+ {"connection-active-timeout", required_argument, nullptr, 'T'},
+ {"connection-inactivity-timeout", required_argument, nullptr, 'N'},
+ {"duration", required_argument, nullptr, 'D'},
+ {"timing-script-file", required_argument, &flag, 3},
+ {"base-uri", required_argument, nullptr, 'B'},
+ {"npn-list", required_argument, &flag, 4},
+ {"rate-period", required_argument, &flag, 5},
+ {"h1", no_argument, &flag, 6},
+ {"header-table-size", required_argument, &flag, 7},
+ {"encoder-header-table-size", required_argument, &flag, 8},
+ {"warm-up-time", required_argument, &flag, 9},
+ {"log-file", required_argument, &flag, 10},
+ {"connect-to", required_argument, &flag, 11},
+ {"rps", required_argument, &flag, 12},
+ {"groups", required_argument, &flag, 13},
+ {"tls13-ciphers", required_argument, &flag, 14},
+ {"no-udp-gso", no_argument, &flag, 15},
+ {"qlog-file-base", required_argument, &flag, 16},
+ {"max-udp-payload-size", required_argument, &flag, 17},
+ {"ktls", no_argument, &flag, 18},
+ {"alpn-list", required_argument, &flag, 19},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ auto c = getopt_long(argc, argv,
+ "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'n': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-n: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nreqs = n;
+ nreqs_set_manually = true;
+ break;
+ }
+ case 'c': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-c: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nclients = n;
+ break;
+ }
+ case 'd':
+ datafile = optarg;
+ break;
+ case 't': {
+#ifdef NOTHREADS
+ std::cerr << "-t: WARNING: Threading disabled at build time, "
+ << "no threads created." << std::endl;
+#else
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-t: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.nthreads = n;
+#endif // NOTHREADS
+ break;
+ }
+ case 'm': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+ break;
+ }
+ case 'f': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--max-frame-size: bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) < 16_k) {
+ std::cerr << "--max-frame-size: minimum 16384" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) > 16_m - 1) {
+ std::cerr << "--max-frame-size: maximum 16777215" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_frame_size = n;
+ 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 = optarg;
+ break;
+ case 'p': {
+ auto proto = StringRef{optarg};
+ if (util::strieq(StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID),
+ proto)) {
+ config.no_tls_proto = Config::PROTO_HTTP2;
+ } else if (util::strieq(NGHTTP2_H1_1, proto)) {
+ config.no_tls_proto = Config::PROTO_HTTP1_1;
+ } else {
+ std::cerr << "-p: unsupported protocol " << proto << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ case 'r': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-r: bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n == 0) {
+ std::cerr << "-r: the rate at which connections are made "
+ << "must be positive." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.rate = n;
+ break;
+ }
+ case 'T':
+ config.conn_active_timeout = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.conn_active_timeout)) {
+ std::cerr << "-T: bad value for the conn_active_timeout wait time: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'N':
+ config.conn_inactivity_timeout = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.conn_inactivity_timeout)) {
+ std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'B': {
+ auto arg = StringRef{optarg};
+ config.base_uri = "";
+ config.base_uri_unix = false;
+
+ if (util::istarts_with_l(arg, UNIX_PATH_PREFIX)) {
+ // UNIX domain socket path
+ sockaddr_un un;
+
+ auto path = StringRef{std::begin(arg) + str_size(UNIX_PATH_PREFIX),
+ std::end(arg)};
+
+ if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) {
+ std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri_unix = true;
+
+ auto &unix_addr = config.unix_addr;
+ std::copy(std::begin(path), std::end(path), unix_addr.sun_path);
+ unix_addr.sun_path[path.size()] = '\0';
+ unix_addr.sun_family = AF_UNIX;
+
+ break;
+ }
+
+ if (!parse_base_uri(arg)) {
+ std::cerr << "--base-uri: invalid base URI: " << arg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.base_uri = arg.str();
+ break;
+ }
+ case 'D':
+ config.duration = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.duration)) {
+ std::cerr << "-D: value error " << 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);
+ case 2:
+ // ciphers option
+ config.ciphers = optarg;
+ break;
+ case 3:
+ // timing-script option
+ config.ifile = optarg;
+ config.timing_script = true;
+ break;
+ case 5:
+ // rate-period
+ config.rate_period = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.rate_period)) {
+ std::cerr << "--rate-period: value error " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 6:
+ // --h1
+ config.alpn_list =
+ util::parse_config_str_list(StringRef::from_lit("http/1.1"));
+ config.no_tls_proto = Config::PROTO_HTTP1_1;
+ break;
+ case 7:
+ // --header-table-size
+ if (parse_header_table_size(config.header_table_size,
+ "header-table-size", optarg) != 0) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 8:
+ // --encoder-header-table-size
+ if (parse_header_table_size(config.encoder_header_table_size,
+ "encoder-header-table-size", optarg) != 0) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 9:
+ // --warm-up-time
+ config.warm_up_time = util::parse_duration_with_unit(optarg);
+ if (!std::isfinite(config.warm_up_time)) {
+ std::cerr << "--warm-up-time: value error " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 10:
+ // --log-file
+ logfile = optarg;
+ break;
+ case 11: {
+ // --connect-to
+ auto p = util::split_hostport(StringRef{optarg});
+ int64_t port = 0;
+ if (p.first.empty() ||
+ (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) {
+ std::cerr << "--connect-to: Invalid value " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.connect_to_host = p.first.str();
+ config.connect_to_port = port;
+ break;
+ }
+ case 12: {
+ char *end;
+ auto v = std::strtod(optarg, &end);
+ if (end == optarg || *end != '\0' || !std::isfinite(v) ||
+ 1. / v < 1e-6) {
+ std::cerr << "--rps: Invalid value " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.rps = v;
+ break;
+ }
+ case 13:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 14:
+ // --tls13-ciphers
+ config.tls13_ciphers = optarg;
+ break;
+ case 15:
+ // --no-udp-gso
+ config.no_udp_gso = true;
+ break;
+ case 16:
+ // --qlog-file-base
+ config.qlog_file_base = optarg;
+ break;
+ case 17: {
+ // --max-udp-payload-size
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--max-udp-payload-size: bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (static_cast<uint64_t>(n) > 64_k) {
+ std::cerr << "--max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_udp_payload_size = n;
+ break;
+ }
+ case 18:
+ // --ktls
+ config.ktls = true;
+ break;
+ case 4:
+ // npn-list option
+ std::cerr << "--npn-list: deprecated. Use --alpn-list instead."
+ << std::endl;
+ // fall through
+ case 19:
+ // alpn-list option
+ config.alpn_list = util::parse_config_str_list(StringRef{optarg});
+ break;
+ }
+ 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.nclients == 0) {
+ std::cerr << "-c: the number of clients must be strictly greater than 0."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.alpn_list.empty()) {
+ config.alpn_list =
+ util::parse_config_str_list(StringRef::from_lit(DEFAULT_ALPN_LIST));
+ }
+
+ // serialize the APLN tokens
+ for (auto &proto : config.alpn_list) {
+ proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
+ }
+
+ 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.timing_script) {
+ 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);
+ }
+ } else {
+ if (config.ifile == "-") {
+ read_script_from_file(std::cin, config.timings, uris);
+ } else {
+ std::ifstream infile(config.ifile);
+ if (!infile) {
+ std::cerr << "cannot read input file: " << config.ifile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ read_script_from_file(infile, config.timings, uris);
+ }
+
+ if (nreqs_set_manually) {
+ if (config.nreqs > uris.size()) {
+ std::cerr << "-n: the number of requests must be less than or equal "
+ "to the number of timing script entries. Setting number "
+ "of requests to "
+ << uris.size() << std::endl;
+
+ config.nreqs = uris.size();
+ }
+ } else {
+ config.nreqs = uris.size();
+ }
+ }
+
+ reqlines = parse_uris(std::begin(uris), std::end(uris));
+ }
+
+ if (reqlines.empty()) {
+ std::cerr << "No URI given" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.is_timing_based_mode() && config.is_rate_mode()) {
+ std::cerr << "-r, -D: they are mutually exclusive." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.timing_script && config.rps_enabled()) {
+ std::cerr << "--timing-script-file, --rps: they are mutually exclusive."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nreqs == 0 && !config.is_timing_based_mode()) {
+ std::cerr << "-n: the number of requests must be strictly greater than 0 "
+ "if timing-based test is not being run."
+ << 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.nthreads > std::thread::hardware_concurrency()) {
+ std::cerr << "-t: warning: the number of threads is greater than hardware "
+ << "cores." << std::endl;
+ }
+
+ // With timing script, we don't distribute config.nreqs to each
+ // client or thread.
+ if (!config.timing_script && config.nreqs < config.nclients &&
+ !config.is_timing_based_mode()) {
+ std::cerr << "-n, -c: the number of requests must be greater than or "
+ << "equal to the clients." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.nclients < config.nthreads) {
+ std::cerr << "-c, -t: the number of clients must be greater than or equal "
+ << "to the number of threads." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.is_timing_based_mode()) {
+ config.nreqs = 0;
+ }
+
+ if (config.is_rate_mode()) {
+ if (config.rate < config.nthreads) {
+ std::cerr << "-r, -t: the connection rate must be greater than or equal "
+ << "to the number of threads." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.rate > config.nclients) {
+ std::cerr << "-r, -c: the connection rate must be smaller than or equal "
+ "to the number of clients."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!datafile.empty()) {
+ config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY);
+ if (config.data_fd == -1) {
+ std::cerr << "-d: Could not open file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ struct stat data_stat;
+ if (fstat(config.data_fd, &data_stat) == -1) {
+ std::cerr << "-d: Could not stat file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data_length = data_stat.st_size;
+ auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED,
+ config.data_fd, 0);
+ if (addr == MAP_FAILED) {
+ std::cerr << "-d: Could not mmap file " << datafile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data = static_cast<uint8_t *>(addr);
+ }
+
+ if (!logfile.empty()) {
+ config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR | S_IRGRP);
+ if (config.log_fd == -1) {
+ std::cerr << "--log-file: Could not open file " << logfile << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!config.qlog_file_base.empty() && !config.is_quic()) {
+ std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring."
+ << std::endl;
+ }
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+
+ auto ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ std::cerr << "Failed to create SSL_CTX: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+ if (config.is_quic()) {
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+ if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) {
+ std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+ if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
+ std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+ } else if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "Could not set TLS versions" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) {
+ std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
+ std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
+ std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ std::vector<unsigned char> proto_list;
+ for (const auto &proto : config.alpn_list) {
+ std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
+ }
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+ }
+ }
+
+ std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
+ Headers shared_nva;
+ shared_nva.emplace_back(":scheme", config.scheme);
+ shared_nva.emplace_back(":authority", make_http_authority(config));
+ shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
+ shared_nva.emplace_back("user-agent", user_agent);
+
+ // list header fields that can be overridden.
+ auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
+ ":scheme", "user-agent");
+
+ 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);
+ }
+ }
+
+ std::string content_length_str;
+ if (config.data_fd != -1) {
+ content_length_str = util::utos(config.data_length);
+ }
+
+ auto method_it =
+ std::find_if(std::begin(shared_nva), std::end(shared_nva),
+ [](const Header &nv) { return nv.name == ":method"; });
+ assert(method_it != std::end(shared_nva));
+
+ config.h1reqs.reserve(reqlines.size());
+ config.nva.reserve(reqlines.size());
+
+ for (auto &req : reqlines) {
+ // For HTTP/1.1
+ auto h1req = (*method_it).value;
+ h1req += ' ';
+ h1req += req;
+ h1req += " HTTP/1.1\r\n";
+ for (auto &nv : shared_nva) {
+ if (nv.name == ":authority") {
+ h1req += "Host: ";
+ h1req += nv.value;
+ h1req += "\r\n";
+ continue;
+ }
+ if (nv.name[0] == ':') {
+ continue;
+ }
+ h1req += nv.name;
+ h1req += ": ";
+ h1req += nv.value;
+ h1req += "\r\n";
+ }
+
+ if (!content_length_str.empty()) {
+ h1req += "Content-Length: ";
+ h1req += content_length_str;
+ h1req += "\r\n";
+ }
+ h1req += "\r\n";
+
+ config.h1reqs.push_back(std::move(h1req));
+
+ // For nghttp2
+ std::vector<nghttp2_nv> nva;
+ // 2 for :path, and possible content-length
+ nva.reserve(2 + shared_nva.size());
+
+ 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));
+ }
+
+ if (!content_length_str.empty()) {
+ nva.push_back(http2::make_nv(StringRef::from_lit("content-length"),
+ StringRef{content_length_str}));
+ }
+
+ config.nva.push_back(std::move(nva));
+ }
+
+ // Don't DOS our server!
+ if (config.host == "nghttp2.org") {
+ std::cerr << "Using h2load against public server " << config.host
+ << " should be prohibited." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ resolve_host();
+
+ std::cout << "starting benchmark..." << std::endl;
+
+ std::vector<std::unique_ptr<Worker>> workers;
+ workers.reserve(config.nthreads);
+
+#ifndef NOTHREADS
+ size_t nreqs_per_thread = 0;
+ ssize_t nreqs_rem = 0;
+
+ if (!config.timing_script) {
+ nreqs_per_thread = config.nreqs / config.nthreads;
+ nreqs_rem = config.nreqs % config.nthreads;
+ }
+
+ size_t nclients_per_thread = config.nclients / config.nthreads;
+ ssize_t nclients_rem = config.nclients % config.nthreads;
+
+ size_t rate_per_thread = config.rate / config.nthreads;
+ ssize_t rate_per_thread_rem = config.rate % config.nthreads;
+
+ size_t max_samples_per_thread =
+ std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
+
+ std::mutex mu;
+ std::condition_variable cv;
+ auto ready = false;
+
+ std::vector<std::future<void>> futures;
+ for (size_t i = 0; i < config.nthreads; ++i) {
+ auto rate = rate_per_thread;
+ if (rate_per_thread_rem > 0) {
+ --rate_per_thread_rem;
+ ++rate;
+ }
+ auto nclients = nclients_per_thread;
+ if (nclients_rem > 0) {
+ --nclients_rem;
+ ++nclients;
+ }
+
+ size_t nreqs;
+ if (config.timing_script) {
+ // With timing script, each client issues config.nreqs requests.
+ // We divide nreqs by number of clients in Worker ctor to
+ // distribute requests to those clients evenly, so multiply
+ // config.nreqs here by config.nclients.
+ nreqs = config.nreqs * nclients;
+ } else {
+ nreqs = nreqs_per_thread;
+ if (nreqs_rem > 0) {
+ --nreqs_rem;
+ ++nreqs;
+ }
+ }
+
+ workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
+ max_samples_per_thread));
+ auto &worker = workers.back();
+ futures.push_back(
+ std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
+ {
+ std::unique_lock<std::mutex> ulk(mu);
+ cv.wait(ulk, [&ready] { return ready; });
+ }
+ worker->run();
+ }));
+ }
+
+ {
+ std::lock_guard<std::mutex> lg(mu);
+ ready = true;
+ cv.notify_all();
+ }
+
+ auto start = std::chrono::steady_clock::now();
+
+ for (auto &fut : futures) {
+ fut.get();
+ }
+
+#else // NOTHREADS
+ auto rate = config.rate;
+ auto nclients = config.nclients;
+ auto nreqs =
+ config.timing_script ? config.nreqs * config.nclients : config.nreqs;
+
+ workers.push_back(
+ create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
+
+ auto start = std::chrono::steady_clock::now();
+
+ workers.back()->run();
+#endif // NOTHREADS
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+
+ Stats stats(0, 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_timedout += s.req_timedout;
+ 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_head_decomp += s.bytes_head_decomp;
+ stats.bytes_body += s.bytes_body;
+ stats.udp_dgram_recv += s.udp_dgram_recv;
+ stats.udp_dgram_sent += s.udp_dgram_sent;
+
+ for (size_t i = 0; i < stats.status.size(); ++i) {
+ stats.status[i] += s.status[i];
+ }
+ }
+
+ auto ts = 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
+ double rps = 0;
+ int64_t bps = 0;
+ if (duration.count() > 0) {
+ if (config.is_timing_based_mode()) {
+ // we only want to consider the main duration if warm-up is given
+ rps = stats.req_success / config.duration;
+ bps = stats.bytes_total / config.duration;
+ } else {
+ auto secd = std::chrono::duration_cast<
+ std::chrono::duration<double, std::chrono::seconds::period>>(
+ duration);
+ rps = stats.req_success / secd.count();
+ bps = stats.bytes_total / secd.count();
+ }
+ }
+
+ double header_space_savings = 0.;
+ if (stats.bytes_head_decomp > 0) {
+ header_space_savings =
+ 1. - static_cast<double>(stats.bytes_head) / stats.bytes_head_decomp;
+ }
+
+ std::cout << std::fixed << std::setprecision(2) << R"(
+finished in )"
+ << util::format_duration(duration) << ", " << rps << " req/s, "
+ << util::utos_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
+ << " errored, " << stats.req_timedout << R"( timeout
+status codes: )"
+ << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
+ << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
+traffic: )" << util::utos_funit(stats.bytes_total)
+ << "B (" << stats.bytes_total << ") total, "
+ << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
+ << ") headers (space savings " << header_space_savings * 100
+ << "%), " << util::utos_funit(stats.bytes_body) << "B ("
+ << stats.bytes_body << R"() data)" << std::endl;
+#ifdef ENABLE_HTTP3
+ if (config.is_quic()) {
+ std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, "
+ << stats.udp_dgram_recv << " received" << std::endl;
+ }
+#endif // ENABLE_HTTP3
+ std::cout
+ << R"( min max mean sd +/- sd
+time for request: )"
+ << std::setw(10) << util::format_duration(ts.request.min) << " "
+ << std::setw(10) << util::format_duration(ts.request.max) << " "
+ << std::setw(10) << util::format_duration(ts.request.mean) << " "
+ << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9)
+ << util::dtos(ts.request.within_sd) << "%"
+ << "\ntime for connect: " << std::setw(10)
+ << util::format_duration(ts.connect.min) << " " << std::setw(10)
+ << util::format_duration(ts.connect.max) << " " << std::setw(10)
+ << util::format_duration(ts.connect.mean) << " " << std::setw(10)
+ << util::format_duration(ts.connect.sd) << std::setw(9)
+ << util::dtos(ts.connect.within_sd) << "%"
+ << "\ntime to 1st byte: " << std::setw(10)
+ << util::format_duration(ts.ttfb.min) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.max) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
+ << util::format_duration(ts.ttfb.sd) << std::setw(9)
+ << util::dtos(ts.ttfb.within_sd) << "%"
+ << "\nreq/s : " << std::setw(10) << ts.rps.min << " "
+ << std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean
+ << " " << std::setw(10) << ts.rps.sd << std::setw(9)
+ << util::dtos(ts.rps.within_sd) << "%" << std::endl;
+
+ SSL_CTX_free(ssl_ctx);
+
+ if (config.log_fd != -1) {
+ close(config.log_fd);
+ }
+
+ 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
index 0000000..11bb54c
--- /dev/null
+++ b/src/h2load.h
@@ -0,0 +1,510 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <sys/un.h>
+
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <memory>
+#include <chrono>
+#include <array>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <ngtcp2/ngtcp2_crypto.h>
+#endif // ENABLE_HTTP3
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "http2.h"
+#ifdef ENABLE_HTTP3
+# include "quic.h"
+#endif // ENABLE_HTTP3
+#include "memchunk.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+constexpr auto BACKOFF_WRITE_BUFFER_THRES = 16_k;
+
+class Session;
+struct Worker;
+
+struct Config {
+ std::vector<std::vector<nghttp2_nv>> nva;
+ std::vector<std::string> h1reqs;
+ std::vector<std::chrono::steady_clock::duration> timings;
+ nghttp2::Headers custom_headers;
+ std::string scheme;
+ std::string host;
+ std::string connect_to_host;
+ std::string ifile;
+ std::string ciphers;
+ std::string tls13_ciphers;
+ // supported groups (or curves).
+ std::string groups;
+ // length of upload data
+ int64_t data_length;
+ // memory mapped upload data
+ uint8_t *data;
+ 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;
+ size_t max_frame_size;
+ // rate at which connections should be made
+ size_t rate;
+ ev_tstamp rate_period;
+ // amount of time for main measurements in timing-based test
+ ev_tstamp duration;
+ // amount of time to wait before starting measurements in timing-based test
+ ev_tstamp warm_up_time;
+ // amount of time to wait for activity on a given connection
+ ev_tstamp conn_active_timeout;
+ // amount of time to wait after the last request is made on a connection
+ ev_tstamp conn_inactivity_timeout;
+ enum { PROTO_HTTP2, PROTO_HTTP1_1 } no_tls_proto;
+ uint32_t header_table_size;
+ uint32_t encoder_header_table_size;
+ // file descriptor for upload data
+ int data_fd;
+ // file descriptor to write per-request stats to.
+ int log_fd;
+ // base file name of qlog output files
+ std::string qlog_file_base;
+ uint16_t port;
+ uint16_t default_port;
+ uint16_t connect_to_port;
+ bool verbose;
+ bool timing_script;
+ std::string base_uri;
+ // true if UNIX domain socket is used. In this case, base_uri is
+ // not used in usual way.
+ bool base_uri_unix;
+ // used when UNIX domain socket is used (base_uri_unix is true).
+ sockaddr_un unix_addr;
+ // list of supported ALPN protocol strings in the order of
+ // preference.
+ std::vector<std::string> alpn_list;
+ // The number of request per second for each client.
+ double rps;
+ // Disables GSO for UDP connections.
+ bool no_udp_gso;
+ // The maximum UDP datagram payload size to send.
+ size_t max_udp_payload_size;
+ // Enable ktls.
+ bool ktls;
+
+ Config();
+ ~Config();
+
+ bool is_rate_mode() const;
+ bool is_timing_based_mode() const;
+ bool has_base_uri() const;
+ bool rps_enabled() const;
+ bool is_quic() const;
+};
+
+struct RequestStat {
+ // time point when request was sent
+ std::chrono::steady_clock::time_point request_time;
+ // same, but in wall clock reference frame
+ std::chrono::system_clock::time_point request_wall_time;
+ // time point when stream was closed
+ std::chrono::steady_clock::time_point stream_close_time;
+ // upload data length sent so far
+ int64_t data_offset;
+ // HTTP status code
+ int status;
+ // 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 ClientStat {
+ // time client started (i.e., first connect starts)
+ std::chrono::steady_clock::time_point client_start_time;
+ // time client end (i.e., client somehow processed all requests it
+ // is responsible for, and disconnected)
+ std::chrono::steady_clock::time_point client_end_time;
+ // The number of requests completed successful, but not necessarily
+ // means successful HTTP status code.
+ size_t req_success;
+
+ // The following 3 numbers are overwritten each time when connection
+ // is made.
+
+ // time connect starts
+ std::chrono::steady_clock::time_point connect_start_time;
+ // time to connect
+ std::chrono::steady_clock::time_point connect_time;
+ // time to first byte (TTFB)
+ std::chrono::steady_clock::time_point ttfb;
+};
+
+struct SDStat {
+ // min, max, mean and sd (standard deviation)
+ double min, max, mean, sd;
+ // percentage of samples inside mean -/+ sd
+ double within_sd;
+};
+
+struct SDStats {
+ // time for request
+ SDStat request;
+ // time for connect
+ SDStat connect;
+ // time to first byte (TTFB)
+ SDStat ttfb;
+ // request per second for each client
+ SDStat rps;
+};
+
+struct Stats {
+ Stats(size_t req_todo, size_t nclients);
+ // 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 successful, 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 requests that failed due to timeout.
+ size_t req_timedout;
+ // 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 for header fields. This is
+ // compressed version.
+ int64_t bytes_head;
+ // The number of bytes received for header fields after they are
+ // decompressed.
+ int64_t bytes_head_decomp;
+ // 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;
+ // The statistics per client
+ std::vector<ClientStat> client_stats;
+ // The number of UDP datagrams received.
+ size_t udp_dgram_recv;
+ // The number of UDP datagrams sent.
+ size_t udp_dgram_sent;
+};
+
+enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
+
+// This type tells whether the client is in warmup phase or not or is over
+enum class Phase {
+ INITIAL_IDLE, // Initial idle state before warm-up phase
+ WARM_UP, // Warm up phase when no measurements are done
+ MAIN_DURATION, // Main measurement phase; if timing-based
+ // test is not run, this is the default phase
+ DURATION_OVER // This phase occurs after the measurements are over
+};
+
+struct Client;
+
+// We use reservoir sampling method
+struct Sampling {
+ // maximum number of samples
+ size_t max_samples;
+ // number of samples seen, including discarded samples.
+ size_t n;
+};
+
+struct Worker {
+ MemchunkPool mcpool;
+ std::mt19937 randgen;
+ Stats stats;
+ Sampling request_times_smp;
+ Sampling client_smp;
+ struct ev_loop *loop;
+ SSL_CTX *ssl_ctx;
+ Config *config;
+ size_t progress_interval;
+ uint32_t id;
+ bool tls_info_report_done;
+ bool app_info_report_done;
+ size_t nconns_made;
+ // number of clients this worker handles
+ size_t nclients;
+ // number of requests each client issues
+ size_t nreqs_per_client;
+ // at most nreqs_rem clients get an extra request
+ size_t nreqs_rem;
+ size_t rate;
+ // maximum number of samples in this worker thread
+ size_t max_samples;
+ ev_timer timeout_watcher;
+ // The next client ID this worker assigns
+ uint32_t next_client_id;
+ // Keeps track of the current phase (for timing-based experiment) for the
+ // worker
+ Phase current_phase;
+ // We need to keep track of the clients in order to stop them when needed
+ std::vector<Client *> clients;
+ // This is only active when there is not a bounded number of requests
+ // specified
+ ev_timer duration_watcher;
+ ev_timer warmup_watcher;
+
+ Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
+ size_t rate, size_t max_samples, Config *config);
+ ~Worker();
+ Worker(Worker &&o) = default;
+ void run();
+ void sample_req_stat(RequestStat *req_stat);
+ void sample_client_stat(ClientStat *cstat);
+ void report_progress();
+ void report_rate_progress();
+ // This function calls the destructors of all the clients.
+ void stop_all_clients();
+ // This function frees a client from the list of clients for this Worker.
+ void free_client(Client *);
+};
+
+struct Stream {
+ RequestStat req_stat;
+ int status_success;
+ Stream();
+};
+
+struct Client {
+ DefaultMemchunks wb;
+ std::unordered_map<int32_t, Stream> streams;
+ ClientStat cstat;
+ std::unique_ptr<Session> session;
+ ev_io wev;
+ ev_io rev;
+ std::function<int(Client &)> readfn, writefn;
+ Worker *worker;
+ SSL *ssl;
+#ifdef ENABLE_HTTP3
+ struct {
+ ngtcp2_crypto_conn_ref conn_ref;
+ ev_timer pkt_timer;
+ ngtcp2_conn *conn;
+ ngtcp2_ccerr last_error;
+ bool close_requested;
+ FILE *qlog_file;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ struct {
+ Address remote_addr;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx;
+ } quic;
+#endif // ENABLE_HTTP3
+ ev_timer request_timeout_watcher;
+ addrinfo *next_addr;
+ // Address for the current address. When try_new_connection() is
+ // used and current_addr is not nullptr, it is used instead of
+ // trying next address though next_addr. To try new address, set
+ // nullptr to current_addr before calling connect().
+ addrinfo *current_addr;
+ size_t reqidx;
+ ClientState state;
+ // The number of requests this client has to issue.
+ size_t req_todo;
+ // The number of requests left to issue
+ size_t req_left;
+ // The number of requests currently have started, but not abandoned
+ // or finished.
+ size_t req_inflight;
+ // 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;
+ // The client id per worker
+ uint32_t id;
+ int fd;
+ Address local_addr;
+ ev_timer conn_active_watcher;
+ ev_timer conn_inactivity_watcher;
+ std::string selected_proto;
+ bool new_connection_requested;
+ // true if the current connection will be closed, and no more new
+ // request cannot be processed.
+ bool final;
+ // rps_watcher is a timer to invoke callback periodically to
+ // generate a new request.
+ ev_timer rps_watcher;
+ // The timestamp that starts the period which contributes to the
+ // next request generation.
+ std::chrono::steady_clock::time_point rps_duration_started;
+ // The number of requests allowed by rps, but limited by stream
+ // concurrency.
+ size_t rps_req_pending;
+ // The number of in-flight streams. req_inflight has similar value
+ // but it only measures requests made during Phase::MAIN_DURATION.
+ // rps_req_inflight measures the number of requests in all phases,
+ // and it is only used if --rps is given.
+ size_t rps_req_inflight;
+
+ enum { ERR_CONNECT_FAIL = -100 };
+
+ Client(uint32_t id, Worker *worker, size_t req_todo);
+ ~Client();
+ int make_socket(addrinfo *addr);
+ int connect();
+ void disconnect();
+ void fail();
+ // Call this function when do_read() returns -1. This function
+ // tries to connect to the remote host again if it is requested. If
+ // so, this function returns 0, and this object should be retained.
+ // Otherwise, this function returns -1, and this object should be
+ // deleted.
+ int try_again_or_fail();
+ void timeout();
+ void restart_timeout();
+ int submit_request();
+ void process_request_failure();
+ void process_timedout_streams();
+ void process_abandoned_streams();
+ void report_tls_info();
+ void report_app_info();
+ void terminate_session();
+ // Asks client to create new connection, instead of just fail.
+ void try_new_connection();
+
+ int do_read();
+ int do_write();
+
+ // low-level I/O callback functions called by do_read/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 connection_made();
+
+ 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_status_code(int32_t stream_id, uint16_t status);
+ // |success| == true means that the request/response was exchanged
+ // |successfully, but it does not mean response carried successful
+ // |HTTP status code.
+ void on_stream_close(int32_t stream_id, bool success, bool final = false);
+ // Returns RequestStat for |stream_id|. This function must be
+ // called after on_request(stream_id), and before
+ // on_stream_close(stream_id, ...). Otherwise, this will return
+ // nullptr.
+ RequestStat *get_req_stat(int32_t stream_id);
+
+ void record_request_time(RequestStat *req_stat);
+ void record_connect_start_time();
+ void record_connect_time();
+ void record_ttfb();
+ void clear_connect_times();
+ void record_client_start_time();
+ void record_client_end_time();
+
+ void signal_write();
+
+#ifdef ENABLE_HTTP3
+ // QUIC
+ int quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
+ const sockaddr *remote_addr, socklen_t remote_addrlen);
+ void quic_free();
+ int read_quic();
+ int write_quic();
+ int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ void on_send_blocked(const ngtcp2_addr &remote_addr, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ int send_blocked_packet();
+ void quic_close_connection();
+
+ int quic_handshake_completed();
+ int quic_recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen);
+ int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen);
+ int quic_stream_close(int64_t stream_id, uint64_t app_error_code);
+ int quic_stream_reset(int64_t stream_id, uint64_t app_error_code);
+ int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int quic_extend_max_local_streams();
+ int quic_extend_max_stream_data(int64_t stream_id);
+
+ int quic_write_client_handshake(ngtcp2_encryption_level level,
+ const uint8_t *data, size_t datalen);
+ int quic_pkt_timeout();
+ void quic_restart_pkt_timer();
+ void quic_write_qlog(const void *data, size_t datalen);
+ int quic_make_http3_session();
+#endif // ENABLE_HTTP3
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_H
diff --git a/src/h2load_http1_session.cc b/src/h2load_http1_session.cc
new file mode 100644
index 0000000..6bdcd04
--- /dev/null
+++ b/src/h2load_http1_session.cc
@@ -0,0 +1,306 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 British Broadcasting Corporation
+ *
+ * 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_http1_session.h"
+
+#include <cassert>
+#include <cerrno>
+
+#include "h2load.h"
+#include "util.h"
+#include "template.h"
+
+#include <iostream>
+#include <fstream>
+
+using namespace nghttp2;
+
+namespace h2load {
+
+namespace {
+// HTTP response message begin
+int htp_msg_begincb(llhttp_t *htp) {
+ auto session = static_cast<Http1Session *>(htp->data);
+
+ if (session->stream_resp_counter_ > session->stream_req_counter_) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// HTTP response status code
+int htp_statuscb(llhttp_t *htp, const char *at, size_t length) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ if (htp->status_code / 100 == 1) {
+ return 0;
+ }
+
+ client->on_status_code(session->stream_resp_counter_, htp->status_code);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// HTTP response message complete
+int htp_msg_completecb(llhttp_t *htp) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ if (htp->status_code / 100 == 1) {
+ return 0;
+ }
+
+ client->final = llhttp_should_keep_alive(htp) == 0;
+ auto req_stat = client->get_req_stat(session->stream_resp_counter_);
+
+ assert(req_stat);
+
+ auto config = client->worker->config;
+ if (req_stat->data_offset >= config->data_length) {
+ client->on_stream_close(session->stream_resp_counter_, true, client->final);
+ }
+
+ session->stream_resp_counter_ += 2;
+
+ if (client->final) {
+ session->stream_req_counter_ = session->stream_resp_counter_;
+
+ // Connection is going down. If we have still request to do,
+ // create new connection and keep on doing the job.
+ if (client->req_left) {
+ client->try_new_connection();
+ }
+
+ return HPE_PAUSED;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->worker->stats.bytes_head += len;
+ client->worker->stats.bytes_head_decomp += len;
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->worker->stats.bytes_head += len;
+ client->worker->stats.bytes_head_decomp += len;
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ return !http2::expect_response_body(htp->status_code);
+}
+} // namespace
+
+namespace {
+int htp_body_cb(llhttp_t *htp, const char *data, size_t len) {
+ auto session = static_cast<Http1Session *>(htp->data);
+ auto client = session->get_client();
+
+ client->record_ttfb();
+ client->worker->stats.bytes_body += len;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ htp_statuscb, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_body_cb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+Http1Session::Http1Session(Client *client)
+ : stream_req_counter_(1),
+ stream_resp_counter_(1),
+ client_(client),
+ htp_(),
+ complete_(false) {
+ llhttp_init(&htp_, HTTP_RESPONSE, &htp_hooks);
+ htp_.data = this;
+}
+
+Http1Session::~Http1Session() {}
+
+void Http1Session::on_connect() { client_->signal_write(); }
+
+int Http1Session::submit_request() {
+ auto config = client_->worker->config;
+ const auto &req = config->h1reqs[client_->reqidx];
+ client_->reqidx++;
+
+ if (client_->reqidx == config->h1reqs.size()) {
+ client_->reqidx = 0;
+ }
+
+ client_->on_request(stream_req_counter_);
+
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+
+ client_->record_request_time(req_stat);
+ client_->wb.append(req);
+
+ if (config->data_fd == -1 || config->data_length == 0) {
+ // increment for next request
+ stream_req_counter_ += 2;
+
+ return 0;
+ }
+
+ return on_write();
+}
+
+int Http1Session::on_read(const uint8_t *data, size_t len) {
+ auto htperr =
+ llhttp_execute(&htp_, reinterpret_cast<const char *>(data), len);
+ auto nread = htperr == HPE_OK
+ ? len
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(&htp_)) -
+ data);
+
+ if (client_->worker->config->verbose) {
+ std::cout.write(reinterpret_cast<const char *>(data), nread);
+ }
+
+ if (htperr == HPE_PAUSED) {
+ // pause is done only when connection: close is requested
+ return -1;
+ }
+
+ if (htperr != HPE_OK) {
+ std::cerr << "[ERROR] HTTP parse error: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&htp_) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http1Session::on_write() {
+ if (complete_) {
+ return -1;
+ }
+
+ auto config = client_->worker->config;
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+ if (!req_stat) {
+ return 0;
+ }
+
+ if (req_stat->data_offset < config->data_length) {
+ auto req_stat = client_->get_req_stat(stream_req_counter_);
+ auto &wb = client_->wb;
+
+ // TODO unfortunately, wb has no interface to use with read(2)
+ // family functions.
+ std::array<uint8_t, 16_k> buf;
+
+ ssize_t nread;
+ while ((nread = pread(config->data_fd, buf.data(), buf.size(),
+ req_stat->data_offset)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ return -1;
+ }
+
+ req_stat->data_offset += nread;
+
+ wb.append(buf.data(), nread);
+
+ if (client_->worker->config->verbose) {
+ std::cout << "[send " << nread << " byte(s)]" << std::endl;
+ }
+
+ if (req_stat->data_offset == config->data_length) {
+ // increment for next request
+ stream_req_counter_ += 2;
+
+ if (stream_resp_counter_ == stream_req_counter_) {
+ // Response has already been received
+ client_->on_stream_close(stream_resp_counter_ - 2, true,
+ client_->final);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void Http1Session::terminate() { complete_ = true; }
+
+Client *Http1Session::get_client() { return client_; }
+
+size_t Http1Session::max_concurrent_streams() {
+ auto config = client_->worker->config;
+
+ return config->data_fd == -1 ? config->max_concurrent_streams : 1;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http1_session.h b/src/h2load_http1_session.h
new file mode 100644
index 0000000..cc10f50
--- /dev/null
+++ b/src/h2load_http1_session.h
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 British Broadcasting Corporation
+ *
+ * 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_HTTP1_SESSION_H
+#define H2LOAD_HTTP1_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+namespace h2load {
+
+struct Client;
+
+class Http1Session : public Session {
+public:
+ Http1Session(Client *client);
+ virtual ~Http1Session();
+ virtual void on_connect();
+ virtual int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+ Client *get_client();
+ int32_t stream_req_counter_;
+ int32_t stream_resp_counter_;
+
+private:
+ Client *client_;
+ llhttp_t htp_;
+ bool complete_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP1_SESSION_H
diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc
new file mode 100644
index 0000000..b058a33
--- /dev/null
+++ b/src/h2load_http2_session.cc
@@ -0,0 +1,314 @@
+/*
+ * 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 <cerrno>
+#include <iostream>
+
+#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) {
+ return 0;
+ }
+ client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
+ client->worker->stats.bytes_head_decomp += namelen + valuelen;
+
+ if (client->worker->config->verbose) {
+ std::cout << "[stream_id=" << frame->hd.stream_id << "] ";
+ std::cout.write(reinterpret_cast<const char *>(name), namelen);
+ std::cout << ": ";
+ std::cout.write(reinterpret_cast<const char *>(value), valuelen);
+ std::cout << "\n";
+ }
+
+ 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);
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ client->worker->stats.bytes_head +=
+ frame->hd.length - frame->headers.padlen -
+ ((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0);
+ // fall through
+ case NGHTTP2_DATA:
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ client->record_ttfb();
+ }
+ 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 client = static_cast<Client *>(user_data);
+ client->record_ttfb();
+ 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);
+ client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
+
+ 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);
+ auto req_stat = client->get_req_stat(frame->hd.stream_id);
+ assert(req_stat);
+ client->record_request_time(req_stat);
+
+ return 0;
+}
+} // 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 client = static_cast<Client *>(user_data);
+ auto config = client->worker->config;
+ auto req_stat = client->get_req_stat(stream_id);
+ assert(req_stat);
+ ssize_t nread;
+ while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
+ -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ req_stat->data_offset += nread;
+
+ if (req_stat->data_offset == config->data_length) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ return nread;
+ }
+
+ if (req_stat->data_offset > config->data_length || nread == 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ 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<Client *>(user_data);
+ auto &wb = client->wb;
+
+ if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ return wb.append(data, length);
+}
+} // namespace
+
+void Http2Session::on_connect() {
+ int rv;
+
+ // This is required with --disable-assert.
+ (void)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_option *opt;
+
+ rv = nghttp2_option_new(&opt);
+ assert(rv == 0);
+
+ auto config = client_->worker->config;
+
+ if (config->encoder_header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ opt, config->encoder_header_table_size);
+ }
+
+ nghttp2_session_client_new2(&session_, callbacks, client_, opt);
+
+ nghttp2_option_del(opt);
+
+ std::array<nghttp2_settings_entry, 4> iv;
+ size_t niv = 2;
+ 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 << config->window_bits) - 1;
+
+ if (config->header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config->header_table_size;
+ ++niv;
+ }
+ if (config->max_frame_size != 16_k) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+ iv[niv].value = config->max_frame_size;
+ ++niv;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), niv);
+
+ assert(rv == 0);
+
+ auto connection_window = (1 << config->connection_window_bits) - 1;
+ nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
+ connection_window);
+
+ client_->signal_write();
+}
+
+int Http2Session::submit_request() {
+ if (nghttp2_session_check_request_allowed(session_) == 0) {
+ return -1;
+ }
+
+ auto config = client_->worker->config;
+ auto &nva = config->nva[client_->reqidx++];
+
+ if (client_->reqidx == config->nva.size()) {
+ client_->reqidx = 0;
+ }
+
+ nghttp2_data_provider prd{{0}, file_read_callback};
+
+ auto stream_id =
+ nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
+ config->data_fd == -1 ? nullptr : &prd, nullptr);
+ if (stream_id < 0) {
+ return -1;
+ }
+
+ client_->on_request(stream_id);
+
+ return 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);
+}
+
+size_t Http2Session::max_concurrent_streams() {
+ return (size_t)client_->worker->config->max_concurrent_streams;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http2_session.h b/src/h2load_http2_session.h
new file mode 100644
index 0000000..1b9b9f7
--- /dev/null
+++ b/src/h2load_http2_session.h
@@ -0,0 +1,54 @@
+/*
+ * 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 int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+
+private:
+ Client *client_;
+ nghttp2_session *session_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP2_SESSION_H
diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc
new file mode 100644
index 0000000..d491cba
--- /dev/null
+++ b/src/h2load_http3_session.cc
@@ -0,0 +1,474 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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_http3_session.h"
+
+#include <iostream>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "h2load.h"
+
+namespace h2load {
+
+Http3Session::Http3Session(Client *client)
+ : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
+
+Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
+
+void Http3Session::on_connect() {}
+
+int Http3Session::submit_request() {
+ if (npending_request_) {
+ ++npending_request_;
+ return 0;
+ }
+
+ auto config = client_->worker->config;
+ reqidx_ = client_->reqidx;
+
+ if (++client_->reqidx == config->nva.size()) {
+ client_->reqidx = 0;
+ }
+
+ auto stream_id = submit_request_internal();
+ if (stream_id < 0) {
+ if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
+ ++npending_request_;
+ return 0;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+
+ s->read_data(vec, veccnt, pflags);
+
+ return 1;
+}
+} // namespace
+
+void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt,
+ uint32_t *pflags) {
+ assert(veccnt > 0);
+
+ auto config = client_->worker->config;
+
+ vec[0].base = config->data;
+ vec[0].len = config->data_length;
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+}
+
+int64_t Http3Session::submit_request_internal() {
+ int rv;
+ int64_t stream_id;
+
+ auto config = client_->worker->config;
+ auto &nva = config->nva[reqidx_];
+
+ rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
+ if (rv != 0) {
+ return rv;
+ }
+
+ nghttp3_data_reader dr{};
+ dr.read_data = h2load::read_data;
+
+ rv = nghttp3_conn_submit_request(
+ conn_, stream_id, reinterpret_cast<nghttp3_nv *>(nva.data()), nva.size(),
+ config->data_fd == -1 ? nullptr : &dr, nullptr);
+ if (rv != 0) {
+ return rv;
+ }
+
+ client_->on_request(stream_id);
+ auto req_stat = client_->get_req_stat(stream_id);
+ assert(req_stat);
+ client_->record_request_time(req_stat);
+
+ return stream_id;
+}
+
+int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
+
+int Http3Session::on_write() { return -1; }
+
+void Http3Session::terminate() {}
+
+size_t Http3Session::max_concurrent_streams() {
+ return (size_t)client_->worker->config->max_concurrent_streams;
+}
+
+namespace {
+int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->stream_close(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
+ }
+ client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR);
+ return 0;
+}
+
+namespace {
+int end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->end_stream(stream_id) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::end_stream(int64_t stream_id) {
+ client_->record_ttfb();
+
+ return 0;
+}
+
+namespace {
+int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->recv_data(stream_id, data, datalen);
+ return 0;
+}
+} // namespace
+
+void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
+ size_t datalen) {
+ client_->record_ttfb();
+ client_->worker->stats.bytes_body += datalen;
+ consume(stream_id, datalen);
+}
+
+namespace {
+int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->consume(stream_id, nconsumed);
+ return 0;
+}
+} // namespace
+
+void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
+ nconsumed);
+ ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
+}
+
+namespace {
+int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ s->begin_headers(stream_id);
+ return 0;
+}
+} // namespace
+
+void Http3Session::begin_headers(int64_t stream_id) {
+ auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
+ assert(payloadlen > 0);
+
+ client_->worker->stats.bytes_head += payloadlen;
+}
+
+namespace {
+int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ auto k = nghttp3_rcbuf_get_buf(name);
+ auto v = nghttp3_rcbuf_get_buf(value);
+ s->recv_header(stream_id, &k, &v);
+ return 0;
+}
+} // namespace
+
+void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
+ const nghttp3_vec *value) {
+ client_->on_header(stream_id, name->base, name->len, value->base, value->len);
+ client_->worker->stats.bytes_head_decomp += name->len + value->len;
+}
+
+namespace {
+int stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::stop_sending(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, 0, stream_id,
+ app_error_code);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data) {
+ auto s = static_cast<Http3Session *>(user_data);
+ if (s->reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Http3Session::reset_stream(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_write(client_->quic.conn, 0, stream_id,
+ app_error_code);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) {
+ auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ return 0;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
+ }
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int Http3Session::shutdown_stream_read(int64_t stream_id) {
+ auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id);
+ if (rv != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::extend_max_local_streams() {
+ auto config = client_->worker->config;
+
+ for (; npending_request_; --npending_request_) {
+ auto stream_id = submit_request_internal();
+ if (stream_id < 0) {
+ if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
+ return 0;
+ }
+ return -1;
+ }
+
+ if (++reqidx_ == config->nva.size()) {
+ reqidx_ = 0;
+ }
+ }
+
+ return 0;
+}
+
+int Http3Session::init_conn() {
+ int rv;
+
+ assert(conn_ == nullptr);
+
+ if (ngtcp2_conn_get_streams_uni_left(client_->quic.conn) < 3) {
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ nullptr, // acked_stream_data
+ h2load::stream_close,
+ h2load::recv_data,
+ h2load::deferred_consume,
+ h2load::begin_headers,
+ h2load::recv_header,
+ nullptr, // end_headers
+ nullptr, // begin_trailers
+ h2load::recv_header,
+ nullptr, // end_trailers
+ h2load::stop_sending,
+ h2load::end_stream,
+ h2load::reset_stream,
+ nullptr, // shutdown
+ };
+
+ auto config = client_->worker->config;
+
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = config->header_table_size;
+ settings.qpack_blocked_streams = 100;
+
+ auto mem = nghttp3_mem_default();
+
+ rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ int64_t ctrl_stream_id;
+
+ rv =
+ ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
+ nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
+ nullptr);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ if (rv != 0) {
+ std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ auto nconsumed = nghttp3_conn_read_stream(
+ conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
+ << std::endl;
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error,
+ nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, 0);
+ return -1;
+ }
+ return nconsumed;
+}
+
+ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
+ nghttp3_vec *vec, size_t veccnt) {
+ auto sveccnt =
+ nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
+ if (sveccnt < 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error,
+ nghttp3_err_infer_quic_app_error_code(sveccnt), nullptr, 0);
+ return -1;
+ }
+ return sveccnt;
+}
+
+void Http3Session::block_stream(int64_t stream_id) {
+ nghttp3_conn_block_stream(conn_, stream_id);
+}
+
+int Http3Session::unblock_stream(int64_t stream_id) {
+ if (nghttp3_conn_unblock_stream(conn_, stream_id) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http3Session::shutdown_stream_write(int64_t stream_id) {
+ nghttp3_conn_shutdown_stream_write(conn_, stream_id);
+}
+
+int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
+ auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
+ nullptr, 0);
+ return -1;
+ }
+ return 0;
+}
+
+int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) {
+ auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_application_error(
+ &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
+ nullptr, 0);
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace h2load
diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h
new file mode 100644
index 0000000..8610417
--- /dev/null
+++ b/src/h2load_http3_session.h
@@ -0,0 +1,84 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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_HTTP3_SESSION_H
+#define H2LOAD_HTTP3_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp3/nghttp3.h>
+
+namespace h2load {
+
+struct Client;
+
+class Http3Session : public Session {
+public:
+ Http3Session(Client *client);
+ virtual ~Http3Session();
+ virtual void on_connect();
+ virtual int submit_request();
+ virtual int on_read(const uint8_t *data, size_t len);
+ virtual int on_write();
+ virtual void terminate();
+ virtual size_t max_concurrent_streams();
+
+ int init_conn();
+ int stream_close(int64_t stream_id, uint64_t app_error_code);
+ int end_stream(int64_t stream_id);
+ void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen);
+ void consume(int64_t stream_id, size_t nconsumed);
+ void begin_headers(int64_t stream_id);
+ void recv_header(int64_t stream_id, const nghttp3_vec *name,
+ const nghttp3_vec *value);
+ int stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int reset_stream(int64_t stream_id, uint64_t app_error_code);
+
+ int close_stream(int64_t stream_id, uint64_t app_error_code);
+ int shutdown_stream_read(int64_t stream_id);
+ int extend_max_local_streams();
+ int64_t submit_request_internal();
+
+ ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec,
+ size_t veccnt);
+ void block_stream(int64_t stream_id);
+ int unblock_stream(int64_t stream_id);
+ void shutdown_stream_write(int64_t stream_id);
+ int add_write_offset(int64_t stream_id, size_t ndatalen);
+ int add_ack_offset(int64_t stream_id, size_t datalen);
+
+ void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags);
+
+private:
+ Client *client_;
+ nghttp3_conn *conn_;
+ size_t npending_request_;
+ size_t reqidx_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP3_SESSION_H
diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc
new file mode 100644
index 0000000..e492a3e
--- /dev/null
+++ b/src/h2load_quic.cc
@@ -0,0 +1,844 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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_quic.h"
+
+#include <netinet/udp.h>
+
+#include <iostream>
+
+#ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+#endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#include "h2load_http3_session.h"
+
+namespace h2load {
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_handshake_completed() { return connection_made(); }
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ // TODO Better to do this gracefully rather than
+ // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call
+ // ngtcp2_conn_write_application_close() ?
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ if (worker->current_phase == Phase::MAIN_DURATION) {
+ worker->stats.bytes_total += datalen;
+ }
+
+ auto s = static_cast<Http3Session *>(session.get());
+ auto nconsumed = s->read_stream(flags, stream_id, data, datalen);
+ if (nconsumed == -1) {
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(quic.conn, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(quic.conn, nconsumed);
+
+ return 0;
+}
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_acked_stream_data_offset(int64_t stream_id, size_t datalen) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->add_ack_offset(stream_id, datalen) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (c->quic_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->close_stream(stream_id, app_error_code) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_stream_reset(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->shutdown_stream_read(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::quic_stream_stop_sending(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->shutdown_stream_read(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_extend_max_local_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_extend_max_local_streams() {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->extend_max_local_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_extend_max_stream_data(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_extend_max_stream_data(int64_t stream_id) {
+ auto s = static_cast<Http3Session *>(session.get());
+ if (s->unblock_stream(stream_id) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (RAND_bytes(cid->data, cidlen) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+
+ if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void debug_log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+} // namespace
+
+namespace {
+int generate_cid(ngtcp2_cid &dest) {
+ dest.datalen = 8;
+
+ if (RAND_bytes(dest.data, dest.datalen) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+ngtcp2_tstamp quic_timestamp() {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+} // namespace
+
+// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc
+namespace {
+void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto c = static_cast<Client *>(user_data);
+ c->quic_write_qlog(data, datalen);
+}
+} // namespace
+
+void Client::quic_write_qlog(const void *data, size_t datalen) {
+ assert(quic.qlog_file != nullptr);
+ fwrite(data, 1, datalen, quic.qlog_file);
+}
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ util::random_bytes(dest, dest + destlen,
+ *static_cast<std::mt19937 *>(rand_ctx->native_handle));
+}
+} // namespace
+
+namespace {
+int recv_rx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+ void *user_data) {
+ if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) {
+ return 0;
+ }
+
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->quic_make_http3_session() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::quic_make_http3_session() {
+ auto s = std::make_unique<Http3Session>(this);
+ if (s->init_conn() == -1) {
+ return -1;
+ }
+ session = std::move(s);
+
+ return 0;
+}
+
+namespace {
+ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto c = static_cast<Client *>(conn_ref->user_data);
+ return c->quic.conn;
+}
+} // namespace
+
+int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
+ const sockaddr *remote_addr, socklen_t remote_addrlen) {
+ int rv;
+
+ if (!ssl) {
+ ssl = SSL_new(worker->ssl_ctx);
+
+ quic.conn_ref.get_conn = get_conn;
+ quic.conn_ref.user_data = this;
+
+ SSL_set_app_data(ssl, &quic.conn_ref);
+ SSL_set_connect_state(ssl);
+ SSL_set_quic_use_legacy_codepoint(ssl, 0);
+ }
+
+ auto callbacks = ngtcp2_callbacks{
+ ngtcp2_crypto_client_initial_cb,
+ nullptr, // recv_client_initial
+ ngtcp2_crypto_recv_crypto_data_cb,
+ h2load::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ h2load::recv_stream_data,
+ h2load::acked_stream_data_offset,
+ nullptr, // stream_open
+ h2load::stream_close,
+ nullptr, // recv_stateless_reset
+ ngtcp2_crypto_recv_retry_cb,
+ h2load::extend_max_local_streams_bidi,
+ nullptr, // extend_max_local_streams_uni
+ h2load::rand,
+ get_new_connection_id,
+ nullptr, // remove_connection_id
+ ngtcp2_crypto_update_key_cb,
+ nullptr, // path_validation
+ nullptr, // select_preferred_addr
+ h2load::stream_reset,
+ nullptr, // extend_max_remote_streams_bidi
+ nullptr, // extend_max_remote_streams_uni
+ h2load::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ h2load::stream_stop_sending,
+ nullptr, // version_negotiation
+ h2load::recv_rx_key,
+ nullptr, // recv_tx_key
+ };
+
+ ngtcp2_cid scid, dcid;
+ if (generate_cid(scid) != 0) {
+ return -1;
+ }
+ if (generate_cid(dcid) != 0) {
+ return -1;
+ }
+
+ auto config = worker->config;
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ if (config->verbose) {
+ settings.log_printf = debug_log_printf;
+ }
+ settings.initial_ts = quic_timestamp();
+ settings.rand_ctx.native_handle = &worker->randgen;
+ if (!config->qlog_file_base.empty()) {
+ assert(quic.qlog_file == nullptr);
+ auto path = config->qlog_file_base;
+ path += '.';
+ path += util::utos(worker->id);
+ path += '.';
+ path += util::utos(id);
+ path += ".sqlog";
+ quic.qlog_file = fopen(path.c_str(), "w");
+ if (quic.qlog_file == nullptr) {
+ std::cerr << "Failed to open a qlog file: " << path << std::endl;
+ return -1;
+ }
+ settings.qlog_write = qlog_write_cb;
+ }
+ if (config->max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config->max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ auto max_stream_data =
+ std::min((1 << 26) - 1, (1 << config->window_bits) - 1);
+ params.initial_max_stream_data_bidi_local = max_stream_data;
+ params.initial_max_stream_data_uni = max_stream_data;
+ params.initial_max_data = (1 << config->connection_window_bits) - 1;
+ params.initial_max_streams_bidi = 0;
+ params.initial_max_streams_uni = 100;
+ params.max_idle_timeout = 30 * NGTCP2_SECONDS;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(local_addr),
+ local_addrlen,
+ },
+ {
+ const_cast<sockaddr *>(remote_addr),
+ remote_addrlen,
+ },
+ };
+
+ assert(config->alpn_list.size());
+
+ uint32_t quic_version;
+
+ if (config->alpn_list[0] == NGHTTP3_ALPN_H3) {
+ quic_version = NGTCP2_PROTO_VER_V1;
+ } else {
+ quic_version = NGTCP2_PROTO_VER_MIN;
+ }
+
+ rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version,
+ &callbacks, &settings, &params, nullptr, this);
+ if (rv != 0) {
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(quic.conn, ssl);
+
+ return 0;
+}
+
+void Client::quic_free() {
+ ngtcp2_conn_del(quic.conn);
+ if (quic.qlog_file != nullptr) {
+ fclose(quic.qlog_file);
+ quic.qlog_file = nullptr;
+ }
+}
+
+void Client::quic_close_connection() {
+ if (!quic.conn) {
+ return;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+ ngtcp2_path_storage ps;
+ ngtcp2_path_storage_zero(&ps);
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ quic.conn, &ps.path, nullptr, buf.data(), buf.size(), &quic.last_error,
+ quic_timestamp());
+
+ if (nwrite <= 0) {
+ return;
+ }
+
+ write_udp(reinterpret_cast<sockaddr *>(ps.path.remote.addr),
+ ps.path.remote.addrlen, buf.data(), nwrite, 0);
+}
+
+int Client::quic_write_client_handshake(ngtcp2_encryption_level level,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+
+ assert(level < 2);
+
+ rv = ngtcp2_conn_submit_crypto_data(quic.conn, level, data, datalen);
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_submit_crypto_data: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ if (c->quic_pkt_timeout() != 0) {
+ c->fail();
+ c->worker->free_client(c);
+ delete c;
+ return;
+ }
+}
+
+int Client::quic_pkt_timeout() {
+ int rv;
+ auto now = quic_timestamp();
+
+ rv = ngtcp2_conn_handle_expiry(quic.conn, now);
+ if (rv != 0) {
+ ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0);
+ return -1;
+ }
+
+ return write_quic();
+}
+
+void Client::quic_restart_pkt_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(quic.conn);
+ auto now = quic_timestamp();
+ auto t = expiry > now ? static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS
+ : 1e-9;
+ quic.pkt_timer.repeat = t;
+ ev_timer_again(worker->loop, &quic.pkt_timer);
+}
+
+int Client::read_quic() {
+ std::array<uint8_t, 65535> buf;
+ sockaddr_union su;
+ int rv;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi{};
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint16_t))];
+ msg.msg_control = msg_ctrl;
+
+ auto ts = quic_timestamp();
+
+ for (;;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(fd, &msg, 0);
+ if (nread == -1) {
+ return 0;
+ }
+
+ auto gso_size = util::msghdr_get_udp_gro(&msg);
+ if (gso_size == 0) {
+ gso_size = static_cast<size_t>(nread);
+ }
+
+ assert(quic.conn);
+
+ ++worker->stats.udp_dgram_recv;
+
+ auto path = ngtcp2_path{
+ {
+ &local_addr.su.sa,
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ &su.sa,
+ msg.msg_namelen,
+ },
+ };
+
+ auto data = buf.data();
+
+ for (;;) {
+ auto datalen = std::min(static_cast<size_t>(nread), gso_size);
+
+ ++pktcnt;
+
+ rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, data, datalen, ts);
+ if (rv != 0) {
+ if (!quic.last_error.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_ccerr_set_tls_alert(&quic.last_error,
+ ngtcp2_conn_get_tls_alert(quic.conn),
+ nullptr, 0);
+ } else {
+ ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0);
+ }
+ }
+
+ return -1;
+ }
+
+ nread -= datalen;
+ if (nread == 0) {
+ break;
+ }
+
+ data += datalen;
+ }
+
+ if (pktcnt >= 100) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int Client::write_quic() {
+ int rv;
+
+ ev_io_stop(worker->loop, &wev);
+
+ if (quic.close_requested) {
+ return -1;
+ }
+
+ if (quic.tx.send_blocked) {
+ rv = send_blocked_packet();
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (quic.tx.send_blocked) {
+ return 0;
+ }
+ }
+
+ std::array<nghttp3_vec, 16> vec;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size =
+ ngtcp2_conn_get_max_tx_udp_payload_size(quic.conn);
+#ifdef UDP_SEGMENT
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(quic.conn);
+#endif // UDP_SEGMENT
+ auto max_pktcnt =
+ ngtcp2_conn_get_send_quantum(quic.conn) / max_udp_payload_size;
+ uint8_t *bufpos = quic.tx.data.get();
+ ngtcp2_path_storage ps;
+ size_t gso_size = 0;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ auto s = static_cast<Http3Session *>(session.get());
+ auto ts = quic_timestamp();
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ ssize_t sveccnt = 0;
+
+ if (session && ngtcp2_conn_get_max_data_left(quic.conn)) {
+ sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size());
+ if (sveccnt == -1) {
+ return -1;
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ quic.conn, &ps.path, nullptr, bufpos, max_udp_payload_size, &ndatalen,
+ flags, stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ s->block_stream(stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ s->shutdown_stream_write(stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ if (s->add_write_offset(stream_id, ndatalen) != 0) {
+ return -1;
+ }
+ continue;
+ }
+
+ ngtcp2_ccerr_set_liberr(&quic.last_error, nwrite, nullptr, 0);
+ return -1;
+ } else if (ndatalen >= 0 && s->add_write_offset(stream_id, ndatalen) != 0) {
+ return -1;
+ }
+
+ quic_restart_pkt_timer();
+
+ if (nwrite == 0) {
+ if (bufpos - quic.tx.data.get()) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data,
+ datalen, gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ signal_write();
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+#ifdef UDP_SEGMENT
+ if (worker->config->no_udp_gso) {
+#endif // UDP_SEGMENT
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ 0);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, 0);
+ signal_write();
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ signal_write();
+ return 0;
+ }
+
+ bufpos = quic.tx.data.get();
+
+#ifdef UDP_SEGMENT
+ continue;
+ }
+#endif // UDP_SEGMENT
+
+#ifdef UDP_SEGMENT
+ if (pktcnt == 0) {
+ gso_size = nwrite;
+ } else if (static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get() - nwrite;
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ on_send_blocked(ps.path.remote, bufpos - nwrite, nwrite, 0);
+ } else {
+ auto data = bufpos - nwrite;
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data,
+ nwrite, 0);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, nwrite, 0);
+ }
+ }
+
+ signal_write();
+ return 0;
+ }
+
+ // Assume that the path does not change.
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto data = quic.tx.data.get();
+ auto datalen = bufpos - quic.tx.data.get();
+ rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen,
+ gso_size);
+ if (rv == 1) {
+ on_send_blocked(ps.path.remote, data, datalen, gso_size);
+ }
+ signal_write();
+ return 0;
+ }
+#endif // UDP_SEGMENT
+ }
+}
+
+void Client::on_send_blocked(const ngtcp2_addr &remote_addr,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(quic.tx.num_blocked || !quic.tx.send_blocked);
+ assert(quic.tx.num_blocked < 2);
+
+ quic.tx.send_blocked = true;
+
+ auto &p = quic.tx.blocked[quic.tx.num_blocked++];
+
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.remote_addr.len = remote_addr.addrlen;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+int Client::send_blocked_packet() {
+ int rv;
+
+ assert(quic.tx.send_blocked);
+
+ for (; quic.tx.num_blocked_sent < quic.tx.num_blocked;
+ ++quic.tx.num_blocked_sent) {
+ auto &p = quic.tx.blocked[quic.tx.num_blocked_sent];
+
+ rv = write_udp(&p.remote_addr.su.sa, p.remote_addr.len, p.data, p.datalen,
+ p.gso_size);
+ if (rv == 1) {
+ signal_write();
+
+ return 0;
+ }
+ }
+
+ quic.tx.send_blocked = false;
+ quic.tx.num_blocked = 0;
+ quic.tx.num_blocked_sent = 0;
+
+ return 0;
+}
+
+} // namespace h2load
diff --git a/src/h2load_quic.h b/src/h2load_quic.h
new file mode 100644
index 0000000..225f00f
--- /dev/null
+++ b/src/h2load_quic.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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_QUIC_H
+#define H2LOAD_QUIC_H
+
+#include "nghttp2_config.h"
+
+#include <ev.h>
+
+#include "h2load.h"
+
+namespace h2load {
+void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
+} // namespace h2load
+
+#endif // H2LOAD_QUIC_H
diff --git a/src/h2load_session.h b/src/h2load_session.h
new file mode 100644
index 0000000..ab3b8ec
--- /dev/null
+++ b/src/h2load_session.h
@@ -0,0 +1,59 @@
+/*
+ * 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 <cinttypes>
+
+#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 int submit_request() = 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;
+ // Return the maximum concurrency per connection
+ virtual size_t max_concurrent_streams() = 0;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_SESSION_H
diff --git a/src/http-parser.patch b/src/http-parser.patch
new file mode 100644
index 0000000..ef80940
--- /dev/null
+++ b/src/http-parser.patch
@@ -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
index 0000000..661c4c9
--- /dev/null
+++ b/src/http2.cc
@@ -0,0 +1,2096 @@
+/*
+ * 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 "llhttp.h"
+
+#include "util.h"
+
+namespace nghttp2 {
+
+namespace http2 {
+
+StringRef get_reason_phrase(unsigned int status_code) {
+ switch (status_code) {
+ case 100:
+ return StringRef::from_lit("Continue");
+ case 101:
+ return StringRef::from_lit("Switching Protocols");
+ case 103:
+ return StringRef::from_lit("Early Hints");
+ case 200:
+ return StringRef::from_lit("OK");
+ case 201:
+ return StringRef::from_lit("Created");
+ case 202:
+ return StringRef::from_lit("Accepted");
+ case 203:
+ return StringRef::from_lit("Non-Authoritative Information");
+ case 204:
+ return StringRef::from_lit("No Content");
+ case 205:
+ return StringRef::from_lit("Reset Content");
+ case 206:
+ return StringRef::from_lit("Partial Content");
+ case 300:
+ return StringRef::from_lit("Multiple Choices");
+ case 301:
+ return StringRef::from_lit("Moved Permanently");
+ case 302:
+ return StringRef::from_lit("Found");
+ case 303:
+ return StringRef::from_lit("See Other");
+ case 304:
+ return StringRef::from_lit("Not Modified");
+ case 305:
+ return StringRef::from_lit("Use Proxy");
+ // case 306: return StringRef::from_lit("(Unused)");
+ case 307:
+ return StringRef::from_lit("Temporary Redirect");
+ case 308:
+ return StringRef::from_lit("Permanent Redirect");
+ case 400:
+ return StringRef::from_lit("Bad Request");
+ case 401:
+ return StringRef::from_lit("Unauthorized");
+ case 402:
+ return StringRef::from_lit("Payment Required");
+ case 403:
+ return StringRef::from_lit("Forbidden");
+ case 404:
+ return StringRef::from_lit("Not Found");
+ case 405:
+ return StringRef::from_lit("Method Not Allowed");
+ case 406:
+ return StringRef::from_lit("Not Acceptable");
+ case 407:
+ return StringRef::from_lit("Proxy Authentication Required");
+ case 408:
+ return StringRef::from_lit("Request Timeout");
+ case 409:
+ return StringRef::from_lit("Conflict");
+ case 410:
+ return StringRef::from_lit("Gone");
+ case 411:
+ return StringRef::from_lit("Length Required");
+ case 412:
+ return StringRef::from_lit("Precondition Failed");
+ case 413:
+ return StringRef::from_lit("Payload Too Large");
+ case 414:
+ return StringRef::from_lit("URI Too Long");
+ case 415:
+ return StringRef::from_lit("Unsupported Media Type");
+ case 416:
+ return StringRef::from_lit("Requested Range Not Satisfiable");
+ case 417:
+ return StringRef::from_lit("Expectation Failed");
+ case 421:
+ return StringRef::from_lit("Misdirected Request");
+ case 425:
+ // https://tools.ietf.org/html/rfc8470
+ return StringRef::from_lit("Too Early");
+ case 426:
+ return StringRef::from_lit("Upgrade Required");
+ case 428:
+ return StringRef::from_lit("Precondition Required");
+ case 429:
+ return StringRef::from_lit("Too Many Requests");
+ case 431:
+ return StringRef::from_lit("Request Header Fields Too Large");
+ case 451:
+ return StringRef::from_lit("Unavailable For Legal Reasons");
+ case 500:
+ return StringRef::from_lit("Internal Server Error");
+ case 501:
+ return StringRef::from_lit("Not Implemented");
+ case 502:
+ return StringRef::from_lit("Bad Gateway");
+ case 503:
+ return StringRef::from_lit("Service Unavailable");
+ case 504:
+ return StringRef::from_lit("Gateway Timeout");
+ case 505:
+ return StringRef::from_lit("HTTP Version Not Supported");
+ case 511:
+ return StringRef::from_lit("Network Authentication Required");
+ default:
+ return StringRef{};
+ }
+}
+
+StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code) {
+ switch (status_code) {
+ case 100:
+ return StringRef::from_lit("100");
+ case 101:
+ return StringRef::from_lit("101");
+ case 103:
+ return StringRef::from_lit("103");
+ case 200:
+ return StringRef::from_lit("200");
+ case 201:
+ return StringRef::from_lit("201");
+ case 202:
+ return StringRef::from_lit("202");
+ case 203:
+ return StringRef::from_lit("203");
+ case 204:
+ return StringRef::from_lit("204");
+ case 205:
+ return StringRef::from_lit("205");
+ case 206:
+ return StringRef::from_lit("206");
+ case 300:
+ return StringRef::from_lit("300");
+ case 301:
+ return StringRef::from_lit("301");
+ case 302:
+ return StringRef::from_lit("302");
+ case 303:
+ return StringRef::from_lit("303");
+ case 304:
+ return StringRef::from_lit("304");
+ case 305:
+ return StringRef::from_lit("305");
+ // case 306: return StringRef::from_lit("306");
+ case 307:
+ return StringRef::from_lit("307");
+ case 308:
+ return StringRef::from_lit("308");
+ case 400:
+ return StringRef::from_lit("400");
+ case 401:
+ return StringRef::from_lit("401");
+ case 402:
+ return StringRef::from_lit("402");
+ case 403:
+ return StringRef::from_lit("403");
+ case 404:
+ return StringRef::from_lit("404");
+ case 405:
+ return StringRef::from_lit("405");
+ case 406:
+ return StringRef::from_lit("406");
+ case 407:
+ return StringRef::from_lit("407");
+ case 408:
+ return StringRef::from_lit("408");
+ case 409:
+ return StringRef::from_lit("409");
+ case 410:
+ return StringRef::from_lit("410");
+ case 411:
+ return StringRef::from_lit("411");
+ case 412:
+ return StringRef::from_lit("412");
+ case 413:
+ return StringRef::from_lit("413");
+ case 414:
+ return StringRef::from_lit("414");
+ case 415:
+ return StringRef::from_lit("415");
+ case 416:
+ return StringRef::from_lit("416");
+ case 417:
+ return StringRef::from_lit("417");
+ case 421:
+ return StringRef::from_lit("421");
+ case 426:
+ return StringRef::from_lit("426");
+ case 428:
+ return StringRef::from_lit("428");
+ case 429:
+ return StringRef::from_lit("429");
+ case 431:
+ return StringRef::from_lit("431");
+ case 451:
+ return StringRef::from_lit("451");
+ case 500:
+ return StringRef::from_lit("500");
+ case 501:
+ return StringRef::from_lit("501");
+ case 502:
+ return StringRef::from_lit("502");
+ case 503:
+ return StringRef::from_lit("503");
+ case 504:
+ return StringRef::from_lit("504");
+ case 505:
+ return StringRef::from_lit("505");
+ case 511:
+ return StringRef::from_lit("511");
+ default:
+ return util::make_string_ref_uint(balloc, status_code);
+ }
+}
+
+void capitalize(DefaultMemchunks *buf, const StringRef &s) {
+ buf->append(util::upcase(s[0]));
+ for (size_t i = 1; i < s.size(); ++i) {
+ if (s[i - 1] == '-') {
+ buf->append(util::upcase(s[i]));
+ } else {
+ buf->append(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, int32_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,
+ int32_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;
+}
+
+bool non_empty_value(const HeaderRefs::value_type *nv) {
+ return nv && !nv->value.empty();
+}
+
+namespace {
+nghttp2_nv make_nv_internal(const std::string &name, const std::string &value,
+ bool no_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags =
+ nv_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};
+}
+} // namespace
+
+namespace {
+nghttp2_nv make_nv_internal(const StringRef &name, const StringRef &value,
+ bool no_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags =
+ nv_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};
+}
+} // namespace
+
+nghttp2_nv make_nv(const std::string &name, const std::string &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
+}
+
+nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
+}
+
+nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE);
+}
+
+nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool no_index) {
+ return make_nv_internal(name, value, no_index,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE);
+}
+
+namespace {
+void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint8_t nv_flags,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ 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_TE:
+ case HD_TRANSFER_ENCODING:
+ case HD_UPGRADE:
+ continue;
+ case HD_EARLY_DATA:
+ if (flags & HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case HD_SEC_WEBSOCKET_ACCEPT:
+ if (flags & HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
+ continue;
+ }
+ break;
+ case HD_SEC_WEBSOCKET_KEY:
+ if (flags & HDOP_STRIP_SEC_WEBSOCKET_KEY) {
+ continue;
+ }
+ break;
+ case HD_FORWARDED:
+ if (flags & HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case HD_X_FORWARDED_FOR:
+ if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case HD_X_FORWARDED_PROTO:
+ if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case HD_VIA:
+ if (flags & HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ nva.push_back(
+ make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
+ }
+}
+} // namespace
+
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE, flags);
+}
+
+void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(
+ nva, headers,
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE, flags);
+}
+
+void build_http1_headers_from_headers(DefaultMemchunks *buf,
+ const HeaderRefs &headers,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ 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_UPGRADE:
+ continue;
+ case HD_EARLY_DATA:
+ if (flags & HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case HD_TRANSFER_ENCODING:
+ if (flags & HDOP_STRIP_TRANSFER_ENCODING) {
+ continue;
+ }
+ break;
+ case HD_FORWARDED:
+ if (flags & HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case HD_X_FORWARDED_FOR:
+ if (flags & HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case HD_X_FORWARDED_PROTO:
+ if (flags & HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case HD_VIA:
+ if (flags & HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ capitalize(buf, kv->name);
+ buf->append(": ");
+ buf->append(kv->value);
+ buf->append("\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) {
+ fprintf(out, "%s: %s\n", nv[i], nv[i + 1]);
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen) {
+ auto end = nva + nvlen;
+ for (; nva != end; ++nva) {
+ fprintf(out, "%s: %s\n", nva->name, nva->value);
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const Headers &nva) {
+ for (auto &nv : nva) {
+ fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str());
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void dump_nv(FILE *out, const HeaderRefs &nva) {
+ for (auto &nv : nva) {
+ fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str());
+ }
+ fputc('\n', out);
+ fflush(out);
+}
+
+void erase_header(HeaderRef *hd) {
+ hd->name = StringRef{};
+ hd->token = -1;
+}
+
+StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
+ const http_parser_url &u,
+ const StringRef &match_host,
+ const StringRef &request_authority,
+ const StringRef &upstream_scheme) {
+ // We just rewrite scheme and authority.
+ if ((u.field_set & (1 << UF_HOST)) == 0) {
+ return StringRef{};
+ }
+ auto field = &u.field_data[UF_HOST];
+ if (!util::starts_with(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 StringRef{};
+ }
+
+ auto len = 0;
+ if (!request_authority.empty()) {
+ len += upstream_scheme.size() + str_size("://") + request_authority.size();
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ field = &u.field_data[UF_PATH];
+ len += field->len;
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ field = &u.field_data[UF_QUERY];
+ len += 1 + field->len;
+ }
+
+ if (u.field_set & (1 << UF_FRAGMENT)) {
+ field = &u.field_data[UF_FRAGMENT];
+ len += 1 + field->len;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if (!request_authority.empty()) {
+ p = std::copy(std::begin(upstream_scheme), std::end(upstream_scheme), p);
+ p = util::copy_lit(p, "://");
+ p = std::copy(std::begin(request_authority), std::end(request_authority),
+ p);
+ }
+ if (u.field_set & (1 << UF_PATH)) {
+ field = &u.field_data[UF_PATH];
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+ if (u.field_set & (1 << UF_QUERY)) {
+ field = &u.field_data[UF_QUERY];
+ *p++ = '?';
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+ if (u.field_set & (1 << UF_FRAGMENT)) {
+ field = &u.field_data[UF_FRAGMENT];
+ *p++ = '#';
+ p = std::copy_n(&uri[field->off], field->len, p);
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+int parse_http_status_code(const StringRef &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 StringRef &name) {
+ return lookup_token(name.byte(), 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[1]) {
+ case 'e':
+ if (util::streq_l("t", name, 1)) {
+ return HD_TE;
+ }
+ break;
+ }
+ break;
+ case 3:
+ switch (name[2]) {
+ case 'a':
+ if (util::streq_l("vi", name, 2)) {
+ return HD_VIA;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'e':
+ if (util::streq_l("dat", name, 3)) {
+ return HD_DATE;
+ }
+ break;
+ case 'k':
+ if (util::streq_l("lin", name, 3)) {
+ return HD_LINK;
+ }
+ break;
+ case 't':
+ if (util::streq_l("hos", name, 3)) {
+ return HD_HOST;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (name[4]) {
+ case 'h':
+ if (util::streq_l(":pat", name, 4)) {
+ return HD__PATH;
+ }
+ break;
+ case 't':
+ if (util::streq_l(":hos", name, 4)) {
+ return HD__HOST;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'e':
+ if (util::streq_l("cooki", name, 5)) {
+ return HD_COOKIE;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("serve", name, 5)) {
+ return HD_SERVER;
+ }
+ break;
+ case 't':
+ if (util::streq_l("expec", name, 5)) {
+ return HD_EXPECT;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'c':
+ if (util::streq_l("alt-sv", name, 6)) {
+ return HD_ALT_SVC;
+ }
+ break;
+ case 'd':
+ if (util::streq_l(":metho", name, 6)) {
+ return HD__METHOD;
+ }
+ break;
+ case 'e':
+ if (util::streq_l(":schem", name, 6)) {
+ return HD__SCHEME;
+ }
+ if (util::streq_l("upgrad", name, 6)) {
+ return HD_UPGRADE;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("traile", name, 6)) {
+ return HD_TRAILER;
+ }
+ break;
+ case 's':
+ if (util::streq_l(":statu", name, 6)) {
+ return HD__STATUS;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'n':
+ if (util::streq_l("locatio", name, 7)) {
+ return HD_LOCATION;
+ }
+ break;
+ case 'y':
+ if (util::streq_l("priorit", name, 7)) {
+ return HD_PRIORITY;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'd':
+ if (util::streq_l("forwarde", name, 8)) {
+ return HD_FORWARDED;
+ }
+ break;
+ case 'l':
+ if (util::streq_l(":protoco", name, 8)) {
+ return HD__PROTOCOL;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'a':
+ if (util::streq_l("early-dat", name, 9)) {
+ return HD_EARLY_DATA;
+ }
+ break;
+ case 'e':
+ if (util::streq_l("keep-aliv", name, 9)) {
+ return HD_KEEP_ALIVE;
+ }
+ break;
+ case 'n':
+ if (util::streq_l("connectio", name, 9)) {
+ return HD_CONNECTION;
+ }
+ break;
+ case 't':
+ if (util::streq_l("user-agen", name, 9)) {
+ return HD_USER_AGENT;
+ }
+ break;
+ case 'y':
+ if (util::streq_l(":authorit", name, 9)) {
+ return HD__AUTHORITY;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case 'e':
+ if (util::streq_l("content-typ", name, 11)) {
+ return HD_CONTENT_TYPE;
+ }
+ break;
+ }
+ break;
+ case 13:
+ switch (name[12]) {
+ case 'l':
+ if (util::streq_l("cache-contro", name, 12)) {
+ return HD_CACHE_CONTROL;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'h':
+ if (util::streq_l("content-lengt", name, 13)) {
+ return HD_CONTENT_LENGTH;
+ }
+ break;
+ case 's':
+ if (util::streq_l("http2-setting", name, 13)) {
+ return HD_HTTP2_SETTINGS;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 'e':
+ if (util::streq_l("accept-languag", name, 14)) {
+ return HD_ACCEPT_LANGUAGE;
+ }
+ break;
+ case 'g':
+ if (util::streq_l("accept-encodin", name, 14)) {
+ return HD_ACCEPT_ENCODING;
+ }
+ break;
+ case 'r':
+ if (util::streq_l("x-forwarded-fo", name, 14)) {
+ return HD_X_FORWARDED_FOR;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'n':
+ if (util::streq_l("proxy-connectio", name, 15)) {
+ return HD_PROXY_CONNECTION;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'e':
+ if (util::streq_l("if-modified-sinc", name, 16)) {
+ return HD_IF_MODIFIED_SINCE;
+ }
+ break;
+ case 'g':
+ if (util::streq_l("transfer-encodin", name, 16)) {
+ return HD_TRANSFER_ENCODING;
+ }
+ break;
+ case 'o':
+ if (util::streq_l("x-forwarded-prot", name, 16)) {
+ return HD_X_FORWARDED_PROTO;
+ }
+ break;
+ case 'y':
+ if (util::streq_l("sec-websocket-ke", name, 16)) {
+ return HD_SEC_WEBSOCKET_KEY;
+ }
+ break;
+ }
+ break;
+ case 20:
+ switch (name[19]) {
+ case 't':
+ if (util::streq_l("sec-websocket-accep", name, 19)) {
+ return HD_SEC_WEBSOCKET_ACCEPT;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+void init_hdidx(HeaderIndex &hdidx) {
+ std::fill(std::begin(hdidx), std::end(hdidx), -1);
+}
+
+void index_header(HeaderIndex &hdidx, int32_t token, size_t idx) {
+ if (token == -1) {
+ return;
+ }
+ assert(token < HD_MAXIDX);
+ hdidx[token] = idx;
+}
+
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ const Headers &nva) {
+ auto i = hdidx[token];
+ if (i == -1) {
+ return nullptr;
+ }
+ return &nva[i];
+}
+
+Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ 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 {
+// Skip to the right dquote ('"'), handling backslash escapes.
+// Returns |last| if input is not terminated with '"'.
+template <typename InputIt>
+InputIt skip_to_right_dquote(InputIt first, InputIt last) {
+ for (; first != last;) {
+ switch (*first) {
+ case '"':
+ return first;
+ // quoted-pair
+ case '\\':
+ ++first;
+ if (first == last) {
+ return first;
+ }
+
+ switch (*first) {
+ case '\t':
+ case ' ':
+ break;
+ default:
+ if ((0x21 <= *first && *first <= 0x7e) /* VCHAR */ ||
+ (0x80 <= *first && *first <= 0xff) /* obs-text */) {
+ break;
+ }
+
+ return last;
+ }
+
+ break;
+ // qdtext
+ case '\t':
+ case ' ':
+ case '!':
+ break;
+ default:
+ if ((0x23 <= *first && *first <= 0x5b) ||
+ (0x5d <= *first && *first <= 0x7e)) {
+ break;
+ }
+
+ return last;
+ }
+ ++first;
+ }
+ return first;
+}
+} // namespace
+
+namespace {
+// Returns true if link-param does not match pattern |pat| of length
+// |patlen| or it has empty value (""). |pat| should be parmname
+// followed by "=".
+bool check_link_param_empty(const char *first, const char *last,
+ const char *pat, size_t patlen) {
+ if (first + patlen <= last) {
+ if (std::equal(pat, pat + patlen, first, util::CaseCmp())) {
+ // we only accept URI if pat is followed by "" (e.g.,
+ // loadpolicy="") here.
+ if (first + patlen + 2 <= last) {
+ if (*(first + patlen) != '"' || *(first + patlen + 1) != '"') {
+ return false;
+ }
+ } else {
+ // here we got invalid production (anchor=") or anchor=?
+ return false;
+ }
+ }
+ }
+ return true;
+}
+} // namespace
+
+namespace {
+// Returns true if link-param consists of only parmname, and it
+// matches string [pat, pat + patlen).
+bool check_link_param_without_value(const char *first, const char *last,
+ const char *pat, size_t patlen) {
+ if (first + patlen > last) {
+ return false;
+ }
+
+ if (first + patlen == last) {
+ return std::equal(pat, pat + patlen, first, util::CaseCmp());
+ }
+
+ switch (*(first + patlen)) {
+ case ';':
+ case ',':
+ return std::equal(pat, pat + patlen, first, util::CaseCmp());
+ }
+
+ return false;
+}
+} // 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 {{StringRef{}}, last};
+ }
+ auto url_first = ++first;
+ first = std::find(first, last, '>');
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ auto url_last = first++;
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ // we expect ';' or ',' here
+ switch (*first) {
+ case ',':
+ return {{StringRef{}}, ++first};
+ case ';':
+ ++first;
+ break;
+ default:
+ return {{StringRef{}}, last};
+ }
+
+ auto ok = false;
+ auto ign = false;
+ for (;;) {
+ first = skip_lws(first, last);
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ // we expect link-param
+
+ if (!ign) {
+ if (!ok) {
+ // rel can take several relations using quoted form.
+ static constexpr char PLP[] = "rel=\"";
+ static constexpr size_t PLPLEN = str_size(PLP);
+
+ static constexpr char PLT[] = "preload";
+ static constexpr size_t PLTLEN = str_size(PLT);
+ if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' &&
+ std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) {
+ // we have to search preload in whitespace separated list:
+ // rel="preload something http://example.org/foo"
+ first += PLPLEN;
+ auto start = first;
+ for (; first != last;) {
+ if (*first != ' ' && *first != '"') {
+ ++first;
+ continue;
+ }
+
+ if (start == first) {
+ return {{StringRef{}}, last};
+ }
+
+ if (!ok && start + PLTLEN == first &&
+ std::equal(PLT, PLT + PLTLEN, start, util::CaseCmp())) {
+ ok = true;
+ }
+
+ if (*first == '"') {
+ break;
+ }
+ first = skip_lws(first, last);
+ start = first;
+ }
+ if (first == last) {
+ return {{StringRef{}}, last};
+ }
+ assert(*first == '"');
+ ++first;
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ if (*first == ';') {
+ ++first;
+ // parse next link-param
+ continue;
+ }
+ return {{StringRef{}}, last};
+ }
+ }
+ // we are only interested in rel=preload parameter. Others are
+ // simply skipped.
+ static constexpr char PL[] = "rel=preload";
+ static constexpr size_t PLLEN = str_size(PL);
+ if (first + PLLEN == last) {
+ if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
+ // 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, util::CaseCmp())) {
+ break;
+ }
+ // ok = true;
+ // skip including ','
+ first += PLLEN + 1;
+ return {{{url_first, url_last}}, first};
+ case ';':
+ if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
+ break;
+ }
+ ok = true;
+ // skip including ';'
+ first += PLLEN + 1;
+ // continue parse next link-param
+ continue;
+ }
+ }
+ // we have to reject URI if we have nonempty anchor parameter.
+ static constexpr char ANCHOR[] = "anchor=";
+ static constexpr size_t ANCHORLEN = str_size(ANCHOR);
+ if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) {
+ ign = true;
+ }
+
+ // reject URI if we have non-empty loadpolicy. This could be
+ // tightened up to just pick up "next" or "insert".
+ static constexpr char LOADPOLICY[] = "loadpolicy=";
+ static constexpr size_t LOADPOLICYLEN = str_size(LOADPOLICY);
+ if (!ign &&
+ !check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) {
+ ign = true;
+ }
+
+ // reject URI if we have nopush attribute.
+ static constexpr char NOPUSH[] = "nopush";
+ static constexpr size_t NOPUSHLEN = str_size(NOPUSH);
+ if (!ign &&
+ check_link_param_without_value(first, last, NOPUSH, NOPUSHLEN)) {
+ ign = true;
+ }
+ }
+
+ 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 {{StringRef{}}, last};
+ }
+ if (param_first == first) {
+ // empty parmname
+ return {{StringRef{}}, 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 link-param value
+ assert(*first == '=');
+ ++first;
+ if (first == last) {
+ // empty value is not acceptable
+ return {{StringRef{}}, first};
+ }
+ if (*first == '"') {
+ // quoted-string
+ first = skip_to_right_dquote(first + 1, last);
+ if (first == last) {
+ return {{StringRef{}}, first};
+ }
+ ++first;
+ if (first == last || *first == ',') {
+ goto almost_done;
+ }
+ if (*first == ';') {
+ ++first;
+ // parse next link-param
+ continue;
+ }
+ return {{StringRef{}}, last};
+ }
+ // not quoted-string, skip to next ',' or ';'
+ if (*first == ',' || *first == ';') {
+ // empty value
+ return {{StringRef{}}, 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 != last) {
+ ++first;
+ }
+ if (ok && !ign) {
+ return {{{url_first, url_last}}, first};
+ }
+ return {{StringRef{}}, first};
+}
+} // namespace
+
+std::vector<LinkHeader> parse_link_header(const StringRef &src) {
+ std::vector<LinkHeader> res;
+ for (auto first = std::begin(src); first != std::end(src);) {
+ auto rv = parse_next_link_header_once(first, std::end(src));
+ first = rv.second;
+ auto &link = rv.first;
+ if (!link.uri.empty()) {
+ res.push_back(link);
+ }
+ }
+ return res;
+}
+
+std::string path_join(const StringRef &base_path, const StringRef &base_query,
+ const StringRef &rel_path, const StringRef &rel_query) {
+ BlockAllocator balloc(1024, 1024);
+
+ return path_join(balloc, base_path, base_query, rel_path, rel_query).str();
+}
+
+bool expect_response_body(int status_code) {
+ return status_code == 101 ||
+ (status_code / 100 != 1 && status_code != 304 && status_code != 204);
+}
+
+bool expect_response_body(const std::string &method, int status_code) {
+ return method != "HEAD" && expect_response_body(status_code);
+}
+
+bool expect_response_body(int method_token, int status_code) {
+ return method_token != HTTP_HEAD && expect_response_body(status_code);
+}
+
+int lookup_method_token(const StringRef &name) {
+ return lookup_method_token(name.byte(), name.size());
+}
+
+// This function was generated by genmethodfunc.py.
+int lookup_method_token(const uint8_t *name, size_t namelen) {
+ switch (namelen) {
+ case 3:
+ switch (name[2]) {
+ case 'L':
+ if (util::streq_l("AC", name, 2)) {
+ return HTTP_ACL;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("GE", name, 2)) {
+ return HTTP_GET;
+ }
+ if (util::streq_l("PU", name, 2)) {
+ return HTTP_PUT;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'D':
+ if (util::streq_l("BIN", name, 3)) {
+ return HTTP_BIND;
+ }
+ if (util::streq_l("HEA", name, 3)) {
+ return HTTP_HEAD;
+ }
+ break;
+ case 'E':
+ if (util::streq_l("MOV", name, 3)) {
+ return HTTP_MOVE;
+ }
+ break;
+ case 'K':
+ if (util::streq_l("LIN", name, 3)) {
+ return HTTP_LINK;
+ }
+ if (util::streq_l("LOC", name, 3)) {
+ return HTTP_LOCK;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("POS", name, 3)) {
+ return HTTP_POST;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("COP", name, 3)) {
+ return HTTP_COPY;
+ }
+ break;
+ }
+ break;
+ case 5:
+ switch (name[4]) {
+ case 'E':
+ if (util::streq_l("MERG", name, 4)) {
+ return HTTP_MERGE;
+ }
+ if (util::streq_l("PURG", name, 4)) {
+ return HTTP_PURGE;
+ }
+ if (util::streq_l("TRAC", name, 4)) {
+ return HTTP_TRACE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("PATC", name, 4)) {
+ return HTTP_PATCH;
+ }
+ break;
+ case 'L':
+ if (util::streq_l("MKCO", name, 4)) {
+ return HTTP_MKCOL;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'D':
+ if (util::streq_l("REBIN", name, 5)) {
+ return HTTP_REBIND;
+ }
+ if (util::streq_l("UNBIN", name, 5)) {
+ return HTTP_UNBIND;
+ }
+ break;
+ case 'E':
+ if (util::streq_l("DELET", name, 5)) {
+ return HTTP_DELETE;
+ }
+ if (util::streq_l("SOURC", name, 5)) {
+ return HTTP_SOURCE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("SEARC", name, 5)) {
+ return HTTP_SEARCH;
+ }
+ break;
+ case 'K':
+ if (util::streq_l("UNLIN", name, 5)) {
+ return HTTP_UNLINK;
+ }
+ if (util::streq_l("UNLOC", name, 5)) {
+ return HTTP_UNLOCK;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("REPOR", name, 5)) {
+ return HTTP_REPORT;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("NOTIF", name, 5)) {
+ return HTTP_NOTIFY;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'H':
+ if (util::streq_l("MSEARC", name, 6)) {
+ return HTTP_MSEARCH;
+ }
+ break;
+ case 'S':
+ if (util::streq_l("OPTION", name, 6)) {
+ return HTTP_OPTIONS;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("CONNEC", name, 6)) {
+ return HTTP_CONNECT;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'D':
+ if (util::streq_l("PROPFIN", name, 7)) {
+ return HTTP_PROPFIND;
+ }
+ break;
+ case 'T':
+ if (util::streq_l("CHECKOU", name, 7)) {
+ return HTTP_CHECKOUT;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'E':
+ if (util::streq_l("SUBSCRIB", name, 8)) {
+ return HTTP_SUBSCRIBE;
+ }
+ break;
+ case 'H':
+ if (util::streq_l("PROPPATC", name, 8)) {
+ return HTTP_PROPPATCH;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'R':
+ if (util::streq_l("MKCALENDA", name, 9)) {
+ return HTTP_MKCALENDAR;
+ }
+ break;
+ case 'Y':
+ if (util::streq_l("MKACTIVIT", name, 9)) {
+ return HTTP_MKACTIVITY;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'E':
+ if (util::streq_l("UNSUBSCRIB", name, 10)) {
+ return HTTP_UNSUBSCRIBE;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+StringRef to_method_string(int method_token) {
+ // we happened to use same value for method with llhttp.
+ return StringRef{
+ llhttp_method_name(static_cast<llhttp_method>(method_token))};
+}
+
+StringRef get_pure_path_component(const StringRef &uri) {
+ int rv;
+
+ http_parser_url u{};
+ rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u);
+ if (rv != 0) {
+ return StringRef{};
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto &f = u.field_data[UF_PATH];
+ return StringRef{uri.c_str() + f.off, f.len};
+ }
+
+ return StringRef::from_lit("/");
+}
+
+int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
+ StringRef &authority, StringRef &path,
+ const StringRef &base, const StringRef &uri) {
+ int rv;
+ StringRef rel, relq;
+
+ if (uri.size() == 0) {
+ return -1;
+ }
+
+ http_parser_url u{};
+
+ rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u);
+
+ if (rv != 0) {
+ if (uri[0] == '/') {
+ return -1;
+ }
+
+ // treat link_url as relative URI.
+ auto end = std::find(std::begin(uri), std::end(uri), '#');
+ auto q = std::find(std::begin(uri), end, '?');
+
+ rel = StringRef{std::begin(uri), q};
+ if (q != end) {
+ relq = StringRef{q + 1, std::end(uri)};
+ }
+ } else {
+ if (u.field_set & (1 << UF_SCHEMA)) {
+ scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+ }
+
+ if (u.field_set & (1 << UF_HOST)) {
+ auto auth = util::get_uri_field(uri.c_str(), u, UF_HOST);
+ auto len = auth.size();
+ auto port_exists = u.field_set & (1 << UF_PORT);
+ if (port_exists) {
+ len += 1 + str_size("65535");
+ }
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(auth), std::end(auth), p);
+ if (port_exists) {
+ *p++ = ':';
+ p = util::utos(p, u.port);
+ }
+ *p = '\0';
+
+ authority = StringRef{iov.base, p};
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto &f = u.field_data[UF_PATH];
+ rel = StringRef{uri.c_str() + f.off, f.len};
+ } else {
+ rel = StringRef::from_lit("/");
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ auto &f = u.field_data[UF_QUERY];
+ relq = StringRef{uri.c_str() + f.off, f.len};
+ }
+ }
+
+ path = http2::path_join(balloc, base, StringRef{}, rel, relq);
+
+ return 0;
+}
+
+namespace {
+template <typename InputIt> InputIt eat_file(InputIt first, InputIt last) {
+ if (first == last) {
+ *first++ = '/';
+ return first;
+ }
+
+ if (*(last - 1) == '/') {
+ return last;
+ }
+
+ auto p = last;
+ for (; p != first && *(p - 1) != '/'; --p)
+ ;
+ if (p == first) {
+ // this should not happened in normal case, where we expect path
+ // starts with '/'
+ *first++ = '/';
+ return first;
+ }
+
+ return p;
+}
+} // namespace
+
+namespace {
+template <typename InputIt> InputIt eat_dir(InputIt first, InputIt last) {
+ auto p = eat_file(first, last);
+
+ --p;
+
+ assert(*p == '/');
+
+ return eat_file(first, p);
+}
+} // namespace
+
+StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
+ const StringRef &base_query, const StringRef &rel_path,
+ const StringRef &rel_query) {
+ auto res = make_byte_ref(
+ balloc, std::max(static_cast<size_t>(1), base_path.size()) +
+ rel_path.size() + 1 +
+ std::max(base_query.size(), rel_query.size()) + 1);
+ auto p = res.base;
+
+ if (rel_path.empty()) {
+ if (base_path.empty()) {
+ *p++ = '/';
+ } else {
+ p = std::copy(std::begin(base_path), std::end(base_path), p);
+ }
+ if (rel_query.empty()) {
+ if (!base_query.empty()) {
+ *p++ = '?';
+ p = std::copy(std::begin(base_query), std::end(base_query), p);
+ }
+ *p = '\0';
+ return StringRef{res.base, p};
+ }
+ *p++ = '?';
+ p = std::copy(std::begin(rel_query), std::end(rel_query), p);
+ *p = '\0';
+ return StringRef{res.base, p};
+ }
+
+ auto first = std::begin(rel_path);
+ auto last = std::end(rel_path);
+
+ if (rel_path[0] == '/') {
+ *p++ = '/';
+ ++first;
+ for (; first != last && *first == '/'; ++first)
+ ;
+ } else if (base_path.empty()) {
+ *p++ = '/';
+ } else {
+ p = std::copy(std::begin(base_path), std::end(base_path), p);
+ }
+
+ for (; first != last;) {
+ if (*first == '.') {
+ if (first + 1 == last) {
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ break;
+ }
+ if (*(first + 1) == '/') {
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ first += 2;
+ continue;
+ }
+ if (*(first + 1) == '.') {
+ if (first + 2 == last) {
+ p = eat_dir(res.base, p);
+ break;
+ }
+ if (*(first + 2) == '/') {
+ p = eat_dir(res.base, p);
+ first += 3;
+ continue;
+ }
+ }
+ }
+ if (*(p - 1) != '/') {
+ p = eat_file(res.base, p);
+ }
+ auto slash = std::find(first, last, '/');
+ if (slash == last) {
+ p = std::copy(first, last, p);
+ break;
+ }
+ p = std::copy(first, slash + 1, p);
+ first = slash + 1;
+ for (; first != last && *first == '/'; ++first)
+ ;
+ }
+ if (!rel_query.empty()) {
+ *p++ = '?';
+ p = std::copy(std::begin(rel_query), std::end(rel_query), p);
+ }
+ *p = '\0';
+ return StringRef{res.base, p};
+}
+
+StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query) {
+ // First, decode %XX for unreserved characters, then do
+ // http2::path_join
+
+ // We won't find %XX if length is less than 3.
+ if (path.size() < 3 ||
+ std::find(std::begin(path), std::end(path), '%') == std::end(path)) {
+ return path_join(balloc, StringRef{}, StringRef{}, path, query);
+ }
+
+ // includes last terminal NULL.
+ auto result = make_byte_ref(balloc, path.size() + 1);
+ auto p = result.base;
+
+ auto it = std::begin(path);
+ for (; it + 2 < std::end(path);) {
+ if (*it == '%') {
+ if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) {
+ auto c =
+ (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2));
+ if (util::in_rfc3986_unreserved_chars(c)) {
+ *p++ = c;
+
+ it += 3;
+
+ continue;
+ }
+ *p++ = '%';
+ *p++ = util::upcase(*(it + 1));
+ *p++ = util::upcase(*(it + 2));
+
+ it += 3;
+
+ continue;
+ }
+ }
+ *p++ = *it++;
+ }
+
+ p = std::copy(it, std::end(path), p);
+ *p = '\0';
+
+ return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p},
+ query);
+}
+
+StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query) {
+ // First, decode %XX for unreserved characters and ':', then do
+ // http2::path_join
+
+ // We won't find %XX if length is less than 3.
+ if (path.size() < 3 ||
+ std::find(std::begin(path), std::end(path), '%') == std::end(path)) {
+ return path_join(balloc, StringRef{}, StringRef{}, path, query);
+ }
+
+ // includes last terminal NULL.
+ auto result = make_byte_ref(balloc, path.size() + 1);
+ auto p = result.base;
+
+ auto it = std::begin(path);
+ for (; it + 2 < std::end(path);) {
+ if (*it == '%') {
+ if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) {
+ auto c =
+ (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2));
+ if (util::in_rfc3986_unreserved_chars(c) || c == ':') {
+ *p++ = c;
+
+ it += 3;
+
+ continue;
+ }
+ *p++ = '%';
+ *p++ = util::upcase(*(it + 1));
+ *p++ = util::upcase(*(it + 2));
+
+ it += 3;
+
+ continue;
+ }
+ }
+ *p++ = *it++;
+ }
+
+ p = std::copy(it, std::end(path), p);
+ *p = '\0';
+
+ return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p},
+ query);
+}
+
+std::string normalize_path(const StringRef &path, const StringRef &query) {
+ BlockAllocator balloc(1024, 1024);
+
+ return normalize_path(balloc, path, query).str();
+}
+
+StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src) {
+ if (src.empty() || src[0] != '/') {
+ return src;
+ }
+ // probably, not necessary most of the case, but just in case.
+ auto fragment = std::find(std::begin(src), std::end(src), '#');
+ auto raw_query = std::find(std::begin(src), fragment, '?');
+ auto query = raw_query;
+ if (query != fragment) {
+ ++query;
+ }
+ return normalize_path(balloc, StringRef{std::begin(src), raw_query},
+ StringRef{query, fragment});
+}
+
+StringRef copy_lower(BlockAllocator &balloc, const StringRef &src) {
+ auto iov = make_byte_ref(balloc, src.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(src), std::end(src), p);
+ *p = '\0';
+ util::inp_strlower(iov.base, p);
+ return StringRef{iov.base, p};
+}
+
+bool contains_trailers(const StringRef &s) {
+ constexpr auto trailers = StringRef::from_lit("trailers");
+
+ for (auto p = std::begin(s), end = std::end(s);; ++p) {
+ p = std::find_if(p, end, [](char c) { return c != ' ' && c != '\t'; });
+ if (p == end || static_cast<size_t>(end - p) < trailers.size()) {
+ return false;
+ }
+ if (util::strieq(trailers, StringRef{p, p + trailers.size()})) {
+ // Make sure that there is no character other than white spaces
+ // before next "," or end of string.
+ p = std::find_if(p + trailers.size(), end,
+ [](char c) { return c != ' ' && c != '\t'; });
+ if (p == end || *p == ',') {
+ return true;
+ }
+ }
+ // Skip to next ",".
+ p = std::find_if(p, end, [](char c) { return c == ','; });
+ if (p == end) {
+ return false;
+ }
+ }
+}
+
+StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key) {
+ static constexpr uint8_t magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ std::array<uint8_t, base64::encode_length(16) + str_size(magic)> s;
+ auto p = std::copy(std::begin(key), std::end(key), std::begin(s));
+ std::copy_n(magic, str_size(magic), p);
+
+ std::array<uint8_t, 20> h;
+ if (util::sha1(h.data(), StringRef{std::begin(s), std::end(s)}) != 0) {
+ return StringRef{};
+ }
+
+ auto end = base64::encode(std::begin(h), std::end(h), dest);
+ return StringRef{dest, end};
+}
+
+bool legacy_http1(int major, int minor) {
+ return major <= 0 || (major == 1 && minor == 0);
+}
+
+bool check_transfer_encoding(const StringRef &s) {
+ if (s.empty()) {
+ return false;
+ }
+
+ auto it = std::begin(s);
+
+ for (;;) {
+ // token
+ if (!util::in_token(*it)) {
+ return false;
+ }
+
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+
+ if (it == std::end(s)) {
+ return true;
+ }
+
+ for (;;) {
+ // OWS
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ if (*it == ',') {
+ ++it;
+
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ break;
+ }
+
+ if (*it != ';') {
+ return false;
+ }
+
+ ++it;
+
+ // transfer-parameter follows
+
+ // OWS
+ it = skip_lws(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ // token
+ if (!util::in_token(*it)) {
+ return false;
+ }
+
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ // No BWS allowed
+ if (*it != '=') {
+ return false;
+ }
+
+ ++it;
+
+ if (util::in_token(*it)) {
+ // token
+ ++it;
+
+ for (; it != std::end(s) && util::in_token(*it); ++it)
+ ;
+ } else if (*it == '"') {
+ // quoted-string
+ ++it;
+
+ it = skip_to_right_dquote(it, std::end(s));
+ if (it == std::end(s)) {
+ return false;
+ }
+
+ ++it;
+ } else {
+ return false;
+ }
+
+ if (it == std::end(s)) {
+ return true;
+ }
+ }
+ }
+}
+
+} // namespace http2
+
+} // namespace nghttp2
diff --git a/src/http2.h b/src/http2.h
new file mode 100644
index 0000000..7cfe461
--- /dev/null
+++ b/src/http2.h
@@ -0,0 +1,457 @@
+/*
+ * 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 "url-parser/url_parser.h"
+
+#include "util.h"
+#include "memchunk.h"
+#include "template.h"
+#include "allocator.h"
+#include "base64.h"
+
+namespace nghttp2 {
+
+struct Header {
+ Header(std::string name, std::string value, bool no_index = false,
+ int32_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;
+ int32_t token;
+ bool no_index;
+};
+
+struct HeaderRef {
+ HeaderRef(const StringRef &name, const StringRef &value,
+ bool no_index = false, int32_t token = -1)
+ : name(name), value(value), token(token), no_index(no_index) {}
+
+ HeaderRef() : token(-1), no_index(false) {}
+
+ bool operator==(const HeaderRef &other) const {
+ return name == other.name && value == other.value;
+ }
+
+ bool operator<(const HeaderRef &rhs) const {
+ return name < rhs.name || (name == rhs.name && value < rhs.value);
+ }
+
+ StringRef name;
+ StringRef value;
+ int32_t token;
+ bool no_index;
+};
+
+using Headers = std::vector<Header>;
+using HeaderRefs = std::vector<HeaderRef>;
+
+namespace http2 {
+
+// Returns reason-phrase for given |status code|. If there is no
+// known reason-phrase for the given code, returns empty string.
+StringRef get_reason_phrase(unsigned int status_code);
+
+// Returns string version of |status_code|. (e.g., "404")
+StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code);
+
+void capitalize(DefaultMemchunks *buf, const StringRef &s);
+
+// 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, int32_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,
+ int32_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 true if the value of |nv| is not empty.
+bool non_empty_value(const HeaderRefs::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);
+
+nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
+ bool no_index = false);
+
+nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool no_index = false);
+
+nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool no_index = false);
+
+// Create nghttp2_nv from string literal |name| and |value|.
+template <size_t N, size_t M>
+constexpr 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_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+// 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_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+// 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_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+template <size_t N>
+nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
+}
+
+enum HeaderBuildOp {
+ HDOP_NONE,
+ // Forwarded header fields must be stripped. If this flag is not
+ // set, all Forwarded header fields other than last one are added.
+ HDOP_STRIP_FORWARDED = 1,
+ // X-Forwarded-For header fields must be stripped. If this flag is
+ // not set, all X-Forwarded-For header fields other than last one
+ // are added.
+ HDOP_STRIP_X_FORWARDED_FOR = 1 << 1,
+ // X-Forwarded-Proto header fields must be stripped. If this flag
+ // is not set, all X-Forwarded-Proto header fields other than last
+ // one are added.
+ HDOP_STRIP_X_FORWARDED_PROTO = 1 << 2,
+ // Via header fields must be stripped. If this flag is not set, all
+ // Via header fields other than last one are added.
+ HDOP_STRIP_VIA = 1 << 3,
+ // Early-Data header fields must be stripped. If this flag is not
+ // set, all Early-Data header fields are added.
+ HDOP_STRIP_EARLY_DATA = 1 << 4,
+ // Strip above all header fields.
+ HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
+ HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA |
+ HDOP_STRIP_EARLY_DATA,
+ // Sec-WebSocket-Accept header field must be stripped. If this flag
+ // is not set, all Sec-WebSocket-Accept header fields are added.
+ HDOP_STRIP_SEC_WEBSOCKET_ACCEPT = 1 << 5,
+ // Sec-WebSocket-Key header field must be stripped. If this flag is
+ // not set, all Sec-WebSocket-Key header fields are added.
+ HDOP_STRIP_SEC_WEBSOCKET_KEY = 1 << 6,
+ // Transfer-Encoding header field must be stripped. If this flag is
+ // not set, all Transfer-Encoding header fields are added.
+ HDOP_STRIP_TRANSFER_ENCODING = 1 << 7,
+};
+
+// 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. |flags|
+// is one or more of HeaderBuildOp flags. They tell function that
+// certain header fields should not be added.
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Just like copy_headers_to_nva(), but this adds
+// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
+void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Appends HTTP/1.1 style header lines to |buf| 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.
+// |flags| is one or more of HeaderBuildOp flags. They tell function
+// that certain header fields should not be added.
+void build_http1_headers_from_headers(DefaultMemchunks *buf,
+ const HeaderRefs &headers,
+ uint32_t flags);
+
+// 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);
+
+void dump_nv(FILE *out, const HeaderRefs &nva);
+
+// Ereases header in |hd|.
+void erase_header(HeaderRef *hd);
+
+// 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
+// empty string.
+StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
+ const http_parser_url &u,
+ const StringRef &match_host,
+ const StringRef &request_authority,
+ const StringRef &upstream_scheme);
+
+// Returns parsed HTTP status code. Returns -1 on failure.
+int parse_http_status_code(const StringRef &src);
+
+// Header fields to be indexed, except HD_MAXIDX which is convenient
+// member to get maximum value.
+//
+// generated by genheaderfunc.py
+enum {
+ HD__AUTHORITY,
+ HD__HOST,
+ HD__METHOD,
+ HD__PATH,
+ HD__PROTOCOL,
+ HD__SCHEME,
+ HD__STATUS,
+ HD_ACCEPT_ENCODING,
+ HD_ACCEPT_LANGUAGE,
+ HD_ALT_SVC,
+ HD_CACHE_CONTROL,
+ HD_CONNECTION,
+ HD_CONTENT_LENGTH,
+ HD_CONTENT_TYPE,
+ HD_COOKIE,
+ HD_DATE,
+ HD_EARLY_DATA,
+ HD_EXPECT,
+ HD_FORWARDED,
+ HD_HOST,
+ HD_HTTP2_SETTINGS,
+ HD_IF_MODIFIED_SINCE,
+ HD_KEEP_ALIVE,
+ HD_LINK,
+ HD_LOCATION,
+ HD_PRIORITY,
+ HD_PROXY_CONNECTION,
+ HD_SEC_WEBSOCKET_ACCEPT,
+ HD_SEC_WEBSOCKET_KEY,
+ 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 StringRef &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, int32_t token, size_t idx);
+
+// Returns header denoted by |token| using index |hdidx|.
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ const Headers &nva);
+
+Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
+ Headers &nva);
+
+struct LinkHeader {
+ // The region of URI. This might not be NULL-terminated.
+ StringRef uri;
+};
+
+// Returns next URI-reference in Link header field value |src|. 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 StringRef &src);
+
+// Constructs path by combining base path |base_path| with another
+// path |rel_path|. The base path and another path can have optional
+// query component. This function assumes |base_path| is normalized.
+// In other words, it does not contain ".." or "." path components
+// and starts with "/" if it is not empty.
+std::string path_join(const StringRef &base, const StringRef &base_query,
+ const StringRef &rel_path, const StringRef &rel_query);
+
+StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
+ const StringRef &base_query, const StringRef &rel_path,
+ const StringRef &rel_query);
+
+// true if response has body, taking into account the request method
+// and status code.
+bool expect_response_body(const std::string &method, int status_code);
+bool expect_response_body(int method_token, int status_code);
+
+// true if response has body, taking into account status code only.
+bool expect_response_body(int status_code);
+
+// Looks up method token for method name |name| of length |namelen|.
+// Only methods defined in llhttp.h (llhttp_method) are tokenized. If
+// method name cannot be tokenized, returns -1.
+int lookup_method_token(const uint8_t *name, size_t namelen);
+int lookup_method_token(const StringRef &name);
+
+// Returns string representation of |method_token|. This is wrapper
+// around llhttp_method_name from llhttp. If |method_token| is
+// unknown, program aborts. The returned StringRef is guaranteed to
+// be NULL-terminated.
+StringRef to_method_string(int method_token);
+
+StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query);
+
+// normalize_path_colon is like normalize_path, but it additionally
+// does percent-decoding %3A in order to workaround the issue that ':'
+// cannot be included in backend pattern.
+StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path,
+ const StringRef &query);
+
+std::string normalize_path(const StringRef &path, const StringRef &query);
+
+StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src);
+
+// Returns path component of |uri|. The returned path does not
+// include query component. This function returns empty string if it
+// fails.
+StringRef get_pure_path_component(const StringRef &uri);
+
+// Deduces scheme, authority and path from given |uri|, and stores
+// them in |scheme|, |authority|, and |path| respectively. If |uri|
+// is relative path, path resolution takes place using path given in
+// |base| of length |baselen|. This function returns 0 if it
+// succeeds, or -1.
+int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
+ StringRef &authority, StringRef &path,
+ const StringRef &base, const StringRef &uri);
+
+// Copies |src| and return its lower-cased version.
+StringRef copy_lower(BlockAllocator &balloc, const StringRef &src);
+
+// Returns true if te header field value |s| contains "trailers".
+bool contains_trailers(const StringRef &s);
+
+// Creates Sec-WebSocket-Accept value for |key|. The capacity of
+// buffer pointed by |dest| must have at least 24 bytes (base64
+// encoded length of 16 bytes data). It returns empty string in case
+// of error.
+StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key);
+
+// Returns true if HTTP version represents pre-HTTP/1.1 (e.g.,
+// HTTP/0.9 or HTTP/1.0).
+bool legacy_http1(int major, int minor);
+
+// Returns true if transfer-encoding field value |s| conforms RFC
+// strictly. This function does not allow empty value, BWS, and empty
+// list elements.
+bool check_transfer_encoding(const StringRef &s);
+
+} // namespace http2
+
+} // namespace nghttp2
+
+#endif // HTTP2_H
diff --git a/src/http2_test.cc b/src/http2_test.cc
new file mode 100644
index 0000000..f8be9f4
--- /dev/null
+++ b/src/http2_test.cc
@@ -0,0 +1,1249 @@
+/*
+ * 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 "url-parser/url_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 HeaderRef &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 = HeaderRefs{
+ {StringRef::from_lit("alpha"), StringRef::from_lit("0"), true},
+ {StringRef::from_lit("bravo"), StringRef::from_lit("1")},
+ {StringRef::from_lit("connection"), StringRef::from_lit("2"), false,
+ http2::HD_CONNECTION},
+ {StringRef::from_lit("connection"), StringRef::from_lit("3"), false,
+ http2::HD_CONNECTION},
+ {StringRef::from_lit("delta"), StringRef::from_lit("4")},
+ {StringRef::from_lit("expect"), StringRef::from_lit("5")},
+ {StringRef::from_lit("foxtrot"), StringRef::from_lit("6")},
+ {StringRef::from_lit("tango"), StringRef::from_lit("7")},
+ {StringRef::from_lit("te"), StringRef::from_lit("8"), false, http2::HD_TE},
+ {StringRef::from_lit("te"), StringRef::from_lit("9"), false, http2::HD_TE},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("10"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("11"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("zulu"), StringRef::from_lit("12")}};
+} // namespace
+
+namespace {
+auto headers2 = HeaderRefs{
+ {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff1"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff2"), false,
+ http2::HD_X_FORWARDED_FOR},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp1"),
+ false, http2::HD_X_FORWARDED_PROTO},
+ {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp2"),
+ false, http2::HD_X_FORWARDED_PROTO},
+ {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd1"), false,
+ http2::HD_FORWARDED},
+ {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd2"), false,
+ http2::HD_FORWARDED},
+ {StringRef::from_lit("via"), StringRef::from_lit("via1"), false,
+ http2::HD_VIA},
+ {StringRef::from_lit("via"), StringRef::from_lit("via2"), false,
+ http2::HD_VIA},
+};
+} // namespace
+
+void test_http2_copy_headers_to_nva(void) {
+ auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
+ std::vector<nghttp2_nv> nva;
+
+ http2::copy_headers_to_nva_nocopy(nva, headers,
+ http2::HDOP_STRIP_X_FORWARDED_FOR);
+ CU_ASSERT(7 == nva.size());
+ for (size_t i = 0; i < ans.size(); ++i) {
+ check_nv(headers[ans[i]], &nva[i]);
+
+ if (ans[i] == 0) {
+ CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE |
+ NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags);
+ } else {
+ CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags);
+ }
+ }
+
+ nva.clear();
+ http2::copy_headers_to_nva(nva, headers, http2::HDOP_STRIP_X_FORWARDED_FOR);
+ CU_ASSERT(7 == nva.size());
+ 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);
+ }
+ }
+
+ nva.clear();
+
+ auto ans2 = std::vector<int>{0, 2, 4, 6};
+ http2::copy_headers_to_nva(nva, headers2, http2::HDOP_NONE);
+ CU_ASSERT(ans2.size() == nva.size());
+ for (size_t i = 0; i < ans2.size(); ++i) {
+ check_nv(headers2[ans2[i]], &nva[i]);
+ }
+
+ nva.clear();
+
+ http2::copy_headers_to_nva(nva, headers2, http2::HDOP_STRIP_ALL);
+ CU_ASSERT(nva.empty());
+}
+
+void test_http2_build_http1_headers_from_headers(void) {
+ MemchunkPool pool;
+ DefaultMemchunks buf(&pool);
+ http2::build_http1_headers_from_headers(&buf, headers,
+ http2::HDOP_STRIP_X_FORWARDED_FOR);
+ auto hdrs = std::string(buf.head->pos, buf.head->last);
+ CU_ASSERT("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" == hdrs);
+
+ buf.reset();
+
+ http2::build_http1_headers_from_headers(&buf, headers2, http2::HDOP_NONE);
+ hdrs = std::string(buf.head->pos, buf.head->last);
+ CU_ASSERT("X-Forwarded-For: xff1\r\n"
+ "X-Forwarded-Proto: xfp1\r\n"
+ "Forwarded: fwd1\r\n"
+ "Via: via1\r\n" == hdrs);
+
+ buf.reset();
+
+ http2::build_http1_headers_from_headers(&buf, headers2,
+ http2::HDOP_STRIP_ALL);
+ CU_ASSERT(0 == buf.rleft());
+}
+
+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) {
+ BlockAllocator balloc(4096, 4096);
+ http_parser_url u{};
+ CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u));
+ auto got = http2::rewrite_location_uri(
+ balloc, StringRef{uri}, u, StringRef{match_host},
+ StringRef{req_authority}, StringRef{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(StringRef::from_lit("200")));
+ CU_ASSERT(102 == http2::parse_http_status_code(StringRef::from_lit("102")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("099")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("99")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("-1")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("20a")));
+ CU_ASSERT(-1 == http2::parse_http_status_code(StringRef{}));
+}
+
+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(StringRef::from_lit(":authority")));
+ CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":authorit")));
+ CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":Authority")));
+ CU_ASSERT(http2::HD_EXPECT ==
+ http2::lookup_token(StringRef::from_lit("expect")));
+}
+
+void test_http2_parse_link_header(void) {
+ {
+ // only URI appears; we don't extract URI unless it bears rel=preload
+ auto res = http2::parse_link_header(StringRef::from_lit("<url>"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // URI url should be extracted
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param. URI url should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; as=file"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param. URI url should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; as=file; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param and quote-string. URI url should be
+ // extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; title="foo,bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With extra link-param and quote-string. URI url should be
+ // extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; title="foo,bar"; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // ',' after quote-string
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; title="foo,bar", <url2>; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Only first URI should be extracted.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload, <url2>"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // Both have rel=preload, so both urls should be extracted
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload, <url2>; rel=preload"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ CU_ASSERT("url2" == res[1].uri);
+ }
+ {
+ // Second URI uri should be extracted.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>, <url2>;rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Error if input ends with ';'
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload;"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Error if link header ends with ';'
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;rel=preload;, <url>"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // OK if input ends with ','
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload,"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // Multiple repeated ','s between fields is OK
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>,,,<url2>;rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // Error if url is not enclosed by <>
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("url>;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Error if url is not enclosed by <>
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;rel=preload; as="));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>;as=;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter value is not allowed
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;as=, <url>;rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Empty parameter name is not allowed
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; =file; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Without whitespaces
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>;as=file;rel=preload,<url2>;rel=preload"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ CU_ASSERT("url2" == res[1].uri);
+ }
+ {
+ // link-extension may have no value
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; as; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // ext-name-star
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*=bar; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // '*' is not allowed expect for trailing one
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; *=bar; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // '*' is not allowed expect for trailing one
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*bar=buzz; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // ext-name-star must be followed by '='
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; foo*; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // '>' is not followed by ';'
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url> rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // Starting with whitespace is no problem.
+ auto res =
+ http2::parse_link_header(StringRef::from_lit(" <url>; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload is a prefix of bogus rel parameter value
+ auto res =
+ http2::parse_link_header(StringRef::from_lit("<url>; rel=preloadx"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list followed by another parameter
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload foo")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list following another parameter
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list between other parameters
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list between other parameters
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo preload bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // no preload in relation-types list
+ auto res =
+ http2::parse_link_header(StringRef::from_lit(R"(<url>; rel="foo")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // no preload in relation-types list, multiple unrelated elements.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="foo bar")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by another link-value.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload", <url2>)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, following another link-value.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>, <url2>; rel="preload")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, followed by another link-param.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload"; as="font")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list, followed by character other
+ // than ';' or ','
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload".)"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by ';' but it
+ // terminates input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload";)"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list, followed by ',' but it
+ // terminates input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload",)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // preload in relation-types list but there is preceding white
+ // space.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=" preload")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // preload in relation-types list but there is trailing white
+ // space.
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel="preload ")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // backslash escaped characters in quoted-string
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; title="foo\"baz\"bar")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // anchor="" is acceptable
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; anchor="")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // With anchor="#foo", url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; anchor="#foo")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // With anchor=f, url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; anchor=f"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // First url is ignored With anchor="#foo", but url should be
+ // accepted.
+ auto res = http2::parse_link_header(StringRef::from_lit(
+ R"(<url>; rel=preload; anchor="#foo", <url2>; rel=preload)"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ }
+ {
+ // With loadpolicy="next", url should be ignored
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; loadpolicy="next")"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // url should be picked up if empty loadpolicy is specified
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; loadpolicy="")"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // case-insensitive match
+ auto res = http2::parse_link_header(
+ StringRef::from_lit(R"(<url>; rel=preload; ANCHOR="#foo", <url2>; )"
+ R"(REL=PRELOAD, <url3>; REL="foo PRELOAD bar")"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("url2" == res[0].uri);
+ CU_ASSERT("url3" == res[1].uri);
+ }
+ {
+ // nopush at the end of input
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; nopush"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // nopush followed by ';'
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; nopush; foo"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // nopush followed by ','
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; nopush; rel=preload"));
+ CU_ASSERT(0 == res.size());
+ }
+ {
+ // string whose prefix is nopush
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; nopushyes; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+ {
+ // rel=preload twice
+ auto res = http2::parse_link_header(
+ StringRef::from_lit("<url>; rel=preload; rel=preload"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("url" == res[0].uri);
+ }
+}
+
+void test_http2_path_join(void) {
+ {
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha");
+ CU_ASSERT("/alpha" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel ends with trailing '/'
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/");
+ CU_ASSERT("/alpha/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel contains multiple components
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/bravo");
+ CU_ASSERT("/alpha/bravo" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel is relative
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("alpha/bravo");
+ CU_ASSERT("/alpha/bravo" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel is relative and base ends without /, which means it refers
+ // to file.
+ auto base = StringRef::from_lit("/alpha");
+ auto rel = StringRef::from_lit("bravo/charlie");
+ CU_ASSERT("/bravo/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel contains repeated '/'s
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/alpha/////bravo/////");
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base ends with '/', so '..' eats 'bravo'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../charlie/delta");
+ CU_ASSERT("/alpha/charlie/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base does not end with '/', so '..' eats 'alpha/bravo'
+ auto base = StringRef::from_lit("/alpha/bravo");
+ auto rel = StringRef::from_lit("../charlie");
+ CU_ASSERT("/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // 'charlie' is eaten by following '..'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../charlie/../delta");
+ CU_ASSERT("/alpha/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // excessive '..' results in '/'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../../../");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // excessive '..' and path component
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("../../../charlie");
+ CU_ASSERT("/charlie" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // rel ends with '..'
+ auto base = StringRef::from_lit("/alpha/bravo/");
+ auto rel = StringRef::from_lit("charlie/..");
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // base empty and rel contains '..'
+ auto base = StringRef{};
+ auto rel = StringRef::from_lit("charlie/..");
+ CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // '.' is ignored
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("charlie/././././delta");
+ CU_ASSERT("/charlie/delta" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // trailing '.' is ignored
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("charlie/.");
+ CU_ASSERT("/charlie/" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // query
+ auto base = StringRef::from_lit("/");
+ auto rel = StringRef::from_lit("/");
+ auto relq = StringRef::from_lit("q");
+ CU_ASSERT("/?q" == http2::path_join(base, StringRef{}, rel, relq));
+ }
+ {
+ // empty rel and query
+ auto base = StringRef::from_lit("/alpha");
+ auto rel = StringRef{};
+ auto relq = StringRef::from_lit("q");
+ CU_ASSERT("/alpha?q" == http2::path_join(base, StringRef{}, rel, relq));
+ }
+ {
+ // both rel and query are empty
+ auto base = StringRef::from_lit("/alpha");
+ auto baseq = StringRef::from_lit("r");
+ auto rel = StringRef{};
+ auto relq = StringRef{};
+ CU_ASSERT("/alpha?r" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ // empty base
+ auto base = StringRef{};
+ auto rel = StringRef::from_lit("/alpha");
+ CU_ASSERT("/alpha" ==
+ http2::path_join(base, StringRef{}, rel, StringRef{}));
+ }
+ {
+ // everything is empty
+ CU_ASSERT("/" == http2::path_join(StringRef{}, StringRef{}, StringRef{},
+ StringRef{}));
+ }
+ {
+ // only baseq is not empty
+ auto base = StringRef{};
+ auto baseq = StringRef::from_lit("r");
+ auto rel = StringRef{};
+ CU_ASSERT("/?r" == http2::path_join(base, baseq, rel, StringRef{}));
+ }
+ {
+ // path starts with multiple '/'s.
+ auto base = StringRef{};
+ auto baseq = StringRef{};
+ auto rel = StringRef::from_lit("//alpha//bravo");
+ auto relq = StringRef::from_lit("charlie");
+ CU_ASSERT("/alpha/bravo?charlie" ==
+ http2::path_join(base, baseq, rel, relq));
+ }
+ // Test cases from RFC 3986, section 5.4.
+ constexpr auto base = StringRef::from_lit("/b/c/d;p");
+ constexpr auto baseq = StringRef::from_lit("q");
+ {
+ auto rel = StringRef::from_lit("g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef{};
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/d;p?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g");
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/g?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(";x");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/;x" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g;x" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x");
+ auto relq = StringRef::from_lit("y");
+ CU_ASSERT("/b/c/g;x?y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef{};
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/d;p?q" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(".");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("..");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../..");
+ auto relq = StringRef{};
+ CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../");
+ auto relq = StringRef{};
+ CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("../../../../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/./g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("/../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g.");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g." == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit(".g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/.g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g..");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g.." == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("..g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/..g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./../g");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("./g/.");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/./h");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g/h" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g/../h");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/h" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x=1/./y");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/g;x=1/y" == http2::path_join(base, baseq, rel, relq));
+ }
+ {
+ auto rel = StringRef::from_lit("g;x=1/../y");
+ auto relq = StringRef{};
+ CU_ASSERT("/b/c/y" == http2::path_join(base, baseq, rel, relq));
+ }
+}
+
+void test_http2_normalize_path(void) {
+ CU_ASSERT("/alpha/charlie" ==
+ http2::normalize_path(
+ StringRef::from_lit("/alpha/bravo/../charlie"), StringRef{}));
+
+ CU_ASSERT("/alpha" ==
+ http2::normalize_path(StringRef::from_lit("/a%6c%70%68%61"),
+ StringRef{}));
+
+ CU_ASSERT(
+ "/alpha%2F%3A" ==
+ http2::normalize_path(StringRef::from_lit("/alpha%2f%3a"), StringRef{}));
+
+ CU_ASSERT("/%2F" ==
+ http2::normalize_path(StringRef::from_lit("%2f"), StringRef{}));
+
+ CU_ASSERT("/%f" ==
+ http2::normalize_path(StringRef::from_lit("%f"), StringRef{}));
+
+ CU_ASSERT("/%" ==
+ http2::normalize_path(StringRef::from_lit("%"), StringRef{}));
+
+ CU_ASSERT("/" == http2::normalize_path(StringRef{}, StringRef{}));
+
+ CU_ASSERT("/alpha?bravo" ==
+ http2::normalize_path(StringRef::from_lit("/alpha"),
+ StringRef::from_lit("bravo")));
+}
+
+void test_http2_rewrite_clean_path(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ // unreserved characters
+ CU_ASSERT("/alpha/bravo/" ==
+ http2::rewrite_clean_path(balloc,
+ StringRef::from_lit("/alpha/%62ravo/")));
+
+ // percent-encoding is converted to upper case.
+ CU_ASSERT("/delta%3A" == http2::rewrite_clean_path(
+ balloc, StringRef::from_lit("/delta%3a")));
+
+ // path component is normalized before matching
+ CU_ASSERT(
+ "/alpha/bravo/" ==
+ http2::rewrite_clean_path(
+ balloc, StringRef::from_lit("/alpha/charlie/%2e././bravo/delta/..")));
+
+ CU_ASSERT("alpha%3a" ==
+ http2::rewrite_clean_path(balloc, StringRef::from_lit("alpha%3a")));
+
+ CU_ASSERT("" == http2::rewrite_clean_path(balloc, StringRef{}));
+
+ CU_ASSERT(
+ "/alpha?bravo" ==
+ http2::rewrite_clean_path(balloc, StringRef::from_lit("//alpha?bravo")));
+}
+
+void test_http2_get_pure_path_component(void) {
+ CU_ASSERT("/" == http2::get_pure_path_component(StringRef::from_lit("/")));
+
+ CU_ASSERT("/foo" ==
+ http2::get_pure_path_component(StringRef::from_lit("/foo")));
+
+ CU_ASSERT("/bar" == http2::get_pure_path_component(
+ StringRef::from_lit("https://example.org/bar")));
+
+ CU_ASSERT("/alpha" == http2::get_pure_path_component(StringRef::from_lit(
+ "https://example.org/alpha?q=a")));
+
+ CU_ASSERT("/bravo" == http2::get_pure_path_component(StringRef::from_lit(
+ "https://example.org/bravo?q=a#fragment")));
+
+ CU_ASSERT("" ==
+ http2::get_pure_path_component(StringRef::from_lit("\x01\x02")));
+}
+
+void test_http2_construct_push_component(void) {
+ BlockAllocator balloc(4096, 4096);
+ StringRef base, uri;
+ StringRef scheme, authority, path;
+
+ base = StringRef::from_lit("/b/");
+ uri = StringRef::from_lit("https://example.org/foo");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("https" == scheme);
+ CU_ASSERT("example.org" == authority);
+ CU_ASSERT("/foo" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("/foo/bar?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/foo/bar?q=a" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("foo/../bar?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/b/bar?q=a" == path);
+
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef{};
+
+ CU_ASSERT(-1 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ scheme = StringRef{};
+ authority = StringRef{};
+ path = StringRef{};
+
+ uri = StringRef::from_lit("?q=a");
+
+ CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
+ path, base, uri));
+ CU_ASSERT("" == scheme);
+ CU_ASSERT("" == authority);
+ CU_ASSERT("/b/?q=a" == path);
+}
+
+void test_http2_contains_trailers(void) {
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers")));
+ // Match must be case-insensitive.
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("TRAILERS")));
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailer")));
+ CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailers 3")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,foo")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers,bar")));
+ CU_ASSERT(
+ http2::contains_trailers(StringRef::from_lit("foo, trailers ,bar")));
+ CU_ASSERT(http2::contains_trailers(StringRef::from_lit(",trailers")));
+}
+
+void test_http2_check_transfer_encoding(void) {
+ CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("chunked")));
+ CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("foo,chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("foo, chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("foo , chunked")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit("chunked ; foo=bar")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar")")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="\bar\"";FOO=BAR)")));
+ CU_ASSERT(
+ http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="")")));
+ CU_ASSERT(http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar" , gzip)")));
+
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef{}));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(",chunked")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked,")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked, ")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("foo,,chunked")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar;")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;?=bar")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit("chunked;=bar")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;;")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked?")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(",")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(" ")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(";")));
+ CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("\"")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar)")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar\)")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="bar\)"
+ "\x0a"
+ R"(")")));
+ CU_ASSERT(
+ !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo=")"
+ "\x0a"
+ R"(")")));
+ CU_ASSERT(!http2::check_transfer_encoding(
+ StringRef::from_lit(R"(chunked;foo="bar",,gzip)")));
+}
+
+} // namespace shrpx
diff --git a/src/http2_test.h b/src/http2_test.h
new file mode 100644
index 0000000..382470d
--- /dev/null
+++ b/src/http2_test.h
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_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_parse_link_header(void);
+void test_http2_path_join(void);
+void test_http2_normalize_path(void);
+void test_http2_rewrite_clean_path(void);
+void test_http2_get_pure_path_component(void);
+void test_http2_construct_push_component(void);
+void test_http2_contains_trailers(void);
+void test_http2_check_transfer_encoding(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_TEST_H
diff --git a/src/http3.cc b/src/http3.cc
new file mode 100644
index 0000000..61134ad
--- /dev/null
+++ b/src/http3.cc
@@ -0,0 +1,206 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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 "http3.h"
+
+namespace nghttp2 {
+
+namespace http3 {
+
+namespace {
+nghttp3_nv make_nv_internal(const std::string &name, const std::string &value,
+ bool never_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags = nv_flags |
+ (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+namespace {
+nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value,
+ bool never_index, uint8_t nv_flags) {
+ uint8_t flags;
+
+ flags = nv_flags |
+ (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
+
+ return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+ value.size(), flags};
+}
+} // namespace
+
+nghttp3_nv make_nv(const std::string &name, const std::string &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
+}
+
+nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
+}
+
+nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME |
+ NGHTTP3_NV_FLAG_NO_COPY_VALUE);
+}
+
+nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool never_index) {
+ return make_nv_internal(name, value, never_index,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME |
+ NGHTTP3_NV_FLAG_NO_COPY_VALUE);
+}
+
+namespace {
+void copy_headers_to_nva_internal(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint8_t nv_flags,
+ uint32_t flags) {
+ auto it_forwarded = std::end(headers);
+ auto it_xff = std::end(headers);
+ auto it_xfp = std::end(headers);
+ auto it_via = std::end(headers);
+
+ for (auto it = std::begin(headers); it != std::end(headers); ++it) {
+ auto kv = &(*it);
+ if (kv->name.empty() || kv->name[0] == ':') {
+ continue;
+ }
+ switch (kv->token) {
+ case http2::HD_COOKIE:
+ case http2::HD_CONNECTION:
+ case http2::HD_HOST:
+ case http2::HD_HTTP2_SETTINGS:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_SERVER:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ case http2::HD_EARLY_DATA:
+ if (flags & http2::HDOP_STRIP_EARLY_DATA) {
+ continue;
+ }
+ break;
+ case http2::HD_SEC_WEBSOCKET_ACCEPT:
+ if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
+ continue;
+ }
+ break;
+ case http2::HD_SEC_WEBSOCKET_KEY:
+ if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) {
+ continue;
+ }
+ break;
+ case http2::HD_FORWARDED:
+ if (flags & http2::HDOP_STRIP_FORWARDED) {
+ continue;
+ }
+
+ if (it_forwarded == std::end(headers)) {
+ it_forwarded = it;
+ continue;
+ }
+
+ kv = &(*it_forwarded);
+ it_forwarded = it;
+ break;
+ case http2::HD_X_FORWARDED_FOR:
+ if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) {
+ continue;
+ }
+
+ if (it_xff == std::end(headers)) {
+ it_xff = it;
+ continue;
+ }
+
+ kv = &(*it_xff);
+ it_xff = it;
+ break;
+ case http2::HD_X_FORWARDED_PROTO:
+ if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) {
+ continue;
+ }
+
+ if (it_xfp == std::end(headers)) {
+ it_xfp = it;
+ continue;
+ }
+
+ kv = &(*it_xfp);
+ it_xfp = it;
+ break;
+ case http2::HD_VIA:
+ if (flags & http2::HDOP_STRIP_VIA) {
+ continue;
+ }
+
+ if (it_via == std::end(headers)) {
+ it_via = it;
+ continue;
+ }
+
+ kv = &(*it_via);
+ it_via = it;
+ break;
+ }
+ nva.push_back(
+ make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
+ }
+}
+} // namespace
+
+void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags);
+}
+
+void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags) {
+ copy_headers_to_nva_internal(
+ nva, headers,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags);
+}
+
+int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen) {
+ if (!nghttp3_check_header_name(name, namelen)) {
+ return 0;
+ }
+ if (!nghttp3_check_header_value(value, valuelen)) {
+ return 0;
+ }
+ return 1;
+}
+
+} // namespace http3
+
+} // namespace nghttp2
diff --git a/src/http3.h b/src/http3.h
new file mode 100644
index 0000000..81ee0d7
--- /dev/null
+++ b/src/http3.h
@@ -0,0 +1,123 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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 HTTP3_H
+#define HTTP3_H
+
+#include "nghttp2_config.h"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <nghttp3/nghttp3.h>
+
+#include "http2.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+namespace http3 {
+
+// Creates nghttp3_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, nghttp3_nv flags member has
+// NGHTTP3_NV_FLAG_NEVER_INDEX flag set.
+nghttp3_nv make_nv(const std::string &name, const std::string &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
+ bool never_index = false);
+
+nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
+ bool never_index = false);
+
+// Create nghttp3_nv from string literal |name| and |value|.
+template <size_t N, size_t M>
+constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp3_nv from string literal |name| and c-string |value|.
+template <size_t N>
+nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
+ return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// Create nghttp3_nv from string literal |name| and std::string
+// |value|.
+template <size_t N>
+nghttp3_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(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+template <size_t N>
+nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
+ return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
+}
+
+// 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/3 spec and headers
+// which require special handling (i.e. via), are not copied. |flags|
+// is one or more of HeaderBuildOp flags. They tell function that
+// certain header fields should not be added.
+void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Just like copy_headers_to_nva(), but this adds
+// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE.
+void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
+ const HeaderRefs &headers, uint32_t flags);
+
+// Checks the header name/value pair using nghttp3_check_header_name()
+// and nghttp3_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);
+
+} // namespace http3
+
+} // namespace nghttp2
+
+#endif // HTTP3_H
diff --git a/src/inflatehd.cc b/src/inflatehd.cc
new file mode 100644
index 0000000..f484042
--- /dev/null
+++ b/src/inflatehd.cc
@@ -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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <assert.h>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "template.h"
+#include "comp_helper.h"
+
+namespace nghttp2 {
+
+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);
+ auto max_dyn_table_size =
+ nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
+ if (old_settings_table_size != max_dyn_table_size) {
+ json_object_set_new(obj, "header_table_size",
+ json_integer(max_dyn_table_size));
+ }
+ if (config.dump_header_table) {
+ json_object_set_new(obj, "header_table",
+ dump_inflate_header_table(inflater));
+ }
+ 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 =
+ nghttp2_hd_inflate_get_max_dynamic_table_size(inflater);
+
+ auto wire = json_object_get(obj, "wire");
+
+ if (wire == nullptr) {
+ fprintf(stderr, "'wire' key is missing at %d\n", seq);
+ return -1;
+ }
+
+ if (!json_is_string(wire)) {
+ fprintf(stderr, "'wire' value is not string 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 = nullptr;
+ 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;
+ ;
+}
+
+constexpr 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;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/libevent_util.cc b/src/libevent_util.cc
new file mode 100644
index 0000000..3b60b6d
--- /dev/null
+++ b/src/libevent_util.cc
@@ -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.
+ */
+#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
index 0000000..1d1ee91
--- /dev/null
+++ b/src/libevent_util.h
@@ -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/memchunk.h b/src/memchunk.h
new file mode 100644
index 0000000..7a7f2e9
--- /dev/null
+++ b/src/memchunk.h
@@ -0,0 +1,664 @@
+/*
+ * 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 <limits.h>
+#ifdef _WIN32
+/* Structure for scatter/gather I/O. */
+struct iovec {
+ void *iov_base; /* Pointer to data. */
+ size_t iov_len; /* Length of data. */
+};
+#else // !_WIN32
+# include <sys/uio.h>
+#endif // !_WIN32
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <array>
+#include <algorithm>
+#include <string>
+#include <utility>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+#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
+
+template <size_t N> struct Memchunk {
+ Memchunk(Memchunk *next_chunk)
+ : pos(std::begin(buf)), last(pos), knext(next_chunk), next(nullptr) {}
+ 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;
+ Memchunk *knext;
+ Memchunk *next;
+ static const size_t size = N;
+};
+
+template <typename T> struct Pool {
+ Pool() : pool(nullptr), freelist(nullptr), poolsize(0), freelistsize(0) {}
+ ~Pool() { clear(); }
+ T *get() {
+ if (freelist) {
+ auto m = freelist;
+ freelist = freelist->next;
+ m->next = nullptr;
+ m->reset();
+ freelistsize -= T::size;
+ return m;
+ }
+
+ pool = new T{pool};
+ poolsize += T::size;
+ return pool;
+ }
+ void recycle(T *m) {
+ m->next = freelist;
+ freelist = m;
+ freelistsize += T::size;
+ }
+ void clear() {
+ freelist = nullptr;
+ freelistsize = 0;
+ for (auto p = pool; p;) {
+ auto knext = p->knext;
+ delete p;
+ p = knext;
+ }
+ pool = nullptr;
+ poolsize = 0;
+ }
+ using value_type = T;
+ T *pool;
+ T *freelist;
+ size_t poolsize;
+ size_t freelistsize;
+};
+
+template <typename Memchunk> struct Memchunks {
+ Memchunks(Pool<Memchunk> *pool)
+ : pool(pool),
+ head(nullptr),
+ tail(nullptr),
+ len(0),
+ mark(nullptr),
+ mark_pos(nullptr),
+ mark_offset(0) {}
+ Memchunks(const Memchunks &) = delete;
+ Memchunks(Memchunks &&other) noexcept
+ : pool{other.pool}, // keep other.pool
+ head{std::exchange(other.head, nullptr)},
+ tail{std::exchange(other.tail, nullptr)},
+ len{std::exchange(other.len, 0)},
+ mark{std::exchange(other.mark, nullptr)},
+ mark_pos{std::exchange(other.mark_pos, nullptr)},
+ mark_offset{std::exchange(other.mark_offset, 0)} {}
+ Memchunks &operator=(const Memchunks &) = delete;
+ Memchunks &operator=(Memchunks &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ reset();
+
+ pool = other.pool;
+ head = std::exchange(other.head, nullptr);
+ tail = std::exchange(other.tail, nullptr);
+ len = std::exchange(other.len, 0);
+ mark = std::exchange(other.mark, nullptr);
+ mark_pos = std::exchange(other.mark_pos, nullptr);
+ mark_offset = std::exchange(other.mark_offset, 0);
+
+ return *this;
+ }
+ ~Memchunks() {
+ if (!pool) {
+ return;
+ }
+ for (auto m = head; m;) {
+ auto next = m->next;
+ pool->recycle(m);
+ m = next;
+ }
+ }
+ size_t append(char c) {
+ if (!tail) {
+ head = tail = pool->get();
+ } else if (tail->left() == 0) {
+ tail->next = pool->get();
+ tail = tail->next;
+ }
+ *tail->last++ = c;
+ ++len;
+ return 1;
+ }
+ 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 append(const std::string &s) { return append(s.c_str(), s.size()); }
+ size_t append(const StringRef &s) { return append(s.c_str(), s.size()); }
+ size_t append(const ImmutableString &s) {
+ return append(s.c_str(), s.size());
+ }
+ size_t copy(Memchunks &dest) {
+ auto m = head;
+ while (m) {
+ dest.append(m->pos, m->len());
+ m = m->next;
+ }
+ return len;
+ }
+ size_t remove(void *dest, size_t count) {
+ assert(mark == nullptr);
+
+ 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 remove(Memchunks &dest, size_t count) {
+ assert(mark == nullptr);
+
+ if (!tail || count == 0) {
+ return 0;
+ }
+
+ auto left = count;
+ auto m = head;
+
+ while (m) {
+ auto next = m->next;
+ auto n = std::min(left, m->len());
+
+ assert(m->len());
+ dest.append(m->pos, n);
+ m->pos += n;
+ len -= n;
+ left -= n;
+ if (m->len() > 0) {
+ break;
+ }
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+
+ return count - left;
+ }
+ size_t remove(Memchunks &dest) {
+ assert(pool == dest.pool);
+ assert(mark == nullptr);
+
+ if (head == nullptr) {
+ return 0;
+ }
+
+ auto n = len;
+
+ if (dest.tail == nullptr) {
+ dest.head = head;
+ } else {
+ dest.tail->next = head;
+ }
+
+ dest.tail = tail;
+ dest.len += len;
+
+ head = tail = nullptr;
+ len = 0;
+
+ return n;
+ }
+ size_t drain(size_t count) {
+ assert(mark == nullptr);
+
+ 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;
+ }
+ size_t drain_mark(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;
+ mark_offset -= n;
+
+ if (m->len() > 0) {
+ assert(mark != m || m->pos <= mark_pos);
+ break;
+ }
+ if (mark == m) {
+ assert(m->pos <= mark_pos);
+
+ mark = nullptr;
+ mark_pos = nullptr;
+ mark_offset = 0;
+ }
+
+ pool->recycle(m);
+ m = next;
+ }
+ head = m;
+ if (head == nullptr) {
+ tail = nullptr;
+ }
+ return ndata - count;
+ }
+ int riovec(struct iovec *iov, int iovcnt) const {
+ 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;
+ }
+ int riovec_mark(struct iovec *iov, int iovcnt) {
+ if (!head || iovcnt == 0) {
+ return 0;
+ }
+
+ int i = 0;
+ Memchunk *m;
+ if (mark) {
+ if (mark_pos != mark->last) {
+ iov[0].iov_base = mark_pos;
+ iov[0].iov_len = mark->len() - (mark_pos - mark->pos);
+
+ mark_pos = mark->last;
+ mark_offset += iov[0].iov_len;
+ i = 1;
+ }
+ m = mark->next;
+ } else {
+ i = 0;
+ m = head;
+ }
+
+ for (; i < iovcnt && m; ++i, m = m->next) {
+ iov[i].iov_base = m->pos;
+ iov[i].iov_len = m->len();
+
+ mark = m;
+ mark_pos = m->last;
+ mark_offset += m->len();
+ }
+
+ return i;
+ }
+ size_t rleft() const { return len; }
+ size_t rleft_mark() const { return len - mark_offset; }
+ void reset() {
+ for (auto m = head; m;) {
+ auto next = m->next;
+ pool->recycle(m);
+ m = next;
+ }
+ len = 0;
+ head = tail = mark = nullptr;
+ mark_pos = nullptr;
+ mark_offset = 0;
+ }
+
+ Pool<Memchunk> *pool;
+ Memchunk *head, *tail;
+ size_t len;
+ Memchunk *mark;
+ uint8_t *mark_pos;
+ size_t mark_offset;
+};
+
+// Wrapper around Memchunks to offer "peeking" functionality.
+template <typename Memchunk> struct PeekMemchunks {
+ PeekMemchunks(Pool<Memchunk> *pool)
+ : memchunks(pool),
+ cur(nullptr),
+ cur_pos(nullptr),
+ cur_last(nullptr),
+ len(0),
+ peeking(true) {}
+ PeekMemchunks(const PeekMemchunks &) = delete;
+ PeekMemchunks(PeekMemchunks &&other) noexcept
+ : memchunks{std::move(other.memchunks)},
+ cur{std::exchange(other.cur, nullptr)},
+ cur_pos{std::exchange(other.cur_pos, nullptr)},
+ cur_last{std::exchange(other.cur_last, nullptr)},
+ len{std::exchange(other.len, 0)},
+ peeking{std::exchange(other.peeking, true)} {}
+ PeekMemchunks &operator=(const PeekMemchunks &) = delete;
+ PeekMemchunks &operator=(PeekMemchunks &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ memchunks = std::move(other.memchunks);
+ cur = std::exchange(other.cur, nullptr);
+ cur_pos = std::exchange(other.cur_pos, nullptr);
+ cur_last = std::exchange(other.cur_last, nullptr);
+ len = std::exchange(other.len, 0);
+ peeking = std::exchange(other.peeking, true);
+
+ return *this;
+ }
+ size_t append(const void *src, size_t count) {
+ count = memchunks.append(src, count);
+ len += count;
+ return count;
+ }
+ size_t remove(void *dest, size_t count) {
+ if (!peeking) {
+ count = memchunks.remove(dest, count);
+ len -= count;
+ return count;
+ }
+
+ if (count == 0 || len == 0) {
+ return 0;
+ }
+
+ if (!cur) {
+ cur = memchunks.head;
+ cur_pos = cur->pos;
+ }
+
+ // cur_last could be updated in append
+ cur_last = cur->last;
+
+ if (cur_pos == cur_last) {
+ assert(cur->next);
+ cur = cur->next;
+ }
+
+ auto first = static_cast<uint8_t *>(dest);
+ auto last = first + count;
+
+ for (;;) {
+ auto n = std::min(last - first, cur_last - cur_pos);
+
+ first = std::copy_n(cur_pos, n, first);
+ cur_pos += n;
+ len -= n;
+
+ if (first == last) {
+ break;
+ }
+ assert(cur_pos == cur_last);
+ if (!cur->next) {
+ break;
+ }
+ cur = cur->next;
+ cur_pos = cur->pos;
+ cur_last = cur->last;
+ }
+ return first - static_cast<uint8_t *>(dest);
+ }
+ size_t rleft() const { return len; }
+ size_t rleft_buffered() const { return memchunks.rleft(); }
+ void disable_peek(bool drain) {
+ if (!peeking) {
+ return;
+ }
+ if (drain) {
+ auto n = rleft_buffered() - rleft();
+ memchunks.drain(n);
+ assert(len == memchunks.rleft());
+ } else {
+ len = memchunks.rleft();
+ }
+ cur = nullptr;
+ cur_pos = cur_last = nullptr;
+ peeking = false;
+ }
+ void reset() {
+ memchunks.reset();
+ cur = nullptr;
+ cur_pos = cur_last = nullptr;
+ len = 0;
+ peeking = true;
+ }
+ Memchunks<Memchunk> memchunks;
+ // Pointer to the Memchunk currently we are reading/writing.
+ Memchunk *cur;
+ // Region inside cur, we have processed to cur_pos.
+ uint8_t *cur_pos, *cur_last;
+ // This is the length we have left unprocessed. len <=
+ // memchunk.rleft() must hold.
+ size_t len;
+ // true if peeking is enabled. Initially it is true.
+ bool peeking;
+};
+
+using Memchunk16K = Memchunk<16_k>;
+using MemchunkPool = Pool<Memchunk16K>;
+using DefaultMemchunks = Memchunks<Memchunk16K>;
+using DefaultPeekMemchunks = PeekMemchunks<Memchunk16K>;
+
+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;
+}
+
+// MemchunkBuffer is similar to Buffer, but it uses pooled Memchunk
+// for its underlying buffer.
+template <typename Memchunk> struct MemchunkBuffer {
+ MemchunkBuffer(Pool<Memchunk> *pool) : pool(pool), chunk(nullptr) {}
+ MemchunkBuffer(const MemchunkBuffer &) = delete;
+ MemchunkBuffer(MemchunkBuffer &&other) noexcept
+ : pool(other.pool), chunk(other.chunk) {
+ other.chunk = nullptr;
+ }
+ MemchunkBuffer &operator=(const MemchunkBuffer &) = delete;
+ MemchunkBuffer &operator=(MemchunkBuffer &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
+ pool = other.pool;
+ chunk = other.chunk;
+
+ other.chunk = nullptr;
+
+ return *this;
+ }
+
+ ~MemchunkBuffer() {
+ if (!pool || !chunk) {
+ return;
+ }
+ pool->recycle(chunk);
+ }
+
+ // Ensures that the underlying buffer is allocated.
+ void ensure_chunk() {
+ if (chunk) {
+ return;
+ }
+ chunk = pool->get();
+ }
+
+ // Releases the underlying buffer.
+ void release_chunk() {
+ if (!chunk) {
+ return;
+ }
+ pool->recycle(chunk);
+ chunk = nullptr;
+ }
+
+ // Returns true if the underlying buffer is allocated.
+ bool chunk_avail() const { return chunk != nullptr; }
+
+ // The functions below must be called after the underlying buffer is
+ // allocated (use ensure_chunk).
+
+ // MemchunkBuffer provides the same interface functions with Buffer.
+ // Since we has chunk as a member variable, pos and last are
+ // implemented as wrapper functions.
+
+ uint8_t *pos() const { return chunk->pos; }
+ uint8_t *last() const { return chunk->last; }
+
+ size_t rleft() const { return chunk->len(); }
+ size_t wleft() const { return chunk->left(); }
+ size_t write(const void *src, size_t count) {
+ count = std::min(count, wleft());
+ auto p = static_cast<const uint8_t *>(src);
+ chunk->last = std::copy_n(p, count, chunk->last);
+ return count;
+ }
+ size_t write(size_t count) {
+ count = std::min(count, wleft());
+ chunk->last += count;
+ return count;
+ }
+ size_t drain(size_t count) {
+ count = std::min(count, rleft());
+ chunk->pos += count;
+ return count;
+ }
+ size_t drain_reset(size_t count) {
+ count = std::min(count, rleft());
+ std::copy(chunk->pos + count, chunk->last, std::begin(chunk->buf));
+ chunk->last = std::begin(chunk->buf) + (chunk->last - (chunk->pos + count));
+ chunk->pos = std::begin(chunk->buf);
+ return count;
+ }
+ void reset() { chunk->reset(); }
+ uint8_t *begin() { return std::begin(chunk->buf); }
+ uint8_t &operator[](size_t n) { return chunk->buf[n]; }
+ const uint8_t &operator[](size_t n) const { return chunk->buf[n]; }
+
+ Pool<Memchunk> *pool;
+ Memchunk *chunk;
+};
+
+using DefaultMemchunkBuffer = MemchunkBuffer<Memchunk16K>;
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_H
diff --git a/src/memchunk_test.cc b/src/memchunk_test.cc
new file mode 100644
index 0000000..236d9ea
--- /dev/null
+++ b/src/memchunk_test.cc
@@ -0,0 +1,340 @@
+/*
+ * 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);
+ CU_ASSERT(MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ auto m2 = pool.get();
+
+ CU_ASSERT(m2 == pool.pool);
+ CU_ASSERT(2 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+ CU_ASSERT(m1 == m2->knext);
+ CU_ASSERT(nullptr == m1->knext);
+
+ auto m3 = pool.get();
+
+ CU_ASSERT(m3 == pool.pool);
+ CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+ CU_ASSERT(nullptr == pool.freelist);
+
+ pool.recycle(m3);
+
+ CU_ASSERT(m3 == pool.pool);
+ 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);
+ 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>;
+using PeekMemchunks16 = PeekMemchunks<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);
+
+ std::array<char, 3 * 16> buf{};
+
+ chunks.append(buf.data(), buf.size());
+
+ 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);
+ std::array<char, 32> buf{};
+ chunks.append(buf.data(), buf.size());
+ }
+ 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);
+}
+
+void test_memchunks_reset(void) {
+ MemchunkPool16 pool;
+ Memchunks16 chunks(&pool);
+
+ std::array<uint8_t, 32> b{};
+
+ chunks.append(b.data(), b.size());
+
+ CU_ASSERT(32 == chunks.rleft());
+
+ chunks.reset();
+
+ CU_ASSERT(0 == chunks.rleft());
+ CU_ASSERT(nullptr == chunks.head);
+ CU_ASSERT(nullptr == chunks.tail);
+
+ auto m = pool.freelist;
+
+ CU_ASSERT(nullptr != m);
+ CU_ASSERT(nullptr != m->next);
+ CU_ASSERT(nullptr == m->next->next);
+}
+
+void test_peek_memchunks_append(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(0 == pchunks.remove(nullptr, 0));
+
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ CU_ASSERT(std::equal(std::begin(b), std::begin(b) + 12, std::begin(d)));
+
+ CU_ASSERT(20 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_disable_peek_drain(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(true);
+
+ CU_ASSERT(!pchunks.peeking);
+ CU_ASSERT(20 == pchunks.rleft());
+ CU_ASSERT(20 == pchunks.rleft_buffered());
+
+ CU_ASSERT(20 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_disable_peek_no_drain(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(false);
+
+ CU_ASSERT(!pchunks.peeking);
+ CU_ASSERT(32 == pchunks.rleft());
+ CU_ASSERT(32 == pchunks.rleft_buffered());
+
+ CU_ASSERT(32 == pchunks.remove(d.data(), d.size()));
+
+ CU_ASSERT(std::equal(std::begin(b), std::end(b), std::begin(d)));
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+}
+
+void test_peek_memchunks_reset(void) {
+ MemchunkPool16 pool;
+ PeekMemchunks16 pchunks(&pool);
+
+ std::array<uint8_t, 32> b{
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
+ },
+ d;
+
+ pchunks.append(b.data(), b.size());
+
+ CU_ASSERT(12 == pchunks.remove(d.data(), 12));
+
+ pchunks.disable_peek(true);
+ pchunks.reset();
+
+ CU_ASSERT(0 == pchunks.rleft());
+ CU_ASSERT(0 == pchunks.rleft_buffered());
+
+ CU_ASSERT(nullptr == pchunks.cur);
+ CU_ASSERT(nullptr == pchunks.cur_pos);
+ CU_ASSERT(nullptr == pchunks.cur_last);
+ CU_ASSERT(pchunks.peeking);
+}
+
+} // namespace nghttp2
diff --git a/src/memchunk_test.h b/src/memchunk_test.h
new file mode 100644
index 0000000..7d677e7
--- /dev/null
+++ b/src/memchunk_test.h
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_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);
+void test_memchunks_reset(void);
+void test_peek_memchunks_append(void);
+void test_peek_memchunks_disable_peek_drain(void);
+void test_peek_memchunks_disable_peek_no_drain(void);
+void test_peek_memchunks_reset(void);
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_TEST_H
diff --git a/src/network.h b/src/network.h
new file mode 100644
index 0000000..45311d8
--- /dev/null
+++ b/src/network.h
@@ -0,0 +1,67 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 NETWORK_H
+#define NETWORK_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef _WIN32
+# include <ws2tcpip.h>
+#else // !_WIN32
+# include <sys/un.h>
+#endif // !_WIN32
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+namespace nghttp2 {
+
+union sockaddr_union {
+ sockaddr_storage storage;
+ sockaddr sa;
+ sockaddr_in6 in6;
+ sockaddr_in in;
+#ifndef _WIN32
+ sockaddr_un un;
+#endif // !_WIN32
+};
+
+struct Address {
+ size_t len;
+ union sockaddr_union su;
+};
+
+} // namespace nghttp2
+
+#endif // NETWORK_H
diff --git a/src/nghttp.cc b/src/nghttp.cc
new file mode 100644
index 0000000..41a88c6
--- /dev/null
+++ b/src/nghttp.cc
@@ -0,0 +1,3115 @@
+/*
+ * 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>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_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>
+
+#ifdef HAVE_JANSSON
+# include <jansson.h>
+#endif // HAVE_JANSSON
+
+#include "app_helper.h"
+#include "HtmlParser.h"
+#include "util.h"
+#include "base64.h"
+#include "tls.h"
+#include "template.h"
+#include "ssl_compat.h"
+
+#ifndef O_BINARY
+# define O_BINARY (0)
+#endif // O_BINARY
+
+namespace nghttp2 {
+
+// The anchor stream nodes when --no-dep is not used. The stream ID =
+// 1 is excluded since it is used as first stream in upgrade case. We
+// follows the same dependency anchor nodes as Firefox does.
+struct Anchor {
+ int32_t stream_id;
+ // stream ID this anchor depends on
+ int32_t dep_stream_id;
+ // .. with this weight.
+ int32_t weight;
+};
+
+// This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html
+// file.
+enum {
+ ANCHOR_LEADERS,
+ ANCHOR_UNBLOCKED,
+ ANCHOR_BACKGROUND,
+ ANCHOR_SPECULATIVE,
+ ANCHOR_FOLLOWERS,
+};
+
+namespace {
+constexpr auto anchors = std::array<Anchor, 5>{{
+ {3, 0, 201},
+ {5, 0, 101},
+ {7, 0, 1},
+ {9, 7, 1},
+ {11, 3, 1},
+}};
+} // namespace
+
+Config::Config()
+ : header_table_size(-1),
+ min_header_table_size(std::numeric_limits<uint32_t>::max()),
+ encoder_header_table_size(-1),
+ padding(0),
+ max_concurrent_streams(100),
+ peer_max_concurrent_streams(100),
+ multiply(1),
+ timeout(0.),
+ window_bits(-1),
+ connection_window_bits(-1),
+ verbose(0),
+ port_override(0),
+ null_out(false),
+ remote_name(false),
+ get_assets(false),
+ stat(false),
+ upgrade(false),
+ continuation(false),
+ no_content_length(false),
+ no_dep(false),
+ hexdump(false),
+ no_push(false),
+ expect_continue(false),
+ verify_peer(true),
+ ktls(false),
+ no_rfc7540_pri(false) {
+ nghttp2_option_new(&http2_option);
+ nghttp2_option_set_peer_max_concurrent_streams(http2_option,
+ peer_max_concurrent_streams);
+ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC);
+ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN);
+}
+
+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
+
+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, int level)
+ : uri(uri),
+ u(u),
+ pri_spec(pri_spec),
+ data_length(data_length),
+ data_offset(0),
+ response_len(0),
+ inflater(nullptr),
+ data_prd(data_prd),
+ header_buffer_size(0),
+ stream_id(-1),
+ status(0),
+ level(level),
+ expect_final_response(false) {
+ http2::init_hdidx(res_hdidx);
+ http2::init_hdidx(req_hdidx);
+}
+
+Request::~Request() { nghttp2_gzip_inflate_del(inflater); }
+
+void Request::init_inflater() {
+ int rv;
+ // This is required with --disable-assert.
+ (void)rv;
+ rv = nghttp2_gzip_inflate_new(&inflater);
+ assert(rv == 0);
+}
+
+StringRef Request::get_real_scheme() const {
+ return config.scheme_override.empty()
+ ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA)
+ : StringRef{config.scheme_override};
+}
+
+StringRef Request::get_real_host() const {
+ return config.host_override.empty()
+ ? util::get_uri_field(uri.c_str(), u, UF_HOST)
+ : StringRef{config.host_override};
+}
+
+uint16_t Request::get_real_port() const {
+ auto scheme = get_real_scheme();
+ return config.host_override.empty() ? util::has_uri_field(u, UF_PORT) ? u.port
+ : scheme == "https" ? 443
+ : 80
+ : config.port_override == 0 ? scheme == "https" ? 443 : 80
+ : config.port_override;
+}
+
+void Request::init_html_parser() {
+ // We crawl HTML using overridden scheme, host, and port.
+ auto scheme = get_real_scheme();
+ auto host = get_real_host();
+ auto port = get_real_port();
+ auto ipv6_lit =
+ std::find(std::begin(host), std::end(host), ':') != std::end(host);
+
+ auto base_uri = scheme.str();
+ base_uri += "://";
+ if (ipv6_lit) {
+ base_uri += '[';
+ }
+ base_uri += host;
+ if (ipv6_lit) {
+ base_uri += ']';
+ }
+ if (!((scheme == "https" && port == 443) ||
+ (scheme == "http" && port == 80))) {
+ base_uri += ':';
+ base_uri += util::utos(port);
+ }
+ base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH);
+ if (util::has_uri_field(u, UF_QUERY)) {
+ base_uri += '?';
+ base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY);
+ }
+
+ html_parser = std::make_unique<HtmlParser>(base_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).str()
+ : "/";
+ 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;
+}
+
+namespace {
+// Perform special handling |host| if it is IPv6 literal and includes
+// zone ID per RFC 6874.
+std::string decode_host(const StringRef &host) {
+ auto zone_start = std::find(std::begin(host), std::end(host), '%');
+ if (zone_start == std::end(host) ||
+ !util::ipv6_numeric_addr(
+ std::string(std::begin(host), zone_start).c_str())) {
+ return host.str();
+ }
+ // case: ::1%
+ if (zone_start + 1 == std::end(host)) {
+ return StringRef{host.c_str(), host.size() - 1}.str();
+ }
+ // case: ::1%12 or ::1%1
+ if (zone_start + 3 >= std::end(host)) {
+ return host.str();
+ }
+ // If we see "%25", followed by more characters, then decode %25 as
+ // '%'.
+ auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
+ ? zone_start + 3
+ : zone_start + 1;
+ auto zone_id = util::percent_decode(zone_id_src, std::end(host));
+ auto res = std::string(std::begin(host), zone_start + 1);
+ res += zone_id;
+ return res;
+}
+} // namespace
+
+namespace {
+nghttp2_priority_spec resolve_dep(int res_type) {
+ nghttp2_priority_spec pri_spec;
+
+ if (config.no_dep) {
+ nghttp2_priority_spec_default_init(&pri_spec);
+
+ return pri_spec;
+ }
+
+ int32_t anchor_id;
+ int32_t weight;
+ switch (res_type) {
+ case REQ_CSS:
+ case REQ_JS:
+ anchor_id = anchors[ANCHOR_LEADERS].stream_id;
+ weight = 32;
+ break;
+ case REQ_UNBLOCK_JS:
+ anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
+ weight = 32;
+ break;
+ case REQ_IMG:
+ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ weight = 12;
+ break;
+ default:
+ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ weight = 32;
+ }
+
+ nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
+ return pri_spec;
+}
+} // namespace
+
+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;
+ }
+}
+
+Headers::value_type *Request::get_res_header(int32_t token) {
+ auto idx = res_hdidx[token];
+ if (idx == -1) {
+ return nullptr;
+ }
+ return &res_nva[idx];
+}
+
+Headers::value_type *Request::get_req_header(int32_t token) {
+ auto idx = req_hdidx[token];
+ if (idx == -1) {
+ return nullptr;
+ }
+ return &req_nva[idx];
+}
+
+void Request::record_request_start_time() {
+ timing.state = RequestState::ON_REQUEST;
+ timing.request_start_time = get_time();
+}
+
+void Request::record_response_start_time() {
+ timing.state = RequestState::ON_RESPONSE;
+ timing.response_start_time = get_time();
+}
+
+void Request::record_response_end_time() {
+ timing.state = RequestState::ON_COMPLETE;
+ timing.response_end_time = get_time();
+}
+
+namespace {
+void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto client = static_cast<HttpClient *>(ev_userdata(loop));
+ auto req = static_cast<Request *>(w->data);
+ int error;
+
+ error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM,
+ req->stream_id, req->data_prd);
+
+ if (error) {
+ std::cerr << "[ERROR] nghttp2_submit_data() returned error: "
+ << nghttp2_strerror(error) << std::endl;
+ nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE,
+ req->stream_id, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ client->signal_write();
+}
+} // namespace
+
+ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) {
+ ev_timer_init(&timer, continue_timeout_cb, 1., 0.);
+ timer.data = req;
+}
+
+ContinueTimer::~ContinueTimer() { stop(); }
+
+void ContinueTimer::start() { ev_timer_start(loop, &timer); }
+
+void ContinueTimer::stop() { ev_timer_stop(loop, &timer); }
+
+void ContinueTimer::dispatch_continue() {
+ // Only dispatch the timeout callback if it hasn't already been called.
+ if (ev_is_active(&timer)) {
+ ev_feed_event(loop, &timer, 0);
+ }
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp) {
+ if (config.verbose) {
+ print_timer();
+ std::cout << " HTTP Upgrade response" << std::endl;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ auto client = static_cast<HttpClient *>(htp->data);
+ client->upgrade_response_status_code = htp->status_code;
+ client->upgrade_response_complete = true;
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ nullptr, // llhttp_data_cb on_header_field;
+ nullptr, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ nullptr, // llhttp_cb on_headers_complete;
+ nullptr, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+namespace {
+int submit_request(HttpClient *client, const Headers &headers, Request *req) {
+ 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", req->make_reqpath()},
+ {":scheme", scheme.str()},
+ {":authority", client->hostport},
+ {"accept", "*/*"},
+ {"accept-encoding", "gzip, deflate"},
+ {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
+ bool expect_continue = false;
+
+ if (config.continuation) {
+ for (size_t i = 0; i < 6; ++i) {
+ build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
+ std::string(4_k, '-'));
+ }
+ }
+
+ auto num_initial_headers = build_headers.size();
+
+ if (req->data_prd) {
+ if (!config.no_content_length) {
+ build_headers.emplace_back("content-length",
+ util::utos(req->data_length));
+ }
+ if (config.expect_continue) {
+ expect_continue = true;
+ build_headers.emplace_back("expect", "100-continue");
+ }
+ }
+
+ 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 method = http2::get_header(build_headers, ":method");
+ assert(method);
+
+ req->method = method->value;
+
+ std::string trailer_names;
+ if (!config.trailer.empty()) {
+ trailer_names = config.trailer[0].name;
+ for (size_t i = 1; i < config.trailer.size(); ++i) {
+ trailer_names += ", ";
+ trailer_names += config.trailer[i].name;
+ }
+ nva.push_back(http2::make_nv_ls("trailer", trailer_names));
+ }
+
+ int32_t stream_id;
+
+ if (expect_continue) {
+ stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec,
+ nva.data(), nva.size(), req);
+ } else {
+ 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_"
+ << (expect_continue ? "headers" : "request")
+ << "() returned error: " << nghttp2_strerror(stream_id)
+ << std::endl;
+ return -1;
+ }
+
+ req->stream_id = stream_id;
+ client->request_done(req);
+
+ req->req_nva = std::move(build_headers);
+
+ if (expect_continue) {
+ auto timer = std::make_unique<ContinueTimer>(client->loop, req);
+ req->continue_timer = std::move(timer);
+ }
+
+ 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->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)
+ : wb(&mcpool),
+ session(nullptr),
+ callbacks(callbacks),
+ loop(loop),
+ ssl_ctx(ssl_ctx),
+ ssl(nullptr),
+ addrs(nullptr),
+ next_addr(nullptr),
+ cur_addr(nullptr),
+ complete(0),
+ success(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;
+ this->host = host;
+ addrinfo 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;
+}
+
+namespace {
+// Just returns 1 to continue handshake.
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
+} // namespace
+
+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_connect_state(ssl);
+
+ // If the user overrode the :authority or host header, use that
+ // value for the SNI extension
+ const auto &host_string =
+ config.host_override.empty() ? host : config.host_override;
+
+ auto param = SSL_get0_param(ssl);
+ X509_VERIFY_PARAM_set_hostflags(param, 0);
+ X509_VERIFY_PARAM_set1_host(param, host_string.c_str(),
+ host_string.size());
+ SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb);
+
+ if (!util::numeric_host(host_string.c_str())) {
+ SSL_set_tlsext_host_name(ssl, host_string.c_str());
+ }
+ }
+
+ 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;
+
+ for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) {
+ if ((*req)->continue_timer) {
+ (*req)->continue_timer->stop();
+ }
+ }
+
+ 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_get_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, 8_k> 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);
+
+ std::array<struct iovec, 2> iov;
+
+ for (;;) {
+ if (on_writefn(*this) != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(iov.data(), iov.size());
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = writev(fd, iov.data(), iovcnt)) == -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);
+ }
+
+ ev_io_stop(loop, &wev);
+ ev_timer_stop(loop, &wt);
+
+ return 0;
+}
+
+int HttpClient::noop() { return 0; }
+
+void HttpClient::connect_fail() {
+ if (state == ClientState::IDLE) {
+ std::cerr << "[ERROR] Could not connect to the address "
+ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
+ << std::endl;
+ }
+ auto cur_state = state;
+ disconnect();
+ if (cur_state == ClientState::IDLE) {
+ if (initiate_connection() == 0) {
+ std::cerr << "Trying next address "
+ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
+ << 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;
+ }
+
+ 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) {
+ SSL_set_fd(ssl, fd);
+
+ readfn = &HttpClient::tls_handshake;
+ writefn = &HttpClient::tls_handshake;
+
+ return do_write();
+ }
+
+ readfn = &HttpClient::read_clear;
+ writefn = &HttpClient::write_clear;
+
+ if (need_upgrade()) {
+ htp = std::make_unique<llhttp_t>();
+ llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks);
+ htp->data = this;
+
+ return do_write();
+ }
+
+ if (connection_made() != 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 = config.max_concurrent_streams;
+
+ 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) {
+ if (config.min_header_table_size < config.header_table_size) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config.min_header_table_size;
+ ++niv;
+ }
+
+ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[niv].value = config.header_table_size;
+ ++niv;
+ }
+
+ if (config.no_push) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[niv].value = 0;
+ ++niv;
+ }
+
+ if (config.no_rfc7540_pri) {
+ iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[niv].value = 1;
+ ++niv;
+ }
+
+ return niv;
+}
+} // namespace
+
+int HttpClient::on_upgrade_connect() {
+ ssize_t rv;
+ record_connect_end_time();
+ assert(!reqvec.empty());
+ std::array<nghttp2_settings_entry, 16> 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 {
+ auto meth = std::find_if(
+ std::begin(config.headers), std::end(config.headers),
+ [](const Header &kv) { return util::streq_l(":method", kv.name); });
+
+ if (meth == std::end(config.headers)) {
+ req = "GET ";
+ reqvec[0]->method = "GET";
+ } else {
+ req = (*meth).value;
+ req += ' ';
+ reqvec[0]->method = (*meth).value;
+ }
+ req += reqvec[0]->make_reqpath();
+ }
+
+ auto headers = Headers{{"host", hostport},
+ {"connection", "Upgrade, HTTP2-Settings"},
+ {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
+ {"http2-settings", std::move(token68)},
+ {"accept", "*/*"},
+ {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
+ auto initial_headerslen = headers.size();
+
+ for (auto &kv : config.headers) {
+ size_t i;
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ for (i = 0; i < initial_headerslen; ++i) {
+ if (kv.name == headers[i].name) {
+ headers[i].value = kv.value;
+ break;
+ }
+ }
+ if (i < initial_headerslen) {
+ continue;
+ }
+ headers.emplace_back(kv.name, kv.value, kv.no_index);
+ }
+
+ req += " HTTP/1.1\r\n";
+
+ for (auto &kv : headers) {
+ req += kv.name;
+ req += ": ";
+ req += kv.value;
+ req += "\r\n";
+ }
+ req += "\r\n";
+
+ wb.append(req);
+
+ if (config.verbose) {
+ print_timer();
+ std::cout << " HTTP Upgrade request\n" << req << std::endl;
+ }
+
+ if (!reqvec[0]->data_prd) {
+ // record request time if this is a part of real request.
+ reqvec[0]->record_request_start_time();
+ reqvec[0]->req_nva = std::move(headers);
+ }
+
+ on_writefn = &HttpClient::noop;
+
+ signal_write();
+
+ return 0;
+}
+
+int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
+ int rv;
+
+ auto htperr =
+ llhttp_execute(htp.get(), reinterpret_cast<const char *>(data), len);
+ auto nread = htperr == HPE_OK
+ ? len
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(htp.get())) -
+ data);
+
+ if (config.verbose) {
+ std::cout.write(reinterpret_cast<const char *>(data), nread);
+ }
+
+ if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) {
+ std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(htp.get()) << 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 = connection_made();
+ 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::connection_made() {
+ int rv;
+
+ if (!need_upgrade()) {
+ record_connect_end_time();
+ }
+
+ if (ssl) {
+ // Check ALPN result
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+ if (next_proto) {
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (config.verbose) {
+ std::cout << "The negotiated protocol: " << proto << std::endl;
+ }
+ if (!util::check_h2_is_selected(proto)) {
+ next_proto = nullptr;
+ }
+ }
+ if (!next_proto) {
+ print_protocol_nego_error();
+ return -1;
+ }
+ }
+
+ 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();
+ }
+ // If HEAD is used, that is only when user specified it with -H
+ // option.
+ auto head_request = stream_user_data && stream_user_data->method == "HEAD";
+ rv = nghttp2_session_upgrade2(session, settings_payload.data(),
+ settings_payloadlen, head_request,
+ 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;
+ request_done(stream_user_data);
+ }
+ }
+ // 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) {
+ // Create anchor stream nodes
+ nghttp2_priority_spec pri_spec;
+
+ for (auto &anchor : anchors) {
+ nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
+ 0);
+ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
+ &pri_spec);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ rv = nghttp2_session_set_next_stream_id(
+ session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (need_upgrade() && !reqvec[0]->data_prd) {
+ // Amend the priority because we cannot send priority in
+ // HTTP/1.1 Upgrade.
+ auto &anchor = anchors[ANCHOR_FOLLOWERS];
+ nghttp2_priority_spec_init(&pri_spec, anchor.stream_id,
+ reqvec[0]->pri_spec.weight, 0);
+
+ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ } else if (need_upgrade() && !reqvec[0]->data_prd &&
+ reqvec[0]->pri_spec.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, reqvec[0]->pri_spec.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 window_size = (1 << config.connection_window_bits) - 1;
+ rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
+ window_size);
+ 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) {
+ if (config.hexdump) {
+ util::hexdump(stdout, data, 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() {
+ for (;;) {
+ if (wb.rleft() >= 16384) {
+ return 0;
+ }
+
+ const uint8_t *data;
+ auto len = nghttp2_session_mem_send(session, &data);
+ if (len < 0) {
+ std::cerr << "[ERROR] nghttp2_session_send() returned error: "
+ << nghttp2_strerror(len) << std::endl;
+ return -1;
+ }
+
+ if (len == 0) {
+ break;
+ }
+
+ wb.append(data, len);
+ }
+
+ 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) {
+ 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 (config.verify_peer) {
+ auto verify_res = SSL_get_verify_result(ssl);
+ if (verify_res != X509_V_OK) {
+ std::cerr << "[WARNING] Certificate verification failed: "
+ << X509_verify_cert_error_string(verify_res) << std::endl;
+ }
+ }
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int HttpClient::read_tls() {
+ ev_timer_again(loop, &rt);
+
+ ERR_clear_error();
+
+ std::array<uint8_t, 8_k> buf;
+ for (;;) {
+ auto rv = SSL_read(ssl, buf.data(), buf.size());
+
+ 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();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (on_writefn(*this) != 0) {
+ return -1;
+ }
+
+ auto iovcnt = wb.riovec(&iov, 1);
+
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
+
+ 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);
+ }
+
+ 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)
+ .str();
+ std::stringstream ss;
+ if (reqvec[0]->is_ipv6_literal_addr()) {
+ // we may have zone ID, which must start with "%25", or "%". RFC
+ // 6874 defines "%25" only, and just "%" is allowed for just
+ // convenience to end-user input.
+ auto host =
+ util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
+ auto end = std::find(std::begin(host), std::end(host), '%');
+ ss << "[";
+ ss.write(host.c_str(), end - std::begin(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, int level) {
+ http_parser_url 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(std::make_unique<Request>(uri, u, data_prd, data_length,
+ pri_spec, level));
+ return true;
+}
+
+void HttpClient::record_start_time() {
+ timing.system_start_time = std::chrono::system_clock::now();
+ timing.start_time = get_time();
+}
+
+void HttpClient::record_domain_lookup_end_time() {
+ timing.domain_lookup_end_time = get_time();
+}
+
+void HttpClient::record_connect_end_time() {
+ timing.connect_end_time = get_time();
+}
+
+void HttpClient::request_done(Request *req) {
+ if (req->stream_id % 2 == 0) {
+ return;
+ }
+}
+
+#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.system_start_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.domain_lookup_end_time - timing.start_time)
+ .count() /
+ 1000.0;
+ auto connect_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ timing.connect_end_time - timing.domain_lookup_end_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.system_start_time
+ : timing.system_start_time +
+ std::chrono::duration_cast<
+ std::chrono::system_clock::duration>(
+ req_timing.request_start_time - timing.start_time);
+
+ auto wait_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ req_timing.response_start_time - req_timing.request_start_time)
+ .count() /
+ 1000.0;
+ auto receive_delta =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ req_timing.response_end_time - req_timing.response_start_time)
+ .count() /
+ 1000.0;
+
+ auto time_sum =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ (i == 0) ? (req_timing.response_end_time - timing.start_time)
+ : (req_timing.response_end_time -
+ req_timing.request_start_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 pushed = req->stream_id % 2 == 0;
+
+ json_object_set_new(entry, "comment",
+ json_string(pushed ? "Pushed Object" : ""));
+
+ auto request = json_object();
+ json_object_set_new(entry, "request", request);
+
+ 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(req->method.c_str()));
+ 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_object_set_new(entry, "connection",
+ json_string(util::utos(req->stream_id).c_str()));
+ }
+
+ 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);
+
+ auto scheme = req->get_real_scheme();
+ auto host = req->get_real_host();
+ auto port = req->get_real_port();
+
+ for (auto &p : req->html_parser->get_links()) {
+ auto uri = strip_fragment(p.first.c_str());
+ auto res_type = p.second;
+
+ http_parser_url u{};
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ continue;
+ }
+
+ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) ||
+ !util::fieldeq(uri.c_str(), u, UF_HOST, host)) {
+ continue;
+ }
+
+ auto link_port = util::has_uri_field(u, UF_PORT) ? u.port
+ : scheme == "https" ? 443
+ : 80;
+
+ if (port != link_port) {
+ continue;
+ }
+
+ // No POST data for assets
+ auto pri_spec = resolve_dep(res_type);
+
+ if (client->add_request(uri, nullptr, 0, pri_spec, 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);
+ }
+
+ req->response_len += len;
+
+ if (req->inflater) {
+ while (len > 0) {
+ const size_t MAX_OUTLEN = 4_k;
+ 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;
+ }
+
+ 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;
+ }
+
+ 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(StringRef{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_l("gzip", nv.value) ||
+ util::strieq_l("deflate", nv.value);
+ continue;
+ }
+ }
+
+ if (req->status / 100 == 1) {
+ if (req->continue_timer && (req->status == 100)) {
+ // If the request is waiting for a 100 Continue, complete the handshake.
+ req->continue_timer->dispatch_continue();
+ }
+
+ req->expect_final_response = true;
+ req->status = 0;
+ req->res_nva.clear();
+ http2::init_hdidx(req->res_hdidx);
+ return;
+ } else if (req->continue_timer) {
+ // A final response stops any pending Expect/Continue handshake.
+ req->continue_timer->stop();
+ }
+
+ 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_HEADERS: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ break;
+ }
+
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_RESPONSE:
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ req->record_response_start_time();
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto stream_id = frame->push_promise.promised_stream_id;
+ http_parser_url u{};
+ // TODO Set pri and level
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_default_init(&pri_spec);
+
+ auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
+ req->stream_id = stream_id;
+
+ nghttp2_session_set_stream_user_data(session, stream_id, req.get());
+
+ client->request_done(req.get());
+ req->record_request_start_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);
+ }
+
+ 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;
+ }
+
+ /* ignore trailer header */
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
+ !req->expect_final_response) {
+ break;
+ }
+
+ if (req->header_buffer_size + namelen + valuelen > 64_k) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ req->header_buffer_size += namelen + valuelen;
+
+ auto token = http2::lookup_token(name, namelen);
+
+ 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;
+ }
+
+ if (req->header_buffer_size + namelen + valuelen > 64_k) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->push_promise.promised_stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ req->header_buffer_size += namelen + valuelen;
+
+ auto token = http2::lookup_token(name, namelen);
+
+ 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_DATA: {
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ return 0;
+ ;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ req->record_response_end_time();
+ ++client->success;
+ }
+
+ break;
+ }
+ 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) {
+ return 0;
+ ;
+ }
+
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_RESPONSE:
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ check_response_header(session, req);
+ break;
+ case NGHTTP2_HCAT_HEADERS:
+ if (req->expect_final_response) {
+ check_response_header(session, req);
+ break;
+ }
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+ return 0;
+ }
+ break;
+ default:
+ assert(0);
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ req->record_response_end_time();
+ ++client->success;
+ }
+
+ 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;
+ }
+
+ // Reset for response header field reception
+ req->header_buffer_size = 0;
+
+ auto scheme = req->get_req_header(http2::HD__SCHEME);
+ auto authority = req->get_req_header(http2::HD__AUTHORITY);
+ auto path = req->get_req_header(http2::HD__PATH);
+
+ if (!authority) {
+ authority = req->get_req_header(http2::HD_HOST);
+ }
+
+ // libnghttp2 guarantees :scheme, :method, :path and (:authority |
+ // host) exist and non-empty.
+ if (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{};
+ 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;
+
+ if (client->path_cache.count(uri)) {
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+ frame->push_promise.promised_stream_id,
+ NGHTTP2_CANCEL);
+ break;
+ }
+
+ if (config.multiply == 1) {
+ client->path_cache.insert(uri);
+ }
+
+ 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));
+ assert(req);
+ req->record_request_start_time();
+ return 0;
+}
+
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ if (config.verbose) {
+ verbose_on_frame_send_callback(session, frame, 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));
+ if (!req) {
+ return 0;
+ }
+
+ // If this request is using Expect/Continue, start its ContinueTimer.
+ if (req->continue_timer) {
+ req->continue_timer->start();
+ }
+
+ 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 ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ auto req = static_cast<Request *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!req) {
+ return 0;
+ }
+
+ std::cerr << "[ERROR] request " << req->uri
+ << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
+
+ 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;
+ }
+
+ // If this request is using Expect/Continue, stop its ContinueTimer.
+ if (req->continue_timer) {
+ req->continue_timer->stop();
+ }
+
+ update_html_parser(client, req, nullptr, 0, 1);
+ ++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.response_end_time < rtiming.response_end_time ||
+ (ltiming.response_end_time == rtiming.response_end_time &&
+ ltiming.request_start_time < rtiming.request_start_time);
+ });
+
+ std::cout << R"(
+Request timing:
+ responseEnd: the time when last byte of response was received
+ relative to connectEnd
+ requestStart: the time just before first byte of request was sent
+ relative to connectEnd. If '*' is shown, this was
+ pushed by server.
+ process: responseEnd - requestStart
+ code: HTTP status code
+ size: number of bytes received as response body without
+ inflation.
+ URI: request URI
+
+see http://www.w3.org/TR/resource-timing/#processing-model
+
+sorted by 'complete'
+
+id responseEnd requestStart process code size request path)"
+ << std::endl;
+
+ const auto &base = client.timing.connect_end_time;
+ for (const auto &req : reqs) {
+ auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.response_end_time - base);
+ auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.request_start_time - base);
+ auto total = std::chrono::duration_cast<std::chrono::microseconds>(
+ req->timing.response_end_time - req->timing.request_start_time);
+ auto pushed = req->stream_id % 2 == 0;
+
+ std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
+ << ("+" + util::format_duration(response_end)) << " "
+ << (pushed ? "*" : " ") << std::setw(11)
+ << ("+" + util::format_duration(request_start)) << " "
+ << std::setw(8) << util::format_duration(total) << " "
+ << std::setw(4) << req->status << " " << std::setw(4)
+ << util::utos_unit(req->response_len) << " "
+ << req->make_reqpath() << std::endl;
+ }
+}
+} // 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, int32_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(TLS_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;
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (config.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+ 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) {
+ std::cerr << "[WARNING] Could not load system trusted CA certificates: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ }
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
+ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
+ std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
+ result = -1;
+ goto fin;
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_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;
+ }
+ }
+
+ auto proto_list = util::get_default_alpn();
+
+ SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+ }
+ {
+ HttpClient client{callbacks, loop, ssl_ctx};
+
+ int32_t dep_stream_id = 0;
+
+ if (!config.no_dep) {
+ dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
+ }
+
+ for (auto &req : requests) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
+
+ for (int i = 0; i < config.multiply; ++i) {
+ client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
+ pri_spec);
+ }
+ }
+ client.update_hostport();
+
+ client.record_start_time();
+
+ if (client.resolve_host(host, port) != 0) {
+ goto fin;
+ }
+
+ client.record_domain_lookup_end_time();
+
+ if (client.initiate_connection() != 0) {
+ std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
+ << std::endl;
+ goto fin;
+ }
+
+ ev_set_userdata(loop, &client);
+ ev_run(loop, 0);
+ ev_set_userdata(loop, nullptr);
+
+#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.success != client.reqvec.size()) {
+ std::cerr << "Some requests were not processed. total="
+ << client.reqvec.size() << ", processed=" << client.success
+ << 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) {
+ int rv;
+ 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;
+ }
+
+ req->data_offset += nread;
+
+ if (req->data_offset == req->data_length) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ if (!config.trailer.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(config.trailer.size());
+ for (auto &kv : config.trailer) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+
+ return nread;
+ }
+
+ if (req->data_offset > req->data_length || nread == 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return nread;
+}
+} // 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_invalid_frame_recv_callback(
+ callbacks, verbose_on_invalid_frame_recv_callback);
+
+ nghttp2_session_callbacks_set_error_callback2(callbacks,
+ verbose_error_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_on_frame_send_callback(callbacks,
+ on_frame_send_callback);
+
+ nghttp2_session_callbacks_set_on_frame_not_send_callback(
+ callbacks, on_frame_not_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, 1_k> 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, int32_t>>
+ requests;
+
+ size_t next_weight_idx = 0;
+
+ for (int i = 0; i < n; ++i) {
+ http_parser_url u{};
+ auto uri = strip_fragment(uris[i]);
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ ++next_weight_idx;
+ std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
+ continue;
+ }
+ if (!util::has_uri_field(u, UF_SCHEMA)) {
+ ++next_weight_idx;
+ std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
+ << std::endl;
+ continue;
+ }
+ auto port = util::has_uri_field(u, UF_PORT)
+ ? u.port
+ : util::get_default_port(uri.c_str(), u);
+ auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
+ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
+ host != prev_host || 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).str();
+ prev_host = std::move(host);
+ prev_port = port;
+ }
+ requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
+ data_stat.st_size, config.weight[next_weight_idx++]);
+ }
+ 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 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 derived from URI. If URI ends with '/',
+ 'index.html' is used as a filename. Not implemented
+ yet.
+ -t, --timeout=<DURATION>
+ Timeout each request after <DURATION>. Set 0 to disable
+ timeout.
+ -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'
+ --trailer=<HEADER>
+ Add a trailer header to the requests. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). To send trailer, one must use -d option to
+ send request body. Example: --trailer 'foo: bar'.
+ --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=<PATH>
+ 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 weight of given URI. This option can be used
+ multiple times, and N-th -p option sets weight of N-th
+ URI in the command line. If the number of -p option is
+ less than the number of URI, the last -p option value is
+ repeated. If there is no -p option, default weight, 16,
+ is assumed. The valid value range is
+ [)"
+ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
+ -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.
+ Default: 100
+ -c, --header-table-size=<SIZE>
+ Specify decoder header table size. If this option is
+ used multiple times, and the minimum value among the
+ given values except for last one is strictly less than
+ the last value, that minimum value is set in SETTINGS
+ frame payload before the last value, to simulate
+ multiple header table size change.
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (server)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which server specified.
+ -b, --padding=<N>
+ Add at most <N> bytes to a frame payload as padding.
+ Specify 0 to disable padding.
+ -r, --har=<PATH>
+ Output HTTP transactions <PATH> 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.
+ --hexdump Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+ --no-push Disable server push.
+ --max-concurrent-streams=<N>
+ The number of concurrent pushed streams this client
+ accepts.
+ --expect-continue
+ Perform an Expect/Continue handshake: wait to send DATA
+ (up to a short timeout) until the server sends a 100
+ Continue interim response. This option is ignored unless
+ combined with the -d option.
+ -y, --no-verify-peer
+ Suppress warning on server certificate verification
+ failure.
+ --ktls Enable ktls.
+ --no-rfc7540-pri
+ Disable RFC7540 priorities.
+ --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).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ bool color = false;
+ while (1) {
+ static int flag = 0;
+ constexpr 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'},
+ {"no-verify-peer", no_argument, nullptr, 'y'},
+ {"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},
+ {"trailer", required_argument, &flag, 9},
+ {"hexdump", no_argument, &flag, 10},
+ {"no-push", no_argument, &flag, 11},
+ {"max-concurrent-streams", required_argument, &flag, 12},
+ {"expect-continue", no_argument, &flag, 13},
+ {"encoder-header-table-size", required_argument, &flag, 14},
+ {"ktls", no_argument, &flag, 15},
+ {"no-rfc7540-pri", no_argument, &flag, 16},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ int c =
+ getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'M': {
+ // peer-max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-M: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.peer_max_concurrent_streams = n;
+ break;
+ }
+ case 'O':
+ config.remote_name = true;
+ break;
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'b': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-b: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.padding = n;
+ break;
+ }
+ case 'n':
+ config.null_out = true;
+ break;
+ case 'p': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || NGHTTP2_MIN_WEIGHT > n || n > NGHTTP2_MAX_WEIGHT) {
+ std::cerr << "-p: specify the integer in the range ["
+ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
+ << "], inclusive" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.weight.push_back(n);
+ 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 = util::parse_duration_with_unit(optarg);
+ if (config.timeout == std::numeric_limits<double>::infinity()) {
+ std::cerr << "-t: bad timeout value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'u':
+ config.upgrade = true;
+ break;
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+ 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);
+ }
+ config.headers.emplace_back(header, value, false);
+ 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': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.multiply = n;
+ break;
+ }
+ case 'c': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "-c: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "-c: Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.header_table_size = n;
+ config.min_header_table_size = std::min(config.min_header_table_size, n);
+ break;
+ }
+ case 'y':
+ config.verify_peer = false;
+ 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 9: {
+ // trailer option
+ auto header = optarg;
+ auto value = strchr(optarg, ':');
+ if (!value) {
+ std::cerr << "--trailer: 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 << "--trailer: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.trailer.emplace_back(header, value, false);
+ util::inp_strlower(config.trailer.back().name);
+ break;
+ }
+ case 10:
+ // hexdump option
+ config.hexdump = true;
+ break;
+ case 11:
+ // no-push option
+ config.no_push = true;
+ break;
+ case 12: {
+ // max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "--max-concurrent-streams: Bad option value: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 13:
+ // expect-continue option
+ config.expect_continue = true;
+ break;
+ case 14: {
+ // encoder-header-table-size option
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--encoder-header-table-size: Bad option value: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--encoder-header-table-size: Value too large. It "
+ "should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.encoder_header_table_size = n;
+ break;
+ }
+ case 15:
+ // ktls option
+ config.ktls = true;
+ break;
+ case 16:
+ // no-rfc7540-pri option
+ config.no_rfc7540_pri = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ int32_t weight_to_fill;
+ if (config.weight.empty()) {
+ weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
+ } else {
+ weight_to_fill = config.weight.back();
+ }
+ config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
+
+ // Find scheme overridden by extra header fields.
+ auto scheme_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == ":scheme"; });
+ if (scheme_it != std::end(config.headers)) {
+ config.scheme_override = (*scheme_it).value;
+ }
+
+ // Find host and port overridden by extra header fields.
+ auto authority_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == ":authority"; });
+ if (authority_it == std::end(config.headers)) {
+ authority_it =
+ std::find_if(std::begin(config.headers), std::end(config.headers),
+ [](const Header &nv) { return nv.name == "host"; });
+ }
+
+ if (authority_it != std::end(config.headers)) {
+ // authority_it may looks like "host:port".
+ auto uri = "https://" + (*authority_it).value;
+ http_parser_url u{};
+ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+ std::cerr << "[ERROR] Could not parse authority in "
+ << (*authority_it).name << ": " << (*authority_it).value
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str();
+ if (util::has_uri_field(u, UF_PORT)) {
+ config.port_override = u.port;
+ }
+ }
+
+ set_color_output(color || isatty(fileno(stdout)));
+
+ nghttp2_option_set_peer_max_concurrent_streams(
+ config.http2_option, config.peer_max_concurrent_streams);
+
+ if (config.encoder_header_table_size != -1) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config.http2_option, config.encoder_header_table_size);
+ }
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+ reset_timer();
+ return run(argv + optind, argc - optind);
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/nghttp.h b/src/nghttp.h
new file mode 100644
index 0000000..a880414
--- /dev/null
+++ b/src/nghttp.h
@@ -0,0 +1,310 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+
+#include <string>
+#include <vector>
+#include <set>
+#include <chrono>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+#include "memchunk.h"
+#include "http2.h"
+#include "nghttp2_gzip.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+class HtmlParser;
+
+struct Config {
+ Config();
+ ~Config();
+
+ Headers headers;
+ Headers trailer;
+ std::vector<int32_t> weight;
+ std::string certfile;
+ std::string keyfile;
+ std::string datafile;
+ std::string harfile;
+ std::string scheme_override;
+ std::string host_override;
+ nghttp2_option *http2_option;
+ int64_t header_table_size;
+ int64_t min_header_table_size;
+ int64_t encoder_header_table_size;
+ size_t padding;
+ size_t max_concurrent_streams;
+ ssize_t peer_max_concurrent_streams;
+ int multiply;
+ // milliseconds
+ ev_tstamp timeout;
+ int window_bits;
+ int connection_window_bits;
+ int verbose;
+ uint16_t port_override;
+ bool null_out;
+ bool remote_name;
+ bool get_assets;
+ bool stat;
+ bool upgrade;
+ bool continuation;
+ bool no_content_length;
+ bool no_dep;
+ bool hexdump;
+ bool no_push;
+ bool expect_continue;
+ bool verify_peer;
+ bool ktls;
+ bool no_rfc7540_pri;
+};
+
+enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
+
+struct RequestTiming {
+ // The point in time when request is started to be sent.
+ // Corresponds to requestStart in Resource Timing TR.
+ std::chrono::steady_clock::time_point request_start_time;
+ // The point in time when first byte of response is received.
+ // Corresponds to responseStart in Resource Timing TR.
+ std::chrono::steady_clock::time_point response_start_time;
+ // The point in time when last byte of response is received.
+ // Corresponds to responseEnd in Resource Timing TR.
+ std::chrono::steady_clock::time_point response_end_time;
+ RequestState state;
+ RequestTiming() : state(RequestState::INITIAL) {}
+};
+
+struct Request; // forward declaration for ContinueTimer
+
+struct ContinueTimer {
+ ContinueTimer(struct ev_loop *loop, Request *req);
+ ~ContinueTimer();
+
+ void start();
+ void stop();
+
+ // Schedules an immediate run of the continue callback on the loop, if the
+ // callback has not already been run
+ void dispatch_continue();
+
+ struct ev_loop *loop;
+ ev_timer timer;
+};
+
+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, 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;
+
+ bool is_ipv6_literal_addr() const;
+
+ Headers::value_type *get_res_header(int32_t token);
+ Headers::value_type *get_req_header(int32_t token);
+
+ void record_request_start_time();
+ void record_response_start_time();
+ void record_response_end_time();
+
+ // Returns scheme taking into account overridden scheme.
+ StringRef get_real_scheme() const;
+ // Returns request host, without port, taking into account
+ // overridden host.
+ StringRef get_real_host() const;
+ // Returns request port, taking into account overridden host, port,
+ // and scheme.
+ uint16_t get_real_port() const;
+
+ Headers res_nva;
+ Headers req_nva;
+ std::string method;
+ // URI without fragment
+ std::string uri;
+ http_parser_url u;
+ 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;
+ std::unique_ptr<HtmlParser> html_parser;
+ const nghttp2_data_provider *data_prd;
+ size_t header_buffer_size;
+ int32_t stream_id;
+ int status;
+ // Recursion level: 0: first entity, 1: entity linked from first entity
+ int level;
+ http2::HeaderIndex res_hdidx;
+ // used for incoming PUSH_PROMISE
+ http2::HeaderIndex req_hdidx;
+ bool expect_final_response;
+ // only assigned if this request is using Expect/Continue
+ std::unique_ptr<ContinueTimer> continue_timer;
+};
+
+struct SessionTiming {
+ // The point in time when operation was started. Corresponds to
+ // startTime in Resource Timing TR, but recorded in system clock time.
+ std::chrono::system_clock::time_point system_start_time;
+ // Same as above, but recorded in steady clock time.
+ std::chrono::steady_clock::time_point start_time;
+ // The point in time when DNS resolution was completed. Corresponds
+ // to domainLookupEnd in Resource Timing TR.
+ std::chrono::steady_clock::time_point domain_lookup_end_time;
+ // The point in time when connection was established or SSL/TLS
+ // handshake was completed. Corresponds to connectEnd in Resource
+ // Timing TR.
+ std::chrono::steady_clock::time_point connect_end_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();
+
+ 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 connection_made();
+ void connect_fail();
+ void request_done(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, int level = 0);
+
+ void record_start_time();
+ void record_domain_lookup_end_time();
+ void record_connect_end_time();
+
+#ifdef HAVE_JANSSON
+ void output_har(FILE *outfile);
+#endif // HAVE_JANSSON
+
+ MemchunkPool mcpool;
+ DefaultMemchunks wb;
+ 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<llhttp_t> 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 number of requests that local endpoint received END_STREAM
+ // from peer.
+ size_t success;
+ // 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;
+ // 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
index 0000000..8e6cd05
--- /dev/null
+++ b/src/nghttp2_config.h
@@ -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_CONFIG_H
+#define NGHTTP2_CONFIG_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#endif // NGHTTP2_CONFIG_H
diff --git a/src/nghttp2_gzip.c b/src/nghttp2_gzip.c
new file mode 100644
index 0000000..5f17ab2
--- /dev/null
+++ b/src/nghttp2_gzip.c
@@ -0,0 +1,87 @@
+/*
+ * 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 = calloc(1, sizeof(nghttp2_gzip));
+ if (*inflater_ptr == NULL) {
+ return -1;
+ }
+ 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;
+ /* FALL THROUGH */
+ 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
index 0000000..a40352c
--- /dev/null
+++ b/src/nghttp2_gzip.h
@@ -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
index 0000000..de19d5d
--- /dev/null
+++ b/src/nghttp2_gzip_test.c
@@ -0,0 +1,111 @@
+/*
+ * 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 size_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+ size_t inlen) {
+ int rv;
+ z_stream zst = {0};
+
+ rv = deflateInit(&zst, Z_DEFAULT_COMPRESSION);
+ CU_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);
+ CU_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
index 0000000..8d554f7
--- /dev/null
+++ b/src/nghttp2_gzip_test.h
@@ -0,0 +1,42 @@
+/*
+ * 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 HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_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
index 0000000..9cad874
--- /dev/null
+++ b/src/nghttpd.cc
@@ -0,0 +1,502 @@
+/*
+ * 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"
+
+#ifdef __sgi
+# define daemon _daemonize
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_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 <nghttp2/nghttp2.h>
+
+#include "app_helper.h"
+#include "HttpServer.h"
+#include "util.h"
+#include "tls.h"
+
+namespace nghttp2 {
+
+namespace {
+int parse_push_config(Config &config, const char *optarg) {
+ const char *eq = strchr(optarg, '=');
+ if (eq == nullptr) {
+ 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 == nullptr) {
+ 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 server" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ Config config;
+ 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:
+ -a, --address=<ADDR>
+ The address to bind to. If not specified the default IP
+ address determined by getaddrinfo is used.
+ -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.
+ --encoder-header-table-size=<SIZE>
+ Specify encoder header table size. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+ --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.
+ -m, --max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ HTTP/2 session.
+ Default: )"
+ << config.max_concurrent_streams << R"(
+ -n, --workers=<N>
+ Set the number of worker threads.
+ Default: 1
+ -e, --error-gzip
+ Make error response gzipped.
+ -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.
+ --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.
+ --trailer=<HEADER>
+ Add a trailer header to a response. <HEADER> must not
+ include pseudo header field (header field name starting
+ with ':'). The trailer is sent only if a response has
+ body part. Example: --trailer 'foo: bar'.
+ --hexdump Display the incoming traffic in hexadecimal (Canonical
+ hex+ASCII display). If SSL/TLS is used, decrypted data
+ are used.
+ --echo-upload
+ Send back uploaded content if method is POST or PUT.
+ --mime-types-file=<PATH>
+ Path to file that contains MIME media types and the
+ extensions that represent them.
+ Default: )"
+ << config.mime_types_file << R"(
+ --no-content-length
+ Don't send content-length header field.
+ --ktls Enable ktls.
+ --no-rfc7540-pri
+ Disable RFC7540 priorities.
+ --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;
+ auto mime_types_file_set_manually = false;
+
+ while (1) {
+ static int flag = 0;
+ constexpr static option long_options[] = {
+ {"address", required_argument, nullptr, 'a'},
+ {"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'},
+ {"max-concurrent-streams", required_argument, nullptr, 'm'},
+ {"workers", required_argument, nullptr, 'n'},
+ {"error-gzip", no_argument, nullptr, 'e'},
+ {"window-bits", required_argument, nullptr, 'w'},
+ {"connection-window-bits", required_argument, nullptr, 'W'},
+ {"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},
+ {"trailer", required_argument, &flag, 6},
+ {"hexdump", no_argument, &flag, 7},
+ {"echo-upload", no_argument, &flag, 8},
+ {"mime-types-file", required_argument, &flag, 9},
+ {"no-content-length", no_argument, &flag, 10},
+ {"encoder-header-table-size", required_argument, &flag, 11},
+ {"ktls", no_argument, &flag, 12},
+ {"no-rfc7540-pri", no_argument, &flag, 13},
+ {nullptr, 0, nullptr, 0}};
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
+ &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'a':
+ config.address = optarg;
+ break;
+ case 'D':
+ config.daemon = true;
+ break;
+ case 'V':
+ config.verify_client = true;
+ break;
+ case 'b': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-b: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.padding = n;
+ break;
+ }
+ case 'd':
+ config.htdocs = optarg;
+ break;
+ case 'e':
+ config.error_gzip = true;
+ break;
+ case 'm': {
+ // max-concurrent-streams option
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-m: invalid argument: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.max_concurrent_streams = n;
+ break;
+ }
+ case 'n': {
+#ifdef NOTHREADS
+ std::cerr << "-n: WARNING: Threading disabled at build time, "
+ << "no threads created." << std::endl;
+#else
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ std::cerr << "-n: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.num_worker = n;
+#endif // NOTHREADS
+ break;
+ }
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'v':
+ config.verbose = true;
+ break;
+ case 'c': {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "-c: Bad option value: " << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "-c: Value too large. It should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.header_table_size = n;
+ break;
+ }
+ case 'p':
+ if (parse_push_config(config, optarg) != 0) {
+ std::cerr << "-p: Bad option value: " << optarg << std::endl;
+ }
+ break;
+ case 'w':
+ case 'W': {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n > 30) {
+ std::cerr << "-" << static_cast<char>(c)
+ << ": specify the integer in the range [0, 30], inclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (c == 'w') {
+ config.window_bits = n;
+ } else {
+ config.connection_window_bits = n;
+ }
+
+ 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;
+ case 6: {
+ // trailer option
+ auto header = optarg;
+ auto value = strchr(optarg, ':');
+ if (!value) {
+ std::cerr << "--trailer: 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 << "--trailer: invalid header - value missing: " << optarg
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.trailer.emplace_back(header, value, false);
+ util::inp_strlower(config.trailer.back().name);
+ break;
+ }
+ case 7:
+ // hexdump option
+ config.hexdump = true;
+ break;
+ case 8:
+ // echo-upload option
+ config.echo_upload = true;
+ break;
+ case 9:
+ // mime-types-file option
+ mime_types_file_set_manually = true;
+ config.mime_types_file = optarg;
+ break;
+ case 10:
+ // no-content-length option
+ config.no_content_length = true;
+ break;
+ case 11: {
+ // encoder-header-table-size option
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ std::cerr << "--encoder-header-table-size: Bad option value: "
+ << optarg << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (n > std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "--encoder-header-table-size: Value too large. It "
+ "should be less than or equal to "
+ << std::numeric_limits<uint32_t>::max() << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.encoder_header_table_size = n;
+ break;
+ }
+ case 12:
+ // tls option
+ config.ktls = true;
+ break;
+ case 13:
+ // no-rfc7540-pri option
+ config.no_rfc7540_pri = 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);
+ }
+
+ {
+ auto portStr = argv[optind++];
+ auto n = util::parse_uint(portStr);
+ if (n == -1 || n > std::numeric_limits<uint16_t>::max()) {
+ std::cerr << "<PORT>: Bad value: " << portStr << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.port = n;
+ }
+
+ 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);
+ }
+#ifdef __sgi
+ if (daemon(0, 0, 0, 0) == -1) {
+#else
+ if (util::daemonize(0, 0) == -1) {
+#endif
+ perror("daemon");
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (config.htdocs.empty()) {
+ config.htdocs = "./";
+ }
+
+ if (util::read_mime_types(config.mime_types,
+ config.mime_types_file.c_str()) != 0) {
+ if (mime_types_file_set_manually) {
+ std::cerr << "--mime-types-file: Could not open mime types file: "
+ << config.mime_types_file << std::endl;
+ }
+ }
+
+ auto &trailer_names = config.trailer_names;
+ for (auto &h : config.trailer) {
+ trailer_names += h.name;
+ trailer_names += ", ";
+ }
+ if (trailer_names.size() >= 2) {
+ trailer_names.resize(trailer_names.size() - 2);
+ }
+
+ set_color_output(color || isatty(fileno(stdout)));
+
+ struct sigaction act {};
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, nullptr);
+
+ reset_timer();
+
+ HttpServer server(&config);
+ if (server.run() != 0) {
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}
diff --git a/src/quic.cc b/src/quic.cc
new file mode 100644
index 0000000..1c68a5b
--- /dev/null
+++ b/src/quic.cc
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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 "quic.h"
+
+#include <cassert>
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace quic {
+
+Error err_transport(int liberr) {
+ if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) {
+ return {ErrorType::TransportVersionNegotiation, 0};
+ }
+ return {ErrorType::Transport,
+ ngtcp2_err_infer_quic_transport_error_code(liberr)};
+}
+
+Error err_transport_idle_timeout() {
+ return {ErrorType::TransportIdleTimeout, 0};
+}
+
+Error err_transport_tls(int alert) {
+ return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code(
+ NGTCP2_CRYPTO_ERROR | alert)};
+}
+
+Error err_application(int liberr) {
+ return {ErrorType::Application,
+ nghttp3_err_infer_quic_app_error_code(liberr)};
+}
+
+} // namespace quic
diff --git a/src/quic.h b/src/quic.h
new file mode 100644
index 0000000..d72ae1f
--- /dev/null
+++ b/src/quic.h
@@ -0,0 +1,56 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2019 nghttp2 contributors
+ *
+ * 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 QUIC_H
+#define QUIC_H
+
+#include "nghttp2_config.h"
+
+#include "stdint.h"
+
+namespace quic {
+
+enum class ErrorType {
+ Transport,
+ TransportVersionNegotiation,
+ TransportIdleTimeout,
+ Application,
+};
+
+struct Error {
+ Error(ErrorType type, uint64_t code) : type(type), code(code) {}
+ Error() : type(ErrorType::Transport), code(0) {}
+
+ ErrorType type;
+ uint64_t code;
+};
+
+Error err_transport(int liberr);
+Error err_transport_idle_timeout();
+Error err_transport_tls(int alert);
+Error err_application(int liberr);
+
+} // namespace quic
+
+#endif // QUIC_H
diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc
new file mode 100644
index 0000000..f089adf
--- /dev/null
+++ b/src/shrpx-unittest.cc
@@ -0,0 +1,246 @@
+/*
+ * 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 test cases' include files here
+#include "shrpx_tls_test.h"
+#include "shrpx_downstream_test.h"
+#include "shrpx_config_test.h"
+#include "shrpx_worker_test.h"
+#include "http2_test.h"
+#include "util_test.h"
+#include "nghttp2_gzip_test.h"
+#include "buffer_test.h"
+#include "memchunk_test.h"
+#include "template_test.h"
+#include "shrpx_http_test.h"
+#include "base64_test.h"
+#include "shrpx_config.h"
+#include "tls.h"
+#include "shrpx_router_test.h"
+#include "shrpx_log.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc, char *argv[]) {
+ CU_pSuite pSuite = nullptr;
+ unsigned int num_tests_failed;
+
+ 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 (nullptr == pSuite) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ // add the tests to the suite
+ if (!CU_add_test(pSuite, "tls_create_lookup_tree",
+ shrpx::test_shrpx_tls_create_lookup_tree) ||
+ !CU_add_test(pSuite, "tls_cert_lookup_tree_add_ssl_ctx",
+ shrpx::test_shrpx_tls_cert_lookup_tree_add_ssl_ctx) ||
+ !CU_add_test(pSuite, "tls_tls_hostname_match",
+ shrpx::test_shrpx_tls_tls_hostname_match) ||
+ !CU_add_test(pSuite, "tls_tls_verify_numeric_hostname",
+ shrpx::test_shrpx_tls_verify_numeric_hostname) ||
+ !CU_add_test(pSuite, "tls_tls_verify_dns_hostname",
+ shrpx::test_shrpx_tls_verify_dns_hostname) ||
+ !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_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, "http2_normalize_path",
+ shrpx::test_http2_normalize_path) ||
+ !CU_add_test(pSuite, "http2_rewrite_clean_path",
+ shrpx::test_http2_rewrite_clean_path) ||
+ !CU_add_test(pSuite, "http2_get_pure_path_component",
+ shrpx::test_http2_get_pure_path_component) ||
+ !CU_add_test(pSuite, "http2_construct_push_component",
+ shrpx::test_http2_construct_push_component) ||
+ !CU_add_test(pSuite, "http2_contains_trailers",
+ shrpx::test_http2_contains_trailers) ||
+ !CU_add_test(pSuite, "http2_check_transfer_encoding",
+ shrpx::test_http2_check_transfer_encoding) ||
+ !CU_add_test(pSuite, "downstream_field_store_append_last_header",
+ shrpx::test_downstream_field_store_append_last_header) ||
+ !CU_add_test(pSuite, "downstream_field_store_header",
+ shrpx::test_downstream_field_store_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, "downstream_supports_non_final_response",
+ shrpx::test_downstream_supports_non_final_response) ||
+ !CU_add_test(pSuite, "downstream_find_affinity_cookie",
+ shrpx::test_downstream_find_affinity_cookie) ||
+ !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, "config_read_tls_ticket_key_file_aes_256",
+ shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) ||
+ !CU_add_test(pSuite, "worker_match_downstream_addr_group",
+ shrpx::test_shrpx_worker_match_downstream_addr_group) ||
+ !CU_add_test(pSuite, "http_create_forwarded",
+ shrpx::test_shrpx_http_create_forwarded) ||
+ !CU_add_test(pSuite, "http_create_via_header_value",
+ shrpx::test_shrpx_http_create_via_header_value) ||
+ !CU_add_test(pSuite, "http_create_affinity_cookie",
+ shrpx::test_shrpx_http_create_affinity_cookie) ||
+ !CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
+ shrpx::test_shrpx_http_create_altsvc_header_value) ||
+ !CU_add_test(pSuite, "http_check_http_scheme",
+ shrpx::test_shrpx_http_check_http_scheme) ||
+ !CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
+ !CU_add_test(pSuite, "router_match_wildcard",
+ shrpx::test_shrpx_router_match_wildcard) ||
+ !CU_add_test(pSuite, "router_match_prefix",
+ shrpx::test_shrpx_router_match_prefix) ||
+ !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_to_token68", shrpx::test_util_to_token68) ||
+ !CU_add_test(pSuite, "util_percent_encode_token",
+ shrpx::test_util_percent_encode_token) ||
+ !CU_add_test(pSuite, "util_percent_decode",
+ shrpx::test_util_percent_decode) ||
+ !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", shrpx::test_util_utos) ||
+ !CU_add_test(pSuite, "util_make_string_ref_uint",
+ shrpx::test_util_make_string_ref_uint) ||
+ !CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) ||
+ !CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_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, "util_starts_with", shrpx::test_util_starts_with) ||
+ !CU_add_test(pSuite, "util_ends_with", shrpx::test_util_ends_with) ||
+ !CU_add_test(pSuite, "util_parse_http_date",
+ shrpx::test_util_parse_http_date) ||
+ !CU_add_test(pSuite, "util_localtime_date",
+ shrpx::test_util_localtime_date) ||
+ !CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) ||
+ !CU_add_test(pSuite, "util_parse_config_str_list",
+ shrpx::test_util_parse_config_str_list) ||
+ !CU_add_test(pSuite, "util_make_http_hostport",
+ shrpx::test_util_make_http_hostport) ||
+ !CU_add_test(pSuite, "util_make_hostport",
+ shrpx::test_util_make_hostport) ||
+ !CU_add_test(pSuite, "util_strifind", shrpx::test_util_strifind) ||
+ !CU_add_test(pSuite, "util_random_alpha_digit",
+ shrpx::test_util_random_alpha_digit) ||
+ !CU_add_test(pSuite, "util_format_hex", shrpx::test_util_format_hex) ||
+ !CU_add_test(pSuite, "util_is_hex_string",
+ shrpx::test_util_is_hex_string) ||
+ !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) ||
+ !CU_add_test(pSuite, "util_extract_host",
+ shrpx::test_util_extract_host) ||
+ !CU_add_test(pSuite, "util_split_hostport",
+ shrpx::test_util_split_hostport) ||
+ !CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) ||
+ !CU_add_test(pSuite, "util_rstrip", shrpx::test_util_rstrip) ||
+ !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_add_test(pSuite, "memchunk_reset", nghttp2::test_memchunks_reset) ||
+ !CU_add_test(pSuite, "peek_memchunk_append",
+ nghttp2::test_peek_memchunks_append) ||
+ !CU_add_test(pSuite, "peek_memchunk_disable_peek_drain",
+ nghttp2::test_peek_memchunks_disable_peek_drain) ||
+ !CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain",
+ nghttp2::test_peek_memchunks_disable_peek_no_drain) ||
+ !CU_add_test(pSuite, "peek_memchunk_reset",
+ nghttp2::test_peek_memchunks_reset) ||
+ !CU_add_test(pSuite, "template_immutable_string",
+ nghttp2::test_template_immutable_string) ||
+ !CU_add_test(pSuite, "template_string_ref",
+ nghttp2::test_template_string_ref) ||
+ !CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) ||
+ !CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) {
+ 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
index 0000000..b42054c
--- /dev/null
+++ b/src/shrpx.cc
@@ -0,0 +1,5329 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#include <sys/un.h>
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <signal.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#include <netinet/tcp.h>
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif // HAVE_LIMITS_H
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif // HAVE_SYS_TIME_H
+#include <sys/resource.h>
+#ifdef HAVE_LIBSYSTEMD
+# include <systemd/sd-daemon.h>
+#endif // HAVE_LIBSYSTEMD
+#ifdef HAVE_LIBBPF
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include <cinttypes>
+#include <limits>
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <initializer_list>
+#include <random>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <nghttp3/nghttp3.h>
+#endif // ENABLE_HTTP3
+
+#include "shrpx_config.h"
+#include "shrpx_tls.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_worker_process.h"
+#include "shrpx_process.h"
+#include "shrpx_signal.h"
+#include "shrpx_connection.h"
+#include "shrpx_log.h"
+#include "shrpx_http.h"
+#include "util.h"
+#include "app_helper.h"
+#include "tls.h"
+#include "template.h"
+#include "allocator.h"
+#include "ssl_compat.h"
+#include "xsi_strerror.h"
+
+extern char **environ;
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// Deprecated: Environment variables to tell new binary the listening
+// socket's file descriptors. They are not close-on-exec.
+constexpr auto ENV_LISTENER4_FD = StringRef::from_lit("NGHTTPX_LISTENER4_FD");
+constexpr auto ENV_LISTENER6_FD = StringRef::from_lit("NGHTTPX_LISTENER6_FD");
+
+// Deprecated: Environment variable to tell new binary the port number
+// the current binary is listening to.
+constexpr auto ENV_PORT = StringRef::from_lit("NGHTTPX_PORT");
+
+// Deprecated: Environment variable to tell new binary the listening
+// socket's file descriptor if frontend listens UNIX domain socket.
+constexpr auto ENV_UNIX_FD = StringRef::from_lit("NGHTTP2_UNIX_FD");
+// Deprecated: Environment variable to tell new binary the UNIX domain
+// socket path.
+constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH");
+
+// Prefix of environment variables to tell new binary the listening
+// socket's file descriptor. They are not close-on-exec. For TCP
+// socket, the value must be comma separated 2 parameters: tcp,<FD>.
+// <FD> is file descriptor. For UNIX domain socket, the value must be
+// comma separated 3 parameters: unix,<FD>,<PATH>. <FD> is file
+// descriptor. <PATH> is a path to UNIX domain socket.
+constexpr auto ENV_ACCEPT_PREFIX = StringRef::from_lit("NGHTTPX_ACCEPT_");
+
+// This environment variable contains PID of the original main
+// process, assuming that it created this main process as a result of
+// SIGUSR2. The new main process is expected to send QUIT signal to
+// the original main process to shut it down gracefully.
+constexpr auto ENV_ORIG_PID = StringRef::from_lit("NGHTTPX_ORIG_PID");
+
+// Prefix of environment variables to tell new binary the QUIC IPC
+// file descriptor and CID prefix of the lingering worker process.
+// The value must be comma separated parameters:
+// <FD>,<CID_PREFIX_0>,<CID_PREFIX_1>,... <FD> is the file
+// descriptor. <CID_PREFIX_I> is the I-th CID prefix in hex encoded
+// string.
+constexpr auto ENV_QUIC_WORKER_PROCESS_PREFIX =
+ StringRef::from_lit("NGHTTPX_QUIC_WORKER_PROCESS_");
+
+#ifndef _KERNEL_FASTOPEN
+# define _KERNEL_FASTOPEN
+// conditional define for TCP_FASTOPEN mostly on ubuntu
+# ifndef TCP_FASTOPEN
+# define TCP_FASTOPEN 23
+# endif
+
+// conditional define for SOL_TCP mostly on ubuntu
+# ifndef SOL_TCP
+# define SOL_TCP 6
+# endif
+#endif
+
+// This configuration is fixed at the first startup of the main
+// process, and does not change after subsequent reloadings.
+struct StartupConfig {
+ // This contains all options given in command-line.
+ std::vector<std::pair<StringRef, StringRef>> cmdcfgs;
+ // The current working directory where this process started.
+ char *cwd;
+ // The pointer to original argv (not sure why we have this?)
+ char **original_argv;
+ // The pointer to argv, this is a deep copy of original argv.
+ char **argv;
+ // The number of elements in argv.
+ int argc;
+};
+
+namespace {
+StartupConfig suconfig;
+} // namespace
+
+struct InheritedAddr {
+ // IP address if TCP socket. Otherwise, UNIX domain socket path.
+ StringRef host;
+ uint16_t port;
+ // true if UNIX domain socket path
+ bool host_unix;
+ int fd;
+ bool used;
+};
+
+namespace {
+void signal_cb(struct ev_loop *loop, ev_signal *w, int revents);
+} // namespace
+
+namespace {
+void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents);
+} // namespace
+
+struct WorkerProcess {
+ WorkerProcess(struct ev_loop *loop, pid_t worker_pid, int ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ int quic_ipc_fd,
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes
+#endif // ENABLE_HTTP3
+ )
+ : loop(loop),
+ worker_pid(worker_pid),
+ ipc_fd(ipc_fd)
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd(quic_ipc_fd),
+ cid_prefixes(cid_prefixes)
+#endif // ENABLE_HTTP3
+ {
+ ev_child_init(&worker_process_childev, worker_process_child_cb, worker_pid,
+ 0);
+ worker_process_childev.data = this;
+ ev_child_start(loop, &worker_process_childev);
+ }
+
+ ~WorkerProcess() {
+ ev_child_stop(loop, &worker_process_childev);
+
+#ifdef ENABLE_HTTP3
+ if (quic_ipc_fd != -1) {
+ close(quic_ipc_fd);
+ }
+#endif // ENABLE_HTTP3
+
+ if (ipc_fd != -1) {
+ shutdown(ipc_fd, SHUT_WR);
+ close(ipc_fd);
+ }
+ }
+
+ ev_child worker_process_childev;
+ struct ev_loop *loop;
+ pid_t worker_pid;
+ int ipc_fd;
+ std::chrono::steady_clock::time_point termination_deadline;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd;
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+#endif // ENABLE_HTTP3
+};
+
+namespace {
+void reload_config();
+} // namespace
+
+namespace {
+std::deque<std::unique_ptr<WorkerProcess>> worker_processes;
+} // namespace
+
+namespace {
+ev_timer worker_process_grace_period_timer;
+} // namespace
+
+namespace {
+void worker_process_grace_period_timercb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto now = std::chrono::steady_clock::now();
+ auto next_repeat = std::chrono::steady_clock::duration::zero();
+
+ for (auto it = std::begin(worker_processes);
+ it != std::end(worker_processes);) {
+ auto &wp = *it;
+ if (wp->termination_deadline.time_since_epoch().count() == 0) {
+ ++it;
+
+ continue;
+ }
+
+ auto d = wp->termination_deadline - now;
+ if (d.count() > 0) {
+ if (next_repeat == std::chrono::steady_clock::duration::zero() ||
+ d < next_repeat) {
+ next_repeat = d;
+ }
+
+ ++it;
+
+ continue;
+ }
+
+ LOG(NOTICE) << "Deleting worker process pid=" << wp->worker_pid
+ << " because its grace shutdown period is over";
+
+ it = worker_processes.erase(it);
+ }
+
+ if (next_repeat.count() > 0) {
+ w->repeat = util::ev_tstamp_from(next_repeat);
+ ev_timer_again(loop, w);
+
+ return;
+ }
+
+ ev_timer_stop(loop, w);
+}
+} // namespace
+
+namespace {
+void worker_process_set_termination_deadline(WorkerProcess *wp,
+ struct ev_loop *loop) {
+ auto config = get_config();
+
+ if (!(config->worker_process_grace_shutdown_period > 0.)) {
+ return;
+ }
+
+ wp->termination_deadline =
+ std::chrono::steady_clock::now() +
+ util::duration_from(config->worker_process_grace_shutdown_period);
+
+ if (!ev_is_active(&worker_process_grace_period_timer)) {
+ worker_process_grace_period_timer.repeat =
+ config->worker_process_grace_shutdown_period;
+
+ ev_timer_again(loop, &worker_process_grace_period_timer);
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_add(std::unique_ptr<WorkerProcess> wp) {
+ worker_processes.push_back(std::move(wp));
+}
+} // namespace
+
+namespace {
+void worker_process_remove(const WorkerProcess *wp, struct ev_loop *loop) {
+ for (auto it = std::begin(worker_processes); it != std::end(worker_processes);
+ ++it) {
+ auto &s = *it;
+
+ if (s.get() != wp) {
+ continue;
+ }
+
+ worker_processes.erase(it);
+
+ if (worker_processes.empty()) {
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+ }
+
+ break;
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_adjust_limit() {
+ auto config = get_config();
+
+ if (config->max_worker_processes &&
+ worker_processes.size() > config->max_worker_processes) {
+ worker_processes.pop_front();
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_remove_all(struct ev_loop *loop) {
+ std::deque<std::unique_ptr<WorkerProcess>>().swap(worker_processes);
+
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+}
+} // namespace
+
+namespace {
+// Send signal |signum| to all worker processes, and clears
+// worker_processes.
+void worker_process_kill(int signum, struct ev_loop *loop) {
+ for (auto &s : worker_processes) {
+ if (s->worker_pid == -1) {
+ continue;
+ }
+ kill(s->worker_pid, signum);
+ }
+ worker_process_remove_all(loop);
+}
+} // namespace
+
+namespace {
+int save_pid() {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto config = get_config();
+
+ constexpr auto SUFFIX = StringRef::from_lit(".XXXXXX");
+ auto &pid_file = config->pid_file;
+
+ auto len = config->pid_file.size() + SUFFIX.size();
+ auto buf = std::make_unique<char[]>(len + 1);
+ auto p = buf.get();
+
+ p = std::copy(std::begin(pid_file), std::end(pid_file), p);
+ p = std::copy(std::begin(SUFFIX), std::end(SUFFIX), p);
+ *p = '\0';
+
+ auto temp_path = buf.get();
+
+ auto fd = mkstemp(temp_path);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto content = util::utos(config->pid) + '\n';
+
+ if (write(fd, content.c_str(), content.size()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ if (fsync(fd) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ close(fd);
+
+ if (rename(temp_path, pid_file.c_str()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not save PID to file " << pid_file << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ unlink(temp_path);
+
+ return -1;
+ }
+
+ if (config->uid != 0) {
+ if (chown(pid_file.c_str(), config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of pid file " << pid_file << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void shrpx_sd_notifyf(int unset_environment, const char *format, ...) {
+#ifdef HAVE_LIBSYSTEMD
+ va_list args;
+
+ va_start(args, format);
+ sd_notifyf(unset_environment, format, va_arg(args, char *));
+ va_end(args);
+#endif // HAVE_LIBSYSTEMD
+}
+} // namespace
+
+namespace {
+void exec_binary() {
+ int rv;
+ sigset_t oldset;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ LOG(NOTICE) << "Executing new binary";
+
+ shrpx_sd_notifyf(0, "RELOADING=1");
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return;
+ }
+
+ auto pid = fork();
+
+ if (pid != 0) {
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "fork() failed errno=" << error;
+ } else {
+ // update PID tracking information in systemd
+ shrpx_sd_notifyf(0, "MAINPID=%d\n", pid);
+ }
+
+ rv = shrpx_signal_set(&oldset);
+
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring signal mask failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ exit(EXIT_FAILURE);
+ }
+
+ return;
+ }
+
+ // child process
+
+ shrpx_signal_unset_main_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Unblocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ auto exec_path =
+ util::get_exec_path(suconfig.argc, suconfig.argv, suconfig.cwd);
+
+ if (!exec_path) {
+ LOG(ERROR) << "Could not resolve the executable path";
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ auto argv = std::make_unique<char *[]>(suconfig.argc + 1);
+
+ argv[0] = exec_path;
+ for (int i = 1; i < suconfig.argc; ++i) {
+ argv[i] = suconfig.argv[i];
+ }
+ argv[suconfig.argc] = nullptr;
+
+ size_t envlen = 0;
+ for (char **p = environ; *p; ++p, ++envlen)
+ ;
+
+ auto config = get_config();
+ auto &listenerconf = config->conn.listener;
+
+ // 2 for ENV_ORIG_PID and terminal nullptr.
+ auto envp = std::make_unique<char *[]>(envlen + listenerconf.addrs.size() +
+ worker_processes.size() + 2);
+ size_t envidx = 0;
+
+ std::vector<ImmutableString> fd_envs;
+ for (size_t i = 0; i < listenerconf.addrs.size(); ++i) {
+ auto &addr = listenerconf.addrs[i];
+ auto s = ENV_ACCEPT_PREFIX.str();
+ s += util::utos(i + 1);
+ s += '=';
+ if (addr.host_unix) {
+ s += "unix,";
+ s += util::utos(addr.fd);
+ s += ',';
+ s += addr.host;
+ } else {
+ s += "tcp,";
+ s += util::utos(addr.fd);
+ }
+
+ fd_envs.emplace_back(s);
+ envp[envidx++] = const_cast<char *>(fd_envs.back().c_str());
+ }
+
+ auto ipc_fd_str = ENV_ORIG_PID.str();
+ ipc_fd_str += '=';
+ ipc_fd_str += util::utos(config->pid);
+ envp[envidx++] = const_cast<char *>(ipc_fd_str.c_str());
+
+#ifdef ENABLE_HTTP3
+ std::vector<ImmutableString> quic_lwps;
+ for (size_t i = 0; i < worker_processes.size(); ++i) {
+ auto &wp = worker_processes[i];
+ auto s = ENV_QUIC_WORKER_PROCESS_PREFIX.str();
+ s += util::utos(i + 1);
+ s += '=';
+ s += util::utos(wp->quic_ipc_fd);
+ for (auto &cid_prefix : wp->cid_prefixes) {
+ s += ',';
+ s += util::format_hex(cid_prefix);
+ }
+
+ quic_lwps.emplace_back(s);
+ envp[envidx++] = const_cast<char *>(quic_lwps.back().c_str());
+ }
+#endif // ENABLE_HTTP3
+
+ for (size_t i = 0; i < envlen; ++i) {
+ auto env = StringRef{environ[i]};
+ if (util::starts_with(env, ENV_ACCEPT_PREFIX) ||
+ util::starts_with(env, ENV_LISTENER4_FD) ||
+ util::starts_with(env, ENV_LISTENER6_FD) ||
+ util::starts_with(env, ENV_PORT) ||
+ util::starts_with(env, ENV_UNIX_FD) ||
+ util::starts_with(env, ENV_UNIX_PATH) ||
+ util::starts_with(env, ENV_ORIG_PID) ||
+ util::starts_with(env, ENV_QUIC_WORKER_PROCESS_PREFIX)) {
+ 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];
+ }
+ }
+
+ // restores original stderr
+ restore_original_fds();
+
+ // reloading finished
+ shrpx_sd_notifyf(0, "READY=1");
+
+ if (execve(argv[0], argv.get(), envp.get()) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "execve failed: errno=" << error;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+}
+} // namespace
+
+namespace {
+void ipc_send(WorkerProcess *wp, uint8_t ipc_event) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ ssize_t nwrite;
+ while ((nwrite = write(wp->ipc_fd, &ipc_event, 1)) == -1 && errno == EINTR)
+ ;
+
+ if (nwrite < 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send IPC event to worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return;
+ }
+
+ if (nwrite == 0) {
+ LOG(ERROR) << "Could not send IPC event due to pipe overflow";
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void reopen_log(WorkerProcess *wp) {
+ LOG(NOTICE) << "Reopening log files: main process";
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ (void)reopen_log_files(loggingconf);
+ redirect_stderr_to_errorlog(loggingconf);
+ ipc_send(wp, SHRPX_IPC_REOPEN_LOG);
+}
+} // namespace
+
+namespace {
+void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
+ switch (w->signum) {
+ case REOPEN_LOG_SIGNAL:
+ for (auto &wp : worker_processes) {
+ reopen_log(wp.get());
+ }
+
+ return;
+ case EXEC_BINARY_SIGNAL:
+ exec_binary();
+ return;
+ case GRACEFUL_SHUTDOWN_SIGNAL: {
+ auto &listenerconf = get_config()->conn.listener;
+ for (auto &addr : listenerconf.addrs) {
+ close(addr.fd);
+ }
+
+ for (auto &wp : worker_processes) {
+ ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
+ worker_process_set_termination_deadline(wp.get(), loop);
+ }
+
+ return;
+ }
+ case RELOAD_SIGNAL:
+ reload_config();
+
+ return;
+ default:
+ worker_process_kill(w->signum, loop);
+ ev_break(loop);
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ auto wp = static_cast<WorkerProcess *>(w->data);
+
+ log_chld(w->rpid, w->rstatus, "Worker process");
+
+ worker_process_remove(wp, loop);
+
+ if (worker_processes.empty()) {
+ ev_break(loop);
+ }
+}
+} // namespace
+
+namespace {
+int create_unix_domain_server_socket(UpstreamAddr &faddr,
+ std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto found = std::find_if(
+ std::begin(iaddrs), std::end(iaddrs), [&faddr](const InheritedAddr &ia) {
+ return !ia.used && ia.host_unix && ia.host == faddr.host;
+ });
+
+ if (found != std::end(iaddrs)) {
+ LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host
+ << (faddr.tls ? ", tls" : "");
+ (*found).used = true;
+ faddr.fd = (*found).fd;
+ faddr.hostport = StringRef::from_lit("localhost");
+
+ return 0;
+ }
+
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(FATAL) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(FATAL) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ 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) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ sockaddr_union addr;
+ addr.un.sun_family = AF_UNIX;
+ if (faddr.host.size() + 1 > sizeof(addr.un.sun_path)) {
+ LOG(FATAL) << "UNIX domain socket path " << faddr.host << " is too long > "
+ << sizeof(addr.un.sun_path);
+ close(fd);
+ return -1;
+ }
+ // copy path including terminal NULL
+ std::copy_n(faddr.host.c_str(), faddr.host.size() + 1, addr.un.sun_path);
+
+ // unlink (remove) already existing UNIX domain socket path
+ unlink(faddr.host.c_str());
+
+ if (bind(fd, &addr.sa, sizeof(addr.un)) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to bind UNIX domain socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto &listenerconf = get_config()->conn.listener;
+
+ if (listen(fd, listenerconf.backlog) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to listen to UNIX domain socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host
+ << (faddr.tls ? ", tls" : "");
+
+ faddr.fd = fd;
+ faddr.hostport = StringRef::from_lit("localhost");
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int create_tcp_server_socket(UpstreamAddr &faddr,
+ std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int fd = -1;
+ int rv;
+
+ auto &listenerconf = get_config()->conn.listener;
+
+ auto service = util::utos(faddr.port);
+ addrinfo hints{};
+ hints.ai_family = faddr.family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+
+ auto node =
+ faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str();
+
+ addrinfo *res, *rp;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+#ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+ }
+#endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
+ << " address for " << faddr.host << ", port " << faddr.port
+ << ": " << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ std::array<char, NI_MAXHOST> host;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+
+ rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
+ nullptr, 0, NI_NUMERICHOST);
+
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
+ continue;
+ }
+
+ auto found = std::find_if(std::begin(iaddrs), std::end(iaddrs),
+ [&host, &faddr](const InheritedAddr &ia) {
+ return !ia.used && !ia.host_unix &&
+ ia.host == host.data() &&
+ ia.port == faddr.port;
+ });
+
+ if (found != std::end(iaddrs)) {
+ (*found).used = true;
+ fd = (*found).fd;
+ break;
+ }
+
+#ifdef SOCK_NONBLOCK
+ fd =
+ socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+#else // !SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ 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) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ if (faddr.family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ 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) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+#endif // TCP_DEFER_ACCEPT
+
+ // When we are executing new binary, and the old binary did not
+ // bind privileged port (< 1024) for some reason, binding to those
+ // ports will fail with permission denied error.
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ auto error = errno;
+ LOG(WARN) << "bind() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (listenerconf.fastopen > 0) {
+ val = listenerconf.fastopen;
+ if (setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ if (listen(fd, listenerconf.backlog) == -1) {
+ auto error = errno;
+ LOG(WARN) << "listen() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ break;
+ }
+
+ if (!rp) {
+ LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
+ << " socket failed";
+
+ return -1;
+ }
+
+ faddr.fd = fd;
+ faddr.hostport = util::make_http_hostport(mod_config()->balloc,
+ StringRef{host.data()}, faddr.port);
+
+ LOG(NOTICE) << "Listening on " << faddr.hostport
+ << (faddr.tls ? ", tls" : "");
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Returns array of InheritedAddr constructed from |config|. This
+// function is intended to be used when reloading configuration, and
+// |config| is usually a current configuration.
+std::vector<InheritedAddr>
+get_inherited_addr_from_config(BlockAllocator &balloc, Config *config) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ auto &listenerconf = config->conn.listener;
+
+ std::vector<InheritedAddr> iaddrs(listenerconf.addrs.size());
+
+ size_t idx = 0;
+ for (auto &addr : listenerconf.addrs) {
+ auto &iaddr = iaddrs[idx++];
+
+ if (addr.host_unix) {
+ iaddr.host = addr.host;
+ iaddr.host_unix = true;
+ iaddr.fd = addr.fd;
+
+ continue;
+ }
+
+ iaddr.port = addr.port;
+ iaddr.fd = addr.fd;
+
+ // We have to getsockname/getnameinfo for fd, since we may have
+ // '*' appear in addr.host, which makes comparison against "real"
+ // address fail.
+
+ sockaddr_union su;
+ socklen_t salen = sizeof(su);
+
+ // We already added entry to iaddrs. Even if we got errors, we
+ // don't remove it. This is required because we have to close the
+ // socket if it is not reused. The empty host name usually does
+ // not match anything.
+
+ if (getsockname(addr.fd, &su.sa, &salen) != 0) {
+ auto error = errno;
+ LOG(WARN) << "getsockname() syscall failed (fd=" << addr.fd
+ << "): " << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+
+ std::array<char, NI_MAXHOST> host;
+ rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed (fd=" << addr.fd
+ << "): " << gai_strerror(rv);
+ continue;
+ }
+
+ iaddr.host = make_string_ref(balloc, StringRef{host.data()});
+ }
+
+ return iaddrs;
+}
+} // namespace
+
+namespace {
+// Returns array of InheritedAddr constructed from environment
+// variables. This function handles the old environment variable
+// names used in 1.7.0 or earlier.
+std::vector<InheritedAddr> get_inherited_addr_from_env(Config *config) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+ std::vector<InheritedAddr> iaddrs;
+
+ {
+ // Upgrade from 1.7.0 or earlier
+ auto portenv = getenv(ENV_PORT.c_str());
+ if (portenv) {
+ size_t i = 1;
+ for (const auto &env_name : {ENV_LISTENER4_FD, ENV_LISTENER6_FD}) {
+ auto fdenv = getenv(env_name.c_str());
+ if (fdenv) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += util::utos(i);
+ std::string value = "tcp,";
+ value += fdenv;
+ setenv(name.c_str(), value.c_str(), 0);
+ ++i;
+ }
+ }
+ } else {
+ // The return value of getenv may be allocated statically.
+ if (getenv(ENV_UNIX_PATH.c_str()) && getenv(ENV_UNIX_FD.c_str())) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += '1';
+ std::string value = "unix,";
+ value += getenv(ENV_UNIX_FD.c_str());
+ value += ',';
+ value += getenv(ENV_UNIX_PATH.c_str());
+ setenv(name.c_str(), value.c_str(), 0);
+ }
+ }
+ }
+
+ for (size_t i = 1;; ++i) {
+ auto name = ENV_ACCEPT_PREFIX.str();
+ name += util::utos(i);
+ auto env = getenv(name.c_str());
+ if (!env) {
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Read env " << name << "=" << env;
+ }
+
+ auto end_type = strchr(env, ',');
+ if (!end_type) {
+ continue;
+ }
+
+ auto type = StringRef(env, end_type);
+ auto value = end_type + 1;
+
+ if (type == StringRef::from_lit("unix")) {
+ auto endfd = strchr(value, ',');
+ if (!endfd) {
+ continue;
+ }
+ auto fd = util::parse_uint(reinterpret_cast<const uint8_t *>(value),
+ endfd - value);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from "
+ << std::string(value, endfd - value);
+ continue;
+ }
+
+ auto path = endfd + 1;
+ if (strlen(path) == 0) {
+ LOG(WARN) << "Empty UNIX domain socket path (fd=" << fd << ")";
+ close(fd);
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit UNIX domain socket fd=" << fd
+ << ", path=" << path;
+ }
+
+ InheritedAddr addr{};
+ addr.host = make_string_ref(config->balloc, StringRef{path});
+ addr.host_unix = true;
+ addr.fd = static_cast<int>(fd);
+ iaddrs.push_back(std::move(addr));
+ }
+
+ if (type == StringRef::from_lit("tcp")) {
+ auto fd = util::parse_uint(value);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from " << value;
+ continue;
+ }
+
+ sockaddr_union su;
+ socklen_t salen = sizeof(su);
+
+ if (getsockname(fd, &su.sa, &salen) != 0) {
+ auto error = errno;
+ LOG(WARN) << "getsockname() syscall failed (fd=" << fd
+ << "): " << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ uint16_t port;
+
+ switch (su.storage.ss_family) {
+ case AF_INET:
+ port = ntohs(su.in.sin_port);
+ break;
+ case AF_INET6:
+ port = ntohs(su.in6.sin6_port);
+ break;
+ default:
+ close(fd);
+ continue;
+ }
+
+ std::array<char, NI_MAXHOST> host;
+ rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed (fd=" << fd
+ << "): " << gai_strerror(rv);
+ close(fd);
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit TCP socket fd=" << fd
+ << ", address=" << host.data() << ", port=" << port;
+ }
+
+ InheritedAddr addr{};
+ addr.host = make_string_ref(config->balloc, StringRef{host.data()});
+ addr.port = static_cast<uint16_t>(port);
+ addr.fd = static_cast<int>(fd);
+ iaddrs.push_back(std::move(addr));
+ continue;
+ }
+ }
+
+ return iaddrs;
+}
+} // namespace
+
+namespace {
+// Closes all sockets which are not reused.
+void close_unused_inherited_addr(const std::vector<InheritedAddr> &iaddrs) {
+ for (auto &ia : iaddrs) {
+ if (ia.used) {
+ continue;
+ }
+
+ close(ia.fd);
+ }
+}
+} // namespace
+
+namespace {
+// Returns the PID of the original main process from environment
+// variable ENV_ORIG_PID.
+pid_t get_orig_pid_from_env() {
+ auto s = getenv(ENV_ORIG_PID.c_str());
+ if (s == nullptr) {
+ return -1;
+ }
+ return util::parse_uint(s);
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+ inherited_quic_lingering_worker_processes;
+} // namespace
+
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+get_inherited_quic_lingering_worker_process_from_env() {
+ std::vector<QUICLingeringWorkerProcess> iwps;
+
+ for (size_t i = 1;; ++i) {
+ auto name = ENV_QUIC_WORKER_PROCESS_PREFIX.str();
+ name += util::utos(i);
+ auto env = getenv(name.c_str());
+ if (!env) {
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Read env " << name << "=" << env;
+ }
+
+ auto envend = env + strlen(env);
+
+ auto end_fd = std::find(env, envend, ',');
+ if (end_fd == envend) {
+ continue;
+ }
+
+ auto fd =
+ util::parse_uint(reinterpret_cast<const uint8_t *>(env), end_fd - env);
+ if (fd == -1) {
+ LOG(WARN) << "Could not parse file descriptor from "
+ << StringRef{env, static_cast<size_t>(end_fd - env)};
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit worker process QUIC IPC socket fd=" << fd;
+ }
+
+ util::make_socket_closeonexec(fd);
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ auto p = end_fd + 1;
+ for (;;) {
+ auto end = std::find(p, envend, ',');
+
+ auto hex_cid_prefix = StringRef{p, end};
+ if (hex_cid_prefix.size() != SHRPX_QUIC_CID_PREFIXLEN * 2 ||
+ !util::is_hex_string(hex_cid_prefix)) {
+ LOG(WARN) << "Found invalid CID prefix=" << hex_cid_prefix;
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Inherit worker process CID prefix=" << hex_cid_prefix;
+ }
+
+ cid_prefixes.emplace_back();
+
+ util::decode_hex(std::begin(cid_prefixes.back()), hex_cid_prefix);
+
+ if (end == envend) {
+ break;
+ }
+
+ p = end + 1;
+ }
+
+ iwps.emplace_back(std::move(cid_prefixes), fd);
+ }
+
+ return iwps;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+namespace {
+int create_acceptor_socket(Config *config, std::vector<InheritedAddr> &iaddrs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto &listenerconf = config->conn.listener;
+
+ for (auto &addr : listenerconf.addrs) {
+ if (addr.host_unix) {
+ if (create_unix_domain_server_socket(addr, iaddrs) != 0) {
+ return -1;
+ }
+
+ if (config->uid != 0) {
+ // fd is not associated to inode, so we cannot use fchown(2)
+ // here. https://lkml.org/lkml/2004/11/1/84
+ if (chown(addr.host.c_str(), config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of UNIX domain socket " << addr.host
+ << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ continue;
+ }
+
+ if (create_tcp_server_socket(addr, iaddrs) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int call_daemon() {
+#ifdef __sgi
+ return _daemonize(0, 0, 0, 0);
+#else // !__sgi
+# ifdef HAVE_LIBSYSTEMD
+ if (sd_booted() && (getenv("NOTIFY_SOCKET") != nullptr)) {
+ LOG(NOTICE) << "Daemonising disabled under systemd";
+ chdir("/");
+ return 0;
+ }
+# endif // HAVE_LIBSYSTEMD
+ return util::daemonize(0, 0);
+#endif // !__sgi
+}
+} // namespace
+
+namespace {
+// Opens IPC socket used to communicate with worker proess. The
+// communication is unidirectional; that is main process sends
+// messages to the worker process. On success, ipc_fd[0] is for
+// reading, and ipc_fd[1] for writing, just like pipe(2).
+int create_ipc_socket(std::array<int, 2> &ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = pipe(ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create pipe to communicate worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ auto fd = ipc_fd[i];
+ util::make_socket_nonblocking(fd);
+ util::make_socket_closeonexec(fd);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int create_worker_process_ready_ipc_socket(std::array<int, 2> &ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create socket pair to communicate worker process "
+ "readiness: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (auto fd : ipc_fd) {
+ util::make_socket_closeonexec(fd);
+ }
+
+ util::make_socket_nonblocking(ipc_fd[0]);
+
+ return 0;
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+int create_quic_ipc_socket(std::array<int, 2> &quic_ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+
+ rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, quic_ipc_fd.data());
+ if (rv == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to create socket pair to communicate worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ for (auto fd : quic_ipc_fd) {
+ util::make_socket_nonblocking(fd);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int generate_cid_prefix(
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> &cid_prefixes,
+ const Config *config) {
+ auto &apiconf = config->api;
+ auto &quicconf = config->quic;
+
+ size_t num_cid_prefix;
+ if (config->single_thread) {
+ num_cid_prefix = 1;
+ } else {
+ num_cid_prefix = config->num_worker;
+
+ // API endpoint occupies the one dedicated worker thread.
+ // Although such worker never gets QUIC traffic, we create CID
+ // prefix for it to make code a bit simpler.
+ if (apiconf.enabled) {
+ ++num_cid_prefix;
+ }
+ }
+
+ cid_prefixes.resize(num_cid_prefix);
+
+ for (auto &cid_prefix : cid_prefixes) {
+ if (create_cid_prefix(cid_prefix.data(), quicconf.server_id.data()) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::vector<QUICLingeringWorkerProcess>
+collect_quic_lingering_worker_processes() {
+ std::vector<QUICLingeringWorkerProcess> quic_lwps{
+ std::begin(inherited_quic_lingering_worker_processes),
+ std::end(inherited_quic_lingering_worker_processes)};
+
+ for (auto &wp : worker_processes) {
+ quic_lwps.emplace_back(wp->cid_prefixes, wp->quic_ipc_fd);
+ }
+
+ return quic_lwps;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+namespace {
+ev_signal reopen_log_signalev;
+ev_signal exec_binary_signalev;
+ev_signal graceful_shutdown_signalev;
+ev_signal reload_signalev;
+} // namespace
+
+namespace {
+void start_signal_watchers(struct ev_loop *loop) {
+ ev_signal_init(&reopen_log_signalev, signal_cb, REOPEN_LOG_SIGNAL);
+ ev_signal_start(loop, &reopen_log_signalev);
+
+ ev_signal_init(&exec_binary_signalev, signal_cb, EXEC_BINARY_SIGNAL);
+ ev_signal_start(loop, &exec_binary_signalev);
+
+ ev_signal_init(&graceful_shutdown_signalev, signal_cb,
+ GRACEFUL_SHUTDOWN_SIGNAL);
+ ev_signal_start(loop, &graceful_shutdown_signalev);
+
+ ev_signal_init(&reload_signalev, signal_cb, RELOAD_SIGNAL);
+ ev_signal_start(loop, &reload_signalev);
+}
+} // namespace
+
+namespace {
+void shutdown_signal_watchers(struct ev_loop *loop) {
+ ev_signal_stop(loop, &reload_signalev);
+ ev_signal_stop(loop, &graceful_shutdown_signalev);
+ ev_signal_stop(loop, &exec_binary_signalev);
+ ev_signal_stop(loop, &reopen_log_signalev);
+}
+} // namespace
+
+namespace {
+// A pair of connected socket with which a worker process tells main
+// process that it is ready for service. A worker process writes its
+// PID to worker_process_ready_ipc_fd[1] and main process reads it
+// from worker_process_ready_ipc_fd[0].
+std::array<int, 2> worker_process_ready_ipc_fd;
+} // namespace
+
+namespace {
+ev_io worker_process_ready_ipcev;
+} // namespace
+
+namespace {
+// PID received via NGHTTPX_ORIG_PID environment variable.
+pid_t orig_pid = -1;
+} // namespace
+
+namespace {
+void worker_process_ready_ipc_readcb(struct ev_loop *loop, ev_io *w,
+ int revents) {
+ std::array<uint8_t, 8> buf;
+ ssize_t nread;
+
+ while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto error = errno;
+
+ LOG(ERROR) << "Failed to read data from worker process ready IPC channel: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return;
+ }
+
+ if (nread == 0) {
+ return;
+ }
+
+ if (nread != sizeof(pid_t)) {
+ LOG(ERROR) << "Read " << nread
+ << " bytes from worker process ready IPC channel";
+
+ return;
+ }
+
+ pid_t pid;
+
+ memcpy(&pid, buf.data(), sizeof(pid));
+
+ LOG(NOTICE) << "Worker process pid=" << pid << " is ready";
+
+ for (auto &wp : worker_processes) {
+ // Send graceful shutdown signal to all worker processes prior to
+ // pid.
+ if (wp->worker_pid == pid) {
+ break;
+ }
+
+ LOG(INFO) << "Sending graceful shutdown event to worker process pid="
+ << wp->worker_pid;
+
+ ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
+ worker_process_set_termination_deadline(wp.get(), loop);
+ }
+
+ if (orig_pid != -1) {
+ LOG(NOTICE) << "Send QUIT signal to the original main process to tell "
+ "that we are ready to serve requests.";
+ kill(orig_pid, SIGQUIT);
+
+ orig_pid = -1;
+ }
+}
+} // namespace
+
+namespace {
+void start_worker_process_ready_ipc_watcher(struct ev_loop *loop) {
+ ev_io_init(&worker_process_ready_ipcev, worker_process_ready_ipc_readcb,
+ worker_process_ready_ipc_fd[0], EV_READ);
+ ev_io_start(loop, &worker_process_ready_ipcev);
+}
+} // namespace
+
+namespace {
+void shutdown_worker_process_ready_ipc_watcher(struct ev_loop *loop) {
+ ev_io_stop(loop, &worker_process_ready_ipcev);
+}
+} // namespace
+
+namespace {
+// Creates worker process, and returns PID of worker process. On
+// success, file descriptor for IPC (send only) is assigned to
+// |main_ipc_fd|. In child process, we will close file descriptors
+// which are inherited from previous configuration/process, but not
+// used in the current configuration.
+pid_t fork_worker_process(
+ int &main_ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ int &wp_quic_ipc_fd
+#endif // ENABLE_HTTP3
+ ,
+ const std::vector<InheritedAddr> &iaddrs
+#ifdef ENABLE_HTTP3
+ ,
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes,
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps
+#endif // ENABLE_HTTP3
+) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int rv;
+ sigset_t oldset;
+
+ std::array<int, 2> ipc_fd;
+
+ rv = create_ipc_socket(ipc_fd);
+ if (rv != 0) {
+ return -1;
+ }
+
+#ifdef ENABLE_HTTP3
+ std::array<int, 2> quic_ipc_fd;
+
+ rv = create_quic_ipc_socket(quic_ipc_fd);
+ if (rv != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ close(ipc_fd[0]);
+ close(ipc_fd[1]);
+
+ return -1;
+ }
+
+ auto config = get_config();
+
+ pid_t pid = 0;
+
+ if (!config->single_process) {
+ pid = fork();
+ }
+
+ if (pid == 0) {
+ // We are in new process now, update pid for logger.
+ log_config()->pid = getpid();
+
+ ev_loop_fork(EV_DEFAULT);
+
+ for (auto &addr : config->conn.listener.addrs) {
+ util::make_socket_closeonexec(addr.fd);
+ }
+
+#ifdef ENABLE_HTTP3
+ util::make_socket_closeonexec(quic_ipc_fd[0]);
+
+ for (auto &lwp : quic_lwps) {
+ util::make_socket_closeonexec(lwp.quic_ipc_fd);
+ }
+
+ for (auto &wp : worker_processes) {
+ util::make_socket_closeonexec(wp->quic_ipc_fd);
+ // Do not close quic_ipc_fd.
+ wp->quic_ipc_fd = -1;
+ }
+#endif // ENABLE_HTTP3
+
+ if (!config->single_process) {
+ close(worker_process_ready_ipc_fd[0]);
+ shutdown_worker_process_ready_ipc_watcher(EV_DEFAULT);
+
+ shutdown_signal_watchers(EV_DEFAULT);
+ }
+
+ // Remove all WorkerProcesses to stop any registered watcher on
+ // default loop.
+ worker_process_remove_all(EV_DEFAULT);
+
+ close_unused_inherited_addr(iaddrs);
+
+ shrpx_signal_set_worker_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Unblocking all signals failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ if (config->single_process) {
+ exit(EXIT_FAILURE);
+ } else {
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!config->single_process) {
+ close(ipc_fd[1]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[1]);
+#endif // ENABLE_HTTP3
+ }
+
+ WorkerProcessConfig wpconf{
+ .ipc_fd = ipc_fd[0],
+ .ready_ipc_fd = worker_process_ready_ipc_fd[1],
+#ifdef ENABLE_HTTP3
+ .cid_prefixes = cid_prefixes,
+ .quic_ipc_fd = quic_ipc_fd[0],
+ .quic_lingering_worker_processes = quic_lwps,
+#endif // ENABLE_HTTP3
+ };
+ rv = worker_process_event_loop(&wpconf);
+ if (rv != 0) {
+ LOG(FATAL) << "Worker process returned error";
+
+ if (config->single_process) {
+ exit(EXIT_FAILURE);
+ } else {
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ }
+
+ LOG(NOTICE) << "Worker process shutting down momentarily";
+
+ // call exit(...) instead of nghttp2_Exit to get leak sanitizer report
+ if (config->single_process) {
+ exit(EXIT_SUCCESS);
+ } else {
+ nghttp2_Exit(EXIT_SUCCESS);
+ }
+ }
+
+ // parent process
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not spawn worker process: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+
+ rv = shrpx_signal_set(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring signal mask failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ exit(EXIT_FAILURE);
+ }
+
+ if (pid == -1) {
+ close(ipc_fd[0]);
+ close(ipc_fd[1]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[0]);
+ close(quic_ipc_fd[1]);
+#endif // ENABLE_HTTP3
+
+ return -1;
+ }
+
+ close(ipc_fd[0]);
+#ifdef ENABLE_HTTP3
+ close(quic_ipc_fd[0]);
+#endif // ENABLE_HTTP3
+
+ main_ipc_fd = ipc_fd[1];
+#ifdef ENABLE_HTTP3
+ wp_quic_ipc_fd = quic_ipc_fd[1];
+#endif // ENABLE_HTTP3
+
+ LOG(NOTICE) << "Worker process [" << pid << "] spawned";
+
+ return pid;
+}
+} // namespace
+
+namespace {
+int event_loop() {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ shrpx_signal_set_main_proc_ign_handler();
+
+ auto config = mod_config();
+
+ if (config->daemon) {
+ if (call_daemon() == -1) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to daemonize: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ // We get new PID after successful daemon().
+ mod_config()->pid = getpid();
+
+ // daemon redirects stderr file descriptor to /dev/null, so we
+ // need this.
+ redirect_stderr_to_errorlog(config->logging);
+ }
+
+ // update systemd PID tracking
+ shrpx_sd_notifyf(0, "MAINPID=%d\n", config->pid);
+
+ {
+ auto iaddrs = get_inherited_addr_from_env(config);
+
+ if (create_acceptor_socket(config, iaddrs) != 0) {
+ return -1;
+ }
+
+ close_unused_inherited_addr(iaddrs);
+ }
+
+ orig_pid = get_orig_pid_from_env();
+
+#ifdef ENABLE_HTTP3
+ inherited_quic_lingering_worker_processes =
+ get_inherited_quic_lingering_worker_process_from_env();
+#endif // ENABLE_HTTP3
+
+ auto loop = ev_default_loop(config->ev_loop_flags);
+
+ int ipc_fd = 0;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd = 0;
+
+ auto quic_lwps = collect_quic_lingering_worker_processes();
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ if (generate_cid_prefix(cid_prefixes, config) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ if (!config->single_process) {
+ start_signal_watchers(loop);
+ }
+
+ create_worker_process_ready_ipc_socket(worker_process_ready_ipc_fd);
+ start_worker_process_ready_ipc_watcher(loop);
+
+ auto pid = fork_worker_process(ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd
+#endif // ENABLE_HTTP3
+ ,
+ {}
+#ifdef ENABLE_HTTP3
+ ,
+ cid_prefixes, quic_lwps
+#endif // ENABLE_HTTP3
+ );
+
+ if (pid == -1) {
+ return -1;
+ }
+
+ ev_timer_init(&worker_process_grace_period_timer,
+ worker_process_grace_period_timercb, 0., 0.);
+
+ worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd, cid_prefixes
+#endif // ENABLE_HTTP3
+ ));
+
+ // Write PID file when we are ready to accept connection from peer.
+ // This makes easier to write restart script for nghttpx. Because
+ // when we know that PID file is recreated, it means we can send
+ // QUIT signal to the old process to make it shutdown gracefully.
+ if (!config->pid_file.empty()) {
+ save_pid();
+ }
+
+ shrpx_sd_notifyf(0, "READY=1");
+
+ ev_run(loop, 0);
+
+ ev_timer_stop(loop, &worker_process_grace_period_timer);
+
+ shutdown_worker_process_ready_ipc_watcher(loop);
+
+ // config is now stale if reload has happened.
+ if (!get_config()->single_process) {
+ shutdown_signal_watchers(loop);
+ }
+
+ 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 {
+constexpr auto DEFAULT_ALPN_LIST =
+ StringRef::from_lit("h2,h2-16,h2-14,http/1.1");
+} // namespace
+
+namespace {
+constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.2");
+#ifdef TLS1_3_VERSION
+constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.3");
+#else // !TLS1_3_VERSION
+constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2");
+#endif // !TLS1_3_VERSION
+} // namespace
+
+namespace {
+constexpr auto DEFAULT_ACCESSLOG_FORMAT =
+ StringRef::from_lit(R"($remote_addr - - [$time_local] )"
+ R"("$request" $status $body_bytes_sent )"
+ R"("$http_referer" "$http_user_agent")");
+} // namespace
+
+namespace {
+void fill_default_config(Config *config) {
+ config->num_worker = 1;
+ config->conf_path = StringRef::from_lit("/etc/nghttpx/nghttpx.conf");
+ config->pid = getpid();
+
+#ifdef NOTHREADS
+ config->single_thread = true;
+#endif // NOTHREADS
+
+ if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
+ config->ev_loop_flags = ev_recommended_backends() | EVBACKEND_KQUEUE;
+ }
+
+ auto &tlsconf = config->tls;
+ {
+ auto &ticketconf = tlsconf.ticket;
+ {
+ auto &memcachedconf = ticketconf.memcached;
+ memcachedconf.max_retry = 3;
+ memcachedconf.max_fail = 2;
+ memcachedconf.interval = 10_min;
+ memcachedconf.family = AF_UNSPEC;
+ }
+
+ auto &session_cacheconf = tlsconf.session_cache;
+ {
+ auto &memcachedconf = session_cacheconf.memcached;
+ memcachedconf.family = AF_UNSPEC;
+ }
+
+ ticketconf.cipher = EVP_aes_128_cbc();
+ }
+
+ {
+ auto &ocspconf = tlsconf.ocsp;
+ // ocsp update interval = 14400 secs = 4 hours, borrowed from h2o
+ ocspconf.update_interval = 4_h;
+ ocspconf.fetch_ocsp_response_file =
+ StringRef::from_lit(PKGDATADIR "/fetch-ocsp-response");
+ }
+
+ {
+ auto &dyn_recconf = tlsconf.dyn_rec;
+ dyn_recconf.warmup_threshold = 1_m;
+ dyn_recconf.idle_timeout = 1_s;
+ }
+
+ tlsconf.session_timeout = std::chrono::hours(12);
+ tlsconf.ciphers = StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST);
+ tlsconf.tls13_ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST);
+ tlsconf.client.ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST);
+ tlsconf.client.tls13_ciphers =
+ StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST);
+ tlsconf.min_proto_version =
+ tls::proto_version_from_string(DEFAULT_TLS_MIN_PROTO_VERSION);
+ tlsconf.max_proto_version =
+ tls::proto_version_from_string(DEFAULT_TLS_MAX_PROTO_VERSION);
+ tlsconf.max_early_data = 16_k;
+ tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521");
+
+ auto &httpconf = config->http;
+ httpconf.server_name = StringRef::from_lit("nghttpx");
+ httpconf.no_host_rewrite = true;
+ httpconf.request_header_field_buffer = 64_k;
+ httpconf.max_request_header_fields = 100;
+ httpconf.response_header_field_buffer = 64_k;
+ httpconf.max_response_header_fields = 500;
+ httpconf.redirect_https_port = StringRef::from_lit("443");
+ httpconf.max_requests = std::numeric_limits<size_t>::max();
+ httpconf.xfp.add = true;
+ httpconf.xfp.strip_incoming = true;
+ httpconf.early_data.strip_incoming = true;
+
+ auto &http2conf = config->http2;
+ {
+ auto &upstreamconf = http2conf.upstream;
+
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ timeoutconf.settings = 10_s;
+ }
+
+ // window size for HTTP/2 upstream connection per stream. 2**16-1
+ // = 64KiB-1, which is HTTP/2 default.
+ upstreamconf.window_size = 64_k - 1;
+ // HTTP/2 has connection-level flow control. The default window
+ // size for HTTP/2 is 64KiB - 1.
+ upstreamconf.connection_window_size = 64_k - 1;
+ upstreamconf.max_concurrent_streams = 100;
+
+ upstreamconf.encoder_dynamic_table_size = 4_k;
+ upstreamconf.decoder_dynamic_table_size = 4_k;
+
+ nghttp2_option_new(&upstreamconf.option);
+ nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1);
+ nghttp2_option_set_no_recv_client_magic(upstreamconf.option, 1);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ upstreamconf.option, upstreamconf.encoder_dynamic_table_size);
+ nghttp2_option_set_server_fallback_rfc7540_priorities(upstreamconf.option,
+ 1);
+ nghttp2_option_set_builtin_recv_extension_type(upstreamconf.option,
+ NGHTTP2_PRIORITY_UPDATE);
+
+ // For API endpoint, we enable automatic window update. This is
+ // because we are a sink.
+ nghttp2_option_new(&upstreamconf.alt_mode_option);
+ nghttp2_option_set_no_recv_client_magic(upstreamconf.alt_mode_option, 1);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ upstreamconf.alt_mode_option, upstreamconf.encoder_dynamic_table_size);
+ }
+
+ http2conf.timeout.stream_write = 1_min;
+
+ {
+ auto &downstreamconf = http2conf.downstream;
+
+ {
+ auto &timeoutconf = downstreamconf.timeout;
+ timeoutconf.settings = 10_s;
+ }
+
+ downstreamconf.window_size = 64_k - 1;
+ downstreamconf.connection_window_size = (1u << 31) - 1;
+ downstreamconf.max_concurrent_streams = 100;
+
+ downstreamconf.encoder_dynamic_table_size = 4_k;
+ downstreamconf.decoder_dynamic_table_size = 4_k;
+
+ nghttp2_option_new(&downstreamconf.option);
+ nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1);
+ nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ downstreamconf.option, downstreamconf.encoder_dynamic_table_size);
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &quicconf = config->quic;
+ {
+ auto &upstreamconf = quicconf.upstream;
+
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ timeoutconf.idle = 30_s;
+ }
+
+ auto &bpfconf = quicconf.bpf;
+ bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o");
+
+ upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
+
+ upstreamconf.initial_rtt =
+ static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT) / NGTCP2_SECONDS;
+ }
+
+ if (RAND_bytes(quicconf.server_id.data(), quicconf.server_id.size()) != 1) {
+ assert(0);
+ abort();
+ }
+
+ auto &http3conf = config->http3;
+ {
+ auto &upstreamconf = http3conf.upstream;
+
+ upstreamconf.max_concurrent_streams = 100;
+ upstreamconf.window_size = 256_k;
+ upstreamconf.connection_window_size = 1_m;
+ upstreamconf.max_window_size = 6_m;
+ upstreamconf.max_connection_window_size = 8_m;
+ }
+#endif // ENABLE_HTTP3
+
+ auto &loggingconf = config->logging;
+ {
+ auto &accessconf = loggingconf.access;
+ accessconf.format =
+ parse_log_format(config->balloc, DEFAULT_ACCESSLOG_FORMAT);
+
+ auto &errorconf = loggingconf.error;
+ errorconf.file = StringRef::from_lit("/dev/stderr");
+ }
+
+ loggingconf.syslog_facility = LOG_DAEMON;
+ loggingconf.severity = NOTICE;
+
+ auto &connconf = config->conn;
+ {
+ auto &listenerconf = connconf.listener;
+ {
+ // Default accept() backlog
+ listenerconf.backlog = 65536;
+ listenerconf.timeout.sleep = 30_s;
+ }
+ }
+
+ {
+ auto &upstreamconf = connconf.upstream;
+ {
+ auto &timeoutconf = upstreamconf.timeout;
+ // Read timeout for HTTP2 upstream connection
+ timeoutconf.http2_read = 3_min;
+
+ // Read timeout for HTTP3 upstream connection
+ timeoutconf.http3_read = 3_min;
+
+ // Read timeout for non-HTTP2 upstream connection
+ timeoutconf.read = 1_min;
+
+ // Write timeout for HTTP2/non-HTTP2 upstream connection
+ timeoutconf.write = 30_s;
+
+ // Keep alive timeout for HTTP/1 upstream connection
+ timeoutconf.idle_read = 1_min;
+ }
+ }
+
+ {
+ connconf.downstream = std::make_shared<DownstreamConfig>();
+ auto &downstreamconf = *connconf.downstream;
+ {
+ auto &timeoutconf = downstreamconf.timeout;
+ // Read/Write timeouts for downstream connection
+ timeoutconf.read = 1_min;
+ timeoutconf.write = 30_s;
+ // Timeout for pooled (idle) connections
+ timeoutconf.idle_read = 2_s;
+ timeoutconf.connect = 30_s;
+ timeoutconf.max_backoff = 120_s;
+ }
+
+ downstreamconf.connections_per_host = 8;
+ downstreamconf.request_buffer_size = 16_k;
+ downstreamconf.response_buffer_size = 128_k;
+ downstreamconf.family = AF_UNSPEC;
+ }
+
+ auto &apiconf = config->api;
+ apiconf.max_request_body = 32_m;
+
+ auto &dnsconf = config->dns;
+ {
+ auto &timeoutconf = dnsconf.timeout;
+ timeoutconf.cache = 10_s;
+ timeoutconf.lookup = 5_s;
+ }
+ dnsconf.max_try = 2;
+}
+
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+ out << "nghttpx nghttp2/" NGHTTP2_VERSION
+#ifdef ENABLE_HTTP3
+ " ngtcp2/" NGTCP2_VERSION " nghttp3/" NGHTTP3_VERSION
+#endif // ENABLE_HTTP3
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+ out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+ auto config = get_config();
+
+ print_usage(out);
+ out << R"(
+ <PRIVATE_KEY>
+ Set path to server's private key. Required unless
+ "no-tls" parameter is used in --frontend option.
+ <CERT> Set path to server's certificate. Required unless
+ "no-tls" parameter is used in --frontend option. To
+ make OCSP stapling work, this must be an absolute path.
+
+Options:
+ The options are categorized into several groups.
+
+Connections:
+ -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...]
+
+ Set backend host and port. The multiple backend
+ addresses are accepted by repeating this option. UNIX
+ domain socket can be specified by prefixing path name
+ with "unix:" (e.g., unix:/var/run/backend.sock).
+
+ Optionally, if <PATTERN>s are given, the backend address
+ is only used if request matches the pattern. The
+ pattern matching is closely designed to ServeMux in
+ net/http package of Go programming language. <PATTERN>
+ consists of path, host + path or just host. The path
+ must start with "/". If it ends with "/", it matches
+ all request path in its subtree. To deal with the
+ request to the directory without trailing slash, the
+ path which ends with "/" also matches the request path
+ which only lacks trailing '/' (e.g., path "/foo/"
+ matches request path "/foo"). If it does not end with
+ "/", it performs exact match against the request path.
+ If host is given, it performs a match against the
+ request host. For a request received on the frontend
+ listener with "sni-fwd" parameter enabled, SNI host is
+ used instead of a request host. If host alone is given,
+ "/" is appended to it, so that it matches all request
+ paths under the host (e.g., specifying "nghttp2.org"
+ equals to "nghttp2.org/"). CONNECT method is treated
+ specially. It does not have path, and we don't allow
+ empty path. To workaround this, we assume that CONNECT
+ method has "/" as path.
+
+ Patterns with host take precedence over patterns with
+ just path. Then, longer patterns take precedence over
+ shorter ones.
+
+ Host can include "*" in the left most position to
+ indicate wildcard match (only suffix match is done).
+ The "*" must match at least one character. For example,
+ host pattern "*.nghttp2.org" matches against
+ "www.nghttp2.org" and "git.ngttp2.org", but does not
+ match against "nghttp2.org". The exact hosts match
+ takes precedence over the wildcard hosts match.
+
+ If path part ends with "*", it is treated as wildcard
+ path. The wildcard path behaves differently from the
+ normal path. For normal path, match is made around the
+ boundary of path component separator,"/". On the other
+ hand, the wildcard path does not take into account the
+ path component separator. All paths which include the
+ wildcard path without last "*" as prefix, and are
+ strictly longer than wildcard path without last "*" are
+ matched. "*" must match at least one character. For
+ example, the pattern "/foo*" matches "/foo/" and
+ "/foobar". But it does not match "/foo", or "/fo".
+
+ If <PATTERN> is omitted or empty string, "/" is used as
+ pattern, which matches all request paths (catch-all
+ pattern). The catch-all backend must be given.
+
+ When doing a match, nghttpx made some normalization to
+ pattern, request host and path. For host part, they are
+ converted to lower case. For path part, percent-encoded
+ unreserved characters defined in RFC 3986 are decoded,
+ and any dot-segments (".." and ".") are resolved and
+ removed.
+
+ For example, -b'127.0.0.1,8080;nghttp2.org/httpbin/'
+ matches the request host "nghttp2.org" and the request
+ path "/httpbin/get", but does not match the request host
+ "nghttp2.org" and the request path "/index.html".
+
+ The multiple <PATTERN>s can be specified, delimiting
+ them by ":". Specifying
+ -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the
+ same effect to specify -b'127.0.0.1,8080;nghttp2.org'
+ and -b'127.0.0.1,8080;www.nghttp2.org'.
+
+ The backend addresses sharing same <PATTERN> are grouped
+ together forming load balancing group.
+
+ Several parameters <PARAM> are accepted after <PATTERN>.
+ The parameters are delimited by ";". The available
+ parameters are: "proto=<PROTO>", "tls",
+ "sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
+ "affinity=<METHOD>", "dns", "redirect-if-not-tls",
+ "upgrade-scheme", "mruby=<PATH>",
+ "read-timeout=<DURATION>", "write-timeout=<DURATION>",
+ "group=<GROUP>", "group-weight=<N>", "weight=<N>", and
+ "dnf". The parameter consists of keyword, and
+ optionally followed by "=" and value. For example, the
+ parameter "proto=h2" consists of the keyword "proto" and
+ value "h2". The parameter "tls" consists of the keyword
+ "tls" without value. Each parameter is described as
+ follows.
+
+ The backend application protocol can be specified using
+ optional "proto" parameter, and in the form of
+ "proto=<PROTO>". <PROTO> should be one of the following
+ list without quotes: "h2", "http/1.1". The default
+ value of <PROTO> is "http/1.1". Note that usually "h2"
+ refers to HTTP/2 over TLS. But in this option, it may
+ mean HTTP/2 over cleartext TCP unless "tls" keyword is
+ used (see below).
+
+ TLS can be enabled by specifying optional "tls"
+ parameter. TLS is not enabled by default.
+
+ With "sni=<SNI_HOST>" parameter, it can override the TLS
+ SNI field value with given <SNI_HOST>. This will
+ default to the backend <HOST> name
+
+ The feature to detect whether backend is online or
+ offline can be enabled using optional "fall" and "rise"
+ parameters. Using "fall=<N>" parameter, if nghttpx
+ cannot connect to a this backend <N> times in a row,
+ this backend is assumed to be offline, and it is
+ excluded from load balancing. If <N> is 0, this backend
+ never be excluded from load balancing whatever times
+ nghttpx cannot connect to it, and this is the default.
+ There is also "rise=<N>" parameter. After backend was
+ excluded from load balancing group, nghttpx periodically
+ attempts to make a connection to the failed backend, and
+ if the connection is made successfully <N> times in a
+ row, the backend is assumed to be online, and it is now
+ eligible for load balancing target. If <N> is 0, a
+ backend is permanently offline, once it goes in that
+ state, and this is the default behaviour.
+
+ The session affinity is enabled using
+ "affinity=<METHOD>" parameter. If "ip" is given in
+ <METHOD>, client IP based session affinity is enabled.
+ If "cookie" is given in <METHOD>, cookie based session
+ affinity is enabled. If "none" is given in <METHOD>,
+ session affinity is disabled, and this is the default.
+ The session affinity is enabled per <PATTERN>. If at
+ least one backend has "affinity" parameter, and its
+ <METHOD> is not "none", session affinity is enabled for
+ all backend servers sharing the same <PATTERN>. It is
+ advised to set "affinity" parameter to all backend
+ explicitly if session affinity is desired. The session
+ affinity may break if one of the backend gets
+ unreachable, or backend settings are reloaded or
+ replaced by API.
+
+ If "affinity=cookie" is used, the additional
+ configuration is required.
+ "affinity-cookie-name=<NAME>" must be used to specify a
+ name of cookie to use. Optionally,
+ "affinity-cookie-path=<PATH>" can be used to specify a
+ path which cookie is applied. The optional
+ "affinity-cookie-secure=<SECURE>" controls the Secure
+ attribute of a cookie. The default value is "auto", and
+ the Secure attribute is determined by a request scheme.
+ If a request scheme is "https", then Secure attribute is
+ set. Otherwise, it is not set. If <SECURE> is "yes",
+ the Secure attribute is always set. If <SECURE> is
+ "no", the Secure attribute is always omitted.
+ "affinity-cookie-stickiness=<STICKINESS>" controls
+ stickiness of this affinity. If <STICKINESS> is
+ "loose", removing or adding a backend server might break
+ the affinity and the request might be forwarded to a
+ different backend server. If <STICKINESS> is "strict",
+ removing the designated backend server breaks affinity,
+ but adding new backend server does not cause breakage.
+ If the designated backend server becomes unavailable,
+ new backend server is chosen as if the request does not
+ have an affinity cookie. <STICKINESS> defaults to
+ "loose".
+
+ By default, name resolution of backend host name is done
+ at start up, or reloading configuration. If "dns"
+ parameter is given, name resolution takes place
+ dynamically. This is useful if backend address changes
+ frequently. If "dns" is given, name resolution of
+ backend host name at start up, or reloading
+ configuration is skipped.
+
+ If "redirect-if-not-tls" parameter is used, the matched
+ backend requires that frontend connection is TLS
+ encrypted. If it isn't, nghttpx responds to the request
+ with 308 status code, and https URI the client should
+ use instead is included in Location header field. The
+ port number in redirect URI is 443 by default, and can
+ be changed using --redirect-https-port option. If at
+ least one backend has "redirect-if-not-tls" parameter,
+ this feature is enabled for all backend servers sharing
+ the same <PATTERN>. It is advised to set
+ "redirect-if-no-tls" parameter to all backends
+ explicitly if this feature is desired.
+
+ If "upgrade-scheme" parameter is used along with "tls"
+ parameter, HTTP/2 :scheme pseudo header field is changed
+ to "https" from "http" when forwarding a request to this
+ particular backend. This is a workaround for a backend
+ server which requires "https" :scheme pseudo header
+ field on TLS encrypted connection.
+
+ "mruby=<PATH>" parameter specifies a path to mruby
+ script file which is invoked when this pattern is
+ matched. All backends which share the same pattern must
+ have the same mruby path.
+
+ "read-timeout=<DURATION>" and "write-timeout=<DURATION>"
+ parameters specify the read and write timeout of the
+ backend connection when this pattern is matched. All
+ backends which share the same pattern must have the same
+ timeouts. If these timeouts are entirely omitted for a
+ pattern, --backend-read-timeout and
+ --backend-write-timeout are used.
+
+ "group=<GROUP>" parameter specifies the name of group
+ this backend address belongs to. By default, it belongs
+ to the unnamed default group. The name of group is
+ unique per pattern. "group-weight=<N>" parameter
+ specifies the weight of the group. The higher weight
+ gets more frequently selected by the load balancing
+ algorithm. <N> must be [1, 256] inclusive. The weight
+ 8 has 4 times more weight than 2. <N> must be the same
+ for all addresses which share the same <GROUP>. If
+ "group-weight" is omitted in an address, but the other
+ address which belongs to the same group specifies
+ "group-weight", its weight is used. If no
+ "group-weight" is specified for all addresses, the
+ weight of a group becomes 1. "group" and "group-weight"
+ are ignored if session affinity is enabled.
+
+ "weight=<N>" parameter specifies the weight of the
+ backend address inside a group which this address
+ belongs to. The higher weight gets more frequently
+ selected by the load balancing algorithm. <N> must be
+ [1, 256] inclusive. The weight 8 has 4 times more
+ weight than weight 2. If this parameter is omitted,
+ weight becomes 1. "weight" is ignored if session
+ affinity is enabled.
+
+ If "dnf" parameter is specified, an incoming request is
+ not forwarded to a backend and just consumed along with
+ the request body (actually a backend server never be
+ contacted). It is expected that the HTTP response is
+ generated by mruby script (see "mruby=<PATH>" parameter
+ above). "dnf" is an abbreviation of "do not forward".
+
+ Since ";" and ":" are used as delimiter, <PATTERN> must
+ not contain these characters. In order to include ":"
+ in <PATTERN>, one has to specify "%3A" (which is
+ percent-encoded from of ":") instead. Since ";" has
+ special meaning in shell, the option value must be
+ quoted.
+
+ Default: )"
+ << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"(
+ -f, --frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...]
+ Set frontend host and port. If <HOST> is '*', it
+ assumes all addresses including both IPv4 and IPv6.
+ UNIX domain socket can be specified by prefixing path
+ name with "unix:" (e.g., unix:/var/run/nghttpx.sock).
+ This option can be used multiple times to listen to
+ multiple addresses.
+
+ This option can take 0 or more parameters, which are
+ described below. Note that "api" and "healthmon"
+ parameters are mutually exclusive.
+
+ Optionally, TLS can be disabled by specifying "no-tls"
+ parameter. TLS is enabled by default.
+
+ If "sni-fwd" parameter is used, when performing a match
+ to select a backend server, SNI host name received from
+ the client is used instead of the request host. See
+ --backend option about the pattern match.
+
+ To make this frontend as API endpoint, specify "api"
+ parameter. This is disabled by default. It is
+ important to limit the access to the API frontend.
+ Otherwise, someone may change the backend server, and
+ break your services, or expose confidential information
+ to the outside the world.
+
+ To make this frontend as health monitor endpoint,
+ specify "healthmon" parameter. This is disabled by
+ default. Any requests which come through this address
+ are replied with 200 HTTP status, without no body.
+
+ To accept PROXY protocol version 1 and 2 on frontend
+ connection, specify "proxyproto" parameter. This is
+ disabled by default.
+
+ To receive HTTP/3 (QUIC) traffic, specify "quic"
+ parameter. It makes nghttpx listen on UDP port rather
+ than TCP port. UNIX domain socket, "api", and
+ "healthmon" parameters cannot be used with "quic"
+ parameter.
+
+ Default: *,3000
+ --backlog=<N>
+ Set listen backlog size.
+ Default: )"
+ << config->conn.listener.backlog << R"(
+ --backend-address-family=(auto|IPv4|IPv6)
+ Specify address family of backend connections. If
+ "auto" is given, both IPv4 and IPv6 are considered. If
+ "IPv4" is given, only IPv4 address is considered. If
+ "IPv6" is given, only IPv6 address is considered.
+ Default: auto
+ --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: )"
+ << config->num_worker << R"(
+ --single-thread
+ Run everything in one thread inside the worker process.
+ This feature is provided for better debugging
+ experience, or for the platforms which lack thread
+ support. If threading is disabled, this option is
+ always enabled.
+ --read-rate=<SIZE>
+ Set maximum average read rate on frontend connection.
+ Setting 0 to this option means read rate is unlimited.
+ Default: )"
+ << config->conn.upstream.ratelimit.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: )"
+ << config->conn.upstream.ratelimit.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: )"
+ << config->conn.upstream.ratelimit.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: )"
+ << config->conn.upstream.ratelimit.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: 0
+ --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
+ --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
+ --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
+ --worker-frontend-connections=<N>
+ Set maximum number of simultaneous connections frontend
+ accepts. Setting 0 means unlimited.
+ Default: )"
+ << config->conn.upstream.worker_connections << R"(
+ --backend-connections-per-host=<N>
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per origin host.
+ This option is meaningful when --http2-proxy option is
+ used. The origin host is determined by authority
+ portion of request URI (or :authority header field for
+ HTTP/2). To limit the number of connections per
+ frontend for default mode, use
+ --backend-connections-per-frontend.
+ Default: )"
+ << config->conn.downstream->connections_per_host << R"(
+ --backend-connections-per-frontend=<N>
+ Set maximum number of backend concurrent connections
+ (and/or streams in case of HTTP/2) per frontend. This
+ option is only used for default mode. 0 means
+ unlimited. To limit the number of connections per host
+ with --http2-proxy option, use
+ --backend-connections-per-host.
+ Default: )"
+ << config->conn.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: )"
+ << config->rlimit_nofile << R"(
+ --rlimit-memlock=<N>
+ Set maximum number of bytes of memory that may be locked
+ into RAM. If 0 is given, nghttpx does not set the
+ limit.
+ Default: )"
+ << config->rlimit_memlock << R"(
+ --backend-request-buffer=<SIZE>
+ Set buffer size used to store backend request.
+ Default: )"
+ << util::utos_unit(config->conn.downstream->request_buffer_size) << R"(
+ --backend-response-buffer=<SIZE>
+ Set buffer size used to store backend response.
+ Default: )"
+ << util::utos_unit(config->conn.downstream->response_buffer_size) << R"(
+ --fastopen=<N>
+ Enables "TCP Fast Open" for the listening socket and
+ limits the maximum length for the queue of connections
+ that have not yet completed the three-way handshake. If
+ value is 0 then fast open is disabled.
+ Default: )"
+ << config->conn.listener.fastopen << R"(
+ --no-kqueue Don't use kqueue. This option is only applicable for
+ the platforms which have kqueue. For other platforms,
+ this option will be simply ignored.
+
+Timeout:
+ --frontend-http2-read-timeout=<DURATION>
+ Specify read timeout for HTTP/2 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.http2_read) << R"(
+ --frontend-http3-read-timeout=<DURATION>
+ Specify read timeout for HTTP/3 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.http3_read) << R"(
+ --frontend-read-timeout=<DURATION>
+ Specify read timeout for HTTP/1.1 frontend connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.read) << R"(
+ --frontend-write-timeout=<DURATION>
+ Specify write timeout for all frontend connections.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.write) << R"(
+ --frontend-keep-alive-timeout=<DURATION>
+ Specify keep-alive timeout for frontend HTTP/1
+ connection.
+ Default: )"
+ << util::duration_str(config->conn.upstream.timeout.idle_read) << R"(
+ --stream-read-timeout=<DURATION>
+ Specify read timeout for HTTP/2 streams. 0 means no
+ timeout.
+ Default: )"
+ << util::duration_str(config->http2.timeout.stream_read) << R"(
+ --stream-write-timeout=<DURATION>
+ Specify write timeout for HTTP/2 streams. 0 means no
+ timeout.
+ Default: )"
+ << util::duration_str(config->http2.timeout.stream_write) << R"(
+ --backend-read-timeout=<DURATION>
+ Specify read timeout for backend connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.read) << R"(
+ --backend-write-timeout=<DURATION>
+ Specify write timeout for backend connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.write) << R"(
+ --backend-connect-timeout=<DURATION>
+ Specify timeout before establishing TCP connection to
+ backend.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.connect) << R"(
+ --backend-keep-alive-timeout=<DURATION>
+ Specify keep-alive timeout for backend HTTP/1
+ connection.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.idle_read) << 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(config->conn.listener.timeout.sleep) << R"(
+ --frontend-http2-setting-timeout=<DURATION>
+ Specify timeout before SETTINGS ACK is received from
+ client.
+ Default: )"
+ << util::duration_str(config->http2.upstream.timeout.settings) << R"(
+ --backend-http2-settings-timeout=<DURATION>
+ Specify timeout before SETTINGS ACK is received from
+ backend server.
+ Default: )"
+ << util::duration_str(config->http2.downstream.timeout.settings) << R"(
+ --backend-max-backoff=<DURATION>
+ Specify maximum backoff interval. This is used when
+ doing health check against offline backend (see "fail"
+ parameter in --backend option). It is also used to
+ limit the maximum interval to temporarily disable
+ backend when nghttpx failed to connect to it. These
+ intervals are calculated using exponential backoff, and
+ consecutive failed attempts increase the interval. This
+ option caps its maximum value.
+ Default: )"
+ << util::duration_str(config->conn.downstream->timeout.max_backoff) << R"(
+
+SSL/TLS:
+ --ciphers=<SUITE>
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use --tls13-ciphers for TLSv1.3.
+ Default: )"
+ << config->tls.ciphers << R"(
+ --tls13-ciphers=<SUITE>
+ Set allowed cipher list for frontend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ --ciphers for TLSv1.2 or earlier.
+ Default: )"
+ << config->tls.tls13_ciphers << R"(
+ --client-ciphers=<SUITE>
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.2 or earlier.
+ Use --tls13-client-ciphers for TLSv1.3.
+ Default: )"
+ << config->tls.client.ciphers << R"(
+ --tls13-client-ciphers=<SUITE>
+ Set allowed cipher list for backend connection. The
+ format of the string is described in OpenSSL ciphers(1).
+ This option sets cipher suites for TLSv1.3. Use
+ --tls13-client-ciphers for TLSv1.2 or earlier.
+ Default: )"
+ << config->tls.client.tls13_ciphers << R"(
+ --ecdh-curves=<LIST>
+ Set supported curve list for frontend connections.
+ <LIST> is a colon separated list of curve NID or names
+ in the preference order. The supported curves depend on
+ the linked OpenSSL library. This function requires
+ OpenSSL >= 1.0.2.
+ Default: )"
+ << config->tls.ecdh_curves << R"(
+ -k, --insecure
+ Don't verify backend server's certificate if TLS is
+ enabled for backend connections.
+ --cacert=<PATH>
+ Set path to trusted CA certificate file. It is used in
+ backend TLS connections to verify peer's certificate.
+ It is also used to verify OCSP response from the script
+ set by --fetch-ocsp-response-file. 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>[[;<PARAM>]...]
+ Specify additional certificate and private key file.
+ nghttpx will choose certificates based on the hostname
+ indicated by client using TLS SNI extension. If nghttpx
+ is built with OpenSSL >= 1.0.2, the shared elliptic
+ curves (e.g., P-256) between client and server are also
+ taken into consideration. This allows nghttpx to send
+ ECDSA certificate to modern clients, while sending RSA
+ based certificate to older clients. This option can be
+ used multiple times. To make OCSP stapling work,
+ <CERTPATH> must be absolute path.
+
+ Additional parameter can be specified in <PARAM>. The
+ available <PARAM> is "sct-dir=<DIR>".
+
+ "sct-dir=<DIR>" specifies the path to directory which
+ contains *.sct files for TLS
+ signed_certificate_timestamp extension (RFC 6962). This
+ feature requires OpenSSL >= 1.0.2. See also
+ --tls-sct-dir option.
+ --dh-param-file=<PATH>
+ Path to file that contains DH parameters in PEM format.
+ Without this option, DHE cipher suites are not
+ available.
+ --alpn-list=<LIST>
+ Comma delimited list of ALPN protocol identifier sorted
+ in the order of preference. That means most desirable
+ protocol comes first. The parameter must be delimited
+ by a single comma only and any white spaces are treated
+ as a part of protocol string.
+ Default: )"
+ << DEFAULT_ALPN_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.
+ --verify-client-tolerate-expired
+ Accept expired client certificate. Operator should
+ handle the expired client certificate by some means
+ (e.g., mruby script). Otherwise, this option might
+ cause a security risk.
+ --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-min-proto-version=<VER>
+ Specify minimum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ --tls-min-proto-version and --tls-max-proto-version are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". If a protocol version lower
+ than TLSv1.2 is specified, make sure that the compatible
+ ciphers are included in --ciphers option. The default
+ cipher list only includes ciphers compatible with
+ TLSv1.2 or above. The available versions are:
+ )"
+#ifdef TLS1_3_VERSION
+ "TLSv1.3, "
+#endif // TLS1_3_VERSION
+ "TLSv1.2, TLSv1.1, and TLSv1.0"
+ R"(
+ Default: )"
+ << DEFAULT_TLS_MIN_PROTO_VERSION
+ << R"(
+ --tls-max-proto-version=<VER>
+ Specify maximum SSL/TLS protocol. The name matching is
+ done in case-insensitive manner. The versions between
+ --tls-min-proto-version and --tls-max-proto-version are
+ enabled. If the protocol list advertised by client does
+ not overlap this range, you will receive the error
+ message "unknown protocol". The available versions are:
+ )"
+#ifdef TLS1_3_VERSION
+ "TLSv1.3, "
+#endif // TLS1_3_VERSION
+ "TLSv1.2, TLSv1.1, and TLSv1.0"
+ R"(
+ Default: )"
+ << DEFAULT_TLS_MAX_PROTO_VERSION << R"(
+ --tls-ticket-key-file=<PATH>
+ Path to file that contains random data to construct TLS
+ session ticket parameters. If aes-128-cbc is given in
+ --tls-ticket-key-cipher, the file must contain exactly
+ 48 bytes. If aes-256-cbc is given in
+ --tls-ticket-key-cipher, the file must contain exactly
+ 80 bytes. 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 every 1 hour internally and
+ they are valid for 12 hours. This is recommended if
+ ticket key sharing between nghttpx instances is not
+ required.
+ --tls-ticket-key-memcached=<HOST>,<PORT>[;tls]
+ Specify address of memcached server to get TLS ticket
+ keys for session resumption. This enables shared TLS
+ ticket key between multiple nghttpx instances. nghttpx
+ does not set TLS ticket key to memcached. The external
+ ticket key generator is required. nghttpx just gets TLS
+ ticket keys from memcached, and use them, possibly
+ replacing current set of keys. It is up to extern TLS
+ ticket key generator to rotate keys frequently. See
+ "TLS SESSION TICKET RESUMPTION" section in manual page
+ to know the data format in memcached entry. Optionally,
+ memcached connection can be encrypted with TLS by
+ specifying "tls" parameter.
+ --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6)
+ Specify address family of memcached connections to get
+ TLS ticket keys. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+ Default: auto
+ --tls-ticket-key-memcached-interval=<DURATION>
+ Set interval to get TLS ticket keys from memcached.
+ Default: )"
+ << util::duration_str(config->tls.ticket.memcached.interval) << R"(
+ --tls-ticket-key-memcached-max-retry=<N>
+ Set maximum number of consecutive retries before
+ abandoning TLS ticket key retrieval. If this number is
+ reached, the attempt is considered as failure, and
+ "failure" count is incremented by 1, which contributed
+ to the value controlled
+ --tls-ticket-key-memcached-max-fail option.
+ Default: )"
+ << config->tls.ticket.memcached.max_retry << R"(
+ --tls-ticket-key-memcached-max-fail=<N>
+ Set maximum number of consecutive failure before
+ disabling TLS ticket until next scheduled key retrieval.
+ Default: )"
+ << config->tls.ticket.memcached.max_fail << R"(
+ --tls-ticket-key-cipher=<CIPHER>
+ Specify cipher to encrypt TLS session ticket. Specify
+ either aes-128-cbc or aes-256-cbc. By default,
+ aes-128-cbc is used.
+ --tls-ticket-key-memcached-cert-file=<PATH>
+ Path to client certificate for memcached connections to
+ get TLS ticket keys.
+ --tls-ticket-key-memcached-private-key-file=<PATH>
+ Path to client private key for memcached connections to
+ get TLS ticket keys.
+ --fetch-ocsp-response-file=<PATH>
+ Path to fetch-ocsp-response script file. It should be
+ absolute path.
+ Default: )"
+ << config->tls.ocsp.fetch_ocsp_response_file << R"(
+ --ocsp-update-interval=<DURATION>
+ Set interval to update OCSP response cache.
+ Default: )"
+ << util::duration_str(config->tls.ocsp.update_interval) << R"(
+ --ocsp-startup
+ Start accepting connections after initial attempts to
+ get OCSP responses finish. It does not matter some of
+ the attempts fail. This feature is useful if OCSP
+ responses must be available before accepting
+ connections.
+ --no-verify-ocsp
+ nghttpx does not verify OCSP response.
+ --no-ocsp Disable OCSP stapling.
+ --tls-session-cache-memcached=<HOST>,<PORT>[;tls]
+ Specify address of memcached server to store session
+ cache. This enables shared session cache between
+ multiple nghttpx instances. Optionally, memcached
+ connection can be encrypted with TLS by specifying "tls"
+ parameter.
+ --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6)
+ Specify address family of memcached connections to store
+ session cache. If "auto" is given, both IPv4 and IPv6
+ are considered. If "IPv4" is given, only IPv4 address
+ is considered. If "IPv6" is given, only IPv6 address is
+ considered.
+ Default: auto
+ --tls-session-cache-memcached-cert-file=<PATH>
+ Path to client certificate for memcached connections to
+ store session cache.
+ --tls-session-cache-memcached-private-key-file=<PATH>
+ Path to client private key for memcached connections to
+ store session cache.
+ --tls-dyn-rec-warmup-threshold=<SIZE>
+ Specify the threshold size for TLS dynamic record size
+ behaviour. During a TLS session, after the threshold
+ number of bytes have been written, the TLS record size
+ will be increased to the maximum allowed (16K). The max
+ record size will continue to be used on the active TLS
+ session. After --tls-dyn-rec-idle-timeout has elapsed,
+ the record size is reduced to 1300 bytes. Specify 0 to
+ always use the maximum record size, regardless of idle
+ period. This behaviour applies to all TLS based
+ frontends, and TLS HTTP/2 backends.
+ Default: )"
+ << util::utos_unit(config->tls.dyn_rec.warmup_threshold) << R"(
+ --tls-dyn-rec-idle-timeout=<DURATION>
+ Specify TLS dynamic record size behaviour timeout. See
+ --tls-dyn-rec-warmup-threshold for more information.
+ This behaviour applies to all TLS based frontends, and
+ TLS HTTP/2 backends.
+ Default: )"
+ << util::duration_str(config->tls.dyn_rec.idle_timeout) << R"(
+ --no-http2-cipher-block-list
+ Allow block listed cipher suite on frontend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+ --client-no-http2-cipher-block-list
+ Allow block listed cipher suite on backend HTTP/2
+ connection. See
+ https://tools.ietf.org/html/rfc7540#appendix-A for the
+ complete HTTP/2 cipher suites block list.
+ --tls-sct-dir=<DIR>
+ Specifies the directory where *.sct files exist. All
+ *.sct files in <DIR> are read, and sent as
+ extension_data of TLS signed_certificate_timestamp (RFC
+ 6962) to client. These *.sct files are for the
+ certificate specified in positional command-line
+ argument <CERT>, or certificate option in configuration
+ file. For additional certificates, use --subcert
+ option. This option requires OpenSSL >= 1.0.2.
+ --psk-secrets=<PATH>
+ Read list of PSK identity and secrets from <PATH>. This
+ is used for frontend connection. The each line of input
+ file is formatted as <identity>:<hex-secret>, where
+ <identity> is PSK identity, and <hex-secret> is secret
+ in hex. An empty line, and line which starts with '#'
+ are skipped. The default enabled cipher list might not
+ contain any PSK cipher suite. In that case, desired PSK
+ cipher suites must be enabled using --ciphers option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use --no-http2-cipher-block-list option.
+ But be aware its implications.
+ --client-psk-secrets=<PATH>
+ Read PSK identity and secrets from <PATH>. This is used
+ for backend connection. The each line of input file is
+ formatted as <identity>:<hex-secret>, where <identity>
+ is PSK identity, and <hex-secret> is secret in hex. An
+ empty line, and line which starts with '#' are skipped.
+ The first identity and secret pair encountered is used.
+ The default enabled cipher list might not contain any
+ PSK cipher suite. In that case, desired PSK cipher
+ suites must be enabled using --client-ciphers option.
+ The desired PSK cipher suite may be block listed by
+ HTTP/2. To use those cipher suites with HTTP/2,
+ consider to use --client-no-http2-cipher-block-list
+ option. But be aware its implications.
+ --tls-no-postpone-early-data
+ By default, except for QUIC connections, nghttpx
+ postpones forwarding HTTP requests sent in early data,
+ including those sent in partially in it, until TLS
+ handshake finishes. If all backend server recognizes
+ "Early-Data" header field, using this option makes
+ nghttpx not postpone forwarding request and get full
+ potential of 0-RTT data.
+ --tls-max-early-data=<SIZE>
+ Sets the maximum amount of 0-RTT data that server
+ accepts.
+ Default: )"
+ << util::utos_unit(config->tls.max_early_data) << R"(
+ --tls-ktls Enable ktls. For server, ktls is enable if
+ --tls-session-cache-memcached is not configured.
+
+HTTP/2:
+ -c, --frontend-http2-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/2 session.
+ Default: )"
+ << config->http2.upstream.max_concurrent_streams << R"(
+ --backend-http2-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ backend HTTP/2 session. This sets maximum number of
+ concurrent opened pushed streams. The maximum number of
+ concurrent requests are set by a remote server.
+ Default: )"
+ << config->http2.downstream.max_concurrent_streams << R"(
+ --frontend-http2-window-size=<SIZE>
+ Sets the per-stream initial window size of HTTP/2
+ frontend connection.
+ Default: )"
+ << config->http2.upstream.window_size << R"(
+ --frontend-http2-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/2 frontend
+ connection.
+ Default: )"
+ << config->http2.upstream.connection_window_size << R"(
+ --backend-http2-window-size=<SIZE>
+ Sets the initial window size of HTTP/2 backend
+ connection.
+ Default: )"
+ << config->http2.downstream.window_size << R"(
+ --backend-http2-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/2 backend
+ connection.
+ Default: )"
+ << config->http2.downstream.connection_window_size << R"(
+ --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 supported by
+ default mode and HTTP/2 frontend via Link header field.
+ It is also supported if both frontend and backend are
+ HTTP/2 in default mode. In this case, server push from
+ backend session is relayed to frontend, and server push
+ via Link header field is also supported.
+ --frontend-http2-optimize-write-buffer-size
+ (Experimental) Enable write buffer size optimization in
+ frontend HTTP/2 TLS connection. This optimization aims
+ to reduce write buffer size so that it only contains
+ bytes which can send immediately. This makes server
+ more responsive to prioritized HTTP/2 stream because the
+ buffering of lower priority stream is reduced. This
+ option is only effective on recent Linux platform.
+ --frontend-http2-optimize-window-size
+ (Experimental) Automatically tune connection level
+ window size of frontend HTTP/2 TLS connection. If this
+ feature is enabled, connection window size starts with
+ the default window size, 65535 bytes. nghttpx
+ automatically adjusts connection window size based on
+ TCP receiving window size. The maximum window size is
+ capped by the value specified by
+ --frontend-http2-connection-window-size. Since the
+ stream is subject to stream level window size, it should
+ be adjusted using --frontend-http2-window-size option as
+ well. This option is only effective on recent Linux
+ platform.
+ --frontend-http2-encoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK encoder
+ in the frontend HTTP/2 connection. The decoder (client)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which client specified.
+ Default: )"
+ << util::utos_unit(config->http2.upstream.encoder_dynamic_table_size)
+ << R"(
+ --frontend-http2-decoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK decoder
+ in the frontend HTTP/2 connection.
+ Default: )"
+ << util::utos_unit(config->http2.upstream.decoder_dynamic_table_size)
+ << R"(
+ --backend-http2-encoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK encoder
+ in the backend HTTP/2 connection. The decoder (backend)
+ specifies the maximum dynamic table size it accepts.
+ Then the negotiated dynamic table size is the minimum of
+ this option value and the value which backend specified.
+ Default: )"
+ << util::utos_unit(config->http2.downstream.encoder_dynamic_table_size)
+ << R"(
+ --backend-http2-decoder-dynamic-table-size=<SIZE>
+ Specify the maximum dynamic table size of HPACK decoder
+ in the backend HTTP/2 connection.
+ Default: )"
+ << util::utos_unit(config->http2.downstream.decoder_dynamic_table_size)
+ << R"(
+
+Mode:
+ (default mode)
+ Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls"
+ parameter is used in --frontend option, accept HTTP/2
+ and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1
+ connection can be upgraded to HTTP/2 through HTTP
+ Upgrade.
+ -s, --http2-proxy
+ Like default mode, but enable forward proxy. This is so
+ called HTTP/2 proxy mode.
+
+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.
+ * $tls_cipher: cipher used for SSL/TLS connection.
+ * $tls_client_fingerprint_sha256: SHA-256 fingerprint of
+ client certificate.
+ * $tls_client_fingerprint_sha1: SHA-1 fingerprint of
+ client certificate.
+ * $tls_client_subject_name: subject name in client
+ certificate.
+ * $tls_client_issuer_name: issuer name in client
+ certificate.
+ * $tls_client_serial: serial number in client
+ certificate.
+ * $tls_protocol: protocol for SSL/TLS connection.
+ * $tls_session_id: session ID for SSL/TLS connection.
+ * $tls_session_reused: "r" if SSL/TLS session was
+ reused. Otherwise, "."
+ * $tls_sni: SNI server name for SSL/TLS connection.
+ * $backend_host: backend host used to fulfill the
+ request. "-" if backend host is not available.
+ * $backend_port: backend port used to fulfill the
+ request. "-" if backend host is not available.
+ * $method: HTTP method
+ * $path: Request path including query. For CONNECT
+ request, authority is recorded.
+ * $path_without_query: $path up to the first '?'
+ character. For CONNECT request, authority is
+ recorded.
+ * $protocol_version: HTTP version (e.g., HTTP/1.1,
+ HTTP/2)
+
+ The variable can be enclosed by "{" and "}" for
+ disambiguation (e.g., ${remote_addr}).
+
+ Default: )"
+ << DEFAULT_ACCESSLOG_FORMAT << R"(
+ --accesslog-write-early
+ Write access log when response header fields are
+ received from backend rather than when request
+ transaction finishes.
+ --errorlog-file=<PATH>
+ Set path to write error log. To reopen file, send USR1
+ signal to nghttpx. stderr will be redirected to the
+ error log file unless --errorlog-syslog is used.
+ Default: )"
+ << config->logging.error.file << 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(config->logging.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-add-x-forwarded-proto
+ Don't append additional X-Forwarded-Proto header field
+ to the backend request. If inbound client sets
+ X-Forwarded-Proto, and
+ --no-strip-incoming-x-forwarded-proto option is used,
+ they are passed to the backend.
+ --no-strip-incoming-x-forwarded-proto
+ Don't strip X-Forwarded-Proto header field from inbound
+ client requests.
+ --add-forwarded=<LIST>
+ Append RFC 7239 Forwarded header field with parameters
+ specified in comma delimited list <LIST>. The supported
+ parameters are "by", "for", "host", and "proto". By
+ default, the value of "by" and "for" parameters are
+ obfuscated string. See --forwarded-by and
+ --forwarded-for options respectively. Note that nghttpx
+ does not translate non-standard X-Forwarded-* header
+ fields into Forwarded header field, and vice versa.
+ --strip-incoming-forwarded
+ Strip Forwarded header field from inbound client
+ requests.
+ --forwarded-by=(obfuscated|ip|<VALUE>)
+ Specify the parameter value sent out with "by" parameter
+ of Forwarded header field. If "obfuscated" is given,
+ the string is randomly generated at startup. If "ip" is
+ given, the interface address of the connection,
+ including port number, is sent with "by" parameter. In
+ case of UNIX domain socket, "localhost" is used instead
+ of address and port. User can also specify the static
+ obfuscated string. The limitation is that it must start
+ with "_", and only consists of character set
+ [A-Za-z0-9._-], as described in RFC 7239.
+ Default: obfuscated
+ --forwarded-for=(obfuscated|ip)
+ Specify the parameter value sent out with "for"
+ parameter of Forwarded header field. If "obfuscated" is
+ given, the string is randomly generated for each client
+ connection. If "ip" is given, the remote client address
+ of the connection, without port number, is sent with
+ "for" parameter. In case of UNIX domain socket,
+ "localhost" is used instead of address.
+ Default: obfuscated
+ --no-via Don't append to Via header field. If Via header field
+ is received, it is left unaltered.
+ --no-strip-incoming-early-data
+ Don't strip Early-Data header field from inbound client
+ requests.
+ --no-location-rewrite
+ Don't rewrite location header field in default mode.
+ When --http2-proxy is used, location header field will
+ not be altered regardless of this option.
+ --host-rewrite
+ Rewrite host and :authority header fields in default
+ mode. When --http2-proxy is used, these headers will
+ not be altered regardless of this option.
+ --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+ Specify protocol ID, port, host and origin of
+ alternative service. <HOST>, <ORIGIN> and <PARAMS> are
+ optional. Empty <HOST> and <ORIGIN> are allowed and
+ they are treated as nothing is specified. They are
+ advertised in alt-svc header field only in HTTP/1.1
+ frontend. This option can be used multiple times to
+ specify multiple alternative services.
+ Example: --altsvc="h2,443,,,ma=3600; persist=1"
+ --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
+ Just like --altsvc option, but this altsvc is only sent
+ in HTTP/2 frontend.
+ --add-request-header=<HEADER>
+ Specify additional header field to add to request header
+ set. The field name must be lowercase. 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-request-header="foo: bar"
+ --add-response-header=<HEADER>
+ Specify additional header field to add to response
+ header set. The field name must be lowercase. 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"
+ --request-header-field-buffer=<SIZE>
+ Set maximum buffer size for incoming HTTP request header
+ field list. This is the sum of header name and value in
+ bytes. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << util::utos_unit(config->http.request_header_field_buffer) << R"(
+ --max-request-header-fields=<N>
+ Set maximum number of incoming HTTP request header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << config->http.max_request_header_fields << R"(
+ --response-header-field-buffer=<SIZE>
+ Set maximum buffer size for incoming HTTP response
+ header field list. This is the sum of header name and
+ value in bytes. If trailer fields exist, they are
+ counted towards this number.
+ Default: )"
+ << util::utos_unit(config->http.response_header_field_buffer) << R"(
+ --max-response-header-fields=<N>
+ Set maximum number of incoming HTTP response header
+ fields. If trailer fields exist, they are counted
+ towards this number.
+ Default: )"
+ << config->http.max_response_header_fields << R"(
+ --error-page=(<CODE>|*)=<PATH>
+ Set file path to custom error page served when nghttpx
+ originally generates HTTP error status code <CODE>.
+ <CODE> must be greater than or equal to 400, and at most
+ 599. If "*" is used instead of <CODE>, it matches all
+ HTTP status code. If error status code comes from
+ backend server, the custom error pages are not used.
+ --server-name=<NAME>
+ Change server response header field value to <NAME>.
+ Default: )"
+ << config->http.server_name << R"(
+ --no-server-rewrite
+ Don't rewrite server header field in default mode. When
+ --http2-proxy is used, these headers will not be altered
+ regardless of this option.
+ --redirect-https-port=<PORT>
+ Specify the port number which appears in Location header
+ field when redirect to HTTPS URI is made due to
+ "redirect-if-not-tls" parameter in --backend option.
+ Default: )"
+ << config->http.redirect_https_port << R"(
+ --require-http-scheme
+ Always require http or https scheme in HTTP request. It
+ also requires that https scheme must be used for an
+ encrypted connection. Otherwise, http scheme must be
+ used. This option is recommended for a server
+ deployment which directly faces clients and the services
+ it provides only require http or https scheme.
+
+API:
+ --api-max-request-body=<SIZE>
+ Set the maximum size of request body for API request.
+ Default: )"
+ << util::utos_unit(config->api.max_request_body) << R"(
+
+DNS:
+ --dns-cache-timeout=<DURATION>
+ Set duration that cached DNS results remain valid. Note
+ that nghttpx caches the unsuccessful results as well.
+ Default: )"
+ << util::duration_str(config->dns.timeout.cache) << R"(
+ --dns-lookup-timeout=<DURATION>
+ Set timeout that DNS server is given to respond to the
+ initial DNS query. For the 2nd and later queries,
+ server is given time based on this timeout, and it is
+ scaled linearly.
+ Default: )"
+ << util::duration_str(config->dns.timeout.lookup) << R"(
+ --dns-max-try=<N>
+ Set the number of DNS query before nghttpx gives up name
+ lookup.
+ Default: )"
+ << config->dns.max_try << R"(
+ --frontend-max-requests=<N>
+ The number of requests that single frontend connection
+ can process. For HTTP/2, this is the number of streams
+ in one HTTP/2 connection. For HTTP/1, this is the
+ number of keep alive requests. This is hint to nghttpx,
+ and it may allow additional few requests. The default
+ value is unlimited.
+
+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.
+ --single-process
+ Run this program in a single process mode for debugging
+ purpose. Without this option, nghttpx creates at least
+ 2 processes: main and worker processes. If this option
+ is used, main and worker are unified into a single
+ process. nghttpx still spawns additional process if
+ neverbleed is used. In the single process mode, the
+ signal handling feature is disabled.
+ --max-worker-processes=<N>
+ The maximum number of worker processes. nghttpx spawns
+ new worker process when it reloads its configuration.
+ The previous worker process enters graceful termination
+ period and will terminate when it finishes handling the
+ existing connections. However, if reloading
+ configurations happen very frequently, the worker
+ processes might be piled up if they take a bit long time
+ to finish the existing connections. With this option,
+ if the number of worker processes exceeds the given
+ value, the oldest worker process is terminated
+ immediately. Specifying 0 means no limit and it is the
+ default behaviour.
+ --worker-process-grace-shutdown-period=<DURATION>
+ Maximum period for a worker process to terminate
+ gracefully. When a worker process enters in graceful
+ shutdown period (e.g., when nghttpx reloads its
+ configuration) and it does not finish handling the
+ existing connections in the given period of time, it is
+ immediately terminated. Specifying 0 means no limit and
+ it is the default behaviour.
+
+Scripting:
+ --mruby-file=<PATH>
+ Set mruby script file
+ --ignore-per-pattern-mruby-error
+ Ignore mruby compile error for per-pattern mruby script
+ file. If error occurred, it is treated as if no mruby
+ file were specified for the pattern.
+)";
+
+#ifdef ENABLE_HTTP3
+ out << R"(
+HTTP/3 and QUIC:
+ --frontend-quic-idle-timeout=<DURATION>
+ Specify an idle timeout for QUIC connection.
+ Default: )"
+ << util::duration_str(config->quic.upstream.timeout.idle) << R"(
+ --frontend-quic-debug-log
+ Output QUIC debug log to /dev/stderr.
+ --quic-bpf-program-file=<PATH>
+ Specify a path to eBPF program file reuseport_kern.o to
+ direct an incoming QUIC UDP datagram to a correct
+ socket.
+ Default: )"
+ << config->quic.bpf.prog_file << R"(
+ --frontend-quic-early-data
+ Enable early data on frontend QUIC connections. nghttpx
+ sends "Early-Data" header field to a backend server if a
+ request is received in early data and handshake has not
+ finished. All backend servers should deal with possibly
+ replayed requests.
+ --frontend-quic-qlog-dir=<DIR>
+ Specify a directory where a qlog file is written for
+ frontend QUIC connections. A qlog file is created per
+ each QUIC connection. The file name is ISO8601 basic
+ format, followed by "-", server Source Connection ID and
+ ".sqlog".
+ --frontend-quic-require-token
+ Require an address validation token for a frontend QUIC
+ connection. Server sends a token in Retry packet or
+ NEW_TOKEN frame in the previous connection.
+ --frontend-quic-congestion-controller=<CC>
+ Specify a congestion controller algorithm for a frontend
+ QUIC connection. <CC> should be either "cubic" or
+ "bbr".
+ Default: )"
+ << (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC
+ ? "cubic"
+ : "bbr")
+ << R"(
+ --frontend-quic-secret-file=<PATH>
+ Path to file that contains secure random data to be used
+ as QUIC keying materials. It is used to derive keys for
+ encrypting tokens and Connection IDs. It is not used to
+ encrypt QUIC packets. Each line of this file must
+ contain exactly 136 bytes hex-encoded string (when
+ decoded the byte string is 68 bytes long). The first 2
+ bits of decoded byte string are used to identify the
+ keying material. An empty line or a line which starts
+ '#' is ignored. The file can contain more than one
+ keying materials. Because the identifier is 2 bits, at
+ most 4 keying materials are read and the remaining data
+ is discarded. The first keying material in the file is
+ primarily used for encryption and decryption for new
+ connection. The other ones are used to decrypt data for
+ the existing connections. Specifying multiple keying
+ materials enables key rotation. Please note that key
+ rotation does not occur automatically. User should
+ update files or change options values and restart
+ nghttpx gracefully. If opening or reading given file
+ fails, all loaded keying materials 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, a keying material is generated
+ internally on startup and reload.
+ --quic-server-id=<HEXSTRING>
+ Specify server ID encoded in Connection ID to identify
+ this particular server instance. Connection ID is
+ encrypted and this part is not visible in public. It
+ must be 4 bytes long and must be encoded in hex string
+ (which is 8 bytes long). If this option is omitted, a
+ random server ID is generated on startup and
+ configuration reload.
+ --frontend-quic-initial-rtt=<DURATION>
+ Specify the initial RTT of the frontend QUIC connection.
+ Default: )"
+ << util::duration_str(config->quic.upstream.initial_rtt) << R"(
+ --no-quic-bpf
+ Disable eBPF.
+ --frontend-http3-window-size=<SIZE>
+ Sets the per-stream initial window size of HTTP/3
+ frontend connection.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.window_size) << R"(
+ --frontend-http3-connection-window-size=<SIZE>
+ Sets the per-connection window size of HTTP/3 frontend
+ connection.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.connection_window_size) << R"(
+ --frontend-http3-max-window-size=<SIZE>
+ Sets the maximum per-stream window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by --frontend-http3-window-size
+ and the window size grows up to <SIZE> bytes.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.max_window_size) << R"(
+ --frontend-http3-max-connection-window-size=<SIZE>
+ Sets the maximum per-connection window size of HTTP/3
+ frontend connection. The window size is adjusted based
+ on the receiving rate of stream data. The initial value
+ is the value specified by
+ --frontend-http3-connection-window-size and the window
+ size grows up to <SIZE> bytes.
+ Default: )"
+ << util::utos_unit(config->http3.upstream.max_connection_window_size)
+ << R"(
+ --frontend-http3-max-concurrent-streams=<N>
+ Set the maximum number of the concurrent streams in one
+ frontend HTTP/3 connection.
+ Default: )"
+ << config->http3.upstream.max_concurrent_streams << R"(
+)";
+#endif // ENABLE_HTTP3
+
+ out << R"(
+Misc:
+ --conf=<PATH>
+ Load configuration from <PATH>. Please note that
+ nghttpx always tries to read the default configuration
+ file if --conf is not given.
+ Default: )"
+ << config->conf_path << R"(
+ --include=<PATH>
+ Load additional configurations from <PATH>. File <PATH>
+ is read when configuration parser encountered this
+ option. This option can be used multiple times, or even
+ recursively.
+ -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 h, m, s or ms
+ (hours, minutes, seconds and milliseconds, respectively). If a unit
+ is omitted, a second is used as unit.)"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+int process_options(Config *config,
+ std::vector<std::pair<StringRef, StringRef>> &cmdcfgs) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ std::map<StringRef, size_t> pattern_addr_indexer;
+ if (conf_exists(config->conf_path.c_str())) {
+ LOG(NOTICE) << "Loading configuration from " << config->conf_path;
+ std::set<StringRef> include_set;
+ if (load_config(config, config->conf_path.c_str(), include_set,
+ pattern_addr_indexer) == -1) {
+ LOG(FATAL) << "Failed to load configuration from " << config->conf_path;
+ return -1;
+ }
+ assert(include_set.empty());
+ }
+
+ // Reopen log files using configurations in file
+ reopen_log_files(config->logging);
+
+ {
+ std::set<StringRef> include_set;
+
+ for (auto &p : cmdcfgs) {
+ if (parse_config(config, p.first, p.second, include_set,
+ pattern_addr_indexer) == -1) {
+ LOG(FATAL) << "Failed to parse command-line argument.";
+ return -1;
+ }
+ }
+
+ assert(include_set.empty());
+ }
+
+ Log::set_severity_level(config->logging.severity);
+
+ auto &loggingconf = config->logging;
+
+ if (loggingconf.access.syslog || loggingconf.error.syslog) {
+ openlog("nghttpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID,
+ loggingconf.syslog_facility);
+ }
+
+ if (reopen_log_files(config->logging) != 0) {
+ LOG(FATAL) << "Failed to open log file";
+ return -1;
+ }
+
+ redirect_stderr_to_errorlog(loggingconf);
+
+ if (config->uid != 0) {
+ if (log_config()->accesslog_fd != -1 &&
+ fchown(log_config()->accesslog_fd, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of access log file failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ if (log_config()->errorlog_fd != -1 &&
+ fchown(log_config()->errorlog_fd, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of error log file failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+ if (config->single_thread) {
+ LOG(WARN) << "single-thread: Set workers to 1";
+ config->num_worker = 1;
+ }
+
+ auto &http2conf = config->http2;
+ {
+ auto &dumpconf = http2conf.upstream.debug.dump;
+
+ if (!dumpconf.request_header_file.empty()) {
+ auto path = dumpconf.request_header_file.c_str();
+ auto f = open_file_for_write(path);
+
+ if (f == nullptr) {
+ LOG(FATAL) << "Failed to open http2 upstream request header file: "
+ << path;
+ return -1;
+ }
+
+ dumpconf.request_header = f;
+
+ if (config->uid != 0) {
+ if (chown(path, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of http2 upstream request header file "
+ << path << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ }
+
+ if (!dumpconf.response_header_file.empty()) {
+ auto path = dumpconf.response_header_file.c_str();
+ auto f = open_file_for_write(path);
+
+ if (f == nullptr) {
+ LOG(FATAL) << "Failed to open http2 upstream response header file: "
+ << path;
+ return -1;
+ }
+
+ dumpconf.response_header = f;
+
+ if (config->uid != 0) {
+ if (chown(path, config->uid, config->gid) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Changing owner of http2 upstream response header file"
+ << " " << path << " failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+ }
+ }
+
+ auto &tlsconf = config->tls;
+
+ if (tlsconf.alpn_list.empty()) {
+ tlsconf.alpn_list = util::split_str(DEFAULT_ALPN_LIST, ',');
+ }
+
+ if (!tlsconf.tls_proto_list.empty()) {
+ tlsconf.tls_proto_mask = tls::create_tls_proto_mask(tlsconf.tls_proto_list);
+ }
+
+ // TODO We depends on the ordering of protocol version macro in
+ // OpenSSL.
+ if (tlsconf.min_proto_version > tlsconf.max_proto_version) {
+ LOG(ERROR) << "tls-max-proto-version must be equal to or larger than "
+ "tls-min-proto-version";
+ return -1;
+ }
+
+ if (tls::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.alpn_list) != 0) {
+ return -1;
+ }
+
+ tlsconf.bio_method = create_bio_method();
+
+ auto &listenerconf = config->conn.listener;
+ auto &upstreamconf = config->conn.upstream;
+
+ if (listenerconf.addrs.empty()) {
+ UpstreamAddr addr{};
+ addr.host = StringRef::from_lit("*");
+ addr.port = 3000;
+ addr.tls = true;
+ addr.family = AF_INET;
+ addr.index = 0;
+ listenerconf.addrs.push_back(addr);
+ addr.family = AF_INET6;
+ addr.index = 1;
+ listenerconf.addrs.push_back(std::move(addr));
+ }
+
+ if (upstreamconf.worker_connections == 0) {
+ upstreamconf.worker_connections = std::numeric_limits<size_t>::max();
+ }
+
+ if (tls::upstream_tls_enabled(config->conn) &&
+ (tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) {
+ LOG(FATAL) << "TLS private key and certificate files are required. "
+ "Specify them in command-line, or in configuration file "
+ "using private-key-file and certificate-file options.";
+ return -1;
+ }
+
+ if (tls::upstream_tls_enabled(config->conn) && !tlsconf.ocsp.disabled) {
+ struct stat buf;
+ if (stat(tlsconf.ocsp.fetch_ocsp_response_file.c_str(), &buf) != 0) {
+ tlsconf.ocsp.disabled = true;
+ LOG(WARN) << "--fetch-ocsp-response-file: "
+ << tlsconf.ocsp.fetch_ocsp_response_file
+ << " not found. OCSP stapling has been disabled.";
+ }
+ }
+
+ if (configure_downstream_group(config, config->http2_proxy, false, tlsconf) !=
+ 0) {
+ return -1;
+ }
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ auto &proxy = config->downstream_http_proxy;
+ if (!proxy.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{proxy.host}, proxy.port);
+ if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port,
+ AF_UNSPEC) == -1) {
+ LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Backend HTTP proxy address: " << hostport << " -> "
+ << util::to_numeric_addr(&proxy.addr);
+ }
+
+ {
+ auto &memcachedconf = tlsconf.session_cache.memcached;
+ if (!memcachedconf.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{memcachedconf.host},
+ memcachedconf.port);
+ if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
+ memcachedconf.port, memcachedconf.family) == -1) {
+ LOG(FATAL)
+ << "Resolving memcached address for TLS session cache failed: "
+ << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Memcached address for TLS session cache: " << hostport
+ << " -> " << util::to_numeric_addr(&memcachedconf.addr);
+ if (memcachedconf.tls) {
+ LOG(NOTICE) << "Connection to memcached for TLS session cache will be "
+ "encrypted by TLS";
+ }
+ }
+ }
+
+ {
+ auto &memcachedconf = tlsconf.ticket.memcached;
+ if (!memcachedconf.host.empty()) {
+ auto hostport = util::make_hostport(std::begin(hostport_buf),
+ StringRef{memcachedconf.host},
+ memcachedconf.port);
+ if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
+ memcachedconf.port, memcachedconf.family) == -1) {
+ LOG(FATAL) << "Resolving memcached address for TLS ticket key failed: "
+ << hostport;
+ return -1;
+ }
+ LOG(NOTICE) << "Memcached address for TLS ticket key: " << hostport
+ << " -> " << util::to_numeric_addr(&memcachedconf.addr);
+ if (memcachedconf.tls) {
+ LOG(NOTICE) << "Connection to memcached for TLS ticket key will be "
+ "encrypted by TLS";
+ }
+ }
+ }
+
+ if (config->rlimit_nofile) {
+ struct rlimit lim = {static_cast<rlim_t>(config->rlimit_nofile),
+ static_cast<rlim_t>(config->rlimit_nofile)};
+ if (setrlimit(RLIMIT_NOFILE, &lim) != 0) {
+ auto error = errno;
+ LOG(WARN) << "Setting rlimit-nofile failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+
+#ifdef RLIMIT_MEMLOCK
+ if (config->rlimit_memlock) {
+ struct rlimit lim = {static_cast<rlim_t>(config->rlimit_memlock),
+ static_cast<rlim_t>(config->rlimit_memlock)};
+ if (setrlimit(RLIMIT_MEMLOCK, &lim) != 0) {
+ auto error = errno;
+ LOG(WARN) << "Setting rlimit-memlock failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ }
+ }
+#endif // RLIMIT_MEMLOCK
+
+ auto &fwdconf = config->http.forwarded;
+
+ if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED &&
+ fwdconf.by_obfuscated.empty()) {
+ // 2 for '_' and terminal NULL
+ auto iov = make_byte_ref(config->balloc, SHRPX_OBFUSCATED_NODE_LENGTH + 2);
+ auto p = iov.base;
+ *p++ = '_';
+ auto gen = util::make_mt19937();
+ p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH, gen);
+ *p = '\0';
+ fwdconf.by_obfuscated = StringRef{iov.base, p};
+ }
+
+ if (config->http2.upstream.debug.frame_debug) {
+ // To make it sync to logging
+ set_output(stderr);
+ if (isatty(fileno(stdout))) {
+ set_color_output(true);
+ }
+ reset_timer();
+ }
+
+ config->http2.upstream.callbacks = create_http2_upstream_callbacks();
+ config->http2.downstream.callbacks = create_http2_downstream_callbacks();
+
+ if (!config->http.altsvcs.empty()) {
+ config->http.altsvc_header_value =
+ http::create_altsvc_header_value(config->balloc, config->http.altsvcs);
+ }
+
+ if (!config->http.http2_altsvcs.empty()) {
+ config->http.http2_altsvc_header_value = http::create_altsvc_header_value(
+ config->balloc, config->http.http2_altsvcs);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Closes file descriptor which are opened for listeners in config,
+// and are not inherited from |iaddrs|.
+void close_not_inherited_fd(Config *config,
+ const std::vector<InheritedAddr> &iaddrs) {
+ auto &listenerconf = config->conn.listener;
+
+ for (auto &addr : listenerconf.addrs) {
+ auto inherited = std::find_if(
+ std::begin(iaddrs), std::end(iaddrs),
+ [&addr](const InheritedAddr &iaddr) { return addr.fd == iaddr.fd; });
+
+ if (inherited != std::end(iaddrs)) {
+ continue;
+ }
+
+ close(addr.fd);
+ }
+}
+} // namespace
+
+namespace {
+void reload_config() {
+ int rv;
+
+ LOG(NOTICE) << "Reloading configuration";
+
+ auto cur_config = mod_config();
+ auto new_config = std::make_unique<Config>();
+
+ fill_default_config(new_config.get());
+
+ new_config->conf_path =
+ make_string_ref(new_config->balloc, cur_config->conf_path);
+ // daemon option is ignored here.
+ new_config->daemon = cur_config->daemon;
+ // loop is reused, and ev_loop_flags gets ignored
+ new_config->ev_loop_flags = cur_config->ev_loop_flags;
+ new_config->config_revision = cur_config->config_revision + 1;
+
+ rv = process_options(new_config.get(), suconfig.cmdcfgs);
+ if (rv != 0) {
+ LOG(ERROR) << "Failed to process new configuration";
+ return;
+ }
+
+ auto iaddrs = get_inherited_addr_from_config(new_config->balloc, cur_config);
+
+ if (create_acceptor_socket(new_config.get(), iaddrs) != 0) {
+ close_not_inherited_fd(new_config.get(), iaddrs);
+ return;
+ }
+
+ // According to libev documentation, flags are ignored since we have
+ // already created first default loop.
+ auto loop = ev_default_loop(new_config->ev_loop_flags);
+
+ int ipc_fd = 0;
+#ifdef ENABLE_HTTP3
+ int quic_ipc_fd = 0;
+
+ auto quic_lwps = collect_quic_lingering_worker_processes();
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+
+ if (generate_cid_prefix(cid_prefixes, new_config.get()) != 0) {
+ close_not_inherited_fd(new_config.get(), iaddrs);
+ return;
+ }
+#endif // ENABLE_HTTP3
+
+ // fork_worker_process and forked child process assumes new
+ // configuration can be obtained from get_config().
+
+ auto old_config = replace_config(std::move(new_config));
+
+ auto pid = fork_worker_process(ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd
+#endif // ENABLE_HTTP3
+
+ ,
+ iaddrs
+#ifdef ENABLE_HTTP3
+ ,
+ cid_prefixes, quic_lwps
+#endif // ENABLE_HTTP3
+ );
+
+ if (pid == -1) {
+ LOG(ERROR) << "Failed to process new configuration";
+
+ new_config = replace_config(std::move(old_config));
+ close_not_inherited_fd(new_config.get(), iaddrs);
+
+ return;
+ }
+
+ close_unused_inherited_addr(iaddrs);
+
+ worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd
+#ifdef ENABLE_HTTP3
+ ,
+ quic_ipc_fd, cid_prefixes
+#endif // ENABLE_HTTP3
+ ));
+
+ worker_process_adjust_limit();
+
+ if (!get_config()->pid_file.empty()) {
+ save_pid();
+ }
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ int rv;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+#ifdef HAVE_LIBBPF
+ libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+#endif // HAVE_LIBBPF
+
+ Log::set_severity_level(NOTICE);
+ create_config();
+ fill_default_config(mod_config());
+
+ // make copy of stderr
+ store_original_fds();
+
+ // First open log files with default configuration, so that we can
+ // log errors/warnings while reading configuration files.
+ reopen_log_files(get_config()->logging);
+
+ suconfig.original_argv = argv;
+
+ // We have to copy argv, since getopt_long may change its content.
+ suconfig.argc = argc;
+ suconfig.argv = new char *[argc];
+
+ for (int i = 0; i < argc; ++i) {
+ suconfig.argv[i] = strdup(argv[i]);
+ if (suconfig.argv[i] == nullptr) {
+ auto error = errno;
+ LOG(FATAL) << "failed to copy argv: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ suconfig.cwd = getcwd(nullptr, 0);
+ if (suconfig.cwd == nullptr) {
+ auto error = errno;
+ LOG(FATAL) << "failed to get current working directory: errno=" << error;
+ exit(EXIT_FAILURE);
+ }
+
+ auto &cmdcfgs = suconfig.cmdcfgs;
+
+ while (1) {
+ static int flag = 0;
+ static constexpr option long_options[] = {
+ {SHRPX_OPT_DAEMON.c_str(), no_argument, nullptr, 'D'},
+ {SHRPX_OPT_LOG_LEVEL.c_str(), required_argument, nullptr, 'L'},
+ {SHRPX_OPT_BACKEND.c_str(), required_argument, nullptr, 'b'},
+ {SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), required_argument,
+ nullptr, 'c'},
+ {SHRPX_OPT_FRONTEND.c_str(), required_argument, nullptr, 'f'},
+ {"help", no_argument, nullptr, 'h'},
+ {SHRPX_OPT_INSECURE.c_str(), no_argument, nullptr, 'k'},
+ {SHRPX_OPT_WORKERS.c_str(), required_argument, nullptr, 'n'},
+ {SHRPX_OPT_CLIENT_PROXY.c_str(), no_argument, nullptr, 'p'},
+ {SHRPX_OPT_HTTP2_PROXY.c_str(), no_argument, nullptr, 's'},
+ {"version", no_argument, nullptr, 'v'},
+ {SHRPX_OPT_FRONTEND_FRAME_DEBUG.c_str(), no_argument, nullptr, 'o'},
+ {SHRPX_OPT_ADD_X_FORWARDED_FOR.c_str(), no_argument, &flag, 1},
+ {SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT.c_str(), required_argument,
+ &flag, 2},
+ {SHRPX_OPT_FRONTEND_READ_TIMEOUT.c_str(), required_argument, &flag, 3},
+ {SHRPX_OPT_FRONTEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 4},
+ {SHRPX_OPT_BACKEND_READ_TIMEOUT.c_str(), required_argument, &flag, 5},
+ {SHRPX_OPT_BACKEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 6},
+ {SHRPX_OPT_ACCESSLOG_FILE.c_str(), required_argument, &flag, 7},
+ {SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag,
+ 8},
+ {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag,
+ 9},
+ {SHRPX_OPT_PID_FILE.c_str(), required_argument, &flag, 10},
+ {SHRPX_OPT_USER.c_str(), required_argument, &flag, 11},
+ {"conf", required_argument, &flag, 12},
+ {SHRPX_OPT_SYSLOG_FACILITY.c_str(), required_argument, &flag, 14},
+ {SHRPX_OPT_BACKLOG.c_str(), required_argument, &flag, 15},
+ {SHRPX_OPT_CIPHERS.c_str(), required_argument, &flag, 16},
+ {SHRPX_OPT_CLIENT.c_str(), no_argument, &flag, 17},
+ {SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag,
+ 18},
+ {SHRPX_OPT_CACERT.c_str(), required_argument, &flag, 19},
+ {SHRPX_OPT_BACKEND_IPV4.c_str(), no_argument, &flag, 20},
+ {SHRPX_OPT_BACKEND_IPV6.c_str(), no_argument, &flag, 21},
+ {SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE.c_str(), required_argument, &flag,
+ 22},
+ {SHRPX_OPT_NO_VIA.c_str(), no_argument, &flag, 23},
+ {SHRPX_OPT_SUBCERT.c_str(), required_argument, &flag, 24},
+ {SHRPX_OPT_HTTP2_BRIDGE.c_str(), no_argument, &flag, 25},
+ {SHRPX_OPT_BACKEND_HTTP_PROXY_URI.c_str(), required_argument, &flag,
+ 26},
+ {SHRPX_OPT_BACKEND_NO_TLS.c_str(), no_argument, &flag, 27},
+ {SHRPX_OPT_OCSP_STARTUP.c_str(), no_argument, &flag, 28},
+ {SHRPX_OPT_FRONTEND_NO_TLS.c_str(), no_argument, &flag, 29},
+ {SHRPX_OPT_NO_VERIFY_OCSP.c_str(), no_argument, &flag, 30},
+ {SHRPX_OPT_BACKEND_TLS_SNI_FIELD.c_str(), required_argument, &flag, 31},
+ {SHRPX_OPT_DH_PARAM_FILE.c_str(), required_argument, &flag, 33},
+ {SHRPX_OPT_READ_RATE.c_str(), required_argument, &flag, 34},
+ {SHRPX_OPT_READ_BURST.c_str(), required_argument, &flag, 35},
+ {SHRPX_OPT_WRITE_RATE.c_str(), required_argument, &flag, 36},
+ {SHRPX_OPT_WRITE_BURST.c_str(), required_argument, &flag, 37},
+ {SHRPX_OPT_NPN_LIST.c_str(), required_argument, &flag, 38},
+ {SHRPX_OPT_VERIFY_CLIENT.c_str(), no_argument, &flag, 39},
+ {SHRPX_OPT_VERIFY_CLIENT_CACERT.c_str(), required_argument, &flag, 40},
+ {SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE.c_str(), required_argument, &flag,
+ 41},
+ {SHRPX_OPT_CLIENT_CERT_FILE.c_str(), required_argument, &flag, 42},
+ {SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER.c_str(),
+ required_argument, &flag, 43},
+ {SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER.c_str(),
+ required_argument, &flag, 44},
+ {SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING.c_str(), no_argument, &flag, 45},
+ {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(),
+ required_argument, &flag, 46},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(),
+ required_argument, &flag, 47},
+ {SHRPX_OPT_TLS_PROTO_LIST.c_str(), required_argument, &flag, 48},
+ {SHRPX_OPT_PADDING.c_str(), required_argument, &flag, 49},
+ {SHRPX_OPT_WORKER_READ_RATE.c_str(), required_argument, &flag, 50},
+ {SHRPX_OPT_WORKER_READ_BURST.c_str(), required_argument, &flag, 51},
+ {SHRPX_OPT_WORKER_WRITE_RATE.c_str(), required_argument, &flag, 52},
+ {SHRPX_OPT_WORKER_WRITE_BURST.c_str(), required_argument, &flag, 53},
+ {SHRPX_OPT_ALTSVC.c_str(), required_argument, &flag, 54},
+ {SHRPX_OPT_ADD_RESPONSE_HEADER.c_str(), required_argument, &flag, 55},
+ {SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS.c_str(), required_argument,
+ &flag, 56},
+ {SHRPX_OPT_ACCESSLOG_SYSLOG.c_str(), no_argument, &flag, 57},
+ {SHRPX_OPT_ERRORLOG_FILE.c_str(), required_argument, &flag, 58},
+ {SHRPX_OPT_ERRORLOG_SYSLOG.c_str(), no_argument, &flag, 59},
+ {SHRPX_OPT_STREAM_READ_TIMEOUT.c_str(), required_argument, &flag, 60},
+ {SHRPX_OPT_STREAM_WRITE_TIMEOUT.c_str(), required_argument, &flag, 61},
+ {SHRPX_OPT_NO_LOCATION_REWRITE.c_str(), no_argument, &flag, 62},
+ {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST.c_str(),
+ required_argument, &flag, 63},
+ {SHRPX_OPT_LISTENER_DISABLE_TIMEOUT.c_str(), required_argument, &flag,
+ 64},
+ {SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR.c_str(), no_argument, &flag,
+ 65},
+ {SHRPX_OPT_ACCESSLOG_FORMAT.c_str(), required_argument, &flag, 66},
+ {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND.c_str(),
+ required_argument, &flag, 67},
+ {SHRPX_OPT_TLS_TICKET_KEY_FILE.c_str(), required_argument, &flag, 68},
+ {SHRPX_OPT_RLIMIT_NOFILE.c_str(), required_argument, &flag, 69},
+ {SHRPX_OPT_BACKEND_RESPONSE_BUFFER.c_str(), required_argument, &flag,
+ 71},
+ {SHRPX_OPT_BACKEND_REQUEST_BUFFER.c_str(), required_argument, &flag,
+ 72},
+ {SHRPX_OPT_NO_HOST_REWRITE.c_str(), no_argument, &flag, 73},
+ {SHRPX_OPT_NO_SERVER_PUSH.c_str(), no_argument, &flag, 74},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER.c_str(),
+ required_argument, &flag, 76},
+ {SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE.c_str(), required_argument, &flag,
+ 77},
+ {SHRPX_OPT_OCSP_UPDATE_INTERVAL.c_str(), required_argument, &flag, 78},
+ {SHRPX_OPT_NO_OCSP.c_str(), no_argument, &flag, 79},
+ {SHRPX_OPT_HEADER_FIELD_BUFFER.c_str(), required_argument, &flag, 80},
+ {SHRPX_OPT_MAX_HEADER_FIELDS.c_str(), required_argument, &flag, 81},
+ {SHRPX_OPT_ADD_REQUEST_HEADER.c_str(), required_argument, &flag, 82},
+ {SHRPX_OPT_INCLUDE.c_str(), required_argument, &flag, 83},
+ {SHRPX_OPT_TLS_TICKET_KEY_CIPHER.c_str(), required_argument, &flag, 84},
+ {SHRPX_OPT_HOST_REWRITE.c_str(), no_argument, &flag, 85},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED.c_str(), required_argument,
+ &flag, 86},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED.c_str(), required_argument, &flag,
+ 87},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL.c_str(), required_argument,
+ &flag, 88},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY.c_str(),
+ required_argument, &flag, 89},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL.c_str(), required_argument,
+ &flag, 90},
+ {SHRPX_OPT_MRUBY_FILE.c_str(), required_argument, &flag, 91},
+ {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL.c_str(), no_argument, &flag, 93},
+ {SHRPX_OPT_FASTOPEN.c_str(), required_argument, &flag, 94},
+ {SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD.c_str(), required_argument,
+ &flag, 95},
+ {SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT.c_str(), required_argument, &flag,
+ 96},
+ {SHRPX_OPT_ADD_FORWARDED.c_str(), required_argument, &flag, 97},
+ {SHRPX_OPT_STRIP_INCOMING_FORWARDED.c_str(), no_argument, &flag, 98},
+ {SHRPX_OPT_FORWARDED_BY.c_str(), required_argument, &flag, 99},
+ {SHRPX_OPT_FORWARDED_FOR.c_str(), required_argument, &flag, 100},
+ {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER.c_str(), required_argument,
+ &flag, 101},
+ {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS.c_str(), required_argument, &flag,
+ 102},
+ {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, &flag, 103},
+ {SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER.c_str(), required_argument,
+ &flag, 104},
+ {SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS.c_str(), required_argument, &flag,
+ 105},
+ {SHRPX_OPT_BACKEND_HTTP1_TLS.c_str(), no_argument, &flag, 106},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS.c_str(), no_argument, &flag,
+ 108},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE.c_str(),
+ required_argument, &flag, 109},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE.c_str(),
+ required_argument, &flag, 110},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS.c_str(), no_argument, &flag,
+ 111},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE.c_str(),
+ required_argument, &flag, 112},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE.c_str(),
+ required_argument, &flag, 113},
+ {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY.c_str(),
+ required_argument, &flag, 114},
+ {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY.c_str(),
+ required_argument, &flag, 115},
+ {SHRPX_OPT_BACKEND_ADDRESS_FAMILY.c_str(), required_argument, &flag,
+ 116},
+ {SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 117},
+ {SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 118},
+ {SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND.c_str(), required_argument,
+ &flag, 119},
+ {SHRPX_OPT_BACKEND_TLS.c_str(), no_argument, &flag, 120},
+ {SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST.c_str(), required_argument,
+ &flag, 121},
+ {SHRPX_OPT_ERROR_PAGE.c_str(), required_argument, &flag, 122},
+ {SHRPX_OPT_NO_KQUEUE.c_str(), no_argument, &flag, 123},
+ {SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument,
+ &flag, 124},
+ {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument,
+ &flag, 125},
+ {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126},
+ {SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127},
+ {SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128},
+ {SHRPX_OPT_NO_SERVER_REWRITE.c_str(), no_argument, &flag, 129},
+ {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE.c_str(),
+ no_argument, &flag, 130},
+ {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE.c_str(), no_argument,
+ &flag, 131},
+ {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 132},
+ {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 133},
+ {SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 134},
+ {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 135},
+ {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 136},
+ {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 137},
+ {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 138},
+ {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(),
+ required_argument, &flag, 139},
+ {SHRPX_OPT_ECDH_CURVES.c_str(), required_argument, &flag, 140},
+ {SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141},
+ {SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag,
+ 142},
+ {SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143},
+ {SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144},
+ {SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
+ {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument,
+ &flag, 146},
+ {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147},
+ {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148},
+ {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument,
+ &flag, 149},
+ {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150},
+ {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151},
+ {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag,
+ 152},
+ {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag,
+ 153},
+ {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154},
+ {SHRPX_OPT_FRONTEND_MAX_REQUESTS.c_str(), required_argument, &flag,
+ 155},
+ {SHRPX_OPT_SINGLE_THREAD.c_str(), no_argument, &flag, 156},
+ {SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 157},
+ {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument,
+ &flag, 158},
+ {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159},
+ {SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED.c_str(), no_argument, &flag,
+ 160},
+ {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag,
+ 161},
+ {SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162},
+ {SHRPX_OPT_TLS_MAX_EARLY_DATA.c_str(), required_argument, &flag, 163},
+ {SHRPX_OPT_TLS13_CIPHERS.c_str(), required_argument, &flag, 164},
+ {SHRPX_OPT_TLS13_CLIENT_CIPHERS.c_str(), required_argument, &flag, 165},
+ {SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA.c_str(), no_argument, &flag,
+ 166},
+ {SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, &flag, 167},
+ {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument,
+ &flag, 168},
+ {SHRPX_OPT_QUIC_BPF_PROGRAM_FILE.c_str(), required_argument, &flag,
+ 169},
+ {SHRPX_OPT_NO_QUIC_BPF.c_str(), no_argument, &flag, 170},
+ {SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171},
+ {SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT.c_str(), required_argument,
+ &flag, 172},
+ {SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT.c_str(), required_argument, &flag,
+ 173},
+ {SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG.c_str(), no_argument, &flag, 174},
+ {SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE.c_str(), required_argument, &flag,
+ 175},
+ {SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 176},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE.c_str(), required_argument,
+ &flag, 177},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE.c_str(),
+ required_argument, &flag, 178},
+ {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(),
+ required_argument, &flag, 179},
+ {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180},
+ {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag,
+ 181},
+ {SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag,
+ 182},
+ {SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(),
+ required_argument, &flag, 183},
+ {SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185},
+ {SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag,
+ 186},
+ {SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187},
+ {SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188},
+ {SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(),
+ required_argument, &flag, 189},
+ {SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag,
+ 190},
+ {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191},
+ {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192},
+ {SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193},
+ {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, StringRef::from_lit("yes"));
+ break;
+ case 'L':
+ cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, StringRef{optarg});
+ break;
+ case 'b':
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, StringRef{optarg});
+ break;
+ case 'c':
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 'f':
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, StringRef{optarg});
+ break;
+ case 'h':
+ print_help(std::cout);
+ exit(EXIT_SUCCESS);
+ case 'k':
+ cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, StringRef::from_lit("yes"));
+ break;
+ case 'n':
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, StringRef{optarg});
+ break;
+ case 'o':
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG,
+ StringRef::from_lit("yes"));
+ break;
+ case 'p':
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, StringRef::from_lit("yes"));
+ break;
+ case 's':
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, StringRef::from_lit("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,
+ StringRef::from_lit("yes"));
+ break;
+ case 2:
+ // --frontend-http2-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 3:
+ // --frontend-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 4:
+ // --frontend-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 5:
+ // --backend-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, StringRef{optarg});
+ break;
+ case 6:
+ // --backend-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 7:
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, StringRef{optarg});
+ break;
+ case 8:
+ // --backend-keep-alive-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 9:
+ // --frontend-http2-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 10:
+ cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, StringRef{optarg});
+ break;
+ case 11:
+ cmdcfgs.emplace_back(SHRPX_OPT_USER, StringRef{optarg});
+ break;
+ case 12:
+ // --conf
+ mod_config()->conf_path =
+ make_string_ref(mod_config()->balloc, StringRef{optarg});
+ break;
+ case 14:
+ // --syslog-facility
+ cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, StringRef{optarg});
+ break;
+ case 15:
+ // --backlog
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, StringRef{optarg});
+ break;
+ case 16:
+ // --ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, StringRef{optarg});
+ break;
+ case 17:
+ // --client
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, StringRef::from_lit("yes"));
+ break;
+ case 18:
+ // --backend-http2-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 19:
+ // --cacert
+ cmdcfgs.emplace_back(SHRPX_OPT_CACERT, StringRef{optarg});
+ break;
+ case 20:
+ // --backend-ipv4
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4,
+ StringRef::from_lit("yes"));
+ break;
+ case 21:
+ // --backend-ipv6
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6,
+ StringRef::from_lit("yes"));
+ break;
+ case 22:
+ // --private-key-passwd-file
+ cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE,
+ StringRef{optarg});
+ break;
+ case 23:
+ // --no-via
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, StringRef::from_lit("yes"));
+ break;
+ case 24:
+ // --subcert
+ cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, StringRef{optarg});
+ break;
+ case 25:
+ // --http2-bridge
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE,
+ StringRef::from_lit("yes"));
+ break;
+ case 26:
+ // --backend-http-proxy-uri
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI,
+ StringRef{optarg});
+ break;
+ case 27:
+ // --backend-no-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 28:
+ // --ocsp-startup
+ cmdcfgs.emplace_back(SHRPX_OPT_OCSP_STARTUP,
+ StringRef::from_lit("yes"));
+ break;
+ case 29:
+ // --frontend-no-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 30:
+ // --no-verify-ocsp
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_VERIFY_OCSP,
+ StringRef::from_lit("yes"));
+ break;
+ case 31:
+ // --backend-tls-sni-field
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD,
+ StringRef{optarg});
+ break;
+ case 33:
+ // --dh-param-file
+ cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, StringRef{optarg});
+ break;
+ case 34:
+ // --read-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, StringRef{optarg});
+ break;
+ case 35:
+ // --read-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, StringRef{optarg});
+ break;
+ case 36:
+ // --write-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, StringRef{optarg});
+ break;
+ case 37:
+ // --write-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, StringRef{optarg});
+ break;
+ case 38:
+ // --npn-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, StringRef{optarg});
+ break;
+ case 39:
+ // --verify-client
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT,
+ StringRef::from_lit("yes"));
+ break;
+ case 40:
+ // --verify-client-cacert
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, StringRef{optarg});
+ break;
+ case 41:
+ // --client-private-key-file
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 42:
+ // --client-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, StringRef{optarg});
+ break;
+ case 43:
+ // --frontend-http2-dump-request-header
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
+ StringRef{optarg});
+ break;
+ case 44:
+ // --frontend-http2-dump-response-header
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
+ StringRef{optarg});
+ break;
+ case 45:
+ // --http2-no-cookie-crumbling
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING,
+ StringRef::from_lit("yes"));
+ break;
+ case 46:
+ // --frontend-http2-connection-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 47:
+ // --backend-http2-connection-window-bits
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
+ StringRef{optarg});
+ break;
+ case 48:
+ // --tls-proto-list
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, StringRef{optarg});
+ break;
+ case 49:
+ // --padding
+ cmdcfgs.emplace_back(SHRPX_OPT_PADDING, StringRef{optarg});
+ break;
+ case 50:
+ // --worker-read-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, StringRef{optarg});
+ break;
+ case 51:
+ // --worker-read-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, StringRef{optarg});
+ break;
+ case 52:
+ // --worker-write-rate
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, StringRef{optarg});
+ break;
+ case 53:
+ // --worker-write-burst
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, StringRef{optarg});
+ break;
+ case 54:
+ // --altsvc
+ cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, StringRef{optarg});
+ break;
+ case 55:
+ // --add-response-header
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, StringRef{optarg});
+ break;
+ case 56:
+ // --worker-frontend-connections
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS,
+ StringRef{optarg});
+ break;
+ case 57:
+ // --accesslog-syslog
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 58:
+ // --errorlog-file
+ cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, StringRef{optarg});
+ break;
+ case 59:
+ // --errorlog-syslog
+ cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 60:
+ // --stream-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, StringRef{optarg});
+ break;
+ case 61:
+ // --stream-write-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, StringRef{optarg});
+ break;
+ case 62:
+ // --no-location-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 63:
+ // --backend-http1-connections-per-host
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
+ StringRef{optarg});
+ break;
+ case 64:
+ // --listener-disable-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 65:
+ // --strip-incoming-x-forwarded-for
+ cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR,
+ StringRef::from_lit("yes"));
+ break;
+ case 66:
+ // --accesslog-format
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, StringRef{optarg});
+ break;
+ case 67:
+ // --backend-http1-connections-per-frontend
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
+ StringRef{optarg});
+ break;
+ case 68:
+ // --tls-ticket-key-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, StringRef{optarg});
+ break;
+ case 69:
+ // --rlimit-nofile
+ cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, StringRef{optarg});
+ break;
+ case 71:
+ // --backend-response-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER,
+ StringRef{optarg});
+ break;
+ case 72:
+ // --backend-request-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER,
+ StringRef{optarg});
+ break;
+ case 73:
+ // --no-host-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 74:
+ // --no-server-push
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH,
+ StringRef::from_lit("yes"));
+ break;
+ case 76:
+ // --backend-http2-connections-per-worker
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
+ StringRef{optarg});
+ break;
+ case 77:
+ // --fetch-ocsp-response-file
+ cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE,
+ StringRef{optarg});
+ break;
+ case 78:
+ // --ocsp-update-interval
+ cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, StringRef{optarg});
+ break;
+ case 79:
+ // --no-ocsp
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, StringRef::from_lit("yes"));
+ break;
+ case 80:
+ // --header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, StringRef{optarg});
+ break;
+ case 81:
+ // --max-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, StringRef{optarg});
+ break;
+ case 82:
+ // --add-request-header
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_REQUEST_HEADER, StringRef{optarg});
+ break;
+ case 83:
+ // --include
+ cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, StringRef{optarg});
+ break;
+ case 84:
+ // --tls-ticket-key-cipher
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_CIPHER,
+ StringRef{optarg});
+ break;
+ case 85:
+ // --host-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 86:
+ // --tls-session-cache-memcached
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED,
+ StringRef{optarg});
+ break;
+ case 87:
+ // --tls-ticket-key-memcached
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED,
+ StringRef{optarg});
+ break;
+ case 88:
+ // --tls-ticket-key-memcached-interval
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL,
+ StringRef{optarg});
+ break;
+ case 89:
+ // --tls-ticket-key-memcached-max-retry
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY,
+ StringRef{optarg});
+ break;
+ case 90:
+ // --tls-ticket-key-memcached-max-fail
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
+ StringRef{optarg});
+ break;
+ case 91:
+ // --mruby-file
+ cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, StringRef{optarg});
+ break;
+ case 93:
+ // --accept-proxy-protocol
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL,
+ StringRef::from_lit("yes"));
+ break;
+ case 94:
+ // --fastopen
+ cmdcfgs.emplace_back(SHRPX_OPT_FASTOPEN, StringRef{optarg});
+ break;
+ case 95:
+ // --tls-dyn-rec-warmup-threshold
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD,
+ StringRef{optarg});
+ break;
+ case 96:
+ // --tls-dyn-rec-idle-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 97:
+ // --add-forwarded
+ cmdcfgs.emplace_back(SHRPX_OPT_ADD_FORWARDED, StringRef{optarg});
+ break;
+ case 98:
+ // --strip-incoming-forwarded
+ cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_FORWARDED,
+ StringRef::from_lit("yes"));
+ break;
+ case 99:
+ // --forwarded-by
+ cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_BY, StringRef{optarg});
+ break;
+ case 100:
+ // --forwarded-for
+ cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, StringRef{optarg});
+ break;
+ case 101:
+ // --response-header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER,
+ StringRef{optarg});
+ break;
+ case 102:
+ // --max-response-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS,
+ StringRef{optarg});
+ break;
+ case 103:
+ // --no-http2-cipher-black-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 104:
+ // --request-header-field-buffer
+ cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER,
+ StringRef{optarg});
+ break;
+ case 105:
+ // --max-request-header-fields
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS,
+ StringRef{optarg});
+ break;
+ case 106:
+ // --backend-http1-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 108:
+ // --tls-session-cache-memcached-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 109:
+ // --tls-session-cache-memcached-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE,
+ StringRef{optarg});
+ break;
+ case 110:
+ // --tls-session-cache-memcached-private-key-file
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 111:
+ // --tls-ticket-key-memcached-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS,
+ StringRef::from_lit("yes"));
+ break;
+ case 112:
+ // --tls-ticket-key-memcached-cert-file
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE,
+ StringRef{optarg});
+ break;
+ case 113:
+ // --tls-ticket-key-memcached-private-key-file
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE,
+ StringRef{optarg});
+ break;
+ case 114:
+ // --tls-ticket-key-memcached-address-family
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 115:
+ // --tls-session-cache-memcached-address-family
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 116:
+ // --backend-address-family
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY,
+ StringRef{optarg});
+ break;
+ case 117:
+ // --frontend-http2-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 118:
+ // --backend-http2-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 119:
+ // --backend-connections-per-frontend
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND,
+ StringRef{optarg});
+ break;
+ case 120:
+ // --backend-tls
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, StringRef::from_lit("yes"));
+ break;
+ case 121:
+ // --backend-connections-per-host
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST,
+ StringRef{optarg});
+ break;
+ case 122:
+ // --error-page
+ cmdcfgs.emplace_back(SHRPX_OPT_ERROR_PAGE, StringRef{optarg});
+ break;
+ case 123:
+ // --no-kqueue
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_KQUEUE, StringRef::from_lit("yes"));
+ break;
+ case 124:
+ // --frontend-http2-settings-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 125:
+ // --backend-http2-settings-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 126:
+ // --api-max-request-body
+ cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg});
+ break;
+ case 127:
+ // --backend-max-backoff
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg});
+ break;
+ case 128:
+ // --server-name
+ cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg});
+ break;
+ case 129:
+ // --no-server-rewrite
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_REWRITE,
+ StringRef::from_lit("yes"));
+ break;
+ case 130:
+ // --frontend-http2-optimize-write-buffer-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE,
+ StringRef::from_lit("yes"));
+ break;
+ case 131:
+ // --frontend-http2-optimize-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE,
+ StringRef::from_lit("yes"));
+ break;
+ case 132:
+ // --frontend-http2-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 133:
+ // --frontend-http2-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 134:
+ // --backend-http2-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 135:
+ // --backend-http2-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 136:
+ // --frontend-http2-encoder-dynamic-table-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 137:
+ // --frontend-http2-decoder-dynamic-table-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 138:
+ // --backend-http2-encoder-dynamic-table-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 139:
+ // --backend-http2-decoder-dynamic-table-size
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ StringRef{optarg});
+ break;
+ case 140:
+ // --ecdh-curves
+ cmdcfgs.emplace_back(SHRPX_OPT_ECDH_CURVES, StringRef{optarg});
+ break;
+ case 141:
+ // --tls-sct-dir
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_SCT_DIR, StringRef{optarg});
+ break;
+ case 142:
+ // --backend-connect-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 143:
+ // --dns-cache-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg});
+ break;
+ case 144:
+ // --dns-lookup-timeou
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg});
+ break;
+ case 145:
+ // --dns-max-try
+ cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg});
+ break;
+ case 146:
+ // --frontend-keep-alive-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 147:
+ // --psk-secrets
+ cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg});
+ break;
+ case 148:
+ // --client-psk-secrets
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg});
+ break;
+ case 149:
+ // --client-no-http2-cipher-black-list
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 150:
+ // --client-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CIPHERS, StringRef{optarg});
+ break;
+ case 151:
+ // --accesslog-write-early
+ cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY,
+ StringRef::from_lit("yes"));
+ break;
+ case 152:
+ // --tls-min-proto-version
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION,
+ StringRef{optarg});
+ break;
+ case 153:
+ // --tls-max-proto-version
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION,
+ StringRef{optarg});
+ break;
+ case 154:
+ // --redirect-https-port
+ cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg});
+ break;
+ case 155:
+ // --frontend-max-requests
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_MAX_REQUESTS,
+ StringRef{optarg});
+ break;
+ case 156:
+ // --single-thread
+ cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_THREAD,
+ StringRef::from_lit("yes"));
+ break;
+ case 157:
+ // --no-add-x-forwarded-proto
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO,
+ StringRef::from_lit("yes"));
+ break;
+ case 158:
+ // --no-strip-incoming-x-forwarded-proto
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO,
+ StringRef::from_lit("yes"));
+ break;
+ case 159:
+ // --single-process
+ cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS,
+ StringRef::from_lit("yes"));
+ break;
+ case 160:
+ // --verify-client-tolerate-expired
+ cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED,
+ StringRef::from_lit("yes"));
+ break;
+ case 161:
+ // --ignore-per-pattern-mruby-error
+ cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR,
+ StringRef::from_lit("yes"));
+ break;
+ case 162:
+ // --tls-no-postpone-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 163:
+ // --tls-max-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_EARLY_DATA, StringRef{optarg});
+ break;
+ case 164:
+ // --tls13-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CIPHERS, StringRef{optarg});
+ break;
+ case 165:
+ // --tls13-client-ciphers
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CLIENT_CIPHERS, StringRef{optarg});
+ break;
+ case 166:
+ // --no-strip-incoming-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 167:
+ // --no-http2-cipher-block-list
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 168:
+ // --client-no-http2-cipher-block-list
+ cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ StringRef::from_lit("yes"));
+ break;
+ case 169:
+ // --quic-bpf-program-file
+ cmdcfgs.emplace_back(SHRPX_OPT_QUIC_BPF_PROGRAM_FILE,
+ StringRef{optarg});
+ break;
+ case 170:
+ // --no-quic-bpf
+ cmdcfgs.emplace_back(SHRPX_OPT_NO_QUIC_BPF, StringRef::from_lit("yes"));
+ break;
+ case 171:
+ // --http2-altsvc
+ cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg});
+ break;
+ case 172:
+ // --frontend-http3-read-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 173:
+ // --frontend-quic-idle-timeout
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT,
+ StringRef{optarg});
+ break;
+ case 174:
+ // --frontend-quic-debug-log
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG,
+ StringRef::from_lit("yes"));
+ break;
+ case 175:
+ // --frontend-http3-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 176:
+ // --frontend-http3-connection-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 177:
+ // --frontend-http3-max-window-size
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 178:
+ // --frontend-http3-max-connection-window-size
+ cmdcfgs.emplace_back(
+ SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE,
+ StringRef{optarg});
+ break;
+ case 179:
+ // --frontend-http3-max-concurrent-streams
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS,
+ StringRef{optarg});
+ break;
+ case 180:
+ // --frontend-quic-early-data
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA,
+ StringRef::from_lit("yes"));
+ break;
+ case 181:
+ // --frontend-quic-qlog-dir
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR,
+ StringRef{optarg});
+ break;
+ case 182:
+ // --frontend-quic-require-token
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN,
+ StringRef::from_lit("yes"));
+ break;
+ case 183:
+ // --frontend-quic-congestion-controller
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER,
+ StringRef{optarg});
+ break;
+ case 185:
+ // --quic-server-id
+ cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg});
+ break;
+ case 186:
+ // --frontend-quic-secret-file
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE,
+ StringRef{optarg});
+ break;
+ case 187:
+ // --rlimit-memlock
+ cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg});
+ break;
+ case 188:
+ // --max-worker-processes
+ cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg});
+ break;
+ case 189:
+ // --worker-process-grace-shutdown-period
+ cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
+ StringRef{optarg});
+ break;
+ case 190:
+ // --frontend-quic-initial-rtt
+ cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT,
+ StringRef{optarg});
+ break;
+ case 191:
+ // --require-http-scheme
+ cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME,
+ StringRef::from_lit("yes"));
+ break;
+ case 192:
+ // --tls-ktls
+ cmdcfgs.emplace_back(SHRPX_OPT_TLS_KTLS, StringRef::from_lit("yes"));
+ break;
+ case 193:
+ // --alpn-list
+ cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg});
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (argc - optind >= 2) {
+ cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, StringRef{argv[optind++]});
+ cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, StringRef{argv[optind++]});
+ }
+
+ rv = process_options(mod_config(), cmdcfgs);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (event_loop() != 0) {
+ return -1;
+ }
+
+ LOG(NOTICE) << "Shutdown momentarily";
+
+ delete_log_config();
+
+ return 0;
+}
+
+} // namespace shrpx
+
+int main(int argc, char **argv) { return run_app(shrpx::main, argc, argv); }
diff --git a/src/shrpx.h b/src/shrpx.h
new file mode 100644
index 0000000..d881ef5
--- /dev/null
+++ b/src/shrpx.h
@@ -0,0 +1,58 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+
+#include <cassert>
+
+#ifndef HAVE__EXIT
+# define nghttp2_Exit(status) _exit(status)
+#else // HAVE__EXIT
+# define nghttp2_Exit(status) _Exit(status)
+#endif // HAVE__EXIT
+
+#define DIE() nghttp2_Exit(EXIT_FAILURE)
+
+#if defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
+inline int initgroups(const char *user, gid_t group) { return 0; }
+#endif // defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
+
+#ifndef HAVE_BPF_STATS_TYPE
+/* Newer kernel should have this defined in linux/bpf.h */
+enum bpf_stats_type {
+ BPF_STATS_RUN_TIME = 0,
+};
+#endif // !HAVE_BPF_STATS_TYPE
+
+#endif // SHRPX_H
diff --git a/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc
new file mode 100644
index 0000000..01b6415
--- /dev/null
+++ b/src/shrpx_accept_handler.cc
@@ -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.
+ */
+#include "shrpx_accept_handler.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cerrno>
+
+#include "shrpx_connection_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_log.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(const UpstreamAddr *faddr, ConnectionHandler *h)
+ : conn_hnr_(h), faddr_(faddr) {
+ ev_io_init(&wev_, acceptcb, faddr_->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(faddr_->fd);
+}
+
+void AcceptHandler::accept_connection() {
+ sockaddr_union sockaddr;
+ socklen_t addrlen = sizeof(sockaddr);
+
+#ifdef HAVE_ACCEPT4
+ auto cfd =
+ accept4(faddr_->fd, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
+#else // !HAVE_ACCEPT4
+ auto cfd = accept(faddr_->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:
+ return;
+ case EMFILE:
+ case ENFILE:
+ LOG(WARN) << "acceptor: running out file descriptor; disable acceptor "
+ "temporarily";
+ conn_hnr_->sleep_acceptor(get_config()->conn.listener.timeout.sleep);
+ return;
+ default:
+ return;
+ }
+ }
+
+#ifndef HAVE_ACCEPT4
+ util::make_socket_nonblocking(cfd);
+ util::make_socket_closeonexec(cfd);
+#endif // !HAVE_ACCEPT4
+
+ conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_);
+}
+
+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 faddr_->fd; }
+
+} // namespace shrpx
diff --git a/src/shrpx_accept_handler.h b/src/shrpx_accept_handler.h
new file mode 100644
index 0000000..853e9a2
--- /dev/null
+++ b/src/shrpx_accept_handler.h
@@ -0,0 +1,54 @@
+/*
+ * 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;
+struct UpstreamAddr;
+
+class AcceptHandler {
+public:
+ AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h);
+ ~AcceptHandler();
+ void accept_connection();
+ void enable();
+ void disable();
+ int get_fd() const;
+
+private:
+ ev_io wev_;
+ ConnectionHandler *conn_hnr_;
+ const UpstreamAddr *faddr_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ACCEPT_HANDLER_H
diff --git a/src/shrpx_api_downstream_connection.cc b/src/shrpx_api_downstream_connection.cc
new file mode 100644
index 0000000..254ab59
--- /dev/null
+++ b/src/shrpx_api_downstream_connection.cc
@@ -0,0 +1,478 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_api_downstream_connection.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <cstdlib>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_worker.h"
+#include "shrpx_connection_handler.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+// List of API endpoints
+const std::array<APIEndpoint, 2> &apis() {
+ static const auto apis = new std::array<APIEndpoint, 2>{
+ APIEndpoint{
+ StringRef::from_lit("/api/v1beta1/backendconfig"),
+ true,
+ (1 << API_METHOD_POST) | (1 << API_METHOD_PUT),
+ &APIDownstreamConnection::handle_backendconfig,
+ },
+ APIEndpoint{
+ StringRef::from_lit("/api/v1beta1/configrevision"),
+ true,
+ (1 << API_METHOD_GET),
+ &APIDownstreamConnection::handle_configrevision,
+ },
+ };
+
+ return *apis;
+}
+} // namespace
+
+namespace {
+// The method string. This must be same order of APIMethod.
+constexpr StringRef API_METHOD_STRING[] = {
+ StringRef::from_lit("GET"),
+ StringRef::from_lit("POST"),
+ StringRef::from_lit("PUT"),
+};
+} // namespace
+
+APIDownstreamConnection::APIDownstreamConnection(Worker *worker)
+ : worker_(worker), api_(nullptr), fd_(-1), shutdown_read_(false) {}
+
+APIDownstreamConnection::~APIDownstreamConnection() {
+ if (fd_ != -1) {
+ close(fd_);
+ }
+}
+
+int APIDownstreamConnection::attach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void APIDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int APIDownstreamConnection::send_reply(unsigned int http_status,
+ APIStatusCode api_status,
+ const StringRef &data) {
+ shutdown_read_ = true;
+
+ auto upstream = downstream_->get_upstream();
+
+ auto &resp = downstream_->response();
+
+ resp.http_status = http_status;
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ StringRef api_status_str;
+
+ switch (api_status) {
+ case APIStatusCode::SUCCESS:
+ api_status_str = StringRef::from_lit("Success");
+ break;
+ case APIStatusCode::FAILURE:
+ api_status_str = StringRef::from_lit("Failure");
+ break;
+ default:
+ assert(0);
+ }
+
+ constexpr auto M1 = StringRef::from_lit("{\"status\":\"");
+ constexpr auto M2 = StringRef::from_lit("\",\"code\":");
+ constexpr auto M3 = StringRef::from_lit("}");
+
+ // 3 is the number of digits in http_status, assuming it is 3 digits
+ // number.
+ auto buflen = M1.size() + M2.size() + M3.size() + data.size() +
+ api_status_str.size() + 3;
+
+ auto buf = make_byte_ref(balloc, buflen);
+ auto p = buf.base;
+
+ p = std::copy(std::begin(M1), std::end(M1), p);
+ p = std::copy(std::begin(api_status_str), std::end(api_status_str), p);
+ p = std::copy(std::begin(M2), std::end(M2), p);
+ p = util::utos(p, http_status);
+ p = std::copy(std::begin(data), std::end(data), p);
+ p = std::copy(std::begin(M3), std::end(M3), p);
+
+ buf.len = p - buf.base;
+
+ auto content_length = util::make_string_ref_uint(balloc, buf.len);
+
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ content_length, false, http2::HD_CONTENT_LENGTH);
+
+ switch (http_status) {
+ case 400:
+ case 405:
+ case 413:
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+ break;
+ }
+
+ if (upstream->send_reply(downstream_, buf.base, buf.len) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+const APIEndpoint *lookup_api(const StringRef &path) {
+ switch (path.size()) {
+ case 26:
+ switch (path[25]) {
+ case 'g':
+ if (util::streq_l("/api/v1beta1/backendconfi", std::begin(path), 25)) {
+ return &apis()[0];
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (path[26]) {
+ case 'n':
+ if (util::streq_l("/api/v1beta1/configrevisio", std::begin(path), 26)) {
+ return &apis()[1];
+ }
+ break;
+ }
+ break;
+ }
+ return nullptr;
+}
+} // namespace
+
+int APIDownstreamConnection::push_request_headers() {
+ auto &req = downstream_->request();
+
+ auto path =
+ StringRef{std::begin(req.path),
+ std::find(std::begin(req.path), std::end(req.path), '?')};
+
+ api_ = lookup_api(path);
+
+ if (!api_) {
+ send_reply(404, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ switch (req.method) {
+ case HTTP_GET:
+ if (!(api_->allowed_methods & (1 << API_METHOD_GET))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ case HTTP_POST:
+ if (!(api_->allowed_methods & (1 << API_METHOD_POST))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ case HTTP_PUT:
+ if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) {
+ error_method_not_allowed();
+ return 0;
+ }
+ break;
+ default:
+ error_method_not_allowed();
+ return 0;
+ }
+
+ // This works with req.fs.content_length == -1
+ if (req.fs.content_length >
+ static_cast<int64_t>(get_config()->api.max_request_body)) {
+ send_reply(413, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ switch (req.method) {
+ case HTTP_POST:
+ case HTTP_PUT: {
+ char tempname[] = "/tmp/nghttpx-api.XXXXXX";
+#ifdef HAVE_MKOSTEMP
+ fd_ = mkostemp(tempname, O_CLOEXEC);
+#else // !HAVE_MKOSTEMP
+ fd_ = mkstemp(tempname);
+#endif // !HAVE_MKOSTEMP
+ if (fd_ == -1) {
+ send_reply(500, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+#ifndef HAVE_MKOSTEMP
+ util::make_socket_closeonexec(fd_);
+#endif // HAVE_MKOSTEMP
+ unlink(tempname);
+ break;
+ }
+ }
+
+ downstream_->set_request_header_sent(true);
+ auto src = downstream_->get_blocked_request_buf();
+ auto dest = downstream_->get_request_buf();
+ src->remove(*dest);
+
+ return 0;
+}
+
+int APIDownstreamConnection::error_method_not_allowed() {
+ auto &resp = downstream_->response();
+
+ size_t len = 0;
+ for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
+ if (api_->allowed_methods & (1 << i)) {
+ // The length of method + ", "
+ len += API_METHOD_STRING[i].size() + 2;
+ }
+ }
+
+ assert(len > 0);
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
+ if (api_->allowed_methods & (1 << i)) {
+ auto &s = API_METHOD_STRING[i];
+ p = std::copy(std::begin(s), std::end(s), p);
+ p = std::copy_n(", ", 2, p);
+ }
+ }
+
+ p -= 2;
+ *p = '\0';
+
+ resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p},
+ false, -1);
+ return send_reply(405, APIStatusCode::FAILURE);
+}
+
+int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (shutdown_read_ || !api_->require_body) {
+ return 0;
+ }
+
+ auto &req = downstream_->request();
+ auto &apiconf = get_config()->api;
+
+ if (static_cast<size_t>(req.recv_body_length) > apiconf.max_request_body) {
+ send_reply(413, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = write(fd_, data, datalen)) == -1 && errno == EINTR)
+ ;
+ if (nwrite == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not write API request body: errno=" << error;
+ send_reply(500, APIStatusCode::FAILURE);
+
+ return 0;
+ }
+
+ // We don't have to call Upstream::resume_read() here, because
+ // request buffer is effectively unlimited. Actually, we cannot
+ // call it here since it could recursively call this function again.
+
+ return 0;
+}
+
+int APIDownstreamConnection::end_upload_data() {
+ if (shutdown_read_) {
+ return 0;
+ }
+
+ return api_->handler(*this);
+}
+
+int APIDownstreamConnection::handle_backendconfig() {
+ auto &req = downstream_->request();
+
+ if (req.recv_body_length == 0) {
+ send_reply(200, APIStatusCode::SUCCESS);
+
+ return 0;
+ }
+
+ auto rp = mmap(nullptr, req.recv_body_length, PROT_READ, MAP_SHARED, fd_, 0);
+ if (rp == reinterpret_cast<void *>(-1)) {
+ send_reply(500, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto unmapper = defer(munmap, rp, req.recv_body_length);
+
+ Config new_config{};
+ new_config.conn.downstream = std::make_shared<DownstreamConfig>();
+ const auto &downstreamconf = new_config.conn.downstream;
+
+ auto config = get_config();
+ auto &src = config->conn.downstream;
+
+ downstreamconf->timeout = src->timeout;
+ downstreamconf->connections_per_host = src->connections_per_host;
+ downstreamconf->connections_per_frontend = src->connections_per_frontend;
+ downstreamconf->request_buffer_size = src->request_buffer_size;
+ downstreamconf->response_buffer_size = src->response_buffer_size;
+ downstreamconf->family = src->family;
+
+ std::set<StringRef> include_set;
+ std::map<StringRef, size_t> pattern_addr_indexer;
+
+ for (auto first = reinterpret_cast<const uint8_t *>(rp),
+ last = first + req.recv_body_length;
+ first != last;) {
+ auto eol = std::find(first, last, '\n');
+ if (eol == last) {
+ break;
+ }
+
+ if (first == eol || *first == '#') {
+ first = ++eol;
+ continue;
+ }
+
+ auto eq = std::find(first, eol, '=');
+ if (eq == eol) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto opt = StringRef{first, eq};
+ auto optval = StringRef{eq + 1, eol};
+
+ auto optid = option_lookup_token(opt.c_str(), opt.size());
+
+ switch (optid) {
+ case SHRPX_OPTID_BACKEND:
+ break;
+ default:
+ first = ++eol;
+ continue;
+ }
+
+ if (parse_config(&new_config, optid, opt, optval, include_set,
+ pattern_addr_indexer) != 0) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ first = ++eol;
+ }
+
+ auto &tlsconf = config->tls;
+ if (configure_downstream_group(&new_config, config->http2_proxy, true,
+ tlsconf) != 0) {
+ send_reply(400, APIStatusCode::FAILURE);
+ return 0;
+ }
+
+ auto conn_handler = worker_->get_connection_handler();
+
+ conn_handler->send_replace_downstream(downstreamconf);
+
+ send_reply(200, APIStatusCode::SUCCESS);
+
+ return 0;
+}
+
+int APIDownstreamConnection::handle_configrevision() {
+ auto config = get_config();
+ auto &balloc = downstream_->get_block_allocator();
+
+ // Construct the following string:
+ // ,
+ // "data":{
+ // "configRevision": N
+ // }
+ auto data = concat_string_ref(
+ balloc, StringRef::from_lit(R"(,"data":{"configRevision":)"),
+ util::make_string_ref_uint(balloc, config->config_revision),
+ StringRef::from_lit("}"));
+
+ send_reply(200, APIStatusCode::SUCCESS, data);
+
+ return 0;
+}
+
+void APIDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int APIDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) {
+ return 0;
+}
+
+void APIDownstreamConnection::force_resume_read() {}
+
+int APIDownstreamConnection::on_read() { return 0; }
+
+int APIDownstreamConnection::on_write() { return 0; }
+
+void APIDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+bool APIDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+APIDownstreamConnection::get_downstream_addr_group() const {
+ static std::shared_ptr<DownstreamAddrGroup> s;
+ return s;
+}
+
+DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_api_downstream_connection.h b/src/shrpx_api_downstream_connection.h
new file mode 100644
index 0000000..5d4182f
--- /dev/null
+++ b/src/shrpx_api_downstream_connection.h
@@ -0,0 +1,114 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_API_DOWNSTREAM_CONNECTION_H
+#define SHRPX_API_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Worker;
+
+// If new method is added, don't forget to update API_METHOD_STRING as
+// well.
+enum APIMethod {
+ API_METHOD_GET,
+ API_METHOD_POST,
+ API_METHOD_PUT,
+ API_METHOD_MAX,
+};
+
+// API status code, which is independent from HTTP status code. But
+// generally, 2xx code for SUCCESS, and otherwise FAILURE.
+enum class APIStatusCode {
+ SUCCESS,
+ FAILURE,
+};
+
+class APIDownstreamConnection;
+
+struct APIEndpoint {
+ // Endpoint path. It must start with "/api/".
+ StringRef path;
+ // true if we evaluate request body.
+ bool require_body;
+ // Allowed methods. This is bitwise OR of one or more of (1 <<
+ // APIMethod value).
+ uint8_t allowed_methods;
+ std::function<int(APIDownstreamConnection &)> handler;
+};
+
+class APIDownstreamConnection : public DownstreamConnection {
+public:
+ APIDownstreamConnection(Worker *worker);
+ virtual ~APIDownstreamConnection();
+ 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);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ int send_reply(unsigned int http_status, APIStatusCode api_status,
+ const StringRef &data = StringRef{});
+ int error_method_not_allowed();
+
+ // Handles backendconfig API request.
+ int handle_backendconfig();
+ // Handles configrevision API request.
+ int handle_configrevision();
+
+private:
+ Worker *worker_;
+ // This points to the requested APIEndpoint struct.
+ const APIEndpoint *api_;
+ // The file descriptor for temporary file to store request body.
+ int fd_;
+ // true if we stop reading request body.
+ bool shutdown_read_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_API_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc
new file mode 100644
index 0000000..1f0c01c
--- /dev/null
+++ b/src/shrpx_client_handler.cc
@@ -0,0 +1,1703 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_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_tls.h"
+#include "shrpx_worker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_downstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_api_downstream_connection.h"
+#include "shrpx_health_monitor_downstream_connection.h"
+#include "shrpx_null_downstream_connection.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_http3_upstream.h"
+#endif // ENABLE_HTTP3
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+#include "tls.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::noop() { return 0; }
+
+int ClientHandler::read_clear() {
+ auto should_break = false;
+ rb_.ensure_chunk();
+ for (;;) {
+ if (rb_.rleft() && on_read() != 0) {
+ return -1;
+ }
+ if (rb_.rleft() == 0) {
+ rb_.reset();
+ } else if (rb_.wleft() == 0) {
+ conn_.rlimit.stopw();
+ return 0;
+ }
+
+ if (!ev_is_active(&conn_.rev) || should_break) {
+ return 0;
+ }
+
+ auto nread = conn_.read_clear(rb_.last(), rb_.wleft());
+
+ if (nread == 0) {
+ if (rb_.rleft() == 0) {
+ rb_.release_chunk();
+ }
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ rb_.write(nread);
+ should_break = true;
+ }
+}
+
+int ClientHandler::write_clear() {
+ std::array<iovec, 2> iov;
+
+ for (;;) {
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = upstream_->response_riovec(iov.data(), iov.size());
+ if (iovcnt == 0) {
+ break;
+ }
+
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ upstream_->response_drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int ClientHandler::proxy_protocol_peek_clear() {
+ rb_.ensure_chunk();
+
+ assert(rb_.rleft() == 0);
+
+ auto nread = conn_.peek_clear(rb_.last(), rb_.wleft());
+ if (nread < 0) {
+ return -1;
+ }
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Peek " << nread
+ << " bytes from socket";
+ }
+
+ rb_.write(nread);
+
+ if (on_read() != 0) {
+ return -1;
+ }
+
+ rb_.reset();
+
+ 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() {
+ auto should_break = false;
+
+ ERR_clear_error();
+
+ rb_.ensure_chunk();
+
+ for (;;) {
+ // we should process buffered data first before we read EOF.
+ if (rb_.rleft() && on_read() != 0) {
+ return -1;
+ }
+ if (rb_.rleft() == 0) {
+ rb_.reset();
+ } else if (rb_.wleft() == 0) {
+ conn_.rlimit.stopw();
+ return 0;
+ }
+
+ if (!ev_is_active(&conn_.rev) || should_break) {
+ return 0;
+ }
+
+ auto nread = conn_.read_tls(rb_.last(), rb_.wleft());
+
+ if (nread == 0) {
+ if (rb_.rleft() == 0) {
+ rb_.release_chunk();
+ }
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ rb_.write(nread);
+ should_break = true;
+ }
+}
+
+int ClientHandler::write_tls() {
+ struct iovec iov;
+
+ ERR_clear_error();
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ auto iovcnt = upstream_->response_riovec(&iov, 1);
+ if (iovcnt == 0) {
+ conn_.start_tls_write_idle();
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+ }
+
+ for (;;) {
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ upstream_->response_drain(nwrite);
+
+ iovcnt = upstream_->response_riovec(&iov, 1);
+ if (iovcnt == 0) {
+ return 0;
+ }
+ }
+}
+
+#ifdef ENABLE_HTTP3
+int ClientHandler::read_quic(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ auto upstream = static_cast<Http3Upstream *>(upstream_.get());
+
+ return upstream->on_read(faddr, remote_addr, local_addr, pi, data, datalen);
+}
+
+int ClientHandler::write_quic() { return upstream_->on_write(); }
+#endif // ENABLE_HTTP3
+
+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() && upstream_->response_empty()) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int ClientHandler::upstream_http2_connhd_read() {
+ auto nread = std::min(left_connhd_len_, rb_.rleft());
+ if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_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);
+ conn_.rlimit.startw();
+
+ 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_MAGIC[NGHTTP2_CLIENT_MAGIC_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_MAGIC_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);
+ conn_.rlimit.startw();
+
+ 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(Worker *worker, int fd, SSL *ssl,
+ const StringRef &ipaddr, const StringRef &port,
+ int family, const UpstreamAddr *faddr)
+ : // We use balloc_ for TLS session ID (64), ipaddr (IPv6) (39),
+ // port (5), forwarded-for (IPv6) (41), alpn (5), proxyproto
+ // ipaddr (15), proxyproto port (5), sni (32, estimated). we
+ // need terminal NULL byte for each. We also require 8 bytes
+ // header for each allocation. We align at 16 bytes boundary,
+ // so the required space is 64 + 48 + 16 + 48 + 16 + 16 + 16 +
+ // 32 + 8 + 8 * 8 = 328.
+ balloc_(512, 512),
+ rb_(worker->get_mcpool()),
+ conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
+ get_config()->conn.upstream.timeout.write,
+ get_config()->conn.upstream.timeout.read,
+ get_config()->conn.upstream.ratelimit.write,
+ get_config()->conn.upstream.ratelimit.read, writecb, readcb,
+ timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout,
+ faddr->quic ? Proto::HTTP3 : Proto::NONE),
+ ipaddr_(make_string_ref(balloc_, ipaddr)),
+ port_(make_string_ref(balloc_, port)),
+ faddr_(faddr),
+ worker_(worker),
+ left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
+ affinity_hash_(0),
+ should_close_after_write_(false),
+ affinity_hash_computed_(false) {
+
+ ++worker_->get_worker_stat()->num_connections;
+
+ ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.);
+
+ reneg_shutdown_timer_.data = this;
+
+ if (!faddr->quic) {
+ conn_.rlimit.startw();
+ }
+ ev_timer_again(conn_.loop, &conn_.rt);
+
+ auto config = get_config();
+
+ if (!faddr->quic) {
+ if (faddr_->accept_proxy_protocol ||
+ config->conn.upstream.accept_proxy_protocol) {
+ read_ = &ClientHandler::proxy_protocol_peek_clear;
+ write_ = &ClientHandler::noop;
+ on_read_ = &ClientHandler::proxy_protocol_read;
+ on_write_ = &ClientHandler::upstream_noop;
+ } else {
+ setup_upstream_io_callback();
+ }
+ }
+
+ auto &fwdconf = config->http.forwarded;
+
+ if (fwdconf.params & FORWARDED_FOR) {
+ if (fwdconf.for_node_type == ForwardedNode::OBFUSCATED) {
+ // 1 for '_'
+ auto len = SHRPX_OBFUSCATED_NODE_LENGTH + 1;
+ // 1 for terminating NUL.
+ auto buf = make_byte_ref(balloc_, len + 1);
+ auto p = buf.base;
+ *p++ = '_';
+ p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH,
+ worker_->get_randgen());
+ *p = '\0';
+
+ forwarded_for_ = StringRef{buf.base, p};
+ } else {
+ init_forwarded_for(family, ipaddr_);
+ }
+ }
+}
+
+void ClientHandler::init_forwarded_for(int family, const StringRef &ipaddr) {
+ if (family == AF_INET6) {
+ // 2 for '[' and ']'
+ auto len = 2 + ipaddr.size();
+ // 1 for terminating NUL.
+ auto buf = make_byte_ref(balloc_, len + 1);
+ auto p = buf.base;
+ *p++ = '[';
+ p = std::copy(std::begin(ipaddr), std::end(ipaddr), p);
+ *p++ = ']';
+ *p = '\0';
+
+ forwarded_for_ = StringRef{buf.base, p};
+ } else {
+ // family == AF_INET or family == AF_UNIX
+ forwarded_for_ = ipaddr;
+ }
+}
+
+void ClientHandler::setup_upstream_io_callback() {
+ if (conn_.tls.ssl) {
+ conn_.prepare_server_handshake();
+ 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_ = std::make_unique<HttpsUpstream>(this);
+ alpn_ = StringRef::from_lit("http/1.1");
+ read_ = &ClientHandler::read_clear;
+ write_ = &ClientHandler::write_clear;
+ on_read_ = &ClientHandler::upstream_http1_connhd_read;
+ on_write_ = &ClientHandler::upstream_noop;
+ }
+}
+
+#ifdef ENABLE_HTTP3
+void ClientHandler::setup_http3_upstream(
+ std::unique_ptr<Http3Upstream> &&upstream) {
+ upstream_ = std::move(upstream);
+ write_ = &ClientHandler::write_quic;
+
+ auto config = get_config();
+
+ reset_upstream_read_timeout(config->conn.upstream.timeout.http3_read);
+}
+#endif // ENABLE_HTTP3
+
+ClientHandler::~ClientHandler() {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Deleting";
+ }
+
+ if (upstream_) {
+ upstream_->on_handler_delete();
+ }
+
+ auto worker_stat = worker_->get_worker_stat();
+ --worker_stat->num_connections;
+
+ if (worker_stat->num_connections == 0) {
+ worker_->schedule_clear_mcpool();
+ }
+
+ 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_->get_graceful_shutdown() && worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 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);
+ }
+}
+
+void ClientHandler::repeat_read_timer() {
+ ev_timer_again(conn_.loop, &conn_.rt);
+}
+
+void ClientHandler::stop_read_timer() { ev_timer_stop(conn_.loop, &conn_.rt); }
+
+int ClientHandler::validate_next_proto() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ // First set callback for catch all cases
+ on_read_ = &ClientHandler::upstream_read;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ StringRef proto;
+
+ if (next_proto) {
+ proto = StringRef{next_proto, next_proto_len};
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "The negotiated next protocol: " << proto;
+ }
+ } else {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1";
+ }
+
+ proto = StringRef::from_lit("http/1.1");
+ }
+
+ if (!tls::in_proto_list(get_config()->tls.alpn_list, proto)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "The negotiated protocol is not supported: " << proto;
+ }
+ return -1;
+ }
+
+ if (util::check_h2_is_selected(proto)) {
+ on_read_ = &ClientHandler::upstream_http2_connhd_read;
+
+ auto http2_upstream = std::make_unique<Http2Upstream>(this);
+
+ upstream_ = std::move(http2_upstream);
+ alpn_ = make_string_ref(balloc_, proto);
+
+ // 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 (proto == StringRef::from_lit("http/1.1")) {
+ upstream_ = std::make_unique<HttpsUpstream>(this);
+ alpn_ = StringRef::from_lit("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() {
+ if (rb_.chunk_avail()) {
+ auto rv = on_read_(*this);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ conn_.handle_tls_pending_read();
+ return 0;
+}
+int ClientHandler::on_write() { return on_write_(*this); }
+
+const StringRef &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 (!dconn->poolable()) {
+ return;
+ }
+
+ dconn->set_client_handler(nullptr);
+
+ auto &group = dconn->get_downstream_addr_group();
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get()
+ << " in group " << group;
+ }
+
+ auto addr = dconn->get_addr();
+ auto &dconn_pool = addr->dconn_pool;
+ dconn_pool->add_downstream_connection(std::move(dconn));
+}
+
+namespace {
+// Computes 32bits hash for session affinity for IP address |ip|.
+uint32_t compute_affinity_from_ip(const StringRef &ip) {
+ int rv;
+ std::array<uint8_t, 32> buf;
+
+ rv = util::sha256(buf.data(), ip);
+ if (rv != 0) {
+ // Not sure when sha256 failed. Just fall back to another
+ // function.
+ return util::hash32(ip);
+ }
+
+ return (static_cast<uint32_t>(buf[0]) << 24) |
+ (static_cast<uint32_t>(buf[1]) << 16) |
+ (static_cast<uint32_t>(buf[2]) << 8) | static_cast<uint32_t>(buf[3]);
+}
+} // namespace
+
+Http2Session *ClientHandler::get_http2_session(
+ const std::shared_ptr<DownstreamAddrGroup> &group, DownstreamAddr *addr) {
+ auto &shared_addr = group->shared_addr;
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Selected DownstreamAddr=" << addr
+ << ", index=" << (addr - shared_addr->addrs.data());
+ }
+
+ for (auto session = addr->http2_extra_freelist.head; session;) {
+ auto next = session->dlnext;
+
+ if (session->max_concurrency_reached(0)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "Maximum streams have been reached for Http2Session(" << session
+ << "). Skip it";
+ }
+
+ session->remove_from_freelist();
+ session = next;
+
+ continue;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Use Http2Session " << session
+ << " from http2_extra_freelist";
+ }
+
+ if (session->max_concurrency_reached(1)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Maximum streams are reached for Http2Session("
+ << session << ").";
+ }
+
+ session->remove_from_freelist();
+ }
+ return session;
+ }
+
+ auto session = new Http2Session(conn_.loop, worker_->get_cl_ssl_ctx(),
+ worker_, group, addr);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Create new Http2Session " << session;
+ }
+
+ session->add_to_extra_freelist();
+
+ return session;
+}
+
+uint32_t ClientHandler::get_affinity_cookie(Downstream *downstream,
+ const StringRef &cookie_name) {
+ auto h = downstream->find_affinity_cookie(cookie_name);
+ if (h) {
+ return h;
+ }
+
+ auto d = std::uniform_int_distribution<uint32_t>(1);
+ auto rh = d(worker_->get_randgen());
+ h = util::hash32(StringRef{reinterpret_cast<uint8_t *>(&rh),
+ reinterpret_cast<uint8_t *>(&rh) + sizeof(rh)});
+
+ downstream->renew_affinity_cookie(h);
+
+ return h;
+}
+
+namespace {
+void reschedule_addr(
+ std::priority_queue<DownstreamAddrEntry, std::vector<DownstreamAddrEntry>,
+ DownstreamAddrEntryGreater> &pq,
+ DownstreamAddr *addr) {
+ auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + addr->pending_penalty;
+ addr->cycle += penalty / addr->weight;
+ addr->pending_penalty = penalty % addr->weight;
+
+ pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle});
+ addr->queued = true;
+}
+} // namespace
+
+namespace {
+void reschedule_wg(
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater> &pq,
+ WeightGroup *wg) {
+ auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + wg->pending_penalty;
+ wg->cycle += penalty / wg->weight;
+ wg->pending_penalty = penalty % wg->weight;
+
+ pq.push(WeightGroupEntry{wg, wg->seq, wg->cycle});
+ wg->queued = true;
+}
+} // namespace
+
+DownstreamAddr *ClientHandler::get_downstream_addr(int &err,
+ DownstreamAddrGroup *group,
+ Downstream *downstream) {
+ err = 0;
+
+ switch (faddr_->alt_mode) {
+ case UpstreamAltMode::API:
+ case UpstreamAltMode::HEALTHMON:
+ assert(0);
+ default:
+ break;
+ }
+
+ auto &shared_addr = group->shared_addr;
+
+ if (shared_addr->affinity.type != SessionAffinity::NONE) {
+ uint32_t hash;
+ switch (shared_addr->affinity.type) {
+ case SessionAffinity::IP:
+ if (!affinity_hash_computed_) {
+ affinity_hash_ = compute_affinity_from_ip(ipaddr_);
+ affinity_hash_computed_ = true;
+ }
+ hash = affinity_hash_;
+ break;
+ case SessionAffinity::COOKIE:
+ if (shared_addr->affinity.cookie.stickiness ==
+ SessionAffinityCookieStickiness::STRICT) {
+ return get_downstream_addr_strict_affinity(err, shared_addr,
+ downstream);
+ }
+
+ hash = get_affinity_cookie(downstream, shared_addr->affinity.cookie.name);
+ break;
+ default:
+ assert(0);
+ }
+
+ const auto &affinity_hash = shared_addr->affinity_hash;
+
+ auto it = std::lower_bound(
+ std::begin(affinity_hash), std::end(affinity_hash), hash,
+ [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; });
+
+ if (it == std::end(affinity_hash)) {
+ it = std::begin(affinity_hash);
+ }
+
+ auto aff_idx =
+ static_cast<size_t>(std::distance(std::begin(affinity_hash), it));
+ auto idx = (*it).idx;
+ auto addr = &shared_addr->addrs[idx];
+
+ if (addr->connect_blocker->blocked()) {
+ size_t i;
+ for (i = aff_idx + 1; i != aff_idx; ++i) {
+ if (i == shared_addr->affinity_hash.size()) {
+ i = 0;
+ }
+ addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx];
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+ break;
+ }
+ if (i == aff_idx) {
+ err = -1;
+ return nullptr;
+ }
+ }
+
+ return addr;
+ }
+
+ auto &wgpq = shared_addr->pq;
+
+ for (;;) {
+ if (wgpq.empty()) {
+ CLOG(INFO, this) << "No working downstream address found";
+ err = -1;
+ return nullptr;
+ }
+
+ auto wg = wgpq.top().wg;
+ wgpq.pop();
+ wg->queued = false;
+
+ for (;;) {
+ if (wg->pq.empty()) {
+ break;
+ }
+
+ auto addr = wg->pq.top().addr;
+ wg->pq.pop();
+ addr->queued = false;
+
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+
+ reschedule_addr(wg->pq, addr);
+ reschedule_wg(wgpq, wg);
+
+ return addr;
+ }
+ }
+}
+
+DownstreamAddr *ClientHandler::get_downstream_addr_strict_affinity(
+ int &err, const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ Downstream *downstream) {
+ const auto &affinity_hash = shared_addr->affinity_hash;
+
+ auto h = downstream->find_affinity_cookie(shared_addr->affinity.cookie.name);
+ if (h) {
+ auto it = shared_addr->affinity_hash_map.find(h);
+ if (it != std::end(shared_addr->affinity_hash_map)) {
+ auto addr = &shared_addr->addrs[(*it).second];
+ if (!addr->connect_blocker->blocked()) {
+ return addr;
+ }
+ }
+ } else {
+ auto d = std::uniform_int_distribution<uint32_t>(1);
+ auto rh = d(worker_->get_randgen());
+ h = util::hash32(StringRef{reinterpret_cast<uint8_t *>(&rh),
+ reinterpret_cast<uint8_t *>(&rh) + sizeof(rh)});
+ }
+
+ // Client is not bound to a particular backend, or the bound backend
+ // is not found, or is blocked. Find new backend using h. Using
+ // existing h allows us to find new server in a deterministic way.
+ // It is preferable because multiple concurrent requests with the
+ // stale cookie might be in-flight.
+ auto it = std::lower_bound(
+ std::begin(affinity_hash), std::end(affinity_hash), h,
+ [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; });
+
+ if (it == std::end(affinity_hash)) {
+ it = std::begin(affinity_hash);
+ }
+
+ auto aff_idx =
+ static_cast<size_t>(std::distance(std::begin(affinity_hash), it));
+ auto idx = (*it).idx;
+ auto addr = &shared_addr->addrs[idx];
+
+ if (addr->connect_blocker->blocked()) {
+ size_t i;
+ for (i = aff_idx + 1; i != aff_idx; ++i) {
+ if (i == shared_addr->affinity_hash.size()) {
+ i = 0;
+ }
+ addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx];
+ if (addr->connect_blocker->blocked()) {
+ continue;
+ }
+ break;
+ }
+ if (i == aff_idx) {
+ err = -1;
+ return nullptr;
+ }
+ }
+
+ downstream->renew_affinity_cookie(addr->affinity_hash);
+
+ return addr;
+}
+
+std::unique_ptr<DownstreamConnection>
+ClientHandler::get_downstream_connection(int &err, Downstream *downstream) {
+ size_t group_idx;
+ auto &downstreamconf = *worker_->get_downstream_config();
+ auto &routerconf = downstreamconf.router;
+
+ auto catch_all = downstreamconf.addr_group_catch_all;
+ auto &groups = worker_->get_downstream_addr_groups();
+
+ auto &req = downstream->request();
+
+ err = 0;
+
+ switch (faddr_->alt_mode) {
+ case UpstreamAltMode::API: {
+ auto dconn = std::make_unique<APIDownstreamConnection>(worker_);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+ case UpstreamAltMode::HEALTHMON: {
+ auto dconn = std::make_unique<HealthMonitorDownstreamConnection>();
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+ default:
+ break;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ StringRef authority, path;
+
+ if (req.forwarded_once) {
+ if (groups.size() != 1) {
+ authority = req.orig_authority;
+ path = req.orig_path;
+ }
+ } else {
+ if (faddr_->sni_fwd) {
+ authority = sni_;
+ } else if (!req.authority.empty()) {
+ authority = req.authority;
+ } else {
+ auto h = req.fs.header(http2::HD_HOST);
+ if (h) {
+ authority = h->value;
+ }
+ }
+
+ // CONNECT method does not have path. But we requires path in
+ // host-path mapping. As workaround, we assume that path is
+ // "/".
+ if (!req.regular_connect_method()) {
+ path = req.path;
+ }
+
+ // Cache the authority and path used for the first-time backend
+ // selection because per-pattern mruby script can change them.
+ req.orig_authority = authority;
+ req.orig_path = path;
+ req.forwarded_once = true;
+ }
+
+ // Fast path. If we have one group, it must be catch-all group.
+ if (groups.size() == 1) {
+ group_idx = 0;
+ } else {
+ group_idx = match_downstream_addr_group(routerconf, authority, path, groups,
+ catch_all, balloc);
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
+ }
+
+ if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream address group " << group_idx
+ << " requires frontend TLS connection.";
+ }
+ err = SHRPX_ERR_TLS_REQUIRED;
+ return nullptr;
+ }
+
+ auto &group = groups[group_idx];
+
+ if (group->shared_addr->dnf) {
+ auto dconn = std::make_unique<NullDownstreamConnection>(group);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ auto addr = get_downstream_addr(err, group.get(), downstream);
+ if (addr == nullptr) {
+ return nullptr;
+ }
+
+ if (addr->proto == Proto::HTTP1) {
+ auto dconn = addr->dconn_pool->pop_downstream_connection();
+ if (dconn) {
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ if (worker_->get_connect_blocker()->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return nullptr;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream connection pool is empty."
+ << " Create new one";
+ }
+
+ dconn = std::make_unique<HttpDownstreamConnection>(group, addr, conn_.loop,
+ worker_);
+ dconn->set_client_handler(this);
+ return dconn;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "Downstream connection pool is empty."
+ << " Create new one";
+ }
+
+ auto http2session = get_http2_session(group, addr);
+ auto dconn = std::make_unique<Http2DownstreamConnection>(http2session);
+ dconn->set_client_handler(this);
+ return dconn;
+}
+
+MemchunkPool *ClientHandler::get_mcpool() { return worker_->get_mcpool(); }
+
+SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; }
+
+void ClientHandler::direct_http2_upgrade() {
+ upstream_ = std::make_unique<Http2Upstream>(this);
+ alpn_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID);
+ on_read_ = &ClientHandler::upstream_read;
+ write_ = &ClientHandler::write_clear;
+}
+
+int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
+ auto upstream = std::make_unique<Http2Upstream>(this);
+
+ auto output = upstream->get_response_buf();
+
+ // We might have written non-final header in response_buf, in this
+ // case, response_state is still INITIAL. If this non-final header
+ // and upgrade header fit in output buffer, do upgrade. Otherwise,
+ // to avoid to send this non-final header as response body in HTTP/2
+ // upstream, fail upgrade.
+ auto downstream = http->get_downstream();
+ auto input = downstream->get_response_buf();
+
+ if (upstream->upgrade_upstream(http) != 0) {
+ return -1;
+ }
+ // http pointer is now owned by upstream.
+ upstream_.release();
+ // 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_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID);
+ on_read_ = &ClientHandler::upstream_http2_connhd_read;
+ write_ = &ClientHandler::write_clear;
+
+ input->remove(*output, input->rleft());
+
+ constexpr auto res =
+ StringRef::from_lit("HTTP/1.1 101 Switching Protocols\r\n"
+ "Connection: Upgrade\r\n"
+ "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
+ "\r\n");
+
+ output->append(res);
+ upstream_ = std::move(upstream);
+
+ signal_write();
+ return 0;
+}
+
+bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; }
+
+StringRef ClientHandler::get_upstream_scheme() const {
+ if (conn_.tls.ssl) {
+ return StringRef::from_lit("https");
+ } else {
+ return StringRef::from_lit("http");
+ }
+}
+
+void ClientHandler::start_immediate_shutdown() {
+ ev_timer_start(conn_.loop, &reneg_shutdown_timer_);
+}
+
+void ClientHandler::write_accesslog(Downstream *downstream) {
+ auto &req = downstream->request();
+
+ auto config = get_config();
+
+ if (!req.tstamp) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ req.tstamp = lgconf->tstamp;
+ }
+
+ upstream_accesslog(
+ config->logging.access.format,
+ LogSpec{
+ downstream,
+ ipaddr_,
+ alpn_,
+ sni_,
+ conn_.tls.ssl,
+ std::chrono::high_resolution_clock::now(), // request_end_time
+ port_,
+ faddr_->port,
+ config->pid,
+ });
+}
+
+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; }
+
+ev_io *ClientHandler::get_wev() { return &conn_.wev; }
+
+Worker *ClientHandler::get_worker() const { return worker_; }
+
+namespace {
+ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
+ auto p = first;
+ int32_t port = 0;
+
+ if (p == last) {
+ return -1;
+ }
+
+ if (*p == '0') {
+ if (p + 1 != last && util::is_digit(*(p + 1))) {
+ return -1;
+ }
+ return 1;
+ }
+
+ for (; p != last && util::is_digit(*p); ++p) {
+ port *= 10;
+ port += *p - '0';
+
+ if (port > 65535) {
+ return -1;
+ }
+ }
+
+ return p - first;
+}
+} // namespace
+
+int ClientHandler::on_proxy_protocol_finish() {
+ auto len = rb_.pos() - rb_.begin();
+
+ assert(len);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Draining " << len
+ << " bytes from socket";
+ }
+
+ rb_.reset();
+
+ if (conn_.read_nolim_clear(rb_.pos(), len) < 0) {
+ return -1;
+ }
+
+ rb_.reset();
+
+ setup_upstream_io_callback();
+
+ return 0;
+}
+
+namespace {
+// PROXY-protocol v2 header signature
+constexpr uint8_t PROXY_PROTO_V2_SIG[] =
+ "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+
+// PROXY-protocol v2 header length
+constexpr size_t PROXY_PROTO_V2_HDLEN =
+ str_size(PROXY_PROTO_V2_SIG) + /* ver_cmd(1) + fam(1) + len(2) = */ 4;
+} // namespace
+
+// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+int ClientHandler::proxy_protocol_read() {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Started";
+ }
+
+ auto first = rb_.pos();
+
+ if (rb_.rleft() >= PROXY_PROTO_V2_HDLEN &&
+ (*(first + str_size(PROXY_PROTO_V2_SIG)) & 0xf0) == 0x20) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol: Detected v2 header signature";
+ }
+ return proxy_protocol_v2_read();
+ }
+
+ // NULL character really destroys functions which expects NULL
+ // terminated string. We won't expect it in PROXY protocol line, so
+ // find it here.
+ auto chrs = std::array<char, 2>{'\n', '\0'};
+
+ constexpr size_t MAX_PROXY_LINELEN = 107;
+
+ auto bufend = rb_.pos() + std::min(MAX_PROXY_LINELEN, rb_.rleft());
+
+ auto end =
+ std::find_first_of(rb_.pos(), bufend, std::begin(chrs), std::end(chrs));
+
+ if (end == bufend || *end == '\0' || end == rb_.pos() || *(end - 1) != '\r') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
+ }
+ return -1;
+ }
+
+ --end;
+
+ constexpr auto HEADER = StringRef::from_lit("PROXY ");
+
+ if (static_cast<size_t>(end - rb_.pos()) < HEADER.size()) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: PROXY version 1 ID not found";
+ }
+ return -1;
+ }
+
+ if (!util::streq(HEADER, StringRef{rb_.pos(), HEADER.size()})) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Bad PROXY protocol version 1 ID";
+ }
+ return -1;
+ }
+
+ rb_.drain(HEADER.size());
+
+ int family;
+
+ if (rb_.pos()[0] == 'T') {
+ if (end - rb_.pos() < 5) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
+ }
+ return -1;
+ }
+
+ if (rb_.pos()[1] != 'C' || rb_.pos()[2] != 'P') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ switch (rb_.pos()[3]) {
+ case '4':
+ family = AF_INET;
+ break;
+ case '6':
+ family = AF_INET6;
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ rb_.drain(5);
+ } else {
+ if (end - rb_.pos() < 7) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
+ }
+ return -1;
+ }
+ if (!util::streq_l("UNKNOWN", rb_.pos(), 7)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
+ }
+ return -1;
+ }
+
+ rb_.drain(end + 2 - rb_.pos());
+
+ return on_proxy_protocol_finish();
+ }
+
+ // source address
+ auto token_end = std::find(rb_.pos(), end, ' ');
+ if (token_end == end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Source address not found";
+ }
+ return -1;
+ }
+
+ *token_end = '\0';
+ if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos()), family)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source address";
+ }
+ return -1;
+ }
+
+ auto src_addr = rb_.pos();
+ auto src_addrlen = token_end - rb_.pos();
+
+ rb_.drain(token_end - rb_.pos() + 1);
+
+ // destination address
+ token_end = std::find(rb_.pos(), end, ' ');
+ if (token_end == end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Destination address not found";
+ }
+ return -1;
+ }
+
+ *token_end = '\0';
+ if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos()), family)) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination address";
+ }
+ return -1;
+ }
+
+ // Currently we don't use destination address
+
+ rb_.drain(token_end - rb_.pos() + 1);
+
+ // source port
+ auto n = parse_proxy_line_port(rb_.pos(), end);
+ if (n <= 0 || *(rb_.pos() + n) != ' ') {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source port";
+ }
+ return -1;
+ }
+
+ rb_.pos()[n] = '\0';
+ auto src_port = rb_.pos();
+ auto src_portlen = n;
+
+ rb_.drain(n + 1);
+
+ // destination port
+ n = parse_proxy_line_port(rb_.pos(), end);
+ if (n <= 0 || rb_.pos() + n != end) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination port";
+ }
+ return -1;
+ }
+
+ // Currently we don't use destination port
+
+ rb_.drain(end + 2 - rb_.pos());
+
+ ipaddr_ =
+ make_string_ref(balloc_, StringRef{src_addr, src_addr + src_addrlen});
+ port_ = make_string_ref(balloc_, StringRef{src_port, src_port + src_portlen});
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v1: Finished, " << (rb_.pos() - first)
+ << " bytes read";
+ }
+
+ auto config = get_config();
+ auto &fwdconf = config->http.forwarded;
+
+ if ((fwdconf.params & FORWARDED_FOR) &&
+ fwdconf.for_node_type == ForwardedNode::IP) {
+ init_forwarded_for(family, ipaddr_);
+ }
+
+ return on_proxy_protocol_finish();
+}
+
+int ClientHandler::proxy_protocol_v2_read() {
+ // Assume that first str_size(PROXY_PROTO_V2_SIG) octets match v2
+ // protocol signature and followed by the bytes which indicates v2.
+ assert(rb_.rleft() >= PROXY_PROTO_V2_HDLEN);
+
+ auto p = rb_.pos() + str_size(PROXY_PROTO_V2_SIG);
+
+ assert(((*p) & 0xf0) == 0x20);
+
+ enum { LOCAL, PROXY } cmd;
+
+ auto cmd_bits = (*p++) & 0xf;
+ switch (cmd_bits) {
+ case 0x0:
+ cmd = LOCAL;
+ break;
+ case 0x01:
+ cmd = PROXY;
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unknown command " << log::hex
+ << cmd_bits;
+ }
+ return -1;
+ }
+
+ auto fam = *p++;
+ uint16_t len;
+ memcpy(&len, p, sizeof(len));
+ len = ntohs(len);
+
+ p += sizeof(len);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Detected family=" << log::hex << fam
+ << ", len=" << log::dec << len;
+ }
+
+ if (rb_.last() - p < len) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "PROXY-protocol-v2: Prematurely truncated header block; require "
+ << len << " bytes, " << rb_.last() - p << " bytes left";
+ }
+ return -1;
+ }
+
+ int family;
+ std::array<char, std::max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)> src_addr,
+ dst_addr;
+ size_t addrlen;
+
+ switch (fam) {
+ case 0x11:
+ case 0x12:
+ if (len < 12) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET addresses";
+ }
+ return -1;
+ }
+ family = AF_INET;
+ addrlen = 4;
+ break;
+ case 0x21:
+ case 0x22:
+ if (len < 36) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET6 addresses";
+ }
+ return -1;
+ }
+ family = AF_INET6;
+ addrlen = 16;
+ break;
+ case 0x31:
+ case 0x32:
+ if (len < 216) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_UNIX addresses";
+ }
+ return -1;
+ }
+ // fall through
+ case 0x00: {
+ // UNSPEC and UNIX are just ignored.
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Ignore combination of address "
+ "family and protocol "
+ << log::hex << fam;
+ }
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unknown combination of address "
+ "family and protocol "
+ << log::hex << fam;
+ }
+ return -1;
+ }
+
+ if (cmd != PROXY) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Ignore non-PROXY command";
+ }
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+ }
+
+ if (inet_ntop(family, p, src_addr.data(), src_addr.size()) == nullptr) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Unable to parse source address";
+ }
+ return -1;
+ }
+
+ p += addrlen;
+
+ if (inet_ntop(family, p, dst_addr.data(), dst_addr.size()) == nullptr) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this)
+ << "PROXY-protocol-v2: Unable to parse destination address";
+ }
+ return -1;
+ }
+
+ p += addrlen;
+
+ uint16_t src_port;
+
+ memcpy(&src_port, p, sizeof(src_port));
+ src_port = ntohs(src_port);
+
+ // We don't use destination port.
+ p += 4;
+
+ ipaddr_ = make_string_ref(balloc_, StringRef{src_addr.data()});
+ port_ = util::make_string_ref_uint(balloc_, src_port);
+
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, this) << "PROXY-protocol-v2: Finished reading proxy addresses, "
+ << p - rb_.pos() << " bytes read, "
+ << PROXY_PROTO_V2_HDLEN + len - (p - rb_.pos())
+ << " bytes left";
+ }
+
+ auto config = get_config();
+ auto &fwdconf = config->http.forwarded;
+
+ if ((fwdconf.params & FORWARDED_FOR) &&
+ fwdconf.for_node_type == ForwardedNode::IP) {
+ init_forwarded_for(family, ipaddr_);
+ }
+
+ rb_.drain(PROXY_PROTO_V2_HDLEN + len);
+ return on_proxy_protocol_finish();
+}
+
+StringRef ClientHandler::get_forwarded_by() const {
+ auto &fwdconf = get_config()->http.forwarded;
+
+ if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED) {
+ return fwdconf.by_obfuscated;
+ }
+
+ return faddr_->hostport;
+}
+
+StringRef ClientHandler::get_forwarded_for() const { return forwarded_for_; }
+
+const UpstreamAddr *ClientHandler::get_upstream_addr() const { return faddr_; }
+
+Connection *ClientHandler::get_connection() { return &conn_; };
+
+void ClientHandler::set_tls_sni(const StringRef &sni) {
+ sni_ = make_string_ref(balloc_, sni);
+}
+
+StringRef ClientHandler::get_tls_sni() const { return sni_; }
+
+StringRef ClientHandler::get_alpn() const { return alpn_; }
+
+BlockAllocator &ClientHandler::get_block_allocator() { return balloc_; }
+
+void ClientHandler::set_alpn_from_conn() {
+ const unsigned char *alpn;
+ unsigned int alpnlen;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &alpn, &alpnlen);
+
+ alpn_ = make_string_ref(balloc_, StringRef{alpn, alpnlen});
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h
new file mode 100644
index 0000000..511dd91
--- /dev/null
+++ b/src/shrpx_client_handler.h
@@ -0,0 +1,236 @@
+/*
+ * 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"
+#include "memchunk.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+class HttpsUpstream;
+class ConnectBlocker;
+class DownstreamConnectionPool;
+class Worker;
+class Downstream;
+struct WorkerStat;
+struct DownstreamAddrGroup;
+struct SharedDownstreamAddr;
+struct DownstreamAddr;
+#ifdef ENABLE_HTTP3
+class Http3Upstream;
+#endif // ENABLE_HTTP3
+
+class ClientHandler {
+public:
+ ClientHandler(Worker *worker, int fd, SSL *ssl, const StringRef &ipaddr,
+ const StringRef &port, int family, const UpstreamAddr *faddr);
+ ~ClientHandler();
+
+ int noop();
+ // Performs clear text I/O
+ int read_clear();
+ int write_clear();
+ // Specialized for PROXY-protocol use; peek data from socket.
+ int proxy_protocol_peek_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();
+
+ int proxy_protocol_read();
+ int proxy_protocol_v2_read();
+ int on_proxy_protocol_finish();
+
+ // 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 StringRef &get_ipaddr() 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);
+ DownstreamAddr *get_downstream_addr(int &err, DownstreamAddrGroup *group,
+ Downstream *downstream);
+ // Returns DownstreamConnection object based on request path. This
+ // function returns non-null DownstreamConnection, and assigns 0 to
+ // |err| if it succeeds, or returns nullptr, and assigns negative
+ // error code to |err|.
+ std::unique_ptr<DownstreamConnection>
+ get_downstream_connection(int &err, Downstream *downstream);
+ MemchunkPool *get_mcpool();
+ SSL *get_ssl() 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"
+ StringRef get_upstream_scheme() const;
+ void start_immediate_shutdown();
+
+ // Writes upstream accesslog using |downstream|. The |downstream|
+ // must not be nullptr.
+ void write_accesslog(Downstream *downstream);
+
+ Worker *get_worker() const;
+
+ // Initializes forwarded_for_.
+ void init_forwarded_for(int family, const StringRef &ipaddr);
+
+ using ReadBuf = DefaultMemchunkBuffer;
+
+ ReadBuf *get_rb();
+
+ RateLimit *get_rlimit();
+ RateLimit *get_wlimit();
+
+ void signal_write();
+ ev_io *get_wev();
+
+ void setup_upstream_io_callback();
+
+#ifdef ENABLE_HTTP3
+ void setup_http3_upstream(std::unique_ptr<Http3Upstream> &&upstream);
+ int read_quic(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+ int write_quic();
+#endif // ENABLE_HTTP3
+
+ // Returns string suitable for use in "by" parameter of Forwarded
+ // header field.
+ StringRef get_forwarded_by() const;
+ // Returns string suitable for use in "for" parameter of Forwarded
+ // header field.
+ StringRef get_forwarded_for() const;
+
+ Http2Session *
+ get_http2_session(const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr);
+
+ // Returns an affinity cookie value for |downstream|. |cookie_name|
+ // is used to inspect cookie header field in request header fields.
+ uint32_t get_affinity_cookie(Downstream *downstream,
+ const StringRef &cookie_name);
+
+ DownstreamAddr *get_downstream_addr_strict_affinity(
+ int &err, const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ Downstream *downstream);
+
+ const UpstreamAddr *get_upstream_addr() const;
+
+ void repeat_read_timer();
+ void stop_read_timer();
+
+ Connection *get_connection();
+
+ // Stores |sni| which is TLS SNI extension value client sent in this
+ // connection.
+ void set_tls_sni(const StringRef &sni);
+ // Returns TLS SNI extension value client sent in this connection.
+ StringRef get_tls_sni() const;
+
+ // Returns ALPN negotiated in this connection.
+ StringRef get_alpn() const;
+
+ BlockAllocator &get_block_allocator();
+
+ void set_alpn_from_conn();
+
+private:
+ // Allocator to allocate memory for connection-wide objects. Make
+ // sure that the allocations must be bounded, and not proportional
+ // to the number of requests.
+ BlockAllocator balloc_;
+ DefaultMemchunkBuffer rb_;
+ Connection conn_;
+ ev_timer reneg_shutdown_timer_;
+ std::unique_ptr<Upstream> upstream_;
+ // IP address of client. If UNIX domain socket is used, this is
+ // "localhost".
+ StringRef ipaddr_;
+ StringRef port_;
+ // The ALPN identifier negotiated for this connection.
+ StringRef alpn_;
+ // The client address used in "for" parameter of Forwarded header
+ // field.
+ StringRef forwarded_for_;
+ // lowercased TLS SNI which client sent.
+ StringRef sni_;
+ std::function<int(ClientHandler &)> read_, write_;
+ std::function<int(ClientHandler &)> on_read_, on_write_;
+ // Address of frontend listening socket
+ const UpstreamAddr *faddr_;
+ Worker *worker_;
+ // The number of bytes of HTTP/2 client connection header to read
+ size_t left_connhd_len_;
+ // hash for session affinity using client IP
+ uint32_t affinity_hash_;
+ bool should_close_after_write_;
+ // true if affinity_hash_ is computed
+ bool affinity_hash_computed_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CLIENT_HANDLER_H
diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc
new file mode 100644
index 0000000..d6d0d07
--- /dev/null
+++ b/src/shrpx_config.cc
@@ -0,0 +1,4694 @@
+/*
+ * 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"
+
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif // HAVE_PWD_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <dirent.h>
+
+#include <cstring>
+#include <cerrno>
+#include <limits>
+#include <fstream>
+#include <unordered_map>
+
+#include <nghttp2/nghttp2.h>
+
+#include "url-parser/url_parser.h"
+
+#include "shrpx_log.h"
+#include "shrpx_tls.h"
+#include "shrpx_http.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "util.h"
+#include "base64.h"
+#include "ssl_compat.h"
+#include "xsi_strerror.h"
+
+namespace shrpx {
+
+namespace {
+Config *config;
+} // namespace
+
+constexpr auto SHRPX_UNIX_PATH_PREFIX = StringRef::from_lit("unix:");
+
+const Config *get_config() { return config; }
+
+Config *mod_config() { return config; }
+
+std::unique_ptr<Config> replace_config(std::unique_ptr<Config> another) {
+ auto p = config;
+ config = another.release();
+ return std::unique_ptr<Config>(p);
+}
+
+void create_config() { config = new Config(); }
+
+Config::~Config() {
+ auto &upstreamconf = http2.upstream;
+
+ nghttp2_option_del(upstreamconf.option);
+ nghttp2_option_del(upstreamconf.alt_mode_option);
+ nghttp2_session_callbacks_del(upstreamconf.callbacks);
+
+ auto &downstreamconf = http2.downstream;
+
+ nghttp2_option_del(downstreamconf.option);
+ nghttp2_session_callbacks_del(downstreamconf.callbacks);
+
+ auto &dumpconf = http2.upstream.debug.dump;
+
+ if (dumpconf.request_header) {
+ fclose(dumpconf.request_header);
+ }
+
+ if (dumpconf.response_header) {
+ fclose(dumpconf.response_header);
+ }
+}
+
+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 StringRef &hostport, const StringRef &opt) {
+ // host and port in |hostport| is separated by single ','.
+ auto sep = std::find(std::begin(hostport), std::end(hostport), ',');
+ if (sep == std::end(hostport)) {
+ LOG(ERROR) << opt << ": Invalid host, port: " << hostport;
+ return -1;
+ }
+ size_t len = sep - std::begin(hostport);
+ if (hostlen < len + 1) {
+ LOG(ERROR) << opt << ": Hostname too long: " << hostport;
+ return -1;
+ }
+ std::copy(std::begin(hostport), sep, host);
+ host[len] = '\0';
+
+ auto portstr = StringRef{sep + 1, std::end(hostport)};
+ auto d = util::parse_uint(portstr);
+ if (1 <= d && d <= std::numeric_limits<uint16_t>::max()) {
+ *port_ptr = d;
+ return 0;
+ }
+
+ LOG(ERROR) << opt << ": Port is invalid: " << portstr;
+ return -1;
+}
+} // namespace
+
+namespace {
+bool is_secure(const StringRef &filename) {
+ struct stat buf;
+ int rv = stat(filename.c_str(), &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<StringRef> &files,
+ const EVP_CIPHER *cipher, const EVP_MD *hmac) {
+ auto ticket_keys = std::make_unique<TicketKeys>();
+ auto &keys = ticket_keys->keys;
+ keys.resize(files.size());
+ auto enc_keylen = EVP_CIPHER_key_length(cipher);
+ auto hmac_keylen = EVP_MD_size(hmac);
+ if (cipher == EVP_aes_128_cbc()) {
+ // backward compatibility, as a legacy of using same file format
+ // with nginx and apache.
+ hmac_keylen = 16;
+ }
+ auto expectedlen = keys[0].data.name.size() + enc_keylen + hmac_keylen;
+ char buf[256];
+ assert(sizeof(buf) >= expectedlen);
+
+ size_t i = 0;
+ for (auto &file : files) {
+ struct stat fst {};
+
+ if (stat(file.c_str(), &fst) == -1) {
+ auto error = errno;
+ LOG(ERROR) << "tls-ticket-key-file: could not stat file " << file
+ << ", errno=" << error;
+ return nullptr;
+ }
+
+ if (static_cast<size_t>(fst.st_size) != expectedlen) {
+ LOG(ERROR) << "tls-ticket-key-file: the expected file size is "
+ << expectedlen << ", the actual file size is " << fst.st_size;
+ return nullptr;
+ }
+
+ std::ifstream f(file.c_str());
+ if (!f) {
+ LOG(ERROR) << "tls-ticket-key-file: could not open file " << file;
+ return nullptr;
+ }
+
+ f.read(buf, expectedlen);
+ if (static_cast<size_t>(f.gcount()) != expectedlen) {
+ LOG(ERROR) << "tls-ticket-key-file: want to read " << expectedlen
+ << " bytes but only read " << f.gcount() << " bytes from "
+ << file;
+ return nullptr;
+ }
+
+ auto &key = keys[i++];
+ key.cipher = cipher;
+ key.hmac = hmac;
+ key.hmac_keylen = hmac_keylen;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "enc_keylen=" << enc_keylen
+ << ", hmac_keylen=" << key.hmac_keylen;
+ }
+
+ auto p = buf;
+ std::copy_n(p, key.data.name.size(), std::begin(key.data.name));
+ p += key.data.name.size();
+ std::copy_n(p, enc_keylen, std::begin(key.data.enc_key));
+ p += enc_keylen;
+ std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key));
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "session ticket key: " << util::format_hex(key.data.name);
+ }
+ }
+ return ticket_keys;
+}
+
+#ifdef ENABLE_HTTP3
+std::shared_ptr<QUICKeyingMaterials>
+read_quic_secret_file(const StringRef &path) {
+ constexpr size_t expectedlen =
+ SHRPX_QUIC_SECRET_RESERVEDLEN + SHRPX_QUIC_SECRETLEN + SHRPX_QUIC_SALTLEN;
+
+ auto qkms = std::make_shared<QUICKeyingMaterials>();
+ auto &kms = qkms->keying_materials;
+
+ std::ifstream f(path.c_str());
+ if (!f) {
+ LOG(ERROR) << "frontend-quic-secret-file: could not open file " << path;
+ return nullptr;
+ }
+
+ std::array<char, 4096> buf;
+
+ while (f.getline(buf.data(), buf.size())) {
+ auto len = strlen(buf.data());
+ if (len == 0 || buf[0] == '#') {
+ continue;
+ }
+
+ auto s = StringRef{std::begin(buf), std::begin(buf) + len};
+ if (s.size() != expectedlen * 2 || !util::is_hex_string(s)) {
+ LOG(ERROR) << "frontend-quic-secret-file: each line must be a "
+ << expectedlen * 2 << " bytes hex encoded string";
+ return nullptr;
+ }
+
+ kms.emplace_back();
+ auto &qkm = kms.back();
+
+ auto p = std::begin(s);
+
+ util::decode_hex(std::begin(qkm.reserved),
+ StringRef{p, p + qkm.reserved.size()});
+ p += qkm.reserved.size() * 2;
+ util::decode_hex(std::begin(qkm.secret),
+ StringRef{p, p + qkm.secret.size()});
+ p += qkm.secret.size() * 2;
+ util::decode_hex(std::begin(qkm.salt), StringRef{p, p + qkm.salt.size()});
+ p += qkm.salt.size() * 2;
+
+ assert(static_cast<size_t>(p - std::begin(s)) == expectedlen * 2);
+
+ qkm.id = qkm.reserved[0] & 0xc0;
+
+ if (kms.size() == 4) {
+ break;
+ }
+ }
+
+ if (f.bad() || (!f.eof() && f.fail())) {
+ LOG(ERROR)
+ << "frontend-quic-secret-file: error occurred while reading file "
+ << path;
+ return nullptr;
+ }
+
+ if (kms.empty()) {
+ LOG(WARN)
+ << "frontend-quic-secret-file: no keying materials are present in file "
+ << path;
+ return nullptr;
+ }
+
+ return qkms;
+}
+#endif // ENABLE_HTTP3
+
+FILE *open_file_for_write(const char *filename) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+#ifdef 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) {
+ util::make_socket_closeonexec(fd);
+ }
+#endif
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return nullptr;
+ }
+ auto f = fdopen(fd, "wb");
+ if (f == nullptr) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return nullptr;
+ }
+
+ return f;
+}
+
+namespace {
+// Read passwd from |filename|
+std::string read_passwd_from_file(const StringRef &opt,
+ const StringRef &filename) {
+ std::string line;
+
+ if (!is_secure(filename)) {
+ LOG(ERROR) << opt << ": Private key passwd file " << filename
+ << " has insecure mode.";
+ return line;
+ }
+
+ std::ifstream in(filename.c_str(), std::ios::binary);
+ if (!in) {
+ LOG(ERROR) << opt << ": Could not open key passwd file " << filename;
+ return line;
+ }
+
+ std::getline(in, line);
+ return line;
+}
+} // namespace
+
+HeaderRefs::value_type parse_header(BlockAllocator &balloc,
+ const StringRef &optarg) {
+ auto colon = std::find(std::begin(optarg), std::end(optarg), ':');
+
+ if (colon == std::end(optarg) || colon == std::begin(optarg)) {
+ return {};
+ }
+
+ auto value = colon + 1;
+ for (; *value == '\t' || *value == ' '; ++value)
+ ;
+
+ auto name_iov =
+ make_byte_ref(balloc, std::distance(std::begin(optarg), colon) + 1);
+ auto p = name_iov.base;
+ p = std::copy(std::begin(optarg), colon, p);
+ util::inp_strlower(name_iov.base, p);
+ *p = '\0';
+
+ auto nv =
+ HeaderRef(StringRef{name_iov.base, p},
+ make_string_ref(balloc, StringRef{value, std::end(optarg)}));
+
+ if (!nghttp2_check_header_name(nv.name.byte(), nv.name.size()) ||
+ !nghttp2_check_header_value_rfc9113(nv.value.byte(), nv.value.size())) {
+ return {};
+ }
+
+ return nv;
+}
+
+template <typename T>
+int parse_uint(T *dest, const StringRef &opt, const StringRef &optarg) {
+ auto val = util::parse_uint(optarg);
+ if (val == -1) {
+ 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 StringRef &opt,
+ const StringRef &optarg) {
+ auto n = util::parse_uint_with_unit(optarg);
+ if (n == -1) {
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+ }
+
+ if (static_cast<uint64_t>(std::numeric_limits<T>::max()) <
+ static_cast<uint64_t>(n)) {
+ LOG(ERROR) << opt
+ << ": too large. The value should be less than or equal to "
+ << std::numeric_limits<T>::max();
+ return -1;
+ }
+
+ *dest = n;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int parse_altsvc(AltSvc &altsvc, const StringRef &opt,
+ const StringRef &optarg) {
+ // PROTOID, PORT, HOST, ORIGIN, PARAMS.
+ auto tokens = util::split_str(optarg, ',', 5);
+
+ if (tokens.size() < 2) {
+ // Requires at least protocol_id and port
+ LOG(ERROR) << opt << ": too few 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.protocol_id = make_string_ref(config->balloc, tokens[0]);
+
+ altsvc.port = port;
+ altsvc.service = make_string_ref(config->balloc, tokens[1]);
+
+ if (tokens.size() > 2) {
+ if (!tokens[2].empty()) {
+ altsvc.host = make_string_ref(config->balloc, tokens[2]);
+ }
+
+ if (tokens.size() > 3) {
+ if (!tokens[3].empty()) {
+ altsvc.origin = make_string_ref(config->balloc, tokens[3]);
+ }
+
+ if (tokens.size() > 4) {
+ if (!tokens[4].empty()) {
+ altsvc.params = make_string_ref(config->balloc, tokens[4]);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// generated by gennghttpxfun.py
+LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
+ switch (namelen) {
+ case 3:
+ switch (name[2]) {
+ case 'd':
+ if (util::strieq_l("pi", name, 2)) {
+ return LogFragmentType::PID;
+ }
+ break;
+ }
+ break;
+ case 4:
+ switch (name[3]) {
+ case 'h':
+ if (util::strieq_l("pat", name, 3)) {
+ return LogFragmentType::PATH;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("alp", name, 3)) {
+ return LogFragmentType::ALPN;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'd':
+ if (util::strieq_l("metho", name, 5)) {
+ return LogFragmentType::METHOD;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("statu", name, 5)) {
+ return LogFragmentType::STATUS;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'i':
+ if (util::strieq_l("tls_sn", name, 6)) {
+ return LogFragmentType::TLS_SNI;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("reques", name, 6)) {
+ return LogFragmentType::REQUEST;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'l':
+ if (util::strieq_l("time_loca", name, 9)) {
+ return LogFragmentType::TIME_LOCAL;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("ssl_ciphe", name, 9)) {
+ return LogFragmentType::SSL_CIPHER;
+ }
+ if (util::strieq_l("tls_ciphe", name, 9)) {
+ return LogFragmentType::TLS_CIPHER;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'r':
+ if (util::strieq_l("remote_add", name, 10)) {
+ return LogFragmentType::REMOTE_ADDR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("remote_por", name, 10)) {
+ return LogFragmentType::REMOTE_PORT;
+ }
+ if (util::strieq_l("server_por", name, 10)) {
+ return LogFragmentType::SERVER_PORT;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case '1':
+ if (util::strieq_l("time_iso860", name, 11)) {
+ return LogFragmentType::TIME_ISO8601;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("request_tim", name, 11)) {
+ return LogFragmentType::REQUEST_TIME;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("ssl_protoco", name, 11)) {
+ return LogFragmentType::SSL_PROTOCOL;
+ }
+ if (util::strieq_l("tls_protoco", name, 11)) {
+ return LogFragmentType::TLS_PROTOCOL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend_hos", name, 11)) {
+ return LogFragmentType::BACKEND_HOST;
+ }
+ if (util::strieq_l("backend_por", name, 11)) {
+ return LogFragmentType::BACKEND_PORT;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'd':
+ if (util::strieq_l("ssl_session_i", name, 13)) {
+ return LogFragmentType::SSL_SESSION_ID;
+ }
+ if (util::strieq_l("tls_session_i", name, 13)) {
+ return LogFragmentType::TLS_SESSION_ID;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 't':
+ if (util::strieq_l("body_bytes_sen", name, 14)) {
+ return LogFragmentType::BODY_BYTES_SENT;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'n':
+ if (util::strieq_l("protocol_versio", name, 15)) {
+ return LogFragmentType::PROTOCOL_VERSION;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'l':
+ if (util::strieq_l("tls_client_seria", name, 16)) {
+ return LogFragmentType::TLS_CLIENT_SERIAL;
+ }
+ break;
+ }
+ break;
+ case 18:
+ switch (name[17]) {
+ case 'd':
+ if (util::strieq_l("ssl_session_reuse", name, 17)) {
+ return LogFragmentType::SSL_SESSION_REUSED;
+ }
+ if (util::strieq_l("tls_session_reuse", name, 17)) {
+ return LogFragmentType::TLS_SESSION_REUSED;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("path_without_quer", name, 17)) {
+ return LogFragmentType::PATH_WITHOUT_QUERY;
+ }
+ break;
+ }
+ break;
+ case 22:
+ switch (name[21]) {
+ case 'e':
+ if (util::strieq_l("tls_client_issuer_nam", name, 21)) {
+ return LogFragmentType::TLS_CLIENT_ISSUER_NAME;
+ }
+ break;
+ }
+ break;
+ case 23:
+ switch (name[22]) {
+ case 'e':
+ if (util::strieq_l("tls_client_subject_nam", name, 22)) {
+ return LogFragmentType::TLS_CLIENT_SUBJECT_NAME;
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (name[26]) {
+ case '1':
+ if (util::strieq_l("tls_client_fingerprint_sha", name, 26)) {
+ return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1;
+ }
+ break;
+ }
+ break;
+ case 29:
+ switch (name[28]) {
+ case '6':
+ if (util::strieq_l("tls_client_fingerprint_sha25", name, 28)) {
+ return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256;
+ }
+ break;
+ }
+ break;
+ }
+ return LogFragmentType::NONE;
+}
+} // namespace
+
+namespace {
+bool var_token(char c) {
+ return util::is_alpha(c) || util::is_digit(c) || c == '_';
+}
+} // namespace
+
+std::vector<LogFragment> parse_log_format(BlockAllocator &balloc,
+ const StringRef &optarg) {
+ auto literal_start = std::begin(optarg);
+ auto p = literal_start;
+ auto eop = std::end(optarg);
+
+ auto res = std::vector<LogFragment>();
+
+ for (; p != eop;) {
+ if (*p != '$') {
+ ++p;
+ continue;
+ }
+
+ auto var_start = p;
+
+ ++p;
+
+ const char *var_name;
+ size_t var_namelen;
+ if (p != eop && *p == '{') {
+ var_name = ++p;
+ for (; p != eop && var_token(*p); ++p)
+ ;
+
+ if (p == eop || *p != '}') {
+ LOG(WARN) << "Missing '}' after " << StringRef{var_start, p};
+ continue;
+ }
+
+ var_namelen = p - var_name;
+ ++p;
+ } else {
+ var_name = p;
+ for (; p != eop && var_token(*p); ++p)
+ ;
+
+ var_namelen = p - var_name;
+ }
+
+ const char *value = nullptr;
+
+ auto type = log_var_lookup_token(var_name, var_namelen);
+
+ if (type == LogFragmentType::NONE) {
+ if (util::istarts_with_l(StringRef{var_name, var_namelen}, "http_")) {
+ if (util::streq_l("host", StringRef{var_name + str_size("http_"),
+ var_namelen - str_size("http_")})) {
+ // Special handling of host header field. We will use
+ // :authority header field if host header is missing. This
+ // is a typical case in HTTP/2.
+ type = LogFragmentType::AUTHORITY;
+ } else {
+ type = LogFragmentType::HTTP;
+ value = var_name + str_size("http_");
+ }
+ } else {
+ LOG(WARN) << "Unrecognized log format variable: "
+ << StringRef{var_name, var_namelen};
+ continue;
+ }
+ }
+
+ if (literal_start < var_start) {
+ res.emplace_back(
+ LogFragmentType::LITERAL,
+ make_string_ref(balloc, StringRef{literal_start, var_start}));
+ }
+
+ literal_start = p;
+
+ if (value == nullptr) {
+ res.emplace_back(type);
+ continue;
+ }
+
+ {
+ auto iov = make_byte_ref(
+ balloc, std::distance(value, var_name + var_namelen) + 1);
+ auto p = iov.base;
+ p = std::copy(value, var_name + var_namelen, p);
+ for (auto cp = iov.base; cp != p; ++cp) {
+ if (*cp == '_') {
+ *cp = '-';
+ }
+ }
+ *p = '\0';
+ res.emplace_back(type, StringRef{iov.base, p});
+ }
+ }
+
+ if (literal_start != eop) {
+ res.emplace_back(LogFragmentType::LITERAL,
+ make_string_ref(balloc, StringRef{literal_start, eop}));
+ }
+
+ return res;
+}
+
+namespace {
+int parse_address_family(int *dest, const StringRef &opt,
+ const StringRef &optarg) {
+ if (util::strieq_l("auto", optarg)) {
+ *dest = AF_UNSPEC;
+ return 0;
+ }
+ if (util::strieq_l("IPv4", optarg)) {
+ *dest = AF_INET;
+ return 0;
+ }
+ if (util::strieq_l("IPv6", optarg)) {
+ *dest = AF_INET6;
+ return 0;
+ }
+
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+}
+} // namespace
+
+namespace {
+int parse_duration(ev_tstamp *dest, const StringRef &opt,
+ const StringRef &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
+
+namespace {
+int parse_tls_proto_version(int &dest, const StringRef &opt,
+ const StringRef &optarg) {
+ auto v = tls::proto_version_from_string(optarg);
+ if (v == -1) {
+ LOG(ERROR) << opt << ": invalid TLS protocol version: " << optarg;
+ return -1;
+ }
+
+ dest = v;
+
+ return 0;
+}
+} // namespace
+
+struct MemcachedConnectionParams {
+ bool tls;
+};
+
+namespace {
+// Parses memcached connection configuration parameter |src_params|,
+// and stores parsed results into |out|. This function returns 0 if
+// it succeeds, or -1.
+int parse_memcached_connection_params(MemcachedConnectionParams &out,
+ const StringRef &src_params,
+ const StringRef &opt) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (!param.empty()) {
+ LOG(ERROR) << opt << ": " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+struct UpstreamParams {
+ UpstreamAltMode alt_mode;
+ bool tls;
+ bool sni_fwd;
+ bool proxyproto;
+ bool quic;
+};
+
+namespace {
+// Parses upstream configuration parameter |src_params|, and stores
+// parsed results into |out|. This function returns 0 if it succeeds,
+// or -1.
+int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("sni-fwd", param)) {
+ out.sni_fwd = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (util::strieq_l("api", param)) {
+ if (out.alt_mode != UpstreamAltMode::NONE &&
+ out.alt_mode != UpstreamAltMode::API) {
+ LOG(ERROR) << "frontend: api and healthmon are mutually exclusive";
+ return -1;
+ }
+ out.alt_mode = UpstreamAltMode::API;
+ } else if (util::strieq_l("healthmon", param)) {
+ if (out.alt_mode != UpstreamAltMode::NONE &&
+ out.alt_mode != UpstreamAltMode::HEALTHMON) {
+ LOG(ERROR) << "frontend: api and healthmon are mutually exclusive";
+ return -1;
+ }
+ out.alt_mode = UpstreamAltMode::HEALTHMON;
+ } else if (util::strieq_l("proxyproto", param)) {
+ out.proxyproto = true;
+ } else if (util::strieq_l("quic", param)) {
+#ifdef ENABLE_HTTP3
+ out.quic = true;
+#else // !ENABLE_HTTP3
+ LOG(ERROR) << "quic: QUIC is disabled at compile time";
+ return -1;
+#endif // !ENABLE_HTTP3
+ } else if (!param.empty()) {
+ LOG(ERROR) << "frontend: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+struct DownstreamParams {
+ StringRef sni;
+ StringRef mruby;
+ StringRef group;
+ AffinityConfig affinity;
+ ev_tstamp read_timeout;
+ ev_tstamp write_timeout;
+ size_t fall;
+ size_t rise;
+ uint32_t weight;
+ uint32_t group_weight;
+ Proto proto;
+ bool tls;
+ bool dns;
+ bool redirect_if_not_tls;
+ bool upgrade_scheme;
+ bool dnf;
+};
+
+namespace {
+// Parses |value| of parameter named |name| as duration. This
+// function returns 0 if it succeeds and the parsed value is assigned
+// to |dest|, or -1.
+int parse_downstream_param_duration(ev_tstamp &dest, const StringRef &name,
+ const StringRef &value) {
+ auto t = util::parse_duration_with_unit(value);
+ if (t == std::numeric_limits<double>::infinity()) {
+ LOG(ERROR) << "backend: " << name << ": bad value: '" << value << "'";
+ return -1;
+ }
+ dest = t;
+ return 0;
+}
+} // namespace
+
+namespace {
+// Parses downstream configuration parameter |src_params|, and stores
+// parsed results into |out|. This function returns 0 if it succeeds,
+// or -1.
+int parse_downstream_params(DownstreamParams &out,
+ const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::istarts_with_l(param, "proto=")) {
+ auto protostr = StringRef{first + str_size("proto="), end};
+ if (protostr.empty()) {
+ LOG(ERROR) << "backend: proto: protocol is empty";
+ return -1;
+ }
+
+ if (util::streq_l("h2", std::begin(protostr), protostr.size())) {
+ out.proto = Proto::HTTP2;
+ } else if (util::streq_l("http/1.1", std::begin(protostr),
+ protostr.size())) {
+ out.proto = Proto::HTTP1;
+ } else {
+ LOG(ERROR) << "backend: proto: unknown protocol " << protostr;
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "fall=")) {
+ auto valstr = StringRef{first + str_size("fall="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: fall: non-negative integer is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n == -1) {
+ LOG(ERROR) << "backend: fall: non-negative integer is expected";
+ return -1;
+ }
+
+ out.fall = n;
+ } else if (util::istarts_with_l(param, "rise=")) {
+ auto valstr = StringRef{first + str_size("rise="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: rise: non-negative integer is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n == -1) {
+ LOG(ERROR) << "backend: rise: non-negative integer is expected";
+ return -1;
+ }
+
+ out.rise = n;
+ } else if (util::strieq_l("tls", param)) {
+ out.tls = true;
+ } else if (util::strieq_l("no-tls", param)) {
+ out.tls = false;
+ } else if (util::istarts_with_l(param, "sni=")) {
+ out.sni = StringRef{first + str_size("sni="), end};
+ } else if (util::istarts_with_l(param, "affinity=")) {
+ auto valstr = StringRef{first + str_size("affinity="), end};
+ if (util::strieq_l("none", valstr)) {
+ out.affinity.type = SessionAffinity::NONE;
+ } else if (util::strieq_l("ip", valstr)) {
+ out.affinity.type = SessionAffinity::IP;
+ } else if (util::strieq_l("cookie", valstr)) {
+ out.affinity.type = SessionAffinity::COOKIE;
+ } else {
+ LOG(ERROR)
+ << "backend: affinity: value must be one of none, ip, and cookie";
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "affinity-cookie-name=")) {
+ auto val = StringRef{first + str_size("affinity-cookie-name="), end};
+ if (val.empty()) {
+ LOG(ERROR)
+ << "backend: affinity-cookie-name: non empty string is expected";
+ return -1;
+ }
+ out.affinity.cookie.name = val;
+ } else if (util::istarts_with_l(param, "affinity-cookie-path=")) {
+ out.affinity.cookie.path =
+ StringRef{first + str_size("affinity-cookie-path="), end};
+ } else if (util::istarts_with_l(param, "affinity-cookie-secure=")) {
+ auto valstr = StringRef{first + str_size("affinity-cookie-secure="), end};
+ if (util::strieq_l("auto", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::AUTO;
+ } else if (util::strieq_l("yes", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::YES;
+ } else if (util::strieq_l("no", valstr)) {
+ out.affinity.cookie.secure = SessionAffinityCookieSecure::NO;
+ } else {
+ LOG(ERROR) << "backend: affinity-cookie-secure: value must be one of "
+ "auto, yes, and no";
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "affinity-cookie-stickiness=")) {
+ auto valstr =
+ StringRef{first + str_size("affinity-cookie-stickiness="), end};
+ if (util::strieq_l("loose", valstr)) {
+ out.affinity.cookie.stickiness = SessionAffinityCookieStickiness::LOOSE;
+ } else if (util::strieq_l("strict", valstr)) {
+ out.affinity.cookie.stickiness =
+ SessionAffinityCookieStickiness::STRICT;
+ } else {
+ LOG(ERROR) << "backend: affinity-cookie-stickiness: value must be "
+ "either loose or strict";
+ return -1;
+ }
+ } else if (util::strieq_l("dns", param)) {
+ out.dns = true;
+ } else if (util::strieq_l("redirect-if-not-tls", param)) {
+ out.redirect_if_not_tls = true;
+ } else if (util::strieq_l("upgrade-scheme", param)) {
+ out.upgrade_scheme = true;
+ } else if (util::istarts_with_l(param, "mruby=")) {
+ auto valstr = StringRef{first + str_size("mruby="), end};
+ out.mruby = valstr;
+ } else if (util::istarts_with_l(param, "read-timeout=")) {
+ if (parse_downstream_param_duration(
+ out.read_timeout, StringRef::from_lit("read-timeout"),
+ StringRef{first + str_size("read-timeout="), end}) == -1) {
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "write-timeout=")) {
+ if (parse_downstream_param_duration(
+ out.write_timeout, StringRef::from_lit("write-timeout"),
+ StringRef{first + str_size("write-timeout="), end}) == -1) {
+ return -1;
+ }
+ } else if (util::istarts_with_l(param, "weight=")) {
+ auto valstr = StringRef{first + str_size("weight="), end};
+ if (valstr.empty()) {
+ LOG(ERROR)
+ << "backend: weight: non-negative integer [1, 256] is expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n < 1 || n > 256) {
+ LOG(ERROR)
+ << "backend: weight: non-negative integer [1, 256] is expected";
+ return -1;
+ }
+ out.weight = n;
+ } else if (util::istarts_with_l(param, "group=")) {
+ auto valstr = StringRef{first + str_size("group="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: group: empty string is not allowed";
+ return -1;
+ }
+ out.group = valstr;
+ } else if (util::istarts_with_l(param, "group-weight=")) {
+ auto valstr = StringRef{first + str_size("group-weight="), end};
+ if (valstr.empty()) {
+ LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is "
+ "expected";
+ return -1;
+ }
+
+ auto n = util::parse_uint(valstr);
+ if (n < 1 || n > 256) {
+ LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is "
+ "expected";
+ return -1;
+ }
+ out.group_weight = n;
+ } else if (util::strieq_l("dnf", param)) {
+ out.dnf = true;
+ } else if (!param.empty()) {
+ LOG(ERROR) << "backend: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Parses host-path mapping patterns in |src_pattern|, and stores
+// mappings in config. We will store each host-path pattern found in
+// |src| with |addr|. |addr| will be copied accordingly. Also we
+// make a group based on the pattern. The "/" pattern is considered
+// as catch-all. We also parse protocol specified in |src_proto|.
+//
+// This function returns 0 if it succeeds, or -1.
+int parse_mapping(Config *config, DownstreamAddrConfig &addr,
+ std::map<StringRef, size_t> &pattern_addr_indexer,
+ const StringRef &src_pattern, const StringRef &src_params) {
+ // This returns at least 1 element (it could be empty string). We
+ // will append '/' to all patterns, so it becomes catch-all pattern.
+ auto mapping = util::split_str(src_pattern, ':');
+ assert(!mapping.empty());
+ auto &downstreamconf = *config->conn.downstream;
+ auto &addr_groups = downstreamconf.addr_groups;
+
+ DownstreamParams params{};
+ params.proto = Proto::HTTP1;
+ params.weight = 1;
+
+ if (parse_downstream_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ if (addr.host_unix && params.dns) {
+ LOG(ERROR) << "backend: dns: cannot be used for UNIX domain socket";
+ return -1;
+ }
+
+ if (params.affinity.type == SessionAffinity::COOKIE &&
+ params.affinity.cookie.name.empty()) {
+ LOG(ERROR) << "backend: affinity-cookie-name is mandatory if "
+ "affinity=cookie is specified";
+ return -1;
+ }
+
+ addr.fall = params.fall;
+ addr.rise = params.rise;
+ addr.weight = params.weight;
+ addr.group = make_string_ref(downstreamconf.balloc, params.group);
+ addr.group_weight = params.group_weight;
+ addr.proto = params.proto;
+ addr.tls = params.tls;
+ addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
+ addr.dns = params.dns;
+ addr.upgrade_scheme = params.upgrade_scheme;
+ addr.dnf = params.dnf;
+
+ auto &routerconf = downstreamconf.router;
+ auto &router = routerconf.router;
+ auto &rw_router = routerconf.rev_wildcard_router;
+ auto &wildcard_patterns = routerconf.wildcard_patterns;
+
+ for (const auto &raw_pattern : mapping) {
+ StringRef pattern;
+ auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/');
+ if (slash == std::end(raw_pattern)) {
+ // This effectively makes empty pattern to "/". 2 for '/' and
+ // terminal NULL character.
+ auto iov = make_byte_ref(downstreamconf.balloc, raw_pattern.size() + 2);
+ auto p = iov.base;
+ p = std::copy(std::begin(raw_pattern), std::end(raw_pattern), p);
+ util::inp_strlower(iov.base, p);
+ *p++ = '/';
+ *p = '\0';
+ pattern = StringRef{iov.base, p};
+ } else {
+ auto path = http2::normalize_path_colon(
+ downstreamconf.balloc, StringRef{slash, std::end(raw_pattern)},
+ StringRef{});
+ auto iov = make_byte_ref(downstreamconf.balloc,
+ std::distance(std::begin(raw_pattern), slash) +
+ path.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(raw_pattern), slash, p);
+ util::inp_strlower(iov.base, p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ *p = '\0';
+ pattern = StringRef{iov.base, p};
+ }
+ auto it = pattern_addr_indexer.find(pattern);
+ if (it != std::end(pattern_addr_indexer)) {
+ auto &g = addr_groups[(*it).second];
+ // Last value wins if we have multiple different affinity
+ // value under one group.
+ if (params.affinity.type != SessionAffinity::NONE) {
+ if (g.affinity.type == SessionAffinity::NONE) {
+ g.affinity.type = params.affinity.type;
+ if (params.affinity.type == SessionAffinity::COOKIE) {
+ g.affinity.cookie.name = make_string_ref(
+ downstreamconf.balloc, params.affinity.cookie.name);
+ if (!params.affinity.cookie.path.empty()) {
+ g.affinity.cookie.path = make_string_ref(
+ downstreamconf.balloc, params.affinity.cookie.path);
+ }
+ g.affinity.cookie.secure = params.affinity.cookie.secure;
+ g.affinity.cookie.stickiness = params.affinity.cookie.stickiness;
+ }
+ } else if (g.affinity.type != params.affinity.type ||
+ g.affinity.cookie.name != params.affinity.cookie.name ||
+ g.affinity.cookie.path != params.affinity.cookie.path ||
+ g.affinity.cookie.secure != params.affinity.cookie.secure ||
+ g.affinity.cookie.stickiness !=
+ params.affinity.cookie.stickiness) {
+ LOG(ERROR) << "backend: affinity: multiple different affinity "
+ "configurations found in a single group";
+ return -1;
+ }
+ }
+ // If at least one backend requires frontend TLS connection,
+ // enable it for all backends sharing the same pattern.
+ if (params.redirect_if_not_tls) {
+ g.redirect_if_not_tls = true;
+ }
+ // All backends in the same group must have the same mruby path.
+ // If some backends do not specify mruby file, and there is at
+ // least one backend with mruby file, it is used for all
+ // backends in the group.
+ if (!params.mruby.empty()) {
+ if (g.mruby_file.empty()) {
+ g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
+ } else if (g.mruby_file != params.mruby) {
+ LOG(ERROR) << "backend: mruby: multiple different mruby file found "
+ "in a single group";
+ return -1;
+ }
+ }
+ // All backends in the same group must have the same read/write
+ // timeout. If some backends do not specify read/write timeout,
+ // and there is at least one backend with read/write timeout, it
+ // is used for all backends in the group.
+ if (params.read_timeout > 1e-9) {
+ if (g.timeout.read < 1e-9) {
+ g.timeout.read = params.read_timeout;
+ } else if (fabs(g.timeout.read - params.read_timeout) > 1e-9) {
+ LOG(ERROR)
+ << "backend: read-timeout: multiple different read-timeout "
+ "found in a single group";
+ return -1;
+ }
+ }
+ if (params.write_timeout > 1e-9) {
+ if (g.timeout.write < 1e-9) {
+ g.timeout.write = params.write_timeout;
+ } else if (fabs(g.timeout.write - params.write_timeout) > 1e-9) {
+ LOG(ERROR) << "backend: write-timeout: multiple different "
+ "write-timeout found in a single group";
+ return -1;
+ }
+ }
+ // All backends in the same group must have the same dnf
+ // setting. If some backends do not specify dnf, and there is
+ // at least one backend with dnf, it is used for all backends in
+ // the group. In general, multiple backends are not necessary
+ // for dnf because there is no need for load balancing.
+ if (params.dnf) {
+ g.dnf = true;
+ }
+
+ g.addrs.push_back(addr);
+ continue;
+ }
+
+ auto idx = addr_groups.size();
+ pattern_addr_indexer.emplace(pattern, idx);
+ addr_groups.emplace_back(pattern);
+ auto &g = addr_groups.back();
+ g.addrs.push_back(addr);
+ g.affinity.type = params.affinity.type;
+ if (params.affinity.type == SessionAffinity::COOKIE) {
+ g.affinity.cookie.name =
+ make_string_ref(downstreamconf.balloc, params.affinity.cookie.name);
+ if (!params.affinity.cookie.path.empty()) {
+ g.affinity.cookie.path =
+ make_string_ref(downstreamconf.balloc, params.affinity.cookie.path);
+ }
+ g.affinity.cookie.secure = params.affinity.cookie.secure;
+ g.affinity.cookie.stickiness = params.affinity.cookie.stickiness;
+ }
+ g.redirect_if_not_tls = params.redirect_if_not_tls;
+ g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
+ g.timeout.read = params.read_timeout;
+ g.timeout.write = params.write_timeout;
+ g.dnf = params.dnf;
+
+ if (pattern[0] == '*') {
+ // wildcard pattern
+ auto path_first =
+ std::find(std::begin(g.pattern), std::end(g.pattern), '/');
+
+ auto host = StringRef{std::begin(g.pattern) + 1, path_first};
+ auto path = StringRef{path_first, std::end(g.pattern)};
+
+ auto path_is_wildcard = false;
+ if (path[path.size() - 1] == '*') {
+ path = StringRef{std::begin(path), std::begin(path) + path.size() - 1};
+ path_is_wildcard = true;
+ }
+
+ auto it = std::find_if(
+ std::begin(wildcard_patterns), std::end(wildcard_patterns),
+ [&host](const WildcardPattern &wp) { return wp.host == host; });
+
+ if (it == std::end(wildcard_patterns)) {
+ wildcard_patterns.emplace_back(host);
+
+ auto &router = wildcard_patterns.back().router;
+ router.add_route(path, idx, path_is_wildcard);
+
+ auto iov = make_byte_ref(downstreamconf.balloc, host.size() + 1);
+ auto p = iov.base;
+ p = std::reverse_copy(std::begin(host), std::end(host), p);
+ *p = '\0';
+ auto rev_host = StringRef{iov.base, p};
+
+ rw_router.add_route(rev_host, wildcard_patterns.size() - 1);
+ } else {
+ (*it).router.add_route(path, idx, path_is_wildcard);
+ }
+
+ continue;
+ }
+
+ auto path_is_wildcard = false;
+ if (pattern[pattern.size() - 1] == '*') {
+ pattern = StringRef{std::begin(pattern),
+ std::begin(pattern) + pattern.size() - 1};
+ path_is_wildcard = true;
+ }
+
+ router.add_route(pattern, idx, path_is_wildcard);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+ForwardedNode parse_forwarded_node_type(const StringRef &optarg) {
+ if (util::strieq_l("obfuscated", optarg)) {
+ return ForwardedNode::OBFUSCATED;
+ }
+
+ if (util::strieq_l("ip", optarg)) {
+ return ForwardedNode::IP;
+ }
+
+ if (optarg.size() < 2 || optarg[0] != '_') {
+ return static_cast<ForwardedNode>(-1);
+ }
+
+ if (std::find_if_not(std::begin(optarg), std::end(optarg), [](char c) {
+ return util::is_alpha(c) || util::is_digit(c) || c == '.' || c == '_' ||
+ c == '-';
+ }) != std::end(optarg)) {
+ return static_cast<ForwardedNode>(-1);
+ }
+
+ return ForwardedNode::OBFUSCATED;
+}
+} // namespace
+
+namespace {
+int parse_error_page(std::vector<ErrorPage> &error_pages, const StringRef &opt,
+ const StringRef &optarg) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto eq = std::find(std::begin(optarg), std::end(optarg), '=');
+ if (eq == std::end(optarg) || eq + 1 == std::end(optarg)) {
+ LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+ return -1;
+ }
+
+ auto codestr = StringRef{std::begin(optarg), eq};
+ unsigned int code;
+
+ if (codestr == StringRef::from_lit("*")) {
+ code = 0;
+ } else {
+ auto n = util::parse_uint(codestr);
+
+ if (n == -1 || n < 400 || n > 599) {
+ LOG(ERROR) << opt << ": bad code: '" << codestr << "'";
+ return -1;
+ }
+
+ code = static_cast<unsigned int>(n);
+ }
+
+ auto path = StringRef{eq + 1, std::end(optarg)};
+
+ std::vector<uint8_t> content;
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << optarg << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto fd_closer = defer(close, fd);
+
+ std::array<uint8_t, 4096> buf;
+ for (;;) {
+ auto n = read(fd, buf.data(), buf.size());
+ if (n == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << optarg << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ if (n == 0) {
+ break;
+ }
+ content.insert(std::end(content), std::begin(buf), std::begin(buf) + n);
+ }
+
+ error_pages.push_back(ErrorPage{std::move(content), code});
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Maximum size of SCT extension payload length.
+constexpr size_t MAX_SCT_EXT_LEN = 16_k;
+} // namespace
+
+struct SubcertParams {
+ StringRef sct_dir;
+};
+
+namespace {
+// Parses subcert parameter |src_params|, and stores parsed results
+// into |out|. This function returns 0 if it succeeds, or -1.
+int parse_subcert_params(SubcertParams &out, const StringRef &src_params) {
+ auto last = std::end(src_params);
+ for (auto first = std::begin(src_params); first != last;) {
+ auto end = std::find(first, last, ';');
+ auto param = StringRef{first, end};
+
+ if (util::istarts_with_l(param, "sct-dir=")) {
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto sct_dir =
+ StringRef{std::begin(param) + str_size("sct-dir="), std::end(param)};
+ if (sct_dir.empty()) {
+ LOG(ERROR) << "subcert: " << param << ": empty sct-dir";
+ return -1;
+ }
+ out.sct_dir = sct_dir;
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ LOG(WARN) << "subcert: sct-dir is ignored because underlying TLS library "
+ "does not support SCT";
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ } else if (!param.empty()) {
+ LOG(ERROR) << "subcert: " << param << ": unknown keyword";
+ return -1;
+ }
+
+ if (end == last) {
+ break;
+ }
+
+ first = end + 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+// Reads *.sct files from directory denoted by |dir_path|. |dir_path|
+// must be NULL-terminated string.
+int read_tls_sct_from_dir(std::vector<uint8_t> &dst, const StringRef &opt,
+ const StringRef &dir_path) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto dir = opendir(dir_path.c_str());
+ if (dir == nullptr) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": " << dir_path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto closer = defer(closedir, dir);
+
+ // 2 bytes total length field
+ auto len_idx = std::distance(std::begin(dst), std::end(dst));
+ dst.insert(std::end(dst), 2, 0);
+
+ for (;;) {
+ errno = 0;
+ auto ent = readdir(dir);
+ if (ent == nullptr) {
+ if (errno != 0) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read directory " << dir_path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ break;
+ }
+
+ auto name = StringRef{ent->d_name};
+
+ if (name[0] == '.' || !util::iends_with_l(name, ".sct")) {
+ continue;
+ }
+
+ std::string path;
+ path.resize(dir_path.size() + 1 + name.size());
+ {
+ auto p = std::begin(path);
+ p = std::copy(std::begin(dir_path), std::end(dir_path), p);
+ *p++ = '/';
+ std::copy(std::begin(name), std::end(name), p);
+ }
+
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read SCT from " << path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ auto closer = defer(close, fd);
+
+ // 2 bytes length field for this SCT.
+ auto len_idx = std::distance(std::begin(dst), std::end(dst));
+ dst.insert(std::end(dst), 2, 0);
+
+ // *.sct file tends to be small; around 110+ bytes.
+ std::array<char, 256> buf;
+ for (;;) {
+ ssize_t nread;
+ while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ auto error = errno;
+ LOG(ERROR) << opt << ": failed to read SCT data from " << path << ": "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ return -1;
+ }
+
+ if (nread == 0) {
+ break;
+ }
+
+ dst.insert(std::end(dst), std::begin(buf), std::begin(buf) + nread);
+
+ if (dst.size() > MAX_SCT_EXT_LEN) {
+ LOG(ERROR) << opt << ": the concatenated SCT data from " << dir_path
+ << " is too large. Max " << MAX_SCT_EXT_LEN;
+ return -1;
+ }
+ }
+
+ auto len = dst.size() - len_idx - 2;
+
+ if (len == 0) {
+ dst.resize(dst.size() - 2);
+ continue;
+ }
+
+ dst[len_idx] = len >> 8;
+ dst[len_idx + 1] = len;
+ }
+
+ auto len = dst.size() - len_idx - 2;
+
+ if (len == 0) {
+ dst.resize(dst.size() - 2);
+ return 0;
+ }
+
+ dst[len_idx] = len >> 8;
+ dst[len_idx + 1] = len;
+
+ return 0;
+}
+} // namespace
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+// Reads PSK secrets from path, and parses each line. The result is
+// directly stored into config->tls.psk_secrets. This function
+// returns 0 if it succeeds, or -1.
+int parse_psk_secrets(Config *config, const StringRef &path) {
+ auto &tlsconf = config->tls;
+
+ std::ifstream f(path.c_str(), std::ios::binary);
+ if (!f) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": could not open file " << path;
+ return -1;
+ }
+
+ size_t lineno = 0;
+ std::string line;
+ while (std::getline(f, line)) {
+ ++lineno;
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto sep_it = std::find(std::begin(line), std::end(line), ':');
+ if (sep_it == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": could not fine separator at line " << lineno;
+ return -1;
+ }
+
+ if (sep_it == std::begin(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line "
+ << lineno;
+ return -1;
+ }
+
+ if (sep_it + 1 == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty secret at line "
+ << lineno;
+ return -1;
+ }
+
+ if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": secret must be hex string at line " << lineno;
+ return -1;
+ }
+
+ auto identity =
+ make_string_ref(config->balloc, StringRef{std::begin(line), sep_it});
+
+ auto secret =
+ util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)});
+
+ auto rv = tlsconf.psk_secrets.emplace(identity, secret);
+ if (!rv.second) {
+ LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
+ << ": identity has already been registered at line " << lineno;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+// Reads PSK secrets from path, and parses each line. The result is
+// directly stored into config->tls.client.psk. This function returns
+// 0 if it succeeds, or -1.
+int parse_client_psk_secrets(Config *config, const StringRef &path) {
+ auto &tlsconf = config->tls;
+
+ std::ifstream f(path.c_str(), std::ios::binary);
+ if (!f) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file "
+ << path;
+ return -1;
+ }
+
+ size_t lineno = 0;
+ std::string line;
+ while (std::getline(f, line)) {
+ ++lineno;
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto sep_it = std::find(std::begin(line), std::end(line), ':');
+ if (sep_it == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS
+ << ": could not find separator at line " << lineno;
+ return -1;
+ }
+
+ if (sep_it == std::begin(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line "
+ << lineno;
+ return -1;
+ }
+
+ if (sep_it + 1 == std::end(line)) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line "
+ << lineno;
+ return -1;
+ }
+
+ if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) {
+ LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS
+ << ": secret must be hex string at line " << lineno;
+ return -1;
+ }
+
+ tlsconf.client.psk.identity =
+ make_string_ref(config->balloc, StringRef{std::begin(line), sep_it});
+
+ tlsconf.client.psk.secret =
+ util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)});
+
+ return 0;
+ }
+
+ return 0;
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+// generated by gennghttpxfun.py
+int option_lookup_token(const char *name, size_t namelen) {
+ switch (namelen) {
+ case 4:
+ switch (name[3]) {
+ case 'f':
+ if (util::strieq_l("con", name, 3)) {
+ return SHRPX_OPTID_CONF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("use", name, 3)) {
+ return SHRPX_OPTID_USER;
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (name[5]) {
+ case 'a':
+ if (util::strieq_l("no-vi", name, 5)) {
+ return SHRPX_OPTID_NO_VIA;
+ }
+ break;
+ case 'c':
+ if (util::strieq_l("altsv", name, 5)) {
+ return SHRPX_OPTID_ALTSVC;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("daemo", name, 5)) {
+ return SHRPX_OPTID_DAEMON;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("cacer", name, 5)) {
+ return SHRPX_OPTID_CACERT;
+ }
+ if (util::strieq_l("clien", name, 5)) {
+ return SHRPX_OPTID_CLIENT;
+ }
+ break;
+ }
+ break;
+ case 7:
+ switch (name[6]) {
+ case 'd':
+ if (util::strieq_l("backen", name, 6)) {
+ return SHRPX_OPTID_BACKEND;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("includ", name, 6)) {
+ return SHRPX_OPTID_INCLUDE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("backlo", name, 6)) {
+ return SHRPX_OPTID_BACKLOG;
+ }
+ if (util::strieq_l("paddin", name, 6)) {
+ return SHRPX_OPTID_PADDING;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("no-ocs", name, 6)) {
+ return SHRPX_OPTID_NO_OCSP;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("cipher", name, 6)) {
+ return SHRPX_OPTID_CIPHERS;
+ }
+ if (util::strieq_l("worker", name, 6)) {
+ return SHRPX_OPTID_WORKERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("subcer", name, 6)) {
+ return SHRPX_OPTID_SUBCERT;
+ }
+ break;
+ }
+ break;
+ case 8:
+ switch (name[7]) {
+ case 'd':
+ if (util::strieq_l("fronten", name, 7)) {
+ return SHRPX_OPTID_FRONTEND;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("insecur", name, 7)) {
+ return SHRPX_OPTID_INSECURE;
+ }
+ if (util::strieq_l("pid-fil", name, 7)) {
+ return SHRPX_OPTID_PID_FILE;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("fastope", name, 7)) {
+ return SHRPX_OPTID_FASTOPEN;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("tls-ktl", name, 7)) {
+ return SHRPX_OPTID_TLS_KTLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("npn-lis", name, 7)) {
+ return SHRPX_OPTID_NPN_LIST;
+ }
+ break;
+ }
+ break;
+ case 9:
+ switch (name[8]) {
+ case 'e':
+ if (util::strieq_l("no-kqueu", name, 8)) {
+ return SHRPX_OPTID_NO_KQUEUE;
+ }
+ if (util::strieq_l("read-rat", name, 8)) {
+ return SHRPX_OPTID_READ_RATE;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("log-leve", name, 8)) {
+ return SHRPX_OPTID_LOG_LEVEL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("alpn-lis", name, 8)) {
+ return SHRPX_OPTID_ALPN_LIST;
+ }
+ break;
+ }
+ break;
+ case 10:
+ switch (name[9]) {
+ case 'e':
+ if (util::strieq_l("error-pag", name, 9)) {
+ return SHRPX_OPTID_ERROR_PAGE;
+ }
+ if (util::strieq_l("mruby-fil", name, 9)) {
+ return SHRPX_OPTID_MRUBY_FILE;
+ }
+ if (util::strieq_l("write-rat", name, 9)) {
+ return SHRPX_OPTID_WRITE_RATE;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("read-burs", name, 9)) {
+ return SHRPX_OPTID_READ_BURST;
+ }
+ break;
+ }
+ break;
+ case 11:
+ switch (name[10]) {
+ case 'e':
+ if (util::strieq_l("server-nam", name, 10)) {
+ return SHRPX_OPTID_SERVER_NAME;
+ }
+ break;
+ case 'f':
+ if (util::strieq_l("no-quic-bp", name, 10)) {
+ return SHRPX_OPTID_NO_QUIC_BPF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("tls-sct-di", name, 10)) {
+ return SHRPX_OPTID_TLS_SCT_DIR;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-tl", name, 10)) {
+ return SHRPX_OPTID_BACKEND_TLS;
+ }
+ if (util::strieq_l("ecdh-curve", name, 10)) {
+ return SHRPX_OPTID_ECDH_CURVES;
+ }
+ if (util::strieq_l("psk-secret", name, 10)) {
+ return SHRPX_OPTID_PSK_SECRETS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("write-burs", name, 10)) {
+ return SHRPX_OPTID_WRITE_BURST;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("dns-max-tr", name, 10)) {
+ return SHRPX_OPTID_DNS_MAX_TRY;
+ }
+ if (util::strieq_l("http2-prox", name, 10)) {
+ return SHRPX_OPTID_HTTP2_PROXY;
+ }
+ break;
+ }
+ break;
+ case 12:
+ switch (name[11]) {
+ case '4':
+ if (util::strieq_l("backend-ipv", name, 11)) {
+ return SHRPX_OPTID_BACKEND_IPV4;
+ }
+ break;
+ case '6':
+ if (util::strieq_l("backend-ipv", name, 11)) {
+ return SHRPX_OPTID_BACKEND_IPV6;
+ }
+ break;
+ case 'c':
+ if (util::strieq_l("http2-altsv", name, 11)) {
+ return SHRPX_OPTID_HTTP2_ALTSVC;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("host-rewrit", name, 11)) {
+ return SHRPX_OPTID_HOST_REWRITE;
+ }
+ if (util::strieq_l("http2-bridg", name, 11)) {
+ return SHRPX_OPTID_HTTP2_BRIDGE;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("ocsp-startu", name, 11)) {
+ return SHRPX_OPTID_OCSP_STARTUP;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("client-prox", name, 11)) {
+ return SHRPX_OPTID_CLIENT_PROXY;
+ }
+ if (util::strieq_l("forwarded-b", name, 11)) {
+ return SHRPX_OPTID_FORWARDED_BY;
+ }
+ break;
+ }
+ break;
+ case 13:
+ switch (name[12]) {
+ case 'd':
+ if (util::strieq_l("add-forwarde", name, 12)) {
+ return SHRPX_OPTID_ADD_FORWARDED;
+ }
+ if (util::strieq_l("single-threa", name, 12)) {
+ return SHRPX_OPTID_SINGLE_THREAD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("dh-param-fil", name, 12)) {
+ return SHRPX_OPTID_DH_PARAM_FILE;
+ }
+ if (util::strieq_l("errorlog-fil", name, 12)) {
+ return SHRPX_OPTID_ERRORLOG_FILE;
+ }
+ if (util::strieq_l("rlimit-nofil", name, 12)) {
+ return SHRPX_OPTID_RLIMIT_NOFILE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("forwarded-fo", name, 12)) {
+ return SHRPX_OPTID_FORWARDED_FOR;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("tls13-cipher", name, 12)) {
+ return SHRPX_OPTID_TLS13_CIPHERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("verify-clien", name, 12)) {
+ return SHRPX_OPTID_VERIFY_CLIENT;
+ }
+ break;
+ }
+ break;
+ case 14:
+ switch (name[13]) {
+ case 'd':
+ if (util::strieq_l("quic-server-i", name, 13)) {
+ return SHRPX_OPTID_QUIC_SERVER_ID;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("accesslog-fil", name, 13)) {
+ return SHRPX_OPTID_ACCESSLOG_FILE;
+ }
+ break;
+ case 'h':
+ if (util::strieq_l("no-server-pus", name, 13)) {
+ return SHRPX_OPTID_NO_SERVER_PUSH;
+ }
+ break;
+ case 'k':
+ if (util::strieq_l("rlimit-memloc", name, 13)) {
+ return SHRPX_OPTID_RLIMIT_MEMLOCK;
+ }
+ break;
+ case 'p':
+ if (util::strieq_l("no-verify-ocs", name, 13)) {
+ return SHRPX_OPTID_NO_VERIFY_OCSP;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-no-tl", name, 13)) {
+ return SHRPX_OPTID_BACKEND_NO_TLS;
+ }
+ if (util::strieq_l("client-cipher", name, 13)) {
+ return SHRPX_OPTID_CLIENT_CIPHERS;
+ }
+ if (util::strieq_l("single-proces", name, 13)) {
+ return SHRPX_OPTID_SINGLE_PROCESS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("tls-proto-lis", name, 13)) {
+ return SHRPX_OPTID_TLS_PROTO_LIST;
+ }
+ break;
+ }
+ break;
+ case 15:
+ switch (name[14]) {
+ case 'e':
+ if (util::strieq_l("no-host-rewrit", name, 14)) {
+ return SHRPX_OPTID_NO_HOST_REWRITE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("errorlog-syslo", name, 14)) {
+ return SHRPX_OPTID_ERRORLOG_SYSLOG;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-no-tl", name, 14)) {
+ return SHRPX_OPTID_FRONTEND_NO_TLS;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("syslog-facilit", name, 14)) {
+ return SHRPX_OPTID_SYSLOG_FACILITY;
+ }
+ break;
+ }
+ break;
+ case 16:
+ switch (name[15]) {
+ case 'e':
+ if (util::strieq_l("certificate-fil", name, 15)) {
+ return SHRPX_OPTID_CERTIFICATE_FILE;
+ }
+ if (util::strieq_l("client-cert-fil", name, 15)) {
+ return SHRPX_OPTID_CLIENT_CERT_FILE;
+ }
+ if (util::strieq_l("private-key-fil", name, 15)) {
+ return SHRPX_OPTID_PRIVATE_KEY_FILE;
+ }
+ if (util::strieq_l("worker-read-rat", name, 15)) {
+ return SHRPX_OPTID_WORKER_READ_RATE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("accesslog-syslo", name, 15)) {
+ return SHRPX_OPTID_ACCESSLOG_SYSLOG;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("accesslog-forma", name, 15)) {
+ return SHRPX_OPTID_ACCESSLOG_FORMAT;
+ }
+ break;
+ }
+ break;
+ case 17:
+ switch (name[16]) {
+ case 'e':
+ if (util::strieq_l("no-server-rewrit", name, 16)) {
+ return SHRPX_OPTID_NO_SERVER_REWRITE;
+ }
+ if (util::strieq_l("worker-write-rat", name, 16)) {
+ return SHRPX_OPTID_WORKER_WRITE_RATE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http1-tl", name, 16)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_TLS;
+ }
+ if (util::strieq_l("max-header-field", name, 16)) {
+ return SHRPX_OPTID_MAX_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("dns-cache-timeou", name, 16)) {
+ return SHRPX_OPTID_DNS_CACHE_TIMEOUT;
+ }
+ if (util::strieq_l("worker-read-burs", name, 16)) {
+ return SHRPX_OPTID_WORKER_READ_BURST;
+ }
+ break;
+ }
+ break;
+ case 18:
+ switch (name[17]) {
+ case 'a':
+ if (util::strieq_l("tls-max-early-dat", name, 17)) {
+ return SHRPX_OPTID_TLS_MAX_EARLY_DATA;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("add-request-heade", name, 17)) {
+ return SHRPX_OPTID_ADD_REQUEST_HEADER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("client-psk-secret", name, 17)) {
+ return SHRPX_OPTID_CLIENT_PSK_SECRETS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("dns-lookup-timeou", name, 17)) {
+ return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT;
+ }
+ if (util::strieq_l("worker-write-burs", name, 17)) {
+ return SHRPX_OPTID_WORKER_WRITE_BURST;
+ }
+ break;
+ }
+ break;
+ case 19:
+ switch (name[18]) {
+ case 'e':
+ if (util::strieq_l("no-location-rewrit", name, 18)) {
+ return SHRPX_OPTID_NO_LOCATION_REWRITE;
+ }
+ if (util::strieq_l("require-http-schem", name, 18)) {
+ return SHRPX_OPTID_REQUIRE_HTTP_SCHEME;
+ }
+ if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
+ }
+ break;
+ case 'f':
+ if (util::strieq_l("backend-max-backof", name, 18)) {
+ return SHRPX_OPTID_BACKEND_MAX_BACKOFF;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("add-response-heade", name, 18)) {
+ return SHRPX_OPTID_ADD_RESPONSE_HEADER;
+ }
+ if (util::strieq_l("add-x-forwarded-fo", name, 18)) {
+ return SHRPX_OPTID_ADD_X_FORWARDED_FOR;
+ }
+ if (util::strieq_l("header-field-buffe", name, 18)) {
+ return SHRPX_OPTID_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("redirect-https-por", name, 18)) {
+ return SHRPX_OPTID_REDIRECT_HTTPS_PORT;
+ }
+ if (util::strieq_l("stream-read-timeou", name, 18)) {
+ return SHRPX_OPTID_STREAM_READ_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 20:
+ switch (name[19]) {
+ case 'g':
+ if (util::strieq_l("frontend-frame-debu", name, 19)) {
+ return SHRPX_OPTID_FRONTEND_FRAME_DEBUG;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("ocsp-update-interva", name, 19)) {
+ return SHRPX_OPTID_OCSP_UPDATE_INTERVAL;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("max-worker-processe", name, 19)) {
+ return SHRPX_OPTID_MAX_WORKER_PROCESSES;
+ }
+ if (util::strieq_l("tls13-client-cipher", name, 19)) {
+ return SHRPX_OPTID_TLS13_CLIENT_CIPHERS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-read-timeou", name, 19)) {
+ return SHRPX_OPTID_BACKEND_READ_TIMEOUT;
+ }
+ if (util::strieq_l("stream-write-timeou", name, 19)) {
+ return SHRPX_OPTID_STREAM_WRITE_TIMEOUT;
+ }
+ if (util::strieq_l("verify-client-cacer", name, 19)) {
+ return SHRPX_OPTID_VERIFY_CLIENT_CACERT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("api-max-request-bod", name, 19)) {
+ return SHRPX_OPTID_API_MAX_REQUEST_BODY;
+ }
+ break;
+ }
+ break;
+ case 21:
+ switch (name[20]) {
+ case 'd':
+ if (util::strieq_l("backend-tls-sni-fiel", name, 20)) {
+ return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("quic-bpf-program-fil", name, 20)) {
+ return SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE;
+ }
+ break;
+ case 'l':
+ if (util::strieq_l("accept-proxy-protoco", name, 20)) {
+ return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("tls-max-proto-versio", name, 20)) {
+ return SHRPX_OPTID_TLS_MAX_PROTO_VERSION;
+ }
+ if (util::strieq_l("tls-min-proto-versio", name, 20)) {
+ return SHRPX_OPTID_TLS_MIN_PROTO_VERSION;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-max-request", name, 20)) {
+ return SHRPX_OPTID_FRONTEND_MAX_REQUESTS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-write-timeou", name, 20)) {
+ return SHRPX_OPTID_BACKEND_WRITE_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-read-timeou", name, 20)) {
+ return SHRPX_OPTID_FRONTEND_READ_TIMEOUT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("accesslog-write-earl", name, 20)) {
+ return SHRPX_OPTID_ACCESSLOG_WRITE_EARLY;
+ }
+ break;
+ }
+ break;
+ case 22:
+ switch (name[21]) {
+ case 'i':
+ if (util::strieq_l("backend-http-proxy-ur", name, 21)) {
+ return SHRPX_OPTID_BACKEND_HTTP_PROXY_URI;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-request-buffe", name, 21)) {
+ return SHRPX_OPTID_BACKEND_REQUEST_BUFFER;
+ }
+ if (util::strieq_l("frontend-quic-qlog-di", name, 21)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-write-timeou", name, 21)) {
+ return SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("backend-address-famil", name, 21)) {
+ return SHRPX_OPTID_BACKEND_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 23:
+ switch (name[22]) {
+ case 'e':
+ if (util::strieq_l("client-private-key-fil", name, 22)) {
+ return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE;
+ }
+ if (util::strieq_l("private-key-passwd-fil", name, 22)) {
+ return SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("frontend-quic-debug-lo", name, 22)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-response-buffe", name, 22)) {
+ return SHRPX_OPTID_BACKEND_RESPONSE_BUFFER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-connect-timeou", name, 22)) {
+ return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 24:
+ switch (name[23]) {
+ case 'a':
+ if (util::strieq_l("frontend-quic-early-dat", name, 23)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA;
+ }
+ break;
+ case 'd':
+ if (util::strieq_l("strip-incoming-forwarde", name, 23)) {
+ return SHRPX_OPTID_STRIP_INCOMING_FORWARDED;
+ }
+ if (util::strieq_l("tls-ticket-key-memcache", name, 23)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("fetch-ocsp-response-fil", name, 23)) {
+ return SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE;
+ }
+ break;
+ case 'o':
+ if (util::strieq_l("no-add-x-forwarded-prot", name, 23)) {
+ return SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("listener-disable-timeou", name, 23)) {
+ return SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT;
+ }
+ if (util::strieq_l("tls-dyn-rec-idle-timeou", name, 23)) {
+ return SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 25:
+ switch (name[24]) {
+ case 'e':
+ if (util::strieq_l("backend-http2-window-siz", name, 24)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-quic-secret-fil", name, 24)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE;
+ }
+ break;
+ case 'g':
+ if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) {
+ return SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http2-window-bit", name, 24)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS;
+ }
+ if (util::strieq_l("max-request-header-field", name, 24)) {
+ return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-quic-initial-rt", name, 24)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT;
+ }
+ break;
+ }
+ break;
+ case 26:
+ switch (name[25]) {
+ case 'a':
+ if (util::strieq_l("tls-no-postpone-early-dat", name, 25)) {
+ return SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("frontend-http2-window-siz", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-window-siz", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS;
+ }
+ if (util::strieq_l("max-response-header-field", name, 25)) {
+ return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
+ return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-quic-idle-timeou", name, 25)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT;
+ }
+ if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) {
+ return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST;
+ }
+ if (util::strieq_l("no-http2-cipher-block-lis", name, 25)) {
+ return SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST;
+ }
+ break;
+ }
+ break;
+ case 27:
+ switch (name[26]) {
+ case 'd':
+ if (util::strieq_l("tls-session-cache-memcache", name, 26)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
+ }
+ break;
+ case 'n':
+ if (util::strieq_l("frontend-quic-require-toke", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("request-header-field-buffe", name, 26)) {
+ return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("worker-frontend-connection", name, 26)) {
+ return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-http2-read-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-http3-read-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT;
+ }
+ if (util::strieq_l("frontend-keep-alive-timeou", name, 26)) {
+ return SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 28:
+ switch (name[27]) {
+ case 'a':
+ if (util::strieq_l("no-strip-incoming-early-dat", name, 27)) {
+ return SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA;
+ }
+ break;
+ case 'd':
+ if (util::strieq_l("tls-dyn-rec-warmup-threshol", name, 27)) {
+ return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("response-header-field-buffe", name, 27)) {
+ return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
+ return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-tl", name, 27)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-connections-per-hos", name, 27)) {
+ return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST;
+ }
+ break;
+ }
+ break;
+ case 30:
+ switch (name[29]) {
+ case 'd':
+ if (util::strieq_l("verify-client-tolerate-expire", name, 29)) {
+ return SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("frontend-http3-max-window-siz", name, 29)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("ignore-per-pattern-mruby-erro", name, 29)) {
+ return SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR;
+ }
+ if (util::strieq_l("strip-incoming-x-forwarded-fo", name, 29)) {
+ return SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-http2-settings-timeou", name, 29)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 31:
+ switch (name[30]) {
+ case 's':
+ if (util::strieq_l("tls-session-cache-memcached-tl", name, 30)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("frontend-http2-settings-timeou", name, 30)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT;
+ }
+ break;
+ }
+ break;
+ case 32:
+ switch (name[31]) {
+ case 'd':
+ if (util::strieq_l("backend-connections-per-fronten", name, 31)) {
+ return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND;
+ }
+ break;
+ }
+ break;
+ case 33:
+ switch (name[32]) {
+ case 'l':
+ if (util::strieq_l("tls-ticket-key-memcached-interva", name, 32)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-max-fai", name, 32)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("client-no-http2-cipher-black-lis", name, 32)) {
+ return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST;
+ }
+ if (util::strieq_l("client-no-http2-cipher-block-lis", name, 32)) {
+ return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST;
+ }
+ break;
+ }
+ break;
+ case 34:
+ switch (name[33]) {
+ case 'e':
+ if (util::strieq_l("tls-ticket-key-memcached-cert-fil", name, 33)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("frontend-http2-dump-request-heade", name, 33)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER;
+ }
+ break;
+ case 't':
+ if (util::strieq_l("backend-http1-connections-per-hos", name, 33)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST;
+ }
+ break;
+ case 'y':
+ if (util::strieq_l("tls-ticket-key-memcached-max-retr", name, 33)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY;
+ }
+ break;
+ }
+ break;
+ case 35:
+ switch (name[34]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE;
+ }
+ break;
+ case 'o':
+ if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) {
+ return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER;
+ }
+ if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) {
+ return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER;
+ }
+ break;
+ }
+ break;
+ case 36:
+ switch (name[35]) {
+ case 'd':
+ if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) {
+ return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD;
+ }
+ break;
+ case 'e':
+ if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE;
+ }
+ break;
+ case 'r':
+ if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS;
+ }
+ if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ break;
+ }
+ break;
+ case 37:
+ switch (name[36]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-connection-window-siz", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-connection-window-siz", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("tls-session-cache-memcached-cert-fil", name, 36)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE;
+ }
+ break;
+ case 's':
+ if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS;
+ }
+ if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS;
+ }
+ if (util::strieq_l("frontend-http3-max-concurrent-stream", name, 36)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS;
+ }
+ break;
+ }
+ break;
+ case 38:
+ switch (name[37]) {
+ case 'd':
+ if (util::strieq_l("backend-http1-connections-per-fronten", name, 37)) {
+ return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND;
+ }
+ break;
+ }
+ break;
+ case 39:
+ switch (name[38]) {
+ case 'y':
+ if (util::strieq_l("tls-ticket-key-memcached-address-famil", name, 38)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 40:
+ switch (name[39]) {
+ case 'e':
+ if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) {
+ return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE;
+ }
+ break;
+ }
+ break;
+ case 41:
+ switch (name[40]) {
+ case 'e':
+ if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE;
+ }
+ if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE;
+ }
+ if (util::strieq_l("frontend-http3-max-connection-window-siz", name,
+ 40)) {
+ return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE;
+ }
+ if (util::strieq_l("tls-ticket-key-memcached-private-key-fil", name,
+ 40)) {
+ return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE;
+ }
+ break;
+ }
+ break;
+ case 42:
+ switch (name[41]) {
+ case 'y':
+ if (util::strieq_l("tls-session-cache-memcached-address-famil", name,
+ 41)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY;
+ }
+ break;
+ }
+ break;
+ case 44:
+ switch (name[43]) {
+ case 'e':
+ if (util::strieq_l("tls-session-cache-memcached-private-key-fil", name,
+ 43)) {
+ return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE;
+ }
+ break;
+ }
+ break;
+ }
+ return -1;
+}
+
+int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
+ std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ auto optid = option_lookup_token(opt.c_str(), opt.size());
+ return parse_config(config, optid, opt, optarg, included_set,
+ pattern_addr_indexer);
+}
+
+int parse_config(Config *config, int optid, const StringRef &opt,
+ const StringRef &optarg, std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ char host[NI_MAXHOST];
+ uint16_t port;
+
+ switch (optid) {
+ case SHRPX_OPTID_BACKEND: {
+ auto &downstreamconf = *config->conn.downstream;
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+
+ DownstreamAddrConfig addr{};
+ if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
+ auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
+ addr.host =
+ make_string_ref(downstreamconf.balloc, StringRef{path, addr_end});
+ addr.host_unix = true;
+ } else {
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ addr.host = make_string_ref(downstreamconf.balloc, StringRef{host});
+ addr.port = port;
+ }
+
+ auto mapping = addr_end == std::end(optarg) ? addr_end : addr_end + 1;
+ auto mapping_end = std::find(mapping, std::end(optarg), ';');
+
+ auto params =
+ mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1;
+
+ if (parse_mapping(config, addr, pattern_addr_indexer,
+ StringRef{mapping, mapping_end},
+ StringRef{params, std::end(optarg)}) != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND: {
+ auto &apiconf = config->api;
+
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{addr_end, std::end(optarg)};
+
+ UpstreamParams params{};
+ params.tls = true;
+
+ if (parse_upstream_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ if (params.sni_fwd && !params.tls) {
+ LOG(ERROR) << "frontend: sni_fwd requires tls";
+ return -1;
+ }
+
+ if (params.quic) {
+ if (params.alt_mode != UpstreamAltMode::NONE) {
+ LOG(ERROR) << "frontend: api or healthmon cannot be used with quic";
+ return -1;
+ }
+
+ if (!params.tls) {
+ LOG(ERROR) << "frontend: quic requires TLS";
+ return -1;
+ }
+ }
+
+ UpstreamAddr addr{};
+ addr.fd = -1;
+ addr.tls = params.tls;
+ addr.sni_fwd = params.sni_fwd;
+ addr.alt_mode = params.alt_mode;
+ addr.accept_proxy_protocol = params.proxyproto;
+ addr.quic = params.quic;
+
+ if (addr.alt_mode == UpstreamAltMode::API) {
+ apiconf.enabled = true;
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &addrs = params.quic ? config->conn.quic_listener.addrs
+ : config->conn.listener.addrs;
+#else // !ENABLE_HTTP3
+ auto &addrs = config->conn.listener.addrs;
+#endif // !ENABLE_HTTP3
+
+ if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
+ if (addr.quic) {
+ LOG(ERROR) << "frontend: quic cannot be used on UNIX domain socket";
+ return -1;
+ }
+
+ auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
+ addr.host = make_string_ref(config->balloc, StringRef{path, addr_end});
+ addr.host_unix = true;
+ addr.index = addrs.size();
+
+ addrs.push_back(std::move(addr));
+
+ return 0;
+ }
+
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ addr.host = make_string_ref(config->balloc, StringRef{host});
+ addr.port = port;
+
+ if (util::numeric_host(host, AF_INET)) {
+ addr.family = AF_INET;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+ return 0;
+ }
+
+ if (util::numeric_host(host, AF_INET6)) {
+ addr.family = AF_INET6;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+ return 0;
+ }
+
+ addr.family = AF_INET;
+ addr.index = addrs.size();
+ addrs.push_back(addr);
+
+ addr.family = AF_INET6;
+ addr.index = addrs.size();
+ addrs.push_back(std::move(addr));
+
+ return 0;
+ }
+ case SHRPX_OPTID_WORKERS:
+#ifdef NOTHREADS
+ LOG(WARN) << "Threading disabled at build time, no threads created.";
+ return 0;
+#else // !NOTHREADS
+ return parse_uint(&config->num_worker, opt, optarg);
+#endif // !NOTHREADS
+ case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and "
+ << SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead.";
+ size_t n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+ auto &http2conf = config->http2;
+ http2conf.upstream.max_concurrent_streams = n;
+ http2conf.downstream.max_concurrent_streams = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_LOG_LEVEL: {
+ auto level = Log::get_severity_level_by_name(optarg);
+ if (level == -1) {
+ LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
+ return -1;
+ }
+ config->logging.severity = level;
+
+ return 0;
+ }
+ case SHRPX_OPTID_DAEMON:
+ config->daemon = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_PROXY:
+ config->http2_proxy = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_BRIDGE:
+ LOG(ERROR) << opt
+ << ": deprecated. Use backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_CLIENT_PROXY:
+ LOG(ERROR)
+ << opt
+ << ": deprecated. Use http2-proxy, frontend=<addr>,<port>;no-tls "
+ "and backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
+ config->http.xff.add = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR:
+ config->http.xff.strip_incoming = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_VIA:
+ config->http.no_via = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.http2_read, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_READ_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.read, opt, optarg);
+ case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.write, opt, optarg);
+ case SHRPX_OPTID_BACKEND_READ_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.read, opt, optarg);
+ case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.write, opt, optarg);
+ case SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.connect, opt,
+ optarg);
+ case SHRPX_OPTID_STREAM_READ_TIMEOUT:
+ return parse_duration(&config->http2.timeout.stream_read, opt, optarg);
+ case SHRPX_OPTID_STREAM_WRITE_TIMEOUT:
+ return parse_duration(&config->http2.timeout.stream_write, opt, optarg);
+ case SHRPX_OPTID_ACCESSLOG_FILE:
+ config->logging.access.file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_SYSLOG:
+ config->logging.access.syslog = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_FORMAT:
+ config->logging.access.format = parse_log_format(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ERRORLOG_FILE:
+ config->logging.error.file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ERRORLOG_SYSLOG:
+ config->logging.error.syslog = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FASTOPEN:
+ return parse_uint(&config->conn.listener.fastopen, opt, optarg);
+ case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT:
+ return parse_duration(&config->conn.downstream->timeout.idle_read, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS:
+ case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS
+ ? SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE
+ : SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE);
+ int32_t *resp;
+
+ if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) {
+ resp = &config->http2.upstream.window_size;
+ } else {
+ resp = &config->http2.downstream.window_size;
+ }
+
+ 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;
+ }
+
+ // Make 16 bits to the HTTP/2 default 64KiB - 1. This is the same
+ // behaviour of previous code.
+ *resp = (1 << n) - 1;
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS:
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS: {
+ LOG(WARN) << opt << ": deprecated. Use "
+ << (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS
+ ? SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE
+ : SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE);
+ int32_t *resp;
+
+ if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) {
+ resp = &config->http2.upstream.connection_window_size;
+ } else {
+ resp = &config->http2.downstream.connection_window_size;
+ }
+
+ 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 = (1 << n) - 1;
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_NO_TLS:
+ LOG(WARN) << opt << ": deprecated. Use no-tls keyword in "
+ << SHRPX_OPT_FRONTEND;
+ return 0;
+ case SHRPX_OPTID_BACKEND_NO_TLS:
+ LOG(WARN) << opt
+ << ": deprecated. backend connection is not encrypted by "
+ "default. See also "
+ << SHRPX_OPT_BACKEND_TLS;
+ return 0;
+ case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
+ LOG(WARN) << opt
+ << ": deprecated. Use sni keyword in --backend option. "
+ "For now, all sni values of all backends are "
+ "overridden by the given value "
+ << optarg;
+ config->tls.backend_sni_name = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_PID_FILE:
+ config->pid_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_USER: {
+ auto pwd = getpwnam(optarg.c_str());
+ if (!pwd) {
+ LOG(ERROR) << opt << ": failed to get uid from " << optarg << ": "
+ << xsi_strerror(errno, errbuf.data(), errbuf.size());
+ return -1;
+ }
+ config->user = make_string_ref(config->balloc, StringRef{pwd->pw_name});
+ config->uid = pwd->pw_uid;
+ config->gid = pwd->pw_gid;
+
+ return 0;
+ }
+ case SHRPX_OPTID_PRIVATE_KEY_FILE:
+ config->tls.private_key_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: {
+ auto passwd = read_passwd_from_file(opt, optarg);
+ if (passwd.empty()) {
+ LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
+ return -1;
+ }
+ config->tls.private_key_passwd =
+ make_string_ref(config->balloc, StringRef{passwd});
+
+ return 0;
+ }
+ case SHRPX_OPTID_CERTIFICATE_FILE:
+ config->tls.cert_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_DH_PARAM_FILE:
+ config->tls.dh_param_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_SUBCERT: {
+ auto end_keys = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{end_keys, std::end(optarg)};
+
+ SubcertParams params;
+ if (parse_subcert_params(params, src_params) != 0) {
+ return -1;
+ }
+
+ std::vector<uint8_t> sct_data;
+
+ if (!params.sct_dir.empty()) {
+ // Make sure that dir_path is NULL terminated string.
+ if (read_tls_sct_from_dir(sct_data, opt,
+ StringRef{params.sct_dir.str()}) != 0) {
+ return -1;
+ }
+ }
+
+ // Private Key file and certificate file separated by ':'.
+ auto sp = std::find(std::begin(optarg), end_keys, ':');
+ if (sp == end_keys) {
+ LOG(ERROR) << opt << ": missing ':' in "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ auto private_key_file = StringRef{std::begin(optarg), sp};
+
+ if (private_key_file.empty()) {
+ LOG(ERROR) << opt << ": missing private key file: "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ auto cert_file = StringRef{sp + 1, end_keys};
+
+ if (cert_file.empty()) {
+ LOG(ERROR) << opt << ": missing certificate file: "
+ << StringRef{std::begin(optarg), end_keys};
+ return -1;
+ }
+
+ config->tls.subcerts.emplace_back(
+ make_string_ref(config->balloc, private_key_file),
+ make_string_ref(config->balloc, cert_file), std::move(sct_data));
+
+ return 0;
+ }
+ case SHRPX_OPTID_SYSLOG_FACILITY: {
+ int facility = int_syslog_facility(optarg);
+ if (facility == -1) {
+ LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg;
+ return -1;
+ }
+ config->logging.syslog_facility = facility;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKLOG:
+ return parse_uint(&config->conn.listener.backlog, opt, optarg);
+ case SHRPX_OPTID_CIPHERS:
+ config->tls.ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS13_CIPHERS:
+ config->tls.tls13_ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT:
+ LOG(ERROR) << opt
+ << ": deprecated. Use frontend=<addr>,<port>;no-tls, "
+ "backend=<addr>,<port>;;proto=h2;tls";
+ return -1;
+ case SHRPX_OPTID_INSECURE:
+ config->tls.insecure = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_CACERT:
+ config->tls.cacert = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_IPV4:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-address-family=IPv4 instead.";
+
+ config->conn.downstream->family = AF_INET;
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_IPV6:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-address-family=IPv6 instead.";
+
+ config->conn.downstream->family = AF_INET6;
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: {
+ auto &proxy = config->downstream_http_proxy;
+ // Reset here so that multiple option occurrence does not merge
+ // the results.
+ proxy = {};
+ // parse URI and get hostname, port and optionally userinfo.
+ http_parser_url u{};
+ int rv = http_parser_parse_url(optarg.c_str(), optarg.size(), 0, &u);
+ if (rv == 0) {
+ if (u.field_set & UF_USERINFO) {
+ auto uf = util::get_uri_field(optarg.c_str(), u, UF_USERINFO);
+ // Surprisingly, u.field_set & UF_USERINFO is nonzero even if
+ // userinfo component is empty string.
+ if (!uf.empty()) {
+ proxy.userinfo = util::percent_decode(config->balloc, uf);
+ }
+ }
+ if (u.field_set & UF_HOST) {
+ proxy.host = make_string_ref(
+ config->balloc, util::get_uri_field(optarg.c_str(), u, UF_HOST));
+ } else {
+ LOG(ERROR) << opt << ": no hostname specified";
+ return -1;
+ }
+ if (u.field_set & UF_PORT) {
+ proxy.port = u.port;
+ } else {
+ LOG(ERROR) << opt << ": no port specified";
+ return -1;
+ }
+ } else {
+ LOG(ERROR) << opt << ": parse error";
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_READ_RATE:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.rate, opt,
+ optarg);
+ case SHRPX_OPTID_READ_BURST:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.burst,
+ opt, optarg);
+ case SHRPX_OPTID_WRITE_RATE:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.rate,
+ opt, optarg);
+ case SHRPX_OPTID_WRITE_BURST:
+ return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.burst,
+ opt, optarg);
+ case SHRPX_OPTID_WORKER_READ_RATE:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_READ_BURST:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_WRITE_RATE:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_WORKER_WRITE_BURST:
+ LOG(WARN) << opt << ": not implemented yet";
+ return 0;
+ case SHRPX_OPTID_TLS_PROTO_LIST: {
+ LOG(WARN) << opt
+ << ": deprecated. Use tls-min-proto-version and "
+ "tls-max-proto-version instead.";
+ auto list = util::split_str(optarg, ',');
+ config->tls.tls_proto_list.resize(list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ config->tls.tls_proto_list[i] = make_string_ref(config->balloc, list[i]);
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_VERIFY_CLIENT:
+ config->tls.client_verify.enabled = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_VERIFY_CLIENT_CACERT:
+ config->tls.client_verify.cacert = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE:
+ config->tls.client.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_CERT_FILE:
+ config->tls.client.cert_file = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER:
+ config->http2.upstream.debug.dump.request_header_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER:
+ config->http2.upstream.debug.dump.response_header_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING:
+ config->http2.no_cookie_crumbling = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_FRAME_DEBUG:
+ config->http2.upstream.debug.frame_debug = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_PADDING:
+ return parse_uint(&config->padding, opt, optarg);
+ case SHRPX_OPTID_ALTSVC: {
+ AltSvc altsvc{};
+
+ if (parse_altsvc(altsvc, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->http.altsvcs.push_back(std::move(altsvc));
+
+ return 0;
+ }
+ case SHRPX_OPTID_ADD_REQUEST_HEADER:
+ case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
+ auto p = parse_header(config->balloc, optarg);
+ if (p.name.empty()) {
+ LOG(ERROR) << opt << ": invalid header field: " << optarg;
+ return -1;
+ }
+ if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
+ config->http.add_request_headers.push_back(std::move(p));
+ } else {
+ config->http.add_response_headers.push_back(std::move(p));
+ }
+ return 0;
+ }
+ case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS:
+ return parse_uint(&config->conn.upstream.worker_connections, opt, optarg);
+ case SHRPX_OPTID_NO_LOCATION_REWRITE:
+ config->http.no_location_rewrite = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_HOST_REWRITE:
+ LOG(WARN) << SHRPX_OPT_NO_HOST_REWRITE
+ << ": deprecated. :authority and host header fields are NOT "
+ "altered by default. To rewrite these headers, use "
+ "--host-rewrite option.";
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST:
+ LOG(WARN) << opt
+ << ": deprecated. Use backend-connections-per-host instead.";
+ // fall through
+ case SHRPX_OPTID_BACKEND_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;
+ }
+
+ config->conn.downstream->connections_per_host = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead.";
+ // fall through
+ case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND:
+ return parse_uint(&config->conn.downstream->connections_per_frontend, opt,
+ optarg);
+ case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
+ return parse_duration(&config->conn.listener.timeout.sleep, opt, optarg);
+ case SHRPX_OPTID_TLS_TICKET_KEY_FILE:
+ config->tls.ticket.files.emplace_back(
+ make_string_ref(config->balloc, optarg));
+ return 0;
+ case SHRPX_OPTID_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;
+ }
+
+ config->rlimit_nofile = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_BACKEND_REQUEST_BUFFER:
+ case SHRPX_OPTID_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 (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) {
+ config->conn.downstream->request_buffer_size = n;
+ } else {
+ config->conn.downstream->response_buffer_size = n;
+ }
+
+ return 0;
+ }
+
+ case SHRPX_OPTID_NO_SERVER_PUSH:
+ config->http2.no_server_push = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
+ LOG(WARN) << opt << ": deprecated.";
+ return 0;
+ case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
+ config->tls.ocsp.fetch_ocsp_response_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_OCSP_UPDATE_INTERVAL:
+ return parse_duration(&config->tls.ocsp.update_interval, opt, optarg);
+ case SHRPX_OPTID_NO_OCSP:
+ config->tls.ocsp.disabled = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_HEADER_FIELD_BUFFER:
+ LOG(WARN) << opt
+ << ": deprecated. Use request-header-field-buffer instead.";
+ // fall through
+ case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER:
+ return parse_uint_with_unit(&config->http.request_header_field_buffer, opt,
+ optarg);
+ case SHRPX_OPTID_MAX_HEADER_FIELDS:
+ LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead.";
+ // fall through
+ case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS:
+ return parse_uint(&config->http.max_request_header_fields, opt, optarg);
+ case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER:
+ return parse_uint_with_unit(&config->http.response_header_field_buffer, opt,
+ optarg);
+ case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS:
+ return parse_uint(&config->http.max_response_header_fields, opt, optarg);
+ case SHRPX_OPTID_INCLUDE: {
+ if (included_set.count(optarg)) {
+ LOG(ERROR) << opt << ": " << optarg << " has already been included";
+ return -1;
+ }
+
+ included_set.insert(optarg);
+ auto rv =
+ load_config(config, optarg.c_str(), included_set, pattern_addr_indexer);
+ included_set.erase(optarg);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER:
+ if (util::strieq_l("aes-128-cbc", optarg)) {
+ config->tls.ticket.cipher = EVP_aes_128_cbc();
+ } else if (util::strieq_l("aes-256-cbc", optarg)) {
+ config->tls.ticket.cipher = EVP_aes_256_cbc();
+ } else {
+ LOG(ERROR) << opt
+ << ": unsupported cipher for ticket encryption: " << optarg;
+ return -1;
+ }
+ config->tls.ticket.cipher_given = true;
+
+ return 0;
+ case SHRPX_OPTID_HOST_REWRITE:
+ config->http.no_host_rewrite = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED:
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: {
+ auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
+ auto src_params = StringRef{addr_end, std::end(optarg)};
+
+ MemcachedConnectionParams params{};
+ if (parse_memcached_connection_params(params, src_params, StringRef{opt}) !=
+ 0) {
+ return -1;
+ }
+
+ if (split_host_port(host, sizeof(host), &port,
+ StringRef{std::begin(optarg), addr_end}, opt) == -1) {
+ return -1;
+ }
+
+ switch (optid) {
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+ memcachedconf.host = make_string_ref(config->balloc, StringRef{host});
+ memcachedconf.port = port;
+ memcachedconf.tls = params.tls;
+ break;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: {
+ auto &memcachedconf = config->tls.ticket.memcached;
+ memcachedconf.host = make_string_ref(config->balloc, StringRef{host});
+ memcachedconf.port = port;
+ memcachedconf.tls = params.tls;
+ break;
+ }
+ };
+
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL:
+ return parse_duration(&config->tls.ticket.memcached.interval, opt, optarg);
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: {
+ int n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n > 30) {
+ LOG(ERROR) << opt << ": must be smaller than or equal to 30";
+ return -1;
+ }
+
+ config->tls.ticket.memcached.max_retry = n;
+ return 0;
+ }
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
+ return parse_uint(&config->tls.ticket.memcached.max_fail, opt, optarg);
+ case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: {
+ size_t n;
+ if (parse_uint_with_unit(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->tls.dyn_rec.warmup_threshold = n;
+
+ return 0;
+ }
+
+ case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT:
+ return parse_duration(&config->tls.dyn_rec.idle_timeout, opt, optarg);
+
+ case SHRPX_OPTID_MRUBY_FILE:
+#ifdef HAVE_MRUBY
+ config->mruby_file = make_string_ref(config->balloc, optarg);
+#else // !HAVE_MRUBY
+ LOG(WARN) << opt
+ << ": ignored because mruby support is disabled at build time.";
+#endif // !HAVE_MRUBY
+ return 0;
+ case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL:
+ LOG(WARN) << opt << ": deprecated. Use proxyproto keyword in "
+ << SHRPX_OPT_FRONTEND << " instead.";
+ config->conn.upstream.accept_proxy_protocol = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_ADD_FORWARDED: {
+ auto &fwdconf = config->http.forwarded;
+ fwdconf.params = FORWARDED_NONE;
+ for (const auto &param : util::split_str(optarg, ',')) {
+ if (util::strieq_l("by", param)) {
+ fwdconf.params |= FORWARDED_BY;
+ continue;
+ }
+ if (util::strieq_l("for", param)) {
+ fwdconf.params |= FORWARDED_FOR;
+ continue;
+ }
+ if (util::strieq_l("host", param)) {
+ fwdconf.params |= FORWARDED_HOST;
+ continue;
+ }
+ if (util::strieq_l("proto", param)) {
+ fwdconf.params |= FORWARDED_PROTO;
+ continue;
+ }
+
+ LOG(ERROR) << opt << ": unknown parameter " << optarg;
+
+ return -1;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_STRIP_INCOMING_FORWARDED:
+ config->http.forwarded.strip_incoming = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FORWARDED_BY:
+ case SHRPX_OPTID_FORWARDED_FOR: {
+ auto type = parse_forwarded_node_type(optarg);
+
+ if (type == static_cast<ForwardedNode>(-1) ||
+ (optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) {
+ LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string "
+ << optarg;
+ return -1;
+ }
+
+ auto &fwdconf = config->http.forwarded;
+
+ switch (optid) {
+ case SHRPX_OPTID_FORWARDED_BY:
+ fwdconf.by_node_type = type;
+ if (optarg[0] == '_') {
+ fwdconf.by_obfuscated = make_string_ref(config->balloc, optarg);
+ } else {
+ fwdconf.by_obfuscated = StringRef::from_lit("");
+ }
+ break;
+ case SHRPX_OPTID_FORWARDED_FOR:
+ fwdconf.for_node_type = type;
+ break;
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+ // fall through
+ case SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST:
+ config->tls.no_http2_cipher_block_list = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP1_TLS:
+ case SHRPX_OPTID_BACKEND_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_BACKEND << " instead.";
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED;
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE:
+ config->tls.session_cache.memcached.cert_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE:
+ config->tls.session_cache.memcached.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS:
+ LOG(WARN) << opt << ": deprecated. Use tls keyword in "
+ << SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED;
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE:
+ config->tls.ticket.memcached.cert_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE:
+ config->tls.ticket.memcached.private_key_file =
+ make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY:
+ return parse_address_family(&config->tls.ticket.memcached.family, opt,
+ optarg);
+ case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY:
+ return parse_address_family(&config->tls.session_cache.memcached.family,
+ opt, optarg);
+ case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY:
+ return parse_address_family(&config->conn.downstream->family, opt, optarg);
+ case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS:
+ return parse_uint(&config->http2.upstream.max_concurrent_streams, opt,
+ optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS:
+ return parse_uint(&config->http2.downstream.max_concurrent_streams, opt,
+ optarg);
+ case SHRPX_OPTID_ERROR_PAGE:
+ return parse_error_page(config->http.error_pages, opt, optarg);
+ case SHRPX_OPTID_NO_KQUEUE:
+ if ((ev_supported_backends() & EVBACKEND_KQUEUE) == 0) {
+ LOG(WARN) << opt << ": kqueue is not supported on this platform";
+ return 0;
+ }
+
+ config->ev_loop_flags = ev_recommended_backends() & ~EVBACKEND_KQUEUE;
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT:
+ return parse_duration(&config->http2.upstream.timeout.settings, opt,
+ optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT:
+ return parse_duration(&config->http2.downstream.timeout.settings, opt,
+ optarg);
+ case SHRPX_OPTID_API_MAX_REQUEST_BODY:
+ return parse_uint_with_unit(&config->api.max_request_body, opt, optarg);
+ case SHRPX_OPTID_BACKEND_MAX_BACKOFF:
+ return parse_duration(&config->conn.downstream->timeout.max_backoff, opt,
+ optarg);
+ case SHRPX_OPTID_SERVER_NAME:
+ config->http.server_name = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_SERVER_REWRITE:
+ config->http.no_server_rewrite = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE:
+ config->http2.upstream.optimize_write_buffer_size =
+ util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE:
+ config->http2.upstream.optimize_window_size = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.downstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE:
+ if (parse_uint_with_unit(&config->http2.downstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE:
+ if (parse_uint_with_unit(&config->http2.upstream.encoder_dynamic_table_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.upstream.option,
+ config->http2.upstream.encoder_dynamic_table_size);
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.upstream.alt_mode_option,
+ config->http2.upstream.encoder_dynamic_table_size);
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE:
+ return parse_uint_with_unit(
+ &config->http2.upstream.decoder_dynamic_table_size, opt, optarg);
+ case SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE:
+ if (parse_uint_with_unit(
+ &config->http2.downstream.encoder_dynamic_table_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+
+ nghttp2_option_set_max_deflate_dynamic_table_size(
+ config->http2.downstream.option,
+ config->http2.downstream.encoder_dynamic_table_size);
+
+ return 0;
+ case SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE:
+ return parse_uint_with_unit(
+ &config->http2.downstream.decoder_dynamic_table_size, opt, optarg);
+ case SHRPX_OPTID_ECDH_CURVES:
+ config->tls.ecdh_curves = make_string_ref(config->balloc, optarg);
+ return 0;
+ case SHRPX_OPTID_TLS_SCT_DIR:
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg);
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support SCT";
+ return 0;
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL
+ case SHRPX_OPTID_DNS_CACHE_TIMEOUT:
+ return parse_duration(&config->dns.timeout.cache, opt, optarg);
+ case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT:
+ return parse_duration(&config->dns.timeout.lookup, opt, optarg);
+ case SHRPX_OPTID_DNS_MAX_TRY: {
+ int n;
+ if (parse_uint(&n, opt, optarg) != 0) {
+ return -1;
+ }
+
+ if (n > 5) {
+ LOG(ERROR) << opt << ": must be smaller than or equal to 5";
+ return -1;
+ }
+
+ config->dns.max_try = n;
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT:
+ return parse_duration(&config->conn.upstream.timeout.idle_read, opt,
+ optarg);
+ case SHRPX_OPTID_PSK_SECRETS:
+#ifndef OPENSSL_NO_PSK
+ return parse_psk_secrets(config, optarg);
+#else // OPENSSL_NO_PSK
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support PSK";
+ return 0;
+#endif // OPENSSL_NO_PSK
+ case SHRPX_OPTID_CLIENT_PSK_SECRETS:
+#ifndef OPENSSL_NO_PSK
+ return parse_client_psk_secrets(config, optarg);
+#else // OPENSSL_NO_PSK
+ LOG(WARN)
+ << opt
+ << ": ignored because underlying TLS library does not support PSK";
+ return 0;
+#endif // OPENSSL_NO_PSK
+ case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST:
+ LOG(WARN) << opt << ": deprecated. Use "
+ << SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+ // fall through
+ case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST:
+ config->tls.client.no_http2_cipher_block_list =
+ util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_CLIENT_CIPHERS:
+ config->tls.client.ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS13_CLIENT_CIPHERS:
+ config->tls.client.tls13_ciphers = make_string_ref(config->balloc, optarg);
+
+ return 0;
+ case SHRPX_OPTID_ACCESSLOG_WRITE_EARLY:
+ config->logging.access.write_early = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_MIN_PROTO_VERSION:
+ return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg);
+ case SHRPX_OPTID_TLS_MAX_PROTO_VERSION:
+ return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg);
+ case SHRPX_OPTID_REDIRECT_HTTPS_PORT: {
+ auto n = util::parse_uint(optarg);
+ if (n == -1 || n < 0 || n > 65535) {
+ LOG(ERROR) << opt
+ << ": bad value. Specify an integer in the range [0, "
+ "65535], inclusive";
+ return -1;
+ }
+ config->http.redirect_https_port = make_string_ref(config->balloc, optarg);
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_MAX_REQUESTS:
+ return parse_uint(&config->http.max_requests, opt, optarg);
+ case SHRPX_OPTID_SINGLE_THREAD:
+ config->single_thread = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_SINGLE_PROCESS:
+ config->single_process = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO:
+ config->http.xfp.add = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO:
+ config->http.xfp.strip_incoming = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_OCSP_STARTUP:
+ config->tls.ocsp.startup = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_NO_VERIFY_OCSP:
+ config->tls.ocsp.no_verify = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED:
+ config->tls.client_verify.tolerate_expired = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR:
+ config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA:
+ config->tls.no_postpone_early_data = util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_TLS_MAX_EARLY_DATA: {
+ return parse_uint_with_unit(&config->tls.max_early_data, opt, optarg);
+ }
+ case SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA:
+ config->http.early_data.strip_incoming = !util::strieq_l("yes", optarg);
+
+ return 0;
+ case SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE:
+#ifdef ENABLE_HTTP3
+ config->quic.bpf.prog_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_NO_QUIC_BPF:
+#ifdef ENABLE_HTTP3
+ config->quic.bpf.disabled = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_HTTP2_ALTSVC: {
+ AltSvc altsvc{};
+
+ if (parse_altsvc(altsvc, opt, optarg) != 0) {
+ return -1;
+ }
+
+ config->http.http2_altsvcs.push_back(std::move(altsvc));
+
+ return 0;
+ }
+ case SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT:
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->conn.upstream.timeout.http3_read, opt,
+ optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT:
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->quic.upstream.timeout.idle, opt, optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.debug.log = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.max_window_size, opt,
+ optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+ if (parse_uint_with_unit(&config->http3.upstream.max_connection_window_size,
+ opt, optarg) != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS:
+#ifdef ENABLE_HTTP3
+ return parse_uint(&config->http3.upstream.max_concurrent_streams, opt,
+ optarg);
+#else // !ENABLE_HTTP3
+ return 0;
+#endif // !ENABLE_HTTP3
+ case SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.early_data = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.require_token = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER:
+#ifdef ENABLE_HTTP3
+ if (util::strieq_l("cubic", optarg)) {
+ config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
+ } else if (util::strieq_l("bbr", optarg)) {
+ config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR;
+ } else {
+ LOG(ERROR) << opt << ": must be either cubic or bbr";
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_QUIC_SERVER_ID:
+#ifdef ENABLE_HTTP3
+ if (optarg.size() != config->quic.server_id.size() * 2 ||
+ !util::is_hex_string(optarg)) {
+ LOG(ERROR) << opt << ": must be a hex-string";
+ return -1;
+ }
+ util::decode_hex(std::begin(config->quic.server_id), optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE:
+#ifdef ENABLE_HTTP3
+ config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ case SHRPX_OPTID_RLIMIT_MEMLOCK: {
+ 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;
+ }
+
+ config->rlimit_memlock = n;
+
+ return 0;
+ }
+ case SHRPX_OPTID_MAX_WORKER_PROCESSES:
+ return parse_uint(&config->max_worker_processes, opt, optarg);
+ case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD:
+ return parse_duration(&config->worker_process_grace_shutdown_period, opt,
+ optarg);
+ case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: {
+#ifdef ENABLE_HTTP3
+ return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg);
+#endif // ENABLE_HTTP3
+
+ return 0;
+ }
+ case SHRPX_OPTID_REQUIRE_HTTP_SCHEME:
+ config->http.require_http_scheme = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_TLS_KTLS:
+ config->tls.ktls = util::strieq_l("yes", optarg);
+ return 0;
+ case SHRPX_OPTID_NPN_LIST:
+ LOG(WARN) << opt << ": deprecated. Use alpn-list instead.";
+ // fall through
+ case SHRPX_OPTID_ALPN_LIST: {
+ auto list = util::split_str(optarg, ',');
+ config->tls.alpn_list.resize(list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ config->tls.alpn_list[i] = make_string_ref(config->balloc, list[i]);
+ }
+
+ return 0;
+ }
+ case SHRPX_OPTID_CONF:
+ LOG(WARN) << "conf: ignored";
+
+ return 0;
+ }
+
+ LOG(ERROR) << "Unknown option: " << opt;
+
+ return -1;
+}
+
+int load_config(Config *config, const char *filename,
+ std::set<StringRef> &include_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer) {
+ 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;
+ }
+ auto eq = std::find(std::begin(line), std::end(line), '=');
+ if (eq == std::end(line)) {
+ LOG(ERROR) << "Bad configuration format in " << filename << " at line "
+ << linenum;
+ return -1;
+ }
+ *eq = '\0';
+
+ if (parse_config(config, StringRef{std::begin(line), eq},
+ StringRef{eq + 1, std::end(line)}, include_set,
+ pattern_addr_indexer) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+StringRef str_syslog_facility(int facility) {
+ switch (facility) {
+ case (LOG_AUTH):
+ return StringRef::from_lit("auth");
+#ifdef LOG_AUTHPRIV
+ case (LOG_AUTHPRIV):
+ return StringRef::from_lit("authpriv");
+#endif // LOG_AUTHPRIV
+ case (LOG_CRON):
+ return StringRef::from_lit("cron");
+ case (LOG_DAEMON):
+ return StringRef::from_lit("daemon");
+#ifdef LOG_FTP
+ case (LOG_FTP):
+ return StringRef::from_lit("ftp");
+#endif // LOG_FTP
+ case (LOG_KERN):
+ return StringRef::from_lit("kern");
+ case (LOG_LOCAL0):
+ return StringRef::from_lit("local0");
+ case (LOG_LOCAL1):
+ return StringRef::from_lit("local1");
+ case (LOG_LOCAL2):
+ return StringRef::from_lit("local2");
+ case (LOG_LOCAL3):
+ return StringRef::from_lit("local3");
+ case (LOG_LOCAL4):
+ return StringRef::from_lit("local4");
+ case (LOG_LOCAL5):
+ return StringRef::from_lit("local5");
+ case (LOG_LOCAL6):
+ return StringRef::from_lit("local6");
+ case (LOG_LOCAL7):
+ return StringRef::from_lit("local7");
+ case (LOG_LPR):
+ return StringRef::from_lit("lpr");
+ case (LOG_MAIL):
+ return StringRef::from_lit("mail");
+ case (LOG_SYSLOG):
+ return StringRef::from_lit("syslog");
+ case (LOG_USER):
+ return StringRef::from_lit("user");
+ case (LOG_UUCP):
+ return StringRef::from_lit("uucp");
+ default:
+ return StringRef::from_lit("(unknown)");
+ }
+}
+
+int int_syslog_facility(const StringRef &strfacility) {
+ if (util::strieq_l("auth", strfacility)) {
+ return LOG_AUTH;
+ }
+
+#ifdef LOG_AUTHPRIV
+ if (util::strieq_l("authpriv", strfacility)) {
+ return LOG_AUTHPRIV;
+ }
+#endif // LOG_AUTHPRIV
+
+ if (util::strieq_l("cron", strfacility)) {
+ return LOG_CRON;
+ }
+
+ if (util::strieq_l("daemon", strfacility)) {
+ return LOG_DAEMON;
+ }
+
+#ifdef LOG_FTP
+ if (util::strieq_l("ftp", strfacility)) {
+ return LOG_FTP;
+ }
+#endif // LOG_FTP
+
+ if (util::strieq_l("kern", strfacility)) {
+ return LOG_KERN;
+ }
+
+ if (util::strieq_l("local0", strfacility)) {
+ return LOG_LOCAL0;
+ }
+
+ if (util::strieq_l("local1", strfacility)) {
+ return LOG_LOCAL1;
+ }
+
+ if (util::strieq_l("local2", strfacility)) {
+ return LOG_LOCAL2;
+ }
+
+ if (util::strieq_l("local3", strfacility)) {
+ return LOG_LOCAL3;
+ }
+
+ if (util::strieq_l("local4", strfacility)) {
+ return LOG_LOCAL4;
+ }
+
+ if (util::strieq_l("local5", strfacility)) {
+ return LOG_LOCAL5;
+ }
+
+ if (util::strieq_l("local6", strfacility)) {
+ return LOG_LOCAL6;
+ }
+
+ if (util::strieq_l("local7", strfacility)) {
+ return LOG_LOCAL7;
+ }
+
+ if (util::strieq_l("lpr", strfacility)) {
+ return LOG_LPR;
+ }
+
+ if (util::strieq_l("mail", strfacility)) {
+ return LOG_MAIL;
+ }
+
+ if (util::strieq_l("news", strfacility)) {
+ return LOG_NEWS;
+ }
+
+ if (util::strieq_l("syslog", strfacility)) {
+ return LOG_SYSLOG;
+ }
+
+ if (util::strieq_l("user", strfacility)) {
+ return LOG_USER;
+ }
+
+ if (util::strieq_l("uucp", strfacility)) {
+ return LOG_UUCP;
+ }
+
+ return -1;
+}
+
+StringRef strproto(Proto proto) {
+ switch (proto) {
+ case Proto::NONE:
+ return StringRef::from_lit("none");
+ case Proto::HTTP1:
+ return StringRef::from_lit("http/1.1");
+ case Proto::HTTP2:
+ return StringRef::from_lit("h2");
+ case Proto::HTTP3:
+ return StringRef::from_lit("h3");
+ case Proto::MEMCACHED:
+ return StringRef::from_lit("memcached");
+ }
+
+ // gcc needs this.
+ assert(0);
+ abort();
+}
+
+namespace {
+// Consistent hashing method described in
+// https://github.com/RJ/ketama. Generate 160 32-bit hashes per |s|,
+// which is usually backend address. The each hash is associated to
+// index of backend address. When all hashes for every backend
+// address are calculated, sort it in ascending order of hash. To
+// choose the index, compute 32-bit hash based on client IP address,
+// and do lower bound search in the array. The returned index is the
+// backend to use.
+int compute_affinity_hash(std::vector<AffinityHash> &res, size_t idx,
+ const StringRef &s) {
+ int rv;
+ std::array<uint8_t, 32> buf;
+
+ for (auto i = 0; i < 20; ++i) {
+ auto t = s.str();
+ t += i;
+
+ rv = util::sha256(buf.data(), StringRef{t});
+ if (rv != 0) {
+ return -1;
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ auto h = (static_cast<uint32_t>(buf[4 * i]) << 24) |
+ (static_cast<uint32_t>(buf[4 * i + 1]) << 16) |
+ (static_cast<uint32_t>(buf[4 * i + 2]) << 8) |
+ static_cast<uint32_t>(buf[4 * i + 3]);
+
+ res.emplace_back(idx, h);
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+// Configures the following member in |config|:
+// conn.downstream_router, conn.downstream.addr_groups,
+// conn.downstream.addr_group_catch_all.
+int configure_downstream_group(Config *config, bool http2_proxy,
+ bool numeric_addr_only,
+ const TLSConfig &tlsconf) {
+ int rv;
+
+ auto &downstreamconf = *config->conn.downstream;
+ auto &addr_groups = downstreamconf.addr_groups;
+ auto &routerconf = downstreamconf.router;
+ auto &router = routerconf.router;
+
+ if (addr_groups.empty()) {
+ DownstreamAddrConfig addr{};
+ addr.host = StringRef::from_lit(DEFAULT_DOWNSTREAM_HOST);
+ addr.port = DEFAULT_DOWNSTREAM_PORT;
+ addr.proto = Proto::HTTP1;
+ addr.weight = 1;
+ addr.group_weight = 1;
+
+ DownstreamAddrGroupConfig g(StringRef::from_lit("/"));
+ g.addrs.push_back(std::move(addr));
+ router.add_route(g.pattern, addr_groups.size());
+ addr_groups.push_back(std::move(g));
+ }
+
+ // backward compatibility: override all SNI fields with the option
+ // value --backend-tls-sni-field
+ if (!tlsconf.backend_sni_name.empty()) {
+ auto &sni = tlsconf.backend_sni_name;
+ for (auto &addr_group : addr_groups) {
+ for (auto &addr : addr_group.addrs) {
+ addr.sni = sni;
+ }
+ }
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Resolving backend address";
+ }
+
+ ssize_t catch_all_group = -1;
+ for (size_t i = 0; i < addr_groups.size(); ++i) {
+ auto &g = addr_groups[i];
+ if (g.pattern == StringRef::from_lit("/")) {
+ catch_all_group = i;
+ }
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern
+ << "'";
+ for (auto &addr : g.addrs) {
+ LOG(INFO) << "group " << i << " -> " << addr.host.c_str()
+ << (addr.host_unix ? "" : ":" + util::utos(addr.port))
+ << ", proto=" << strproto(addr.proto)
+ << (addr.tls ? ", tls" : "");
+ }
+ }
+#ifdef HAVE_MRUBY
+ // Try compile mruby script and catch compile error early.
+ if (!g.mruby_file.empty()) {
+ if (mruby::create_mruby_context(g.mruby_file) == nullptr) {
+ LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL)
+ << "backend: Could not compile mruby file for pattern "
+ << g.pattern;
+ if (!config->ignore_per_pattern_mruby_error) {
+ return -1;
+ }
+ g.mruby_file = StringRef{};
+ }
+ }
+#endif // HAVE_MRUBY
+ }
+
+#ifdef HAVE_MRUBY
+ // Try compile mruby script (--mruby-file) here to catch compile
+ // error early.
+ if (!config->mruby_file.empty()) {
+ if (mruby::create_mruby_context(config->mruby_file) == nullptr) {
+ LOG(FATAL) << "mruby-file: Could not compile mruby file";
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ if (catch_all_group == -1) {
+ LOG(FATAL) << "backend: No catch-all backend address is configured";
+ return -1;
+ }
+
+ downstreamconf.addr_group_catch_all = catch_all_group;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Catch-all pattern is group " << catch_all_group;
+ }
+
+ auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0;
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ for (auto &g : addr_groups) {
+ std::unordered_map<StringRef, uint32_t> wgchk;
+ for (auto &addr : g.addrs) {
+ if (addr.group_weight) {
+ auto it = wgchk.find(addr.group);
+ if (it == std::end(wgchk)) {
+ wgchk.emplace(addr.group, addr.group_weight);
+ } else if ((*it).second != addr.group_weight) {
+ LOG(FATAL) << "backend: inconsistent group-weight for a single group";
+ return -1;
+ }
+ }
+
+ if (addr.host_unix) {
+ // for AF_UNIX socket, we use "localhost" as host for backend
+ // hostport. This is used as Host header field to backend and
+ // not going to be passed to any syscalls.
+ addr.hostport = StringRef::from_lit("localhost");
+
+ auto path = addr.host.c_str();
+ auto pathlen = addr.host.size();
+
+ if (pathlen + 1 > sizeof(addr.addr.su.un.sun_path)) {
+ LOG(FATAL) << "UNIX domain socket path " << path << " is too long > "
+ << sizeof(addr.addr.su.un.sun_path);
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Use UNIX domain socket path " << path
+ << " for backend connection";
+ }
+
+ addr.addr.su.un.sun_family = AF_UNIX;
+ // copy path including terminal NULL
+ std::copy_n(path, pathlen + 1, addr.addr.su.un.sun_path);
+ addr.addr.len = sizeof(addr.addr.su.un);
+
+ continue;
+ }
+
+ addr.hostport =
+ util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port);
+
+ auto hostport =
+ util::make_hostport(std::begin(hostport_buf), addr.host, addr.port);
+
+ if (!addr.dns) {
+ if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
+ downstreamconf.family, resolve_flags) == -1) {
+ LOG(FATAL) << "Resolving backend address failed: " << hostport;
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Resolved backend address: " << hostport << " -> "
+ << util::to_numeric_addr(&addr.addr);
+ }
+ } else {
+ LOG(INFO) << "Resolving backend address " << hostport
+ << " takes place dynamically";
+ }
+ }
+
+ for (auto &addr : g.addrs) {
+ if (addr.group_weight == 0) {
+ auto it = wgchk.find(addr.group);
+ if (it == std::end(wgchk)) {
+ addr.group_weight = 1;
+ } else {
+ addr.group_weight = (*it).second;
+ }
+ }
+ }
+
+ if (g.affinity.type != SessionAffinity::NONE) {
+ size_t idx = 0;
+ for (auto &addr : g.addrs) {
+ StringRef key;
+ if (addr.dns) {
+ if (addr.host_unix) {
+ key = addr.host;
+ } else {
+ key = addr.hostport;
+ }
+ } else {
+ auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
+ key = StringRef{p, addr.addr.len};
+ }
+ rv = compute_affinity_hash(g.affinity_hash, idx, key);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (g.affinity.cookie.stickiness ==
+ SessionAffinityCookieStickiness::STRICT) {
+ addr.affinity_hash = util::hash32(key);
+ g.affinity_hash_map.emplace(addr.affinity_hash, idx);
+ }
+
+ ++idx;
+ }
+
+ std::sort(std::begin(g.affinity_hash), std::end(g.affinity_hash),
+ [](const AffinityHash &lhs, const AffinityHash &rhs) {
+ return lhs.hash < rhs.hash;
+ });
+ }
+
+ auto &timeout = g.timeout;
+ if (timeout.read < 1e-9) {
+ timeout.read = downstreamconf.timeout.read;
+ }
+ if (timeout.write < 1e-9) {
+ timeout.write = downstreamconf.timeout.write;
+ }
+ }
+
+ return 0;
+}
+
+int resolve_hostname(Address *addr, const char *hostname, uint16_t port,
+ int family, int additional_flags) {
+ int rv;
+
+ auto service = util::utos(port);
+
+ addrinfo hints{};
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= additional_flags;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+ addrinfo *res;
+
+ rv = getaddrinfo(hostname, service.c_str(), &hints, &res);
+#ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(hostname, service.c_str(), &hints, &res);
+ }
+#endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to resolve address for " << hostname << ": "
+ << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ char host[NI_MAXHOST];
+ rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), nullptr,
+ 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(FATAL) << "Address resolution for " << hostname
+ << " failed: " << gai_strerror(rv);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Address resolution for " << hostname
+ << " succeeded: " << host;
+ }
+
+ memcpy(&addr->su, res->ai_addr, res->ai_addrlen);
+ addr->len = res->ai_addrlen;
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config.h b/src/shrpx_config.h
new file mode 100644
index 0000000..7f316eb
--- /dev/null
+++ b/src/shrpx_config.h
@@ -0,0 +1,1450 @@
+/*
+ * 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 <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#include <sys/un.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+#include <cinttypes>
+#include <cstdio>
+#include <vector>
+#include <memory>
+#include <set>
+#include <unordered_map>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_router.h"
+#if ENABLE_HTTP3
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+#include "template.h"
+#include "http2.h"
+#include "network.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct LogFragment;
+class ConnectBlocker;
+class Http2Session;
+
+namespace tls {
+
+class CertLookupTree;
+
+} // namespace tls
+
+constexpr auto SHRPX_OPT_PRIVATE_KEY_FILE =
+ StringRef::from_lit("private-key-file");
+constexpr auto SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE =
+ StringRef::from_lit("private-key-passwd-file");
+constexpr auto SHRPX_OPT_CERTIFICATE_FILE =
+ StringRef::from_lit("certificate-file");
+constexpr auto SHRPX_OPT_DH_PARAM_FILE = StringRef::from_lit("dh-param-file");
+constexpr auto SHRPX_OPT_SUBCERT = StringRef::from_lit("subcert");
+constexpr auto SHRPX_OPT_BACKEND = StringRef::from_lit("backend");
+constexpr auto SHRPX_OPT_FRONTEND = StringRef::from_lit("frontend");
+constexpr auto SHRPX_OPT_WORKERS = StringRef::from_lit("workers");
+constexpr auto SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_LOG_LEVEL = StringRef::from_lit("log-level");
+constexpr auto SHRPX_OPT_DAEMON = StringRef::from_lit("daemon");
+constexpr auto SHRPX_OPT_HTTP2_PROXY = StringRef::from_lit("http2-proxy");
+constexpr auto SHRPX_OPT_HTTP2_BRIDGE = StringRef::from_lit("http2-bridge");
+constexpr auto SHRPX_OPT_CLIENT_PROXY = StringRef::from_lit("client-proxy");
+constexpr auto SHRPX_OPT_ADD_X_FORWARDED_FOR =
+ StringRef::from_lit("add-x-forwarded-for");
+constexpr auto SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR =
+ StringRef::from_lit("strip-incoming-x-forwarded-for");
+constexpr auto SHRPX_OPT_NO_VIA = StringRef::from_lit("no-via");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT =
+ StringRef::from_lit("frontend-http2-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_READ_TIMEOUT =
+ StringRef::from_lit("frontend-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_WRITE_TIMEOUT =
+ StringRef::from_lit("frontend-write-timeout");
+constexpr auto SHRPX_OPT_BACKEND_READ_TIMEOUT =
+ StringRef::from_lit("backend-read-timeout");
+constexpr auto SHRPX_OPT_BACKEND_WRITE_TIMEOUT =
+ StringRef::from_lit("backend-write-timeout");
+constexpr auto SHRPX_OPT_STREAM_READ_TIMEOUT =
+ StringRef::from_lit("stream-read-timeout");
+constexpr auto SHRPX_OPT_STREAM_WRITE_TIMEOUT =
+ StringRef::from_lit("stream-write-timeout");
+constexpr auto SHRPX_OPT_ACCESSLOG_FILE = StringRef::from_lit("accesslog-file");
+constexpr auto SHRPX_OPT_ACCESSLOG_SYSLOG =
+ StringRef::from_lit("accesslog-syslog");
+constexpr auto SHRPX_OPT_ACCESSLOG_FORMAT =
+ StringRef::from_lit("accesslog-format");
+constexpr auto SHRPX_OPT_ERRORLOG_FILE = StringRef::from_lit("errorlog-file");
+constexpr auto SHRPX_OPT_ERRORLOG_SYSLOG =
+ StringRef::from_lit("errorlog-syslog");
+constexpr auto SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT =
+ StringRef::from_lit("backend-keep-alive-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS =
+ StringRef::from_lit("frontend-http2-window-bits");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS =
+ StringRef::from_lit("backend-http2-window-bits");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS =
+ StringRef::from_lit("frontend-http2-connection-window-bits");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS =
+ StringRef::from_lit("backend-http2-connection-window-bits");
+constexpr auto SHRPX_OPT_FRONTEND_NO_TLS =
+ StringRef::from_lit("frontend-no-tls");
+constexpr auto SHRPX_OPT_BACKEND_NO_TLS = StringRef::from_lit("backend-no-tls");
+constexpr auto SHRPX_OPT_BACKEND_TLS_SNI_FIELD =
+ StringRef::from_lit("backend-tls-sni-field");
+constexpr auto SHRPX_OPT_PID_FILE = StringRef::from_lit("pid-file");
+constexpr auto SHRPX_OPT_USER = StringRef::from_lit("user");
+constexpr auto SHRPX_OPT_SYSLOG_FACILITY =
+ StringRef::from_lit("syslog-facility");
+constexpr auto SHRPX_OPT_BACKLOG = StringRef::from_lit("backlog");
+constexpr auto SHRPX_OPT_CIPHERS = StringRef::from_lit("ciphers");
+constexpr auto SHRPX_OPT_CLIENT = StringRef::from_lit("client");
+constexpr auto SHRPX_OPT_INSECURE = StringRef::from_lit("insecure");
+constexpr auto SHRPX_OPT_CACERT = StringRef::from_lit("cacert");
+constexpr auto SHRPX_OPT_BACKEND_IPV4 = StringRef::from_lit("backend-ipv4");
+constexpr auto SHRPX_OPT_BACKEND_IPV6 = StringRef::from_lit("backend-ipv6");
+constexpr auto SHRPX_OPT_BACKEND_HTTP_PROXY_URI =
+ StringRef::from_lit("backend-http-proxy-uri");
+constexpr auto SHRPX_OPT_READ_RATE = StringRef::from_lit("read-rate");
+constexpr auto SHRPX_OPT_READ_BURST = StringRef::from_lit("read-burst");
+constexpr auto SHRPX_OPT_WRITE_RATE = StringRef::from_lit("write-rate");
+constexpr auto SHRPX_OPT_WRITE_BURST = StringRef::from_lit("write-burst");
+constexpr auto SHRPX_OPT_WORKER_READ_RATE =
+ StringRef::from_lit("worker-read-rate");
+constexpr auto SHRPX_OPT_WORKER_READ_BURST =
+ StringRef::from_lit("worker-read-burst");
+constexpr auto SHRPX_OPT_WORKER_WRITE_RATE =
+ StringRef::from_lit("worker-write-rate");
+constexpr auto SHRPX_OPT_WORKER_WRITE_BURST =
+ StringRef::from_lit("worker-write-burst");
+constexpr auto SHRPX_OPT_NPN_LIST = StringRef::from_lit("npn-list");
+constexpr auto SHRPX_OPT_TLS_PROTO_LIST = StringRef::from_lit("tls-proto-list");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT = StringRef::from_lit("verify-client");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT_CACERT =
+ StringRef::from_lit("verify-client-cacert");
+constexpr auto SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE =
+ StringRef::from_lit("client-private-key-file");
+constexpr auto SHRPX_OPT_CLIENT_CERT_FILE =
+ StringRef::from_lit("client-cert-file");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER =
+ StringRef::from_lit("frontend-http2-dump-request-header");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER =
+ StringRef::from_lit("frontend-http2-dump-response-header");
+constexpr auto SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING =
+ StringRef::from_lit("http2-no-cookie-crumbling");
+constexpr auto SHRPX_OPT_FRONTEND_FRAME_DEBUG =
+ StringRef::from_lit("frontend-frame-debug");
+constexpr auto SHRPX_OPT_PADDING = StringRef::from_lit("padding");
+constexpr auto SHRPX_OPT_ALTSVC = StringRef::from_lit("altsvc");
+constexpr auto SHRPX_OPT_ADD_REQUEST_HEADER =
+ StringRef::from_lit("add-request-header");
+constexpr auto SHRPX_OPT_ADD_RESPONSE_HEADER =
+ StringRef::from_lit("add-response-header");
+constexpr auto SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS =
+ StringRef::from_lit("worker-frontend-connections");
+constexpr auto SHRPX_OPT_NO_LOCATION_REWRITE =
+ StringRef::from_lit("no-location-rewrite");
+constexpr auto SHRPX_OPT_NO_HOST_REWRITE =
+ StringRef::from_lit("no-host-rewrite");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST =
+ StringRef::from_lit("backend-http1-connections-per-host");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND =
+ StringRef::from_lit("backend-http1-connections-per-frontend");
+constexpr auto SHRPX_OPT_LISTENER_DISABLE_TIMEOUT =
+ StringRef::from_lit("listener-disable-timeout");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_FILE =
+ StringRef::from_lit("tls-ticket-key-file");
+constexpr auto SHRPX_OPT_RLIMIT_NOFILE = StringRef::from_lit("rlimit-nofile");
+constexpr auto SHRPX_OPT_BACKEND_REQUEST_BUFFER =
+ StringRef::from_lit("backend-request-buffer");
+constexpr auto SHRPX_OPT_BACKEND_RESPONSE_BUFFER =
+ StringRef::from_lit("backend-response-buffer");
+constexpr auto SHRPX_OPT_NO_SERVER_PUSH = StringRef::from_lit("no-server-push");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER =
+ StringRef::from_lit("backend-http2-connections-per-worker");
+constexpr auto SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE =
+ StringRef::from_lit("fetch-ocsp-response-file");
+constexpr auto SHRPX_OPT_OCSP_UPDATE_INTERVAL =
+ StringRef::from_lit("ocsp-update-interval");
+constexpr auto SHRPX_OPT_NO_OCSP = StringRef::from_lit("no-ocsp");
+constexpr auto SHRPX_OPT_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_HEADER_FIELDS =
+ StringRef::from_lit("max-header-fields");
+constexpr auto SHRPX_OPT_INCLUDE = StringRef::from_lit("include");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_CIPHER =
+ StringRef::from_lit("tls-ticket-key-cipher");
+constexpr auto SHRPX_OPT_HOST_REWRITE = StringRef::from_lit("host-rewrite");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED =
+ StringRef::from_lit("tls-session-cache-memcached");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED =
+ StringRef::from_lit("tls-ticket-key-memcached");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL =
+ StringRef::from_lit("tls-ticket-key-memcached-interval");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY =
+ StringRef::from_lit("tls-ticket-key-memcached-max-retry");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL =
+ StringRef::from_lit("tls-ticket-key-memcached-max-fail");
+constexpr auto SHRPX_OPT_MRUBY_FILE = StringRef::from_lit("mruby-file");
+constexpr auto SHRPX_OPT_ACCEPT_PROXY_PROTOCOL =
+ StringRef::from_lit("accept-proxy-protocol");
+constexpr auto SHRPX_OPT_FASTOPEN = StringRef::from_lit("fastopen");
+constexpr auto SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD =
+ StringRef::from_lit("tls-dyn-rec-warmup-threshold");
+constexpr auto SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT =
+ StringRef::from_lit("tls-dyn-rec-idle-timeout");
+constexpr auto SHRPX_OPT_ADD_FORWARDED = StringRef::from_lit("add-forwarded");
+constexpr auto SHRPX_OPT_STRIP_INCOMING_FORWARDED =
+ StringRef::from_lit("strip-incoming-forwarded");
+constexpr auto SHRPX_OPT_FORWARDED_BY = StringRef::from_lit("forwarded-by");
+constexpr auto SHRPX_OPT_FORWARDED_FOR = StringRef::from_lit("forwarded-for");
+constexpr auto SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("request-header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS =
+ StringRef::from_lit("max-request-header-fields");
+constexpr auto SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER =
+ StringRef::from_lit("response-header-field-buffer");
+constexpr auto SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS =
+ StringRef::from_lit("max-response-header-fields");
+constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST =
+ StringRef::from_lit("no-http2-cipher-block-list");
+constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST =
+ StringRef::from_lit("no-http2-cipher-black-list");
+constexpr auto SHRPX_OPT_BACKEND_HTTP1_TLS =
+ StringRef::from_lit("backend-http1-tls");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS =
+ StringRef::from_lit("tls-session-cache-memcached-tls");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE =
+ StringRef::from_lit("tls-session-cache-memcached-cert-file");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE =
+ StringRef::from_lit("tls-session-cache-memcached-private-key-file");
+constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY =
+ StringRef::from_lit("tls-session-cache-memcached-address-family");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS =
+ StringRef::from_lit("tls-ticket-key-memcached-tls");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE =
+ StringRef::from_lit("tls-ticket-key-memcached-cert-file");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE =
+ StringRef::from_lit("tls-ticket-key-memcached-private-key-file");
+constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY =
+ StringRef::from_lit("tls-ticket-key-memcached-address-family");
+constexpr auto SHRPX_OPT_BACKEND_ADDRESS_FAMILY =
+ StringRef::from_lit("backend-address-family");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("frontend-http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("backend-http2-max-concurrent-streams");
+constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND =
+ StringRef::from_lit("backend-connections-per-frontend");
+constexpr auto SHRPX_OPT_BACKEND_TLS = StringRef::from_lit("backend-tls");
+constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST =
+ StringRef::from_lit("backend-connections-per-host");
+constexpr auto SHRPX_OPT_ERROR_PAGE = StringRef::from_lit("error-page");
+constexpr auto SHRPX_OPT_NO_KQUEUE = StringRef::from_lit("no-kqueue");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT =
+ StringRef::from_lit("frontend-http2-settings-timeout");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT =
+ StringRef::from_lit("backend-http2-settings-timeout");
+constexpr auto SHRPX_OPT_API_MAX_REQUEST_BODY =
+ StringRef::from_lit("api-max-request-body");
+constexpr auto SHRPX_OPT_BACKEND_MAX_BACKOFF =
+ StringRef::from_lit("backend-max-backoff");
+constexpr auto SHRPX_OPT_SERVER_NAME = StringRef::from_lit("server-name");
+constexpr auto SHRPX_OPT_NO_SERVER_REWRITE =
+ StringRef::from_lit("no-server-rewrite");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE =
+ StringRef::from_lit("frontend-http2-optimize-write-buffer-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-optimize-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http2-connection-window-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE =
+ StringRef::from_lit("backend-http2-window-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("backend-http2-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("frontend-http2-encoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("frontend-http2-decoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("backend-http2-encoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE =
+ StringRef::from_lit("backend-http2-decoder-dynamic-table-size");
+constexpr auto SHRPX_OPT_ECDH_CURVES = StringRef::from_lit("ecdh-curves");
+constexpr auto SHRPX_OPT_TLS_SCT_DIR = StringRef::from_lit("tls-sct-dir");
+constexpr auto SHRPX_OPT_BACKEND_CONNECT_TIMEOUT =
+ StringRef::from_lit("backend-connect-timeout");
+constexpr auto SHRPX_OPT_DNS_CACHE_TIMEOUT =
+ StringRef::from_lit("dns-cache-timeout");
+constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT =
+ StringRef::from_lit("dns-lookup-timeout");
+constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try");
+constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT =
+ StringRef::from_lit("frontend-keep-alive-timeout");
+constexpr auto SHRPX_OPT_PSK_SECRETS = StringRef::from_lit("psk-secrets");
+constexpr auto SHRPX_OPT_CLIENT_PSK_SECRETS =
+ StringRef::from_lit("client-psk-secrets");
+constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST =
+ StringRef::from_lit("client-no-http2-cipher-block-list");
+constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST =
+ StringRef::from_lit("client-no-http2-cipher-black-list");
+constexpr auto SHRPX_OPT_CLIENT_CIPHERS = StringRef::from_lit("client-ciphers");
+constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY =
+ StringRef::from_lit("accesslog-write-early");
+constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION =
+ StringRef::from_lit("tls-min-proto-version");
+constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION =
+ StringRef::from_lit("tls-max-proto-version");
+constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT =
+ StringRef::from_lit("redirect-https-port");
+constexpr auto SHRPX_OPT_FRONTEND_MAX_REQUESTS =
+ StringRef::from_lit("frontend-max-requests");
+constexpr auto SHRPX_OPT_SINGLE_THREAD = StringRef::from_lit("single-thread");
+constexpr auto SHRPX_OPT_SINGLE_PROCESS = StringRef::from_lit("single-process");
+constexpr auto SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO =
+ StringRef::from_lit("no-add-x-forwarded-proto");
+constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO =
+ StringRef::from_lit("no-strip-incoming-x-forwarded-proto");
+constexpr auto SHRPX_OPT_OCSP_STARTUP = StringRef::from_lit("ocsp-startup");
+constexpr auto SHRPX_OPT_NO_VERIFY_OCSP = StringRef::from_lit("no-verify-ocsp");
+constexpr auto SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED =
+ StringRef::from_lit("verify-client-tolerate-expired");
+constexpr auto SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR =
+ StringRef::from_lit("ignore-per-pattern-mruby-error");
+constexpr auto SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA =
+ StringRef::from_lit("tls-no-postpone-early-data");
+constexpr auto SHRPX_OPT_TLS_MAX_EARLY_DATA =
+ StringRef::from_lit("tls-max-early-data");
+constexpr auto SHRPX_OPT_TLS13_CIPHERS = StringRef::from_lit("tls13-ciphers");
+constexpr auto SHRPX_OPT_TLS13_CLIENT_CIPHERS =
+ StringRef::from_lit("tls13-client-ciphers");
+constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA =
+ StringRef::from_lit("no-strip-incoming-early-data");
+constexpr auto SHRPX_OPT_QUIC_BPF_PROGRAM_FILE =
+ StringRef::from_lit("quic-bpf-program-file");
+constexpr auto SHRPX_OPT_NO_QUIC_BPF = StringRef::from_lit("no-quic-bpf");
+constexpr auto SHRPX_OPT_HTTP2_ALTSVC = StringRef::from_lit("http2-altsvc");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT =
+ StringRef::from_lit("frontend-http3-read-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT =
+ StringRef::from_lit("frontend-quic-idle-timeout");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG =
+ StringRef::from_lit("frontend-quic-debug-log");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-max-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE =
+ StringRef::from_lit("frontend-http3-max-connection-window-size");
+constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS =
+ StringRef::from_lit("frontend-http3-max-concurrent-streams");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA =
+ StringRef::from_lit("frontend-quic-early-data");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR =
+ StringRef::from_lit("frontend-quic-qlog-dir");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN =
+ StringRef::from_lit("frontend-quic-require-token");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER =
+ StringRef::from_lit("frontend-quic-congestion-controller");
+constexpr auto SHRPX_OPT_QUIC_SERVER_ID = StringRef::from_lit("quic-server-id");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE =
+ StringRef::from_lit("frontend-quic-secret-file");
+constexpr auto SHRPX_OPT_RLIMIT_MEMLOCK = StringRef::from_lit("rlimit-memlock");
+constexpr auto SHRPX_OPT_MAX_WORKER_PROCESSES =
+ StringRef::from_lit("max-worker-processes");
+constexpr auto SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD =
+ StringRef::from_lit("worker-process-grace-shutdown-period");
+constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT =
+ StringRef::from_lit("frontend-quic-initial-rtt");
+constexpr auto SHRPX_OPT_REQUIRE_HTTP_SCHEME =
+ StringRef::from_lit("require-http-scheme");
+constexpr auto SHRPX_OPT_TLS_KTLS = StringRef::from_lit("tls-ktls");
+constexpr auto SHRPX_OPT_ALPN_LIST = StringRef::from_lit("alpn-list");
+
+constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
+
+constexpr char DEFAULT_DOWNSTREAM_HOST[] = "127.0.0.1";
+constexpr int16_t DEFAULT_DOWNSTREAM_PORT = 80;
+
+enum class Proto {
+ NONE,
+ HTTP1,
+ HTTP2,
+ HTTP3,
+ MEMCACHED,
+};
+
+enum class SessionAffinity {
+ // No session affinity
+ NONE,
+ // Client IP affinity
+ IP,
+ // Cookie based affinity
+ COOKIE,
+};
+
+enum class SessionAffinityCookieSecure {
+ // Secure attribute of session affinity cookie is determined by the
+ // request scheme.
+ AUTO,
+ // Secure attribute of session affinity cookie is always set.
+ YES,
+ // Secure attribute of session affinity cookie is always unset.
+ NO,
+};
+
+enum class SessionAffinityCookieStickiness {
+ // Backend server might be changed when an existing backend server
+ // is removed, or new backend server is added.
+ LOOSE,
+ // Backend server might be changed when a designated backend server
+ // is removed, but adding new backend server does not cause
+ // breakage.
+ STRICT,
+};
+
+struct AffinityConfig {
+ // Type of session affinity.
+ SessionAffinity type;
+ struct {
+ // Name of a cookie to use.
+ StringRef name;
+ // Path which a cookie is applied to.
+ StringRef path;
+ // Secure attribute
+ SessionAffinityCookieSecure secure;
+ // Affinity Stickiness
+ SessionAffinityCookieStickiness stickiness;
+ } cookie;
+};
+
+enum shrpx_forwarded_param {
+ FORWARDED_NONE = 0,
+ FORWARDED_BY = 0x1,
+ FORWARDED_FOR = 0x2,
+ FORWARDED_HOST = 0x4,
+ FORWARDED_PROTO = 0x8,
+};
+
+enum class ForwardedNode {
+ OBFUSCATED,
+ IP,
+};
+
+struct AltSvc {
+ StringRef protocol_id, host, origin, service, params;
+
+ uint16_t port;
+};
+
+enum class UpstreamAltMode {
+ // No alternative mode
+ NONE,
+ // API processing mode
+ API,
+ // Health monitor mode
+ HEALTHMON,
+};
+
+struct UpstreamAddr {
+ // The unique index of this address.
+ size_t index;
+ // The frontend address (e.g., FQDN, hostname, IP address). If
+ // |host_unix| is true, this is UNIX domain socket path. This must
+ // be NULL terminated string.
+ StringRef host;
+ // For TCP socket, this is <IP address>:<PORT>. For IPv6 address,
+ // address is surrounded by square brackets. If socket is UNIX
+ // domain socket, this is "localhost".
+ StringRef hostport;
+ // frontend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // For TCP socket, this is either AF_INET or AF_INET6. For UNIX
+ // domain socket, this is 0.
+ int family;
+ // Alternate mode
+ UpstreamAltMode alt_mode;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+ // true if TLS is enabled.
+ bool tls;
+ // true if SNI host should be used as a host when selecting backend
+ // server.
+ bool sni_fwd;
+ // true if client is supposed to send PROXY protocol v1 header.
+ bool accept_proxy_protocol;
+ bool quic;
+ int fd;
+};
+
+struct DownstreamAddrConfig {
+ // Resolved address if |dns| is false
+ Address addr;
+ // backend address. If |host_unix| is true, this is UNIX domain
+ // socket path. This must be NULL terminated string.
+ StringRef host;
+ // <HOST>:<PORT>. This does not treat 80 and 443 specially. If
+ // |host_unix| is true, this is "localhost".
+ StringRef hostport;
+ // hostname sent as SNI field
+ StringRef sni;
+ // name of group which this address belongs to.
+ StringRef group;
+ size_t fall;
+ size_t rise;
+ // weight of this address inside a weight group. Its range is [1,
+ // 256], inclusive.
+ uint32_t weight;
+ // weight of the weight group. Its range is [1, 256], inclusive.
+ uint32_t group_weight;
+ // affinity hash for this address. It is assigned when strict
+ // stickiness is enabled.
+ uint32_t affinity_hash;
+ // Application protocol used in this group
+ Proto proto;
+ // backend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+ bool tls;
+ // true if dynamic DNS is enabled
+ bool dns;
+ // true if :scheme pseudo header field should be upgraded to secure
+ // variant (e.g., "https") when forwarding request to a backend
+ // connected by TLS connection.
+ bool upgrade_scheme;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+};
+
+// Mapping hash to idx which is an index into
+// DownstreamAddrGroupConfig::addrs.
+struct AffinityHash {
+ AffinityHash(size_t idx, uint32_t hash) : idx(idx), hash(hash) {}
+
+ size_t idx;
+ uint32_t hash;
+};
+
+struct DownstreamAddrGroupConfig {
+ DownstreamAddrGroupConfig(const StringRef &pattern)
+ : pattern(pattern),
+ affinity{SessionAffinity::NONE},
+ redirect_if_not_tls(false),
+ dnf{false},
+ timeout{} {}
+
+ StringRef pattern;
+ StringRef mruby_file;
+ std::vector<DownstreamAddrConfig> addrs;
+ // Bunch of session affinity hash. Only used if affinity ==
+ // SessionAffinity::IP.
+ std::vector<AffinityHash> affinity_hash;
+ // Maps affinity hash of each DownstreamAddrConfig to its index in
+ // addrs. It is only assigned when strict stickiness is enabled.
+ std::unordered_map<uint32_t, size_t> affinity_hash_map;
+ // Cookie based session affinity configuration.
+ AffinityConfig affinity;
+ // true if this group requires that client connection must be TLS,
+ // and the request must be redirected to https URI.
+ bool redirect_if_not_tls;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+ // Timeouts for backend connection.
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ } timeout;
+};
+
+struct TicketKey {
+ const EVP_CIPHER *cipher;
+ const EVP_MD *hmac;
+ size_t hmac_keylen;
+ struct {
+ // name of this ticket configuration
+ std::array<uint8_t, 16> name;
+ // encryption key for |cipher|
+ std::array<uint8_t, 32> enc_key;
+ // hmac key for |hmac|
+ std::array<uint8_t, 32> hmac_key;
+ } data;
+};
+
+struct TicketKeys {
+ ~TicketKeys();
+ std::vector<TicketKey> keys;
+};
+
+struct TLSCertificate {
+ TLSCertificate(StringRef private_key_file, StringRef cert_file,
+ std::vector<uint8_t> sct_data)
+ : private_key_file(std::move(private_key_file)),
+ cert_file(std::move(cert_file)),
+ sct_data(std::move(sct_data)) {}
+
+ StringRef private_key_file;
+ StringRef cert_file;
+ std::vector<uint8_t> sct_data;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICKeyingMaterial {
+ std::array<uint8_t, SHRPX_QUIC_SECRET_RESERVEDLEN> reserved;
+ std::array<uint8_t, SHRPX_QUIC_SECRETLEN> secret;
+ std::array<uint8_t, SHRPX_QUIC_SALTLEN> salt;
+ std::array<uint8_t, SHRPX_QUIC_CID_ENCRYPTION_KEYLEN> cid_encryption_key;
+ // Identifier of this keying material. Only the first 2 bits are
+ // used.
+ uint8_t id;
+};
+
+struct QUICKeyingMaterials {
+ std::vector<QUICKeyingMaterial> keying_materials;
+};
+#endif // ENABLE_HTTP3
+
+struct HttpProxy {
+ Address addr;
+ // host in http proxy URI
+ StringRef host;
+ // userinfo in http proxy URI, not percent-encoded form
+ StringRef userinfo;
+ // port in http proxy URI
+ uint16_t port;
+};
+
+struct TLSConfig {
+ // RFC 5077 Session ticket related configurations
+ struct {
+ struct {
+ Address addr;
+ uint16_t port;
+ // Hostname of memcached server. This is also used as SNI field
+ // if TLS is enabled.
+ StringRef host;
+ // Client private key and certificate for authentication
+ StringRef private_key_file;
+ StringRef cert_file;
+ ev_tstamp interval;
+ // Maximum number of retries when getting TLS ticket key from
+ // mamcached, due to network error.
+ size_t max_retry;
+ // Maximum number of consecutive error from memcached, when this
+ // limit reached, TLS ticket is disabled.
+ size_t max_fail;
+ // Address family of memcached connection. One of either
+ // AF_INET, AF_INET6 or AF_UNSPEC.
+ int family;
+ bool tls;
+ } memcached;
+ std::vector<StringRef> files;
+ const EVP_CIPHER *cipher;
+ // true if --tls-ticket-key-cipher is used
+ bool cipher_given;
+ } ticket;
+
+ // Session cache related configurations
+ struct {
+ struct {
+ Address addr;
+ uint16_t port;
+ // Hostname of memcached server. This is also used as SNI field
+ // if TLS is enabled.
+ StringRef host;
+ // Client private key and certificate for authentication
+ StringRef private_key_file;
+ StringRef cert_file;
+ // Address family of memcached connection. One of either
+ // AF_INET, AF_INET6 or AF_UNSPEC.
+ int family;
+ bool tls;
+ } memcached;
+ } session_cache;
+
+ // Dynamic record sizing configurations
+ struct {
+ size_t warmup_threshold;
+ ev_tstamp idle_timeout;
+ } dyn_rec;
+
+ // OCSP related configurations
+ struct {
+ ev_tstamp update_interval;
+ StringRef fetch_ocsp_response_file;
+ bool disabled;
+ bool startup;
+ bool no_verify;
+ } ocsp;
+
+ // Client verification configurations
+ struct {
+ // Path to file containing CA certificate solely used for client
+ // certificate validation
+ StringRef cacert;
+ bool enabled;
+ // true if we accept an expired client certificate.
+ bool tolerate_expired;
+ } client_verify;
+
+ // Client (backend connection) TLS configuration.
+ struct {
+ // Client PSK configuration
+ struct {
+ // identity must be NULL terminated string.
+ StringRef identity;
+ StringRef secret;
+ } psk;
+ StringRef private_key_file;
+ StringRef cert_file;
+ StringRef ciphers;
+ StringRef tls13_ciphers;
+ bool no_http2_cipher_block_list;
+ } client;
+
+ // PSK secrets. The key is identity, and the associated value is
+ // its secret.
+ std::map<StringRef, StringRef> psk_secrets;
+ // The list of additional TLS certificate pair
+ std::vector<TLSCertificate> subcerts;
+ std::vector<unsigned char> alpn_prefs;
+ // list of supported ALPN protocol strings in the order of
+ // preference.
+ std::vector<StringRef> alpn_list;
+ // list of supported SSL/TLS protocol strings.
+ std::vector<StringRef> tls_proto_list;
+ std::vector<uint8_t> sct_data;
+ BIO_METHOD *bio_method;
+ // Bit mask to disable SSL/TLS protocol versions. This will be
+ // passed to SSL_CTX_set_options().
+ long int tls_proto_mask;
+ StringRef backend_sni_name;
+ std::chrono::seconds session_timeout;
+ StringRef private_key_file;
+ StringRef private_key_passwd;
+ StringRef cert_file;
+ StringRef dh_param_file;
+ StringRef ciphers;
+ StringRef tls13_ciphers;
+ StringRef ecdh_curves;
+ StringRef cacert;
+ // The maximum amount of 0-RTT data that server accepts.
+ uint32_t max_early_data;
+ // The minimum and maximum TLS version. These values are defined in
+ // OpenSSL header file.
+ int min_proto_version;
+ int max_proto_version;
+ bool insecure;
+ bool no_http2_cipher_block_list;
+ // true if forwarding requests included in TLS early data should not
+ // be postponed until TLS handshake finishes.
+ bool no_postpone_early_data;
+ bool ktls;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICConfig {
+ struct {
+ struct {
+ ev_tstamp idle;
+ } timeout;
+ struct {
+ bool log;
+ } debug;
+ struct {
+ StringRef dir;
+ } qlog;
+ ngtcp2_cc_algo congestion_controller;
+ bool early_data;
+ bool require_token;
+ StringRef secret_file;
+ ev_tstamp initial_rtt;
+ } upstream;
+ struct {
+ StringRef prog_file;
+ bool disabled;
+ } bpf;
+ std::array<uint8_t, SHRPX_QUIC_SERVER_IDLEN> server_id;
+};
+
+struct Http3Config {
+ struct {
+ size_t max_concurrent_streams;
+ int32_t window_size;
+ int32_t connection_window_size;
+ int32_t max_window_size;
+ int32_t max_connection_window_size;
+ } upstream;
+};
+#endif // ENABLE_HTTP3
+
+// custom error page
+struct ErrorPage {
+ // not NULL-terminated
+ std::vector<uint8_t> content;
+ // 0 is special value, and it matches all HTTP status code.
+ unsigned int http_status;
+};
+
+struct HttpConfig {
+ struct {
+ // obfuscated value used in "by" parameter of Forwarded header
+ // field. This is only used when user defined static obfuscated
+ // string is provided.
+ StringRef by_obfuscated;
+ // bitwise-OR of one or more of shrpx_forwarded_param values.
+ uint32_t params;
+ // type of value recorded in "by" parameter of Forwarded header
+ // field.
+ ForwardedNode by_node_type;
+ // type of value recorded in "for" parameter of Forwarded header
+ // field.
+ ForwardedNode for_node_type;
+ bool strip_incoming;
+ } forwarded;
+ struct {
+ bool add;
+ bool strip_incoming;
+ } xff;
+ struct {
+ bool add;
+ bool strip_incoming;
+ } xfp;
+ struct {
+ bool strip_incoming;
+ } early_data;
+ std::vector<AltSvc> altsvcs;
+ // altsvcs serialized in a wire format.
+ StringRef altsvc_header_value;
+ std::vector<AltSvc> http2_altsvcs;
+ // http2_altsvcs serialized in a wire format.
+ StringRef http2_altsvc_header_value;
+ std::vector<ErrorPage> error_pages;
+ HeaderRefs add_request_headers;
+ HeaderRefs add_response_headers;
+ StringRef server_name;
+ // Port number which appears in Location header field when https
+ // redirect is made.
+ StringRef redirect_https_port;
+ size_t request_header_field_buffer;
+ size_t max_request_header_fields;
+ size_t response_header_field_buffer;
+ size_t max_response_header_fields;
+ size_t max_requests;
+ bool no_via;
+ bool no_location_rewrite;
+ bool no_host_rewrite;
+ bool no_server_rewrite;
+ bool require_http_scheme;
+};
+
+struct Http2Config {
+ struct {
+ struct {
+ struct {
+ StringRef request_header_file;
+ StringRef response_header_file;
+ FILE *request_header;
+ FILE *response_header;
+ } dump;
+ bool frame_debug;
+ } debug;
+ struct {
+ ev_tstamp settings;
+ } timeout;
+ nghttp2_option *option;
+ nghttp2_option *alt_mode_option;
+ nghttp2_session_callbacks *callbacks;
+ size_t max_concurrent_streams;
+ size_t encoder_dynamic_table_size;
+ size_t decoder_dynamic_table_size;
+ int32_t window_size;
+ int32_t connection_window_size;
+ bool optimize_write_buffer_size;
+ bool optimize_window_size;
+ } upstream;
+ struct {
+ struct {
+ ev_tstamp settings;
+ } timeout;
+ nghttp2_option *option;
+ nghttp2_session_callbacks *callbacks;
+ size_t encoder_dynamic_table_size;
+ size_t decoder_dynamic_table_size;
+ int32_t window_size;
+ int32_t connection_window_size;
+ size_t max_concurrent_streams;
+ } downstream;
+ struct {
+ ev_tstamp stream_read;
+ ev_tstamp stream_write;
+ } timeout;
+ bool no_cookie_crumbling;
+ bool no_server_push;
+};
+
+struct LoggingConfig {
+ struct {
+ std::vector<LogFragment> format;
+ StringRef file;
+ // Send accesslog to syslog, ignoring accesslog_file.
+ bool syslog;
+ // Write accesslog when response headers are received from
+ // backend, rather than response body is received and sent.
+ bool write_early;
+ } access;
+ struct {
+ StringRef file;
+ // Send errorlog to syslog, ignoring errorlog_file.
+ bool syslog;
+ } error;
+ int syslog_facility;
+ int severity;
+};
+
+struct RateLimitConfig {
+ size_t rate;
+ size_t burst;
+};
+
+// Wildcard host pattern routing. We strips left most '*' from host
+// field. router includes all path patterns sharing the same wildcard
+// host.
+struct WildcardPattern {
+ WildcardPattern(const StringRef &host) : host(host) {}
+
+ // This might not be NULL terminated. Currently it is only used for
+ // comparison.
+ StringRef host;
+ Router router;
+};
+
+// Configuration to select backend to forward request
+struct RouterConfig {
+ Router router;
+ // Router for reversed wildcard hosts. Since this router has
+ // wildcard hosts reversed without '*', one should call match()
+ // function with reversed host stripping last character. This is
+ // because we require at least one character must match for '*'.
+ // The index stored in this router is index of wildcard_patterns.
+ Router rev_wildcard_router;
+ std::vector<WildcardPattern> wildcard_patterns;
+};
+
+struct DownstreamConfig {
+ DownstreamConfig()
+ : balloc(1024, 1024),
+ timeout{},
+ addr_group_catch_all{0},
+ connections_per_host{0},
+ connections_per_frontend{0},
+ request_buffer_size{0},
+ response_buffer_size{0},
+ family{0} {}
+
+ DownstreamConfig(const DownstreamConfig &) = delete;
+ DownstreamConfig(DownstreamConfig &&) = delete;
+ DownstreamConfig &operator=(const DownstreamConfig &) = delete;
+ DownstreamConfig &operator=(DownstreamConfig &&) = delete;
+
+ // Allocator to allocate memory for Downstream configuration. Since
+ // we may swap around DownstreamConfig in arbitrary times with API
+ // calls, we should use their own allocator instead of per Config
+ // allocator.
+ BlockAllocator balloc;
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ ev_tstamp idle_read;
+ ev_tstamp connect;
+ // The maximum backoff while checking health check for offline
+ // backend or while detaching failed backend from load balancing
+ // group temporarily.
+ ev_tstamp max_backoff;
+ } timeout;
+ RouterConfig router;
+ std::vector<DownstreamAddrGroupConfig> addr_groups;
+ // The index of catch-all group in downstream_addr_groups.
+ size_t addr_group_catch_all;
+ size_t connections_per_host;
+ size_t connections_per_frontend;
+ size_t request_buffer_size;
+ size_t response_buffer_size;
+ // Address family of backend connection. One of either AF_INET,
+ // AF_INET6 or AF_UNSPEC. This is ignored if backend connection
+ // is made via Unix domain socket.
+ int family;
+};
+
+struct ConnectionConfig {
+ struct {
+ struct {
+ ev_tstamp sleep;
+ } timeout;
+ // address of frontend acceptors
+ std::vector<UpstreamAddr> addrs;
+ int backlog;
+ // TCP fastopen. If this is positive, it is passed to
+ // setsockopt() along with TCP_FASTOPEN.
+ int fastopen;
+ } listener;
+
+#ifdef ENABLE_HTTP3
+ struct {
+ std::vector<UpstreamAddr> addrs;
+ } quic_listener;
+#endif // ENABLE_HTTP3
+
+ struct {
+ struct {
+ ev_tstamp http2_read;
+ ev_tstamp http3_read;
+ ev_tstamp read;
+ ev_tstamp write;
+ ev_tstamp idle_read;
+ } timeout;
+ struct {
+ RateLimitConfig read;
+ RateLimitConfig write;
+ } ratelimit;
+ size_t worker_connections;
+ // Deprecated. See UpstreamAddr.accept_proxy_protocol.
+ bool accept_proxy_protocol;
+ } upstream;
+
+ std::shared_ptr<DownstreamConfig> downstream;
+};
+
+struct APIConfig {
+ // Maximum request body size for one API request
+ size_t max_request_body;
+ // true if at least one of UpstreamAddr has api enabled
+ bool enabled;
+};
+
+struct DNSConfig {
+ struct {
+ ev_tstamp cache;
+ ev_tstamp lookup;
+ } timeout;
+ // The number of tries name resolver makes before abandoning
+ // request.
+ size_t max_try;
+};
+
+struct Config {
+ Config()
+ : balloc(4096, 4096),
+ downstream_http_proxy{},
+ http{},
+ http2{},
+ tls{},
+#ifdef ENABLE_HTTP3
+ quic{},
+#endif // ENABLE_HTTP3
+ logging{},
+ conn{},
+ api{},
+ dns{},
+ config_revision{0},
+ num_worker{0},
+ padding{0},
+ rlimit_nofile{0},
+ rlimit_memlock{0},
+ uid{0},
+ gid{0},
+ pid{0},
+ verbose{false},
+ daemon{false},
+ http2_proxy{false},
+ single_process{false},
+ single_thread{false},
+ ignore_per_pattern_mruby_error{false},
+ ev_loop_flags{0},
+ max_worker_processes{0},
+ worker_process_grace_shutdown_period{0.} {
+ }
+ ~Config();
+
+ Config(Config &&) = delete;
+ Config(const Config &&) = delete;
+ Config &operator=(Config &&) = delete;
+ Config &operator=(const Config &&) = delete;
+
+ // Allocator to allocate memory for this object except for
+ // DownstreamConfig. Currently, it is used to allocate memory for
+ // strings.
+ BlockAllocator balloc;
+ HttpProxy downstream_http_proxy;
+ HttpConfig http;
+ Http2Config http2;
+ TLSConfig tls;
+#ifdef ENABLE_HTTP3
+ QUICConfig quic;
+ Http3Config http3;
+#endif // ENABLE_HTTP3
+ LoggingConfig logging;
+ ConnectionConfig conn;
+ APIConfig api;
+ DNSConfig dns;
+ StringRef pid_file;
+ StringRef conf_path;
+ StringRef user;
+ StringRef mruby_file;
+ // The revision of configuration which is opaque string, and changes
+ // on each configuration reloading. This does not change on
+ // backendconfig API call. This value is returned in health check
+ // as "nghttpx-conf-rev" response header field. The external
+ // program can check this value to know whether reloading has
+ // completed or not.
+ uint64_t config_revision;
+ size_t num_worker;
+ size_t padding;
+ size_t rlimit_nofile;
+ size_t rlimit_memlock;
+ uid_t uid;
+ gid_t gid;
+ pid_t pid;
+ bool verbose;
+ bool daemon;
+ bool http2_proxy;
+ // Run nghttpx in single process mode. With this mode, signal
+ // handling is omitted.
+ bool single_process;
+ bool single_thread;
+ // Ignore mruby compile error for per-pattern mruby script.
+ bool ignore_per_pattern_mruby_error;
+ // flags passed to ev_default_loop() and ev_loop_new()
+ int ev_loop_flags;
+ size_t max_worker_processes;
+ ev_tstamp worker_process_grace_shutdown_period;
+};
+
+const Config *get_config();
+Config *mod_config();
+// Replaces the current config with given |new_config|. The old config is
+// returned.
+std::unique_ptr<Config> replace_config(std::unique_ptr<Config> new_config);
+void create_config();
+
+// generated by gennghttpxfun.py
+enum {
+ SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL,
+ SHRPX_OPTID_ACCESSLOG_FILE,
+ SHRPX_OPTID_ACCESSLOG_FORMAT,
+ SHRPX_OPTID_ACCESSLOG_SYSLOG,
+ SHRPX_OPTID_ACCESSLOG_WRITE_EARLY,
+ SHRPX_OPTID_ADD_FORWARDED,
+ SHRPX_OPTID_ADD_REQUEST_HEADER,
+ SHRPX_OPTID_ADD_RESPONSE_HEADER,
+ SHRPX_OPTID_ADD_X_FORWARDED_FOR,
+ SHRPX_OPTID_ALPN_LIST,
+ SHRPX_OPTID_ALTSVC,
+ SHRPX_OPTID_API_MAX_REQUEST_BODY,
+ SHRPX_OPTID_BACKEND,
+ SHRPX_OPTID_BACKEND_ADDRESS_FAMILY,
+ SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT,
+ SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND,
+ SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST,
+ SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
+ SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
+ SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
+ SHRPX_OPTID_BACKEND_HTTP1_TLS,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
+ SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT,
+ SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
+ SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE,
+ SHRPX_OPTID_BACKEND_IPV4,
+ SHRPX_OPTID_BACKEND_IPV6,
+ SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT,
+ SHRPX_OPTID_BACKEND_MAX_BACKOFF,
+ SHRPX_OPTID_BACKEND_NO_TLS,
+ SHRPX_OPTID_BACKEND_READ_TIMEOUT,
+ SHRPX_OPTID_BACKEND_REQUEST_BUFFER,
+ SHRPX_OPTID_BACKEND_RESPONSE_BUFFER,
+ SHRPX_OPTID_BACKEND_TLS,
+ SHRPX_OPTID_BACKEND_TLS_SNI_FIELD,
+ SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
+ SHRPX_OPTID_BACKLOG,
+ SHRPX_OPTID_CACERT,
+ SHRPX_OPTID_CERTIFICATE_FILE,
+ SHRPX_OPTID_CIPHERS,
+ SHRPX_OPTID_CLIENT,
+ SHRPX_OPTID_CLIENT_CERT_FILE,
+ SHRPX_OPTID_CLIENT_CIPHERS,
+ SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST,
+ SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST,
+ SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_CLIENT_PROXY,
+ SHRPX_OPTID_CLIENT_PSK_SECRETS,
+ SHRPX_OPTID_CONF,
+ SHRPX_OPTID_DAEMON,
+ SHRPX_OPTID_DH_PARAM_FILE,
+ SHRPX_OPTID_DNS_CACHE_TIMEOUT,
+ SHRPX_OPTID_DNS_LOOKUP_TIMEOUT,
+ SHRPX_OPTID_DNS_MAX_TRY,
+ SHRPX_OPTID_ECDH_CURVES,
+ SHRPX_OPTID_ERROR_PAGE,
+ SHRPX_OPTID_ERRORLOG_FILE,
+ SHRPX_OPTID_ERRORLOG_SYSLOG,
+ SHRPX_OPTID_FASTOPEN,
+ SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE,
+ SHRPX_OPTID_FORWARDED_BY,
+ SHRPX_OPTID_FORWARDED_FOR,
+ SHRPX_OPTID_FRONTEND,
+ SHRPX_OPTID_FRONTEND_FRAME_DEBUG,
+ SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
+ SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
+ SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
+ SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS,
+ SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE,
+ SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_MAX_REQUESTS,
+ SHRPX_OPTID_FRONTEND_NO_TLS,
+ SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER,
+ SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG,
+ SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA,
+ SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT,
+ SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR,
+ SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN,
+ SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE,
+ SHRPX_OPTID_FRONTEND_READ_TIMEOUT,
+ SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
+ SHRPX_OPTID_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_HOST_REWRITE,
+ SHRPX_OPTID_HTTP2_ALTSVC,
+ SHRPX_OPTID_HTTP2_BRIDGE,
+ SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
+ SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,
+ SHRPX_OPTID_HTTP2_PROXY,
+ SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR,
+ SHRPX_OPTID_INCLUDE,
+ SHRPX_OPTID_INSECURE,
+ SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
+ SHRPX_OPTID_LOG_LEVEL,
+ SHRPX_OPTID_MAX_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
+ SHRPX_OPTID_MAX_WORKER_PROCESSES,
+ SHRPX_OPTID_MRUBY_FILE,
+ SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO,
+ SHRPX_OPTID_NO_HOST_REWRITE,
+ SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST,
+ SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST,
+ SHRPX_OPTID_NO_KQUEUE,
+ SHRPX_OPTID_NO_LOCATION_REWRITE,
+ SHRPX_OPTID_NO_OCSP,
+ SHRPX_OPTID_NO_QUIC_BPF,
+ SHRPX_OPTID_NO_SERVER_PUSH,
+ SHRPX_OPTID_NO_SERVER_REWRITE,
+ SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA,
+ SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO,
+ SHRPX_OPTID_NO_VERIFY_OCSP,
+ SHRPX_OPTID_NO_VIA,
+ SHRPX_OPTID_NPN_LIST,
+ SHRPX_OPTID_OCSP_STARTUP,
+ SHRPX_OPTID_OCSP_UPDATE_INTERVAL,
+ SHRPX_OPTID_PADDING,
+ SHRPX_OPTID_PID_FILE,
+ SHRPX_OPTID_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
+ SHRPX_OPTID_PSK_SECRETS,
+ SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE,
+ SHRPX_OPTID_QUIC_SERVER_ID,
+ SHRPX_OPTID_READ_BURST,
+ SHRPX_OPTID_READ_RATE,
+ SHRPX_OPTID_REDIRECT_HTTPS_PORT,
+ SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_REQUIRE_HTTP_SCHEME,
+ SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
+ SHRPX_OPTID_RLIMIT_MEMLOCK,
+ SHRPX_OPTID_RLIMIT_NOFILE,
+ SHRPX_OPTID_SERVER_NAME,
+ SHRPX_OPTID_SINGLE_PROCESS,
+ SHRPX_OPTID_SINGLE_THREAD,
+ SHRPX_OPTID_STREAM_READ_TIMEOUT,
+ SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
+ SHRPX_OPTID_STRIP_INCOMING_FORWARDED,
+ SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR,
+ SHRPX_OPTID_SUBCERT,
+ SHRPX_OPTID_SYSLOG_FACILITY,
+ SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT,
+ SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD,
+ SHRPX_OPTID_TLS_KTLS,
+ SHRPX_OPTID_TLS_MAX_EARLY_DATA,
+ SHRPX_OPTID_TLS_MAX_PROTO_VERSION,
+ SHRPX_OPTID_TLS_MIN_PROTO_VERSION,
+ SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA,
+ SHRPX_OPTID_TLS_PROTO_LIST,
+ SHRPX_OPTID_TLS_SCT_DIR,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS,
+ SHRPX_OPTID_TLS_TICKET_KEY_CIPHER,
+ SHRPX_OPTID_TLS_TICKET_KEY_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE,
+ SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS,
+ SHRPX_OPTID_TLS13_CIPHERS,
+ SHRPX_OPTID_TLS13_CLIENT_CIPHERS,
+ SHRPX_OPTID_USER,
+ SHRPX_OPTID_VERIFY_CLIENT,
+ SHRPX_OPTID_VERIFY_CLIENT_CACERT,
+ SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED,
+ SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS,
+ SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
+ SHRPX_OPTID_WORKER_READ_BURST,
+ SHRPX_OPTID_WORKER_READ_RATE,
+ SHRPX_OPTID_WORKER_WRITE_BURST,
+ SHRPX_OPTID_WORKER_WRITE_RATE,
+ SHRPX_OPTID_WORKERS,
+ SHRPX_OPTID_WRITE_BURST,
+ SHRPX_OPTID_WRITE_RATE,
+ SHRPX_OPTID_MAXIDX,
+};
+
+// Looks up token for given option name |name| of length |namelen|.
+int option_lookup_token(const char *name, size_t namelen);
+
+// Parses option name |opt| and value |optarg|. The results are
+// stored into the object pointed by |config|. This function returns 0
+// if it succeeds, or -1. The |included_set| contains the all paths
+// already included while processing this configuration, to avoid loop
+// in --include option. The |pattern_addr_indexer| contains a pair of
+// pattern of backend, and its index in DownstreamConfig::addr_groups.
+// It is introduced to speed up loading configuration file with lots
+// of backends.
+int parse_config(Config *config, const StringRef &opt, const StringRef &optarg,
+ std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// Similar to parse_config() above, but additional |optid| which
+// should be the return value of option_lookup_token(opt).
+int parse_config(Config *config, int optid, const StringRef &opt,
+ const StringRef &optarg, std::set<StringRef> &included_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// Loads configurations from |filename| and stores them in |config|.
+// This function returns 0 if it succeeds, or -1. See parse_config()
+// for |include_set|.
+int load_config(Config *config, const char *filename,
+ std::set<StringRef> &include_set,
+ std::map<StringRef, size_t> &pattern_addr_indexer);
+
+// 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.
+HeaderRefs::value_type parse_header(BlockAllocator &balloc,
+ const StringRef &optarg);
+
+std::vector<LogFragment> parse_log_format(BlockAllocator &balloc,
+ const StringRef &optarg);
+
+// Returns string for syslog |facility|.
+StringRef str_syslog_facility(int facility);
+
+// Returns integer value of syslog |facility| string.
+int int_syslog_facility(const StringRef &strfacility);
+
+FILE *open_file_for_write(const char *filename);
+
+// Reads TLS ticket key file in |files| and returns TicketKey which
+// stores read key data. The given |cipher| and |hmac| determine the
+// expected file size. This function returns TicketKey if it
+// succeeds, or nullptr.
+std::unique_ptr<TicketKeys>
+read_tls_ticket_key_file(const std::vector<StringRef> &files,
+ const EVP_CIPHER *cipher, const EVP_MD *hmac);
+
+#ifdef ENABLE_HTTP3
+std::shared_ptr<QUICKeyingMaterials>
+read_quic_secret_file(const StringRef &path);
+#endif // ENABLE_HTTP3
+
+// Returns string representation of |proto|.
+StringRef strproto(Proto proto);
+
+int configure_downstream_group(Config *config, bool http2_proxy,
+ bool numeric_addr_only,
+ const TLSConfig &tlsconf);
+
+int resolve_hostname(Address *addr, const char *hostname, uint16_t port,
+ int family, int additional_flags = 0);
+
+} // namespace shrpx
+
+#endif // SHRPX_CONFIG_H
diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc
new file mode 100644
index 0000000..a8f0962
--- /dev/null
+++ b/src/shrpx_config_test.cc
@@ -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.
+ */
+#include "shrpx_config_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_config_parse_header(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ auto p = parse_header(balloc, StringRef::from_lit("a: b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT("b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit("a: b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT("b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit(":a: b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("a: :b"));
+ CU_ASSERT("a" == p.name);
+ CU_ASSERT(":b" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit(": b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("alpha: bravo charlie"));
+ CU_ASSERT("alpha" == p.name);
+ CU_ASSERT("bravo charlie" == p.value);
+
+ p = parse_header(balloc, StringRef::from_lit("a,: b"));
+ CU_ASSERT(p.name.empty());
+
+ p = parse_header(balloc, StringRef::from_lit("a: b\x0a"));
+ CU_ASSERT(p.name.empty());
+}
+
+void test_shrpx_config_parse_log_format(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ auto res = parse_log_format(
+ balloc, StringRef::from_lit(
+ R"($remote_addr - $remote_user [$time_local] )"
+ R"("$request" $status $body_bytes_sent )"
+ R"("${http_referer}" $http_host "$http_user_agent")"));
+ CU_ASSERT(16 == res.size());
+
+ CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[0].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[1].type);
+ CU_ASSERT(" - $remote_user [" == res[1].value);
+
+ CU_ASSERT(LogFragmentType::TIME_LOCAL == res[2].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[3].type);
+ CU_ASSERT("] \"" == res[3].value);
+
+ CU_ASSERT(LogFragmentType::REQUEST == res[4].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[5].type);
+ CU_ASSERT("\" " == res[5].value);
+
+ CU_ASSERT(LogFragmentType::STATUS == res[6].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[7].type);
+ CU_ASSERT(" " == res[7].value);
+
+ CU_ASSERT(LogFragmentType::BODY_BYTES_SENT == res[8].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[9].type);
+ CU_ASSERT(" \"" == res[9].value);
+
+ CU_ASSERT(LogFragmentType::HTTP == res[10].type);
+ CU_ASSERT("referer" == res[10].value);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[11].type);
+ CU_ASSERT("\" " == res[11].value);
+
+ CU_ASSERT(LogFragmentType::AUTHORITY == res[12].type);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[13].type);
+ CU_ASSERT(" \"" == res[13].value);
+
+ CU_ASSERT(LogFragmentType::HTTP == res[14].type);
+ CU_ASSERT("user-agent" == res[14].value);
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[15].type);
+ CU_ASSERT("\"" == res[15].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("$"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("$" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${a"));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${a" == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("${a "));
+
+ CU_ASSERT(1 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("${a " == res[0].value);
+
+ res = parse_log_format(balloc, StringRef::from_lit("$$remote_addr"));
+
+ CU_ASSERT(2 == res.size());
+
+ CU_ASSERT(LogFragmentType::LITERAL == res[0].type);
+ CU_ASSERT("$" == res[0].value);
+
+ CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[1].type);
+ CU_ASSERT("" == res[1].value);
+}
+
+void test_shrpx_config_read_tls_ticket_key_file(void) {
+ char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd1 = mkstemp(file1);
+ CU_ASSERT(fd1 != -1);
+ CU_ASSERT(48 ==
+ write(fd1, "0..............12..............34..............5", 48));
+ char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd2 = mkstemp(file2);
+ CU_ASSERT(fd2 != -1);
+ CU_ASSERT(48 ==
+ write(fd2, "6..............78..............9a..............b", 48));
+
+ close(fd1);
+ close(fd2);
+ auto ticket_keys = read_tls_ticket_key_file(
+ {StringRef{file1}, StringRef{file2}}, EVP_aes_128_cbc(), EVP_sha256());
+ 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(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "0..............1"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::begin(key->data.enc_key) + 16, "2..............3"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::begin(key->data.hmac_key) + 16,
+ "4..............5"));
+ CU_ASSERT(16 == key->hmac_keylen);
+
+ key = &ticket_keys->keys[1];
+ CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "6..............7"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::begin(key->data.enc_key) + 16, "8..............9"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::begin(key->data.hmac_key) + 16,
+ "a..............b"));
+ CU_ASSERT(16 == key->hmac_keylen);
+}
+
+void test_shrpx_config_read_tls_ticket_key_file_aes_256(void) {
+ char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd1 = mkstemp(file1);
+ CU_ASSERT(fd1 != -1);
+ CU_ASSERT(80 == write(fd1,
+ "0..............12..............................34..."
+ "...........................5",
+ 80));
+ char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
+ auto fd2 = mkstemp(file2);
+ CU_ASSERT(fd2 != -1);
+ CU_ASSERT(80 == write(fd2,
+ "6..............78..............................9a..."
+ "...........................b",
+ 80));
+
+ close(fd1);
+ close(fd2);
+ auto ticket_keys = read_tls_ticket_key_file(
+ {StringRef{file1}, StringRef{file2}}, EVP_aes_256_cbc(), EVP_sha256());
+ 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(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "0..............1"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::end(key->data.enc_key),
+ "2..............................3"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::end(key->data.hmac_key),
+ "4..............................5"));
+
+ key = &ticket_keys->keys[1];
+ CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name),
+ "6..............7"));
+ CU_ASSERT(std::equal(std::begin(key->data.enc_key),
+ std::end(key->data.enc_key),
+ "8..............................9"));
+ CU_ASSERT(std::equal(std::begin(key->data.hmac_key),
+ std::end(key->data.hmac_key),
+ "a..............................b"));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h
new file mode 100644
index 0000000..a30de41
--- /dev/null
+++ b/src/shrpx_config_test.h
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+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);
+void test_shrpx_config_read_tls_ticket_key_file_aes_256(void);
+void test_shrpx_config_match_downstream_addr_group(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
index 0000000..ff767b0
--- /dev/null
+++ b/src/shrpx_connect_blocker.cc
@@ -0,0 +1,143 @@
+/*
+ * 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"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto connect_blocker = static_cast<ConnectBlocker *>(w->data);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Unblock";
+ }
+
+ connect_blocker->call_unblock_func();
+}
+} // namespace
+
+ConnectBlocker::ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop,
+ std::function<void()> block_func,
+ std::function<void()> unblock_func)
+ : gen_(gen),
+ block_func_(std::move(block_func)),
+ unblock_func_(std::move(unblock_func)),
+ loop_(loop),
+ fail_count_(0),
+ offline_(false) {
+ ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
+ timer_.data = this;
+}
+
+ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
+
+bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
+
+void ConnectBlocker::on_success() {
+ if (ev_is_active(&timer_)) {
+ return;
+ }
+
+ fail_count_ = 0;
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 1.6;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void ConnectBlocker::on_failure() {
+ if (ev_is_active(&timer_)) {
+ return;
+ }
+
+ call_block_func();
+
+ ++fail_count_;
+
+ auto base_backoff =
+ util::int_pow(MULTIPLIER, std::min(MAX_BACKOFF_EXP, fail_count_));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ auto backoff =
+ std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_));
+
+ LOG(WARN) << "Could not connect " << fail_count_
+ << " times in a row; sleep for " << backoff << " seconds";
+
+ ev_timer_set(&timer_, backoff, 0.);
+ ev_timer_start(loop_, &timer_);
+}
+
+size_t ConnectBlocker::get_fail_count() const { return fail_count_; }
+
+void ConnectBlocker::offline() {
+ if (offline_) {
+ return;
+ }
+
+ if (!ev_is_active(&timer_)) {
+ call_block_func();
+ }
+
+ offline_ = true;
+
+ ev_timer_stop(loop_, &timer_);
+ ev_timer_set(&timer_, std::numeric_limits<double>::max(), 0.);
+ ev_timer_start(loop_, &timer_);
+}
+
+void ConnectBlocker::online() {
+ ev_timer_stop(loop_, &timer_);
+
+ call_unblock_func();
+
+ fail_count_ = 0;
+
+ offline_ = false;
+}
+
+bool ConnectBlocker::in_offline() const { return offline_; }
+
+void ConnectBlocker::call_block_func() {
+ if (block_func_) {
+ block_func_();
+ }
+}
+
+void ConnectBlocker::call_unblock_func() {
+ if (unblock_func_) {
+ unblock_func_();
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connect_blocker.h b/src/shrpx_connect_blocker.h
new file mode 100644
index 0000000..1ebe789
--- /dev/null
+++ b/src/shrpx_connect_blocker.h
@@ -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 SHRPX_CONNECT_BLOCKER_H
+#define SHRPX_CONNECT_BLOCKER_H
+
+#include "shrpx.h"
+
+#include <random>
+#include <functional>
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ConnectBlocker {
+public:
+ ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop,
+ std::function<void()> block_func,
+ std::function<void()> unblock_func);
+ ~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 operations failed. This will start
+ // timer and blocks connection establishment with exponential
+ // backoff.
+ void on_failure();
+
+ size_t get_fail_count() const;
+
+ // Peer is now considered offline. This effectively means that the
+ // connection is blocked until online() is called.
+ void offline();
+
+ // Peer is now considered online
+ void online();
+
+ // Returns true if peer is considered offline.
+ bool in_offline() const;
+
+ void call_block_func();
+ void call_unblock_func();
+
+private:
+ std::mt19937 &gen_;
+ // Called when blocking is started
+ std::function<void()> block_func_;
+ // Called when unblocked
+ std::function<void()> unblock_func_;
+ ev_timer timer_;
+ struct ev_loop *loop_;
+ // The number of consecutive connection failure. Reset to 0 on
+ // success.
+ size_t fail_count_;
+ // true if peer is considered offline.
+ bool offline_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECT_BLOCKER_H
diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc
new file mode 100644
index 0000000..a5ab390
--- /dev/null
+++ b/src/shrpx_connection.cc
@@ -0,0 +1,1275 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <netinet/tcp.h>
+
+#include <limits>
+
+#include <openssl/err.h>
+
+#include "shrpx_tls.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_log.h"
+#include "memchunk.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+using namespace std::chrono_literals;
+
+namespace shrpx {
+
+Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
+ MemchunkPool *mcpool, ev_tstamp write_timeout,
+ ev_tstamp read_timeout,
+ const RateLimitConfig &write_limit,
+ const RateLimitConfig &read_limit, IOCb writecb,
+ IOCb readcb, TimerCb timeoutcb, void *data,
+ size_t tls_dyn_rec_warmup_threshold,
+ ev_tstamp tls_dyn_rec_idle_timeout, Proto proto)
+ :
+#ifdef ENABLE_HTTP3
+ conn_ref{nullptr, this},
+#endif // ENABLE_HTTP3
+ tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool),
+ DefaultMemchunks(mcpool)},
+ wlimit(loop, &wev, write_limit.rate, write_limit.burst),
+ rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
+ loop(loop),
+ data(data),
+ fd(fd),
+ tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
+ tls_dyn_rec_idle_timeout(util::duration_from(tls_dyn_rec_idle_timeout)),
+ proto(proto),
+ read_timeout(read_timeout) {
+
+ ev_io_init(&wev, writecb, fd, EV_WRITE);
+ ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : 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;
+
+ if (ssl) {
+ set_ssl(ssl);
+ }
+}
+
+Connection::~Connection() { disconnect(); }
+
+void Connection::disconnect() {
+ if (tls.ssl) {
+ if (proto != Proto::HTTP3) {
+ SSL_set_shutdown(tls.ssl,
+ SSL_get_shutdown(tls.ssl) | SSL_RECEIVED_SHUTDOWN);
+ ERR_clear_error();
+
+ if (tls.cached_session) {
+ SSL_SESSION_free(tls.cached_session);
+ tls.cached_session = nullptr;
+ }
+
+ if (tls.cached_session_lookup_req) {
+ tls.cached_session_lookup_req->canceled = true;
+ tls.cached_session_lookup_req = nullptr;
+ }
+
+ SSL_shutdown(tls.ssl);
+ }
+
+ SSL_free(tls.ssl);
+ tls.ssl = nullptr;
+
+ tls.wbuf.reset();
+ tls.rbuf.reset();
+ tls.last_write_idle = {};
+ tls.warmup_writelen = 0;
+ tls.last_writelen = 0;
+ tls.last_readlen = 0;
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ tls.initial_handshake_done = false;
+ tls.reneg_started = false;
+ tls.sct_requested = false;
+ tls.early_data_finish = false;
+ }
+
+ if (proto != Proto::HTTP3 && fd != -1) {
+ shutdown(fd, SHUT_WR);
+ close(fd);
+ fd = -1;
+ }
+
+ // Stop watchers here because they could be activated in
+ // SSL_shutdown().
+ ev_timer_stop(loop, &rt);
+ ev_timer_stop(loop, &wt);
+
+ rlimit.stopw();
+ wlimit.stopw();
+}
+
+void Connection::prepare_client_handshake() {
+ SSL_set_connect_state(tls.ssl);
+ // This prevents SSL_read_early_data from being called.
+ tls.early_data_finish = true;
+}
+
+void Connection::prepare_server_handshake() {
+ auto &tlsconf = get_config()->tls;
+ if (proto != Proto::HTTP3 && !tlsconf.session_cache.memcached.host.empty()) {
+ auto bio = BIO_new(tlsconf.bio_method);
+ BIO_set_data(bio, this);
+ SSL_set_bio(tls.ssl, bio, bio);
+ }
+
+ SSL_set_accept_state(tls.ssl);
+ tls.server_handshake = true;
+}
+
+// BIO implementation is inspired by openldap implementation:
+// http://www.openldap.org/devel/cvsweb.cgi/~checkout~/libraries/libldap/tls_o.c
+namespace {
+int shrpx_bio_write(BIO *b, const char *buf, int len) {
+ if (buf == nullptr || len <= 0) {
+ return 0;
+ }
+
+ auto conn = static_cast<Connection *>(BIO_get_data(b));
+ auto &wbuf = conn->tls.wbuf;
+
+ BIO_clear_retry_flags(b);
+
+ if (conn->tls.initial_handshake_done) {
+ // After handshake finished, send |buf| of length |len| to the
+ // socket directly.
+
+ // Only when TLS session was prematurely ended before server sent
+ // all handshake message, this condition is true. This could be
+ // alert from SSL_shutdown(). Since connection is already down,
+ // just return error.
+ if (wbuf.rleft()) {
+ return -1;
+ }
+ auto nwrite = conn->write_clear(buf, len);
+ if (nwrite < 0) {
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ BIO_set_retry_write(b);
+ return -1;
+ }
+
+ return nwrite;
+ }
+
+ wbuf.append(buf, len);
+
+ return len;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_read(BIO *b, char *buf, int len) {
+ if (buf == nullptr || len <= 0) {
+ return 0;
+ }
+
+ auto conn = static_cast<Connection *>(BIO_get_data(b));
+ auto &rbuf = conn->tls.rbuf;
+
+ BIO_clear_retry_flags(b);
+
+ if (conn->tls.initial_handshake_done && rbuf.rleft() == 0) {
+ auto nread = conn->read_clear(buf, len);
+ if (nread < 0) {
+ return -1;
+ }
+ if (nread == 0) {
+ BIO_set_retry_read(b);
+ return -1;
+ }
+ return nread;
+ }
+
+ if (rbuf.rleft() == 0) {
+ BIO_set_retry_read(b);
+ return -1;
+ }
+
+ return rbuf.remove(buf, len);
+}
+} // namespace
+
+namespace {
+int shrpx_bio_puts(BIO *b, const char *str) {
+ return shrpx_bio_write(b, str, strlen(str));
+}
+} // namespace
+
+namespace {
+int shrpx_bio_gets(BIO *b, char *buf, int len) { return -1; }
+} // namespace
+
+namespace {
+long shrpx_bio_ctrl(BIO *b, int cmd, long num, void *ptr) {
+ switch (cmd) {
+ case BIO_CTRL_FLUSH:
+ return 1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_create(BIO *b) {
+ BIO_set_init(b, 1);
+
+ return 1;
+}
+} // namespace
+
+namespace {
+int shrpx_bio_destroy(BIO *b) {
+ if (b == nullptr) {
+ return 0;
+ }
+
+ return 1;
+}
+} // namespace
+
+BIO_METHOD *create_bio_method() {
+ auto meth = BIO_meth_new(BIO_TYPE_FD, "nghttpx-bio");
+ BIO_meth_set_write(meth, shrpx_bio_write);
+ BIO_meth_set_read(meth, shrpx_bio_read);
+ BIO_meth_set_puts(meth, shrpx_bio_puts);
+ BIO_meth_set_gets(meth, shrpx_bio_gets);
+ BIO_meth_set_ctrl(meth, shrpx_bio_ctrl);
+ BIO_meth_set_create(meth, shrpx_bio_create);
+ BIO_meth_set_destroy(meth, shrpx_bio_destroy);
+
+ return meth;
+}
+
+void Connection::set_ssl(SSL *ssl) {
+ tls.ssl = ssl;
+
+ SSL_set_app_data(tls.ssl, this);
+}
+
+namespace {
+// We should buffer at least full encrypted TLS record here.
+// Theoretically, peer can send client hello in several TLS records,
+// which could exceed this limit, but it is not portable, and we don't
+// have to handle such exotic behaviour.
+bool read_buffer_full(DefaultPeekMemchunks &rbuf) {
+ return rbuf.rleft_buffered() >= 20_k;
+}
+} // namespace
+
+int Connection::tls_handshake() {
+ wlimit.stopw();
+ ev_timer_stop(loop, &wt);
+
+ auto &tlsconf = get_config()->tls;
+
+ if (!tls.server_handshake || tlsconf.session_cache.memcached.host.empty()) {
+ return tls_handshake_simple();
+ }
+
+ std::array<uint8_t, 16_k> buf;
+
+ if (ev_is_active(&rev)) {
+ auto nread = read_clear(buf.data(), buf.size());
+ if (nread < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake read error";
+ }
+ return -1;
+ }
+ tls.rbuf.append(buf.data(), nread);
+ if (read_buffer_full(tls.rbuf)) {
+ rlimit.stopw();
+ }
+ }
+
+ if (tls.initial_handshake_done) {
+ return write_tls_pending_handshake();
+ }
+
+ switch (tls.handshake_state) {
+ case TLSHandshakeState::WAIT_FOR_SESSION_CACHE:
+ return SHRPX_ERR_INPROGRESS;
+ case TLSHandshakeState::GOT_SESSION_CACHE: {
+ // Use the same trick invented by @kazuho in h2o project.
+
+ // Discard all outgoing data.
+ tls.wbuf.reset();
+ // Rewind buffered incoming data to replay client hello.
+ tls.rbuf.disable_peek(false);
+
+ auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl);
+ auto ssl_opts = SSL_get_options(tls.ssl);
+ SSL_free(tls.ssl);
+
+ auto ssl = tls::create_ssl(ssl_ctx);
+ if (!ssl) {
+ return -1;
+ }
+ if (ssl_opts & SSL_OP_NO_TICKET) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+
+ set_ssl(ssl);
+
+ prepare_server_handshake();
+
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ break;
+ }
+ case TLSHandshakeState::CANCEL_SESSION_CACHE:
+ tls.handshake_state = TLSHandshakeState::NORMAL;
+ break;
+ default:
+ break;
+ }
+
+ int rv;
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.server_handshake || tls.early_data_finish) {
+ rv = SSL_do_handshake(tls.ssl);
+ } else {
+ for (;;) {
+ size_t nread;
+
+ rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ // If we have early data, and server sends ServerHello, assume
+ // that handshake is completed in server side, and start
+ // processing request. If we don't exit handshake code here,
+ // server waits for EndOfEarlyData and Finished message from
+ // client, which voids the purpose of 0-RTT data. The left
+ // over of handshake is done through write_tls or read_tls.
+ if (tlsconf.no_postpone_early_data &&
+ (tls.handshake_state == TLSHandshakeState::WRITE_STARTED ||
+ tls.wbuf.rleft()) &&
+ tls.earlybuf.rleft()) {
+ rv = 1;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ tls.earlybuf.append(buf.data(), nread);
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data; total "
+ << tls.earlybuf.rleft() << " bytes";
+ }
+ tls.early_data_finish = true;
+ // The same reason stated above.
+ if (tlsconf.no_postpone_early_data &&
+ (tls.handshake_state == TLSHandshakeState::WRITE_STARTED ||
+ tls.wbuf.rleft()) &&
+ tls.earlybuf.rleft()) {
+ rv = 1;
+ } else {
+ ERR_clear_error();
+ rv = SSL_do_handshake(tls.ssl);
+ }
+ break;
+ }
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ rv = SSL_do_handshake(tls.ssl);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (read_buffer_full(tls.rbuf)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake message is too large";
+ }
+ return -1;
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_SSL: {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+
+ struct iovec iov[1];
+ auto iovcnt = tls.wbuf.riovec(iov, 1);
+ auto nwrite = writev_clear(iov, iovcnt);
+ if (nwrite > 0) {
+ tls.wbuf.drain(nwrite);
+ }
+
+ return SHRPX_ERR_NETWORK;
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (tls.handshake_state == TLSHandshakeState::WAIT_FOR_SESSION_CACHE) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+ // Don't send handshake data if handshake was completed in OpenSSL
+ // routine. We have to check HTTP/2 requirement if HTTP/2 was
+ // negotiated before sending finished message to the peer.
+ if ((rv != 1
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ || SSL_in_init(tls.ssl)
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ ) &&
+ tls.wbuf.rleft()) {
+ // First write indicates that resumption stuff has done.
+ if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) {
+ tls.handshake_state = TLSHandshakeState::WRITE_STARTED;
+ // If peek has already disabled, this is noop.
+ tls.rbuf.disable_peek(true);
+ }
+ std::array<struct iovec, 4> iov;
+ auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
+ auto nwrite = writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake write error";
+ }
+ return -1;
+ }
+ tls.wbuf.drain(nwrite);
+
+ if (tls.wbuf.rleft()) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ }
+ }
+
+ if (!read_buffer_full(tls.rbuf)) {
+ // We may have stopped reading
+ rlimit.startw();
+ }
+
+ if (rv != 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) &&
+ SSL_in_init(tls.ssl)) {
+ auto nread = SSL_read(tls.ssl, buf.data(), buf.size());
+ if (nread <= 0) {
+ auto err = SSL_get_error(tls.ssl, nread);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ return SHRPX_ERR_EOF;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ } else {
+ tls.earlybuf.append(buf.data(), nread);
+ }
+
+ if (SSL_in_init(tls.ssl)) {
+ return SHRPX_ERR_INPROGRESS;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // Handshake was done
+
+ rv = check_http2_requirement();
+ if (rv != 0) {
+ return -1;
+ }
+
+ // Just in case
+ tls.rbuf.disable_peek(true);
+
+ tls.initial_handshake_done = true;
+
+ return write_tls_pending_handshake();
+}
+
+int Connection::tls_handshake_simple() {
+ wlimit.stopw();
+ ev_timer_stop(loop, &wt);
+
+ if (tls.initial_handshake_done) {
+ return write_tls_pending_handshake();
+ }
+
+ if (SSL_get_fd(tls.ssl) == -1) {
+ SSL_set_fd(tls.ssl, fd);
+ }
+
+ int rv;
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto &tlsconf = get_config()->tls;
+ std::array<uint8_t, 16_k> buf;
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.server_handshake || tls.early_data_finish) {
+ rv = SSL_do_handshake(tls.ssl);
+ } else {
+ for (;;) {
+ size_t nread;
+
+ rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ // If we have early data, and server sends ServerHello, assume
+ // that handshake is completed in server side, and start
+ // processing request. If we don't exit handshake code here,
+ // server waits for EndOfEarlyData and Finished message from
+ // client, which voids the purpose of 0-RTT data. The left
+ // over of handshake is done through write_tls or read_tls.
+ if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) {
+ rv = 1;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ tls.earlybuf.append(buf.data(), nread);
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data; total "
+ << tls.earlybuf.rleft() << " bytes";
+ }
+ tls.early_data_finish = true;
+ // The same reason stated above.
+ if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) {
+ rv = 1;
+ } else {
+ ERR_clear_error();
+ rv = SSL_do_handshake(tls.ssl);
+ }
+ break;
+ }
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ rv = SSL_do_handshake(tls.ssl);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (read_buffer_full(tls.rbuf)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake message is too large";
+ }
+ return -1;
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ break;
+ case SSL_ERROR_SSL: {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake libssl error " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (rv != 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake is still in progress";
+ }
+ return SHRPX_ERR_INPROGRESS;
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) &&
+ SSL_in_init(tls.ssl)) {
+ auto nread = SSL_read(tls.ssl, buf.data(), buf.size());
+ if (nread <= 0) {
+ auto err = SSL_get_error(tls.ssl, nread);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ return SHRPX_ERR_EOF;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ } else {
+ tls.earlybuf.append(buf.data(), nread);
+ }
+
+ if (SSL_in_init(tls.ssl)) {
+ return SHRPX_ERR_INPROGRESS;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // Handshake was done
+
+ rv = check_http2_requirement();
+ if (rv != 0) {
+ return -1;
+ }
+
+ tls.initial_handshake_done = true;
+
+ return write_tls_pending_handshake();
+}
+
+int Connection::write_tls_pending_handshake() {
+ // Send handshake data left in the buffer
+ while (tls.wbuf.rleft()) {
+ std::array<struct iovec, 4> iov;
+ auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
+ auto nwrite = writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: handshake write error";
+ }
+ return -1;
+ }
+ if (nwrite == 0) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+
+ return SHRPX_ERR_INPROGRESS;
+ }
+ tls.wbuf.drain(nwrite);
+ }
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (!SSL_in_init(tls.ssl)) {
+ // This will send a session ticket.
+ auto nwrite = SSL_write(tls.ssl, "", 0);
+ if (nwrite < 0) {
+ auto err = SSL_get_error(tls.ssl, nwrite);
+ 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:
+ break;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // We have to start read watcher, since later stage of code expects
+ // this.
+ rlimit.startw();
+
+ // We may have whole request in tls.rbuf. This means that we don't
+ // get notified further read event. This is especially true for
+ // HTTP/1.1.
+ handle_tls_pending_read();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ nghttp2::tls::TLSSessionInfo tls_info{};
+ if (nghttp2::tls::get_tls_session_info(&tls_info, tls.ssl)) {
+ LOG(INFO) << "cipher=" << tls_info.cipher
+ << " protocol=" << tls_info.protocol
+ << " resumption=" << (tls_info.session_reused ? "yes" : "no")
+ << " session_id="
+ << util::format_hex(tls_info.session_id,
+ tls_info.session_id_length);
+ }
+ }
+
+ return 0;
+}
+
+int Connection::check_http2_requirement() {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len;
+
+ SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len);
+ if (next_proto == nullptr ||
+ !util::check_h2_is_selected(StringRef{next_proto, next_proto_len})) {
+ return 0;
+ }
+ if (!nghttp2::tls::check_http2_tls_version(tls.ssl)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used.";
+ }
+ return -1;
+ }
+
+ auto check_block_list = false;
+ if (tls.server_handshake) {
+ check_block_list = !get_config()->tls.no_http2_cipher_block_list;
+ } else {
+ check_block_list = !get_config()->tls.client.no_http2_cipher_block_list;
+ }
+
+ if (check_block_list &&
+ nghttp2::tls::check_http2_cipher_block_list(tls.ssl)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite "
+ "block list. HTTP/2 must not be used.";
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+constexpr size_t SHRPX_SMALL_WRITE_LIMIT = 1300;
+} // namespace
+
+size_t Connection::get_tls_write_limit() {
+
+ if (tls_dyn_rec_warmup_threshold == 0) {
+ return std::numeric_limits<ssize_t>::max();
+ }
+
+ auto t = std::chrono::steady_clock::now();
+
+ if (tls.last_write_idle.time_since_epoch().count() >= 0 &&
+ t - tls.last_write_idle > tls_dyn_rec_idle_timeout) {
+ // Time out, use small record size
+ tls.warmup_writelen = 0;
+ return SHRPX_SMALL_WRITE_LIMIT;
+ }
+
+ if (tls.warmup_writelen >= tls_dyn_rec_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 < tls_dyn_rec_warmup_threshold) {
+ tls.warmup_writelen += n;
+ }
+}
+
+void Connection::start_tls_write_idle() {
+ if (tls.last_write_idle.time_since_epoch().count() < 0) {
+ tls.last_write_idle = std::chrono::steady_clock::now();
+ }
+}
+
+ssize_t Connection::write_tls(const void *data, size_t len) {
+ // 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 length passed to SSL_write to
+ // tls.last_writelen if SSL_write indicated I/O blocking.
+ if (tls.last_writelen == 0) {
+ len = std::min(len, wlimit.avail());
+ len = std::min(len, get_tls_write_limit());
+ if (len == 0) {
+ return 0;
+ }
+ } else {
+ len = tls.last_writelen;
+ tls.last_writelen = 0;
+ }
+
+ tls.last_write_idle = std::chrono::steady_clock::time_point(-1s);
+
+ auto &tlsconf = get_config()->tls;
+ auto via_bio =
+ tls.server_handshake && !tlsconf.session_cache.memcached.host.empty();
+
+ ERR_clear_error();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ int rv;
+ if (SSL_is_init_finished(tls.ssl)) {
+ rv = SSL_write(tls.ssl, data, len);
+ } else {
+ size_t nwrite;
+ rv = SSL_write_early_data(tls.ssl, data, len, &nwrite);
+ // Use the same semantics with SSL_write.
+ if (rv == 1) {
+ rv = nwrite;
+ }
+ }
+#else // !NGHTTP2_GENUINE_OPENSSL
+ auto rv = SSL_write(tls.ssl, data, len);
+#endif // !NGHTTP2_GENUINE_OPENSSL
+
+ 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 = len;
+ // starting write watcher and timer is done in write_clear via
+ // bio otherwise.
+ if (!via_bio) {
+ wlimit.startw();
+ ev_timer_again(loop, &wt);
+ }
+
+ return 0;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (!via_bio) {
+ wlimit.drain(rv);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+ }
+
+ update_tls_warmup_writelen(rv);
+
+ return rv;
+}
+
+ssize_t Connection::read_tls(void *data, size_t len) {
+ ERR_clear_error();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (tls.earlybuf.rleft()) {
+ return tls.earlybuf.remove(data, len);
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // 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 length passed
+ // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O
+ // blocking.
+ if (tls.last_readlen == 0) {
+ len = std::min(len, rlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+ } else {
+ len = tls.last_readlen;
+ tls.last_readlen = 0;
+ }
+
+ auto &tlsconf = get_config()->tls;
+ auto via_bio =
+ tls.server_handshake && !tlsconf.session_cache.memcached.host.empty();
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (!tls.early_data_finish) {
+ // TLSv1.3 handshake is still going on.
+ size_t nread;
+ auto rv = SSL_read_early_data(tls.ssl, data, len, &nread);
+ if (rv == SSL_READ_EARLY_DATA_ERROR) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ tls.last_readlen = len;
+ return 0;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read early data " << nread << " bytes";
+ }
+
+ if (rv == SSL_READ_EARLY_DATA_FINISH) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "tls: read all early data";
+ }
+ tls.early_data_finish = true;
+ // We may have stopped write watcher in write_tls.
+ wlimit.startw();
+ }
+
+ if (!via_bio) {
+ rlimit.drain(nread);
+ }
+
+ return nread;
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ auto rv = SSL_read(tls.ssl, data, len);
+
+ if (rv <= 0) {
+ auto err = SSL_get_error(tls.ssl, rv);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ tls.last_readlen = len;
+ 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;
+ case SSL_ERROR_SSL:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr);
+ }
+ return SHRPX_ERR_NETWORK;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+ }
+
+ if (!via_bio) {
+ rlimit.drain(rv);
+ }
+
+ return rv;
+}
+
+ssize_t Connection::write_clear(const void *data, size_t len) {
+ len = std::min(len, wlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+
+ ssize_t nwrite;
+ while ((nwrite = write(fd, data, len)) == -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);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+
+ 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);
+
+ if (ev_is_active(&wt)) {
+ ev_timer_again(loop, &wt);
+ }
+
+ return nwrite;
+}
+
+ssize_t Connection::read_clear(void *data, size_t len) {
+ len = std::min(len, rlimit.avail());
+ if (len == 0) {
+ return 0;
+ }
+
+ ssize_t nread;
+ while ((nread = read(fd, data, len)) == -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;
+}
+
+ssize_t Connection::read_nolim_clear(void *data, size_t len) {
+ ssize_t nread;
+ while ((nread = read(fd, data, len)) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (nread == 0) {
+ return SHRPX_ERR_EOF;
+ }
+
+ return nread;
+}
+
+ssize_t Connection::peek_clear(void *data, size_t len) {
+ ssize_t nread;
+ while ((nread = recv(fd, data, len, MSG_PEEK)) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (nread == 0) {
+ return SHRPX_ERR_EOF;
+ }
+
+ return nread;
+}
+
+void Connection::handle_tls_pending_read() {
+ if (!ev_is_active(&rev)) {
+ return;
+ }
+ rlimit.handle_tls_pending_read();
+}
+
+int Connection::get_tcp_hint(TCPHint *hint) const {
+#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+ struct tcp_info tcp_info;
+ socklen_t tcp_info_len = sizeof(tcp_info);
+ int rv;
+
+ rv = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto avail_packets = tcp_info.tcpi_snd_cwnd > tcp_info.tcpi_unacked
+ ? tcp_info.tcpi_snd_cwnd - tcp_info.tcpi_unacked
+ : 0;
+
+ // http://www.slideshare.net/kazuho/programming-tcp-for-responsiveness
+
+ // TODO 29 (5 (header) + 8 (explicit nonce) + 16 (tag)) is TLS
+ // overhead for AES-GCM. For CHACHA20_POLY1305, it is 21 since it
+ // does not need 8 bytes explicit nonce.
+ //
+ // For TLSv1.3, AES-GCM and CHACHA20_POLY1305 overhead are now 22
+ // bytes (5 (header) + 1 (ContentType) + 16 (tag)).
+ size_t tls_overhead;
+# ifdef TLS1_3_VERSION
+ if (SSL_version(tls.ssl) == TLS1_3_VERSION) {
+ tls_overhead = 22;
+ } else
+# endif // TLS1_3_VERSION
+ {
+ tls_overhead = 29;
+ }
+
+ auto writable_size =
+ (avail_packets + 2) * (tcp_info.tcpi_snd_mss - tls_overhead);
+ if (writable_size > 16_k) {
+ writable_size = writable_size & ~(16_k - 1);
+ } else {
+ if (writable_size < 536) {
+ LOG(INFO) << "writable_size is too small: " << writable_size;
+ }
+ // TODO is this required?
+ writable_size = std::max(writable_size, static_cast<size_t>(536 * 2));
+ }
+
+ // if (LOG_ENABLED(INFO)) {
+ // LOG(INFO) << "snd_cwnd=" << tcp_info.tcpi_snd_cwnd
+ // << ", unacked=" << tcp_info.tcpi_unacked
+ // << ", snd_mss=" << tcp_info.tcpi_snd_mss
+ // << ", rtt=" << tcp_info.tcpi_rtt << "us"
+ // << ", rcv_space=" << tcp_info.tcpi_rcv_space
+ // << ", writable=" << writable_size;
+ // }
+
+ hint->write_buffer_size = writable_size;
+ // TODO tcpi_rcv_space is considered as rwin, is that correct?
+ hint->rwin = tcp_info.tcpi_rcv_space;
+
+ return 0;
+#else // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT)
+ return -1;
+#endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT)
+}
+
+void Connection::again_rt(ev_tstamp t) {
+ read_timeout = t;
+ rt.repeat = t;
+ ev_timer_again(loop, &rt);
+ last_read = std::chrono::steady_clock::now();
+}
+
+void Connection::again_rt() {
+ rt.repeat = read_timeout;
+ ev_timer_again(loop, &rt);
+ last_read = std::chrono::steady_clock::now();
+}
+
+bool Connection::expired_rt() {
+ auto delta = read_timeout - util::ev_tstamp_from(
+ std::chrono::steady_clock::now() - last_read);
+ if (delta < 1e-9) {
+ return true;
+ }
+ rt.repeat = delta;
+ ev_timer_again(loop, &rt);
+ return false;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h
new file mode 100644
index 0000000..10526f7
--- /dev/null
+++ b/src/shrpx_connection.h
@@ -0,0 +1,203 @@
+/*
+ * 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>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2_crypto.h>
+#endif // ENABLE_HTTP3
+
+#include "shrpx_rate_limit.h"
+#include "shrpx_error.h"
+#include "memchunk.h"
+
+namespace shrpx {
+
+struct MemcachedRequest;
+
+namespace tls {
+struct TLSSessionCache;
+} // namespace tls
+
+enum class TLSHandshakeState {
+ NORMAL,
+ WAIT_FOR_SESSION_CACHE,
+ GOT_SESSION_CACHE,
+ CANCEL_SESSION_CACHE,
+ WRITE_STARTED,
+};
+
+struct TLSConnection {
+ DefaultMemchunks wbuf;
+ DefaultPeekMemchunks rbuf;
+ // Stores TLSv1.3 early data.
+ DefaultMemchunks earlybuf;
+ SSL *ssl;
+ SSL_SESSION *cached_session;
+ MemcachedRequest *cached_session_lookup_req;
+ tls::TLSSessionCache *client_session_cache;
+ std::chrono::steady_clock::time_point last_write_idle;
+ 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;
+ TLSHandshakeState handshake_state;
+ bool initial_handshake_done;
+ bool reneg_started;
+ // true if ssl is prepared to do handshake as server.
+ bool server_handshake;
+ // true if ssl is initialized as server, and client requested
+ // signed_certificate_timestamp extension.
+ bool sct_requested;
+ // true if TLSv1.3 early data has been completely received. Since
+ // SSL_read_early_data acts like SSL_do_handshake, this field may be
+ // true even if the negotiated TLS version is TLSv1.2 or earlier.
+ // This value is also true if this is client side connection for
+ // convenience.
+ bool early_data_finish;
+};
+
+struct TCPHint {
+ size_t write_buffer_size;
+ uint32_t rwin;
+};
+
+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, MemchunkPool *mcpool,
+ ev_tstamp write_timeout, ev_tstamp read_timeout,
+ const RateLimitConfig &write_limit,
+ const RateLimitConfig &read_limit, IOCb writecb, IOCb readcb,
+ TimerCb timeoutcb, void *data, size_t tls_dyn_rec_warmup_threshold,
+ ev_tstamp tls_dyn_rec_idle_timeout, Proto proto);
+ ~Connection();
+
+ void disconnect();
+
+ void prepare_client_handshake();
+ void prepare_server_handshake();
+
+ int tls_handshake();
+ int tls_handshake_simple();
+ int write_tls_pending_handshake();
+
+ int check_http2_requirement();
+
+ // 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);
+
+ size_t get_tls_write_limit();
+ // Updates the number of bytes written in warm up period.
+ void update_tls_warmup_writelen(size_t n);
+ // Tells there is no immediate write now. This triggers timer to
+ // determine fallback to short record size mode.
+ void start_tls_write_idle();
+
+ 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);
+ // Read at most |len| bytes of data from socket without rate limit.
+ ssize_t read_nolim_clear(void *data, size_t len);
+ // Peek at most |len| bytes of data from socket without rate limit.
+ ssize_t peek_clear(void *data, size_t len);
+
+ void handle_tls_pending_read();
+
+ void set_ssl(SSL *ssl);
+
+ int get_tcp_hint(TCPHint *hint) const;
+
+ // These functions are provided for read timer which is frequently
+ // restarted. We do a trick to make a bit more efficient than just
+ // calling ev_timer_again().
+
+ // Restarts read timer with timeout value |t|.
+ void again_rt(ev_tstamp t);
+ // Restarts read timer without changing timeout.
+ void again_rt();
+ // Returns true if read timer expired.
+ bool expired_rt();
+
+#ifdef ENABLE_HTTP3
+ // This must be the first member of Connection.
+ ngtcp2_crypto_conn_ref conn_ref;
+#endif // ENABLE_HTTP3
+ TLSConnection tls;
+ ev_io wev;
+ ev_io rev;
+ ev_timer wt;
+ ev_timer rt;
+ RateLimit wlimit;
+ RateLimit rlimit;
+ struct ev_loop *loop;
+ void *data;
+ int fd;
+ size_t tls_dyn_rec_warmup_threshold;
+ std::chrono::steady_clock::duration tls_dyn_rec_idle_timeout;
+ // Application protocol used over the connection. This field is not
+ // used in this object at the moment. The rest of the program may
+ // use this value when it is useful.
+ Proto proto;
+ // The point of time when last read is observed. Note: since we use
+ // |rt| as idle timer, the activity is not limited to read.
+ std::chrono::steady_clock::time_point last_read;
+ // Timeout for read timer |rt|.
+ ev_tstamp read_timeout;
+};
+
+#ifdef ENABLE_HTTP3
+static_assert(std::is_standard_layout<Connection>::value,
+ "Connection is not standard layout");
+#endif // ENABLE_HTTP3
+
+// Creates BIO_method shared by all SSL objects.
+BIO_METHOD *create_bio_method();
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_H
diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc
new file mode 100644
index 0000000..330e832
--- /dev/null
+++ b/src/shrpx_connection_handler.cc
@@ -0,0 +1,1319 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <cerrno>
+#include <thread>
+#include <random>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_tls.h"
+#include "shrpx_worker.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 "shrpx_memcached_dispatcher.h"
+#include "shrpx_signal.h"
+#include "shrpx_log.h"
+#include "xsi_strerror.h"
+#include "util.h"
+#include "template.h"
+#include "ssl_compat.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 (h->get_graceful_shutdown()) {
+ return;
+ }
+
+ h->enable_acceptor();
+}
+} // namespace
+
+namespace {
+void ocsp_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 won't do ocsp query.
+ if (h->get_graceful_shutdown()) {
+ return;
+ }
+
+ LOG(NOTICE) << "Start ocsp update";
+
+ h->proceed_next_cert_ocsp();
+}
+} // namespace
+
+namespace {
+void ocsp_read_cb(struct ev_loop *loop, ev_io *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->read_ocsp_chunk();
+}
+} // namespace
+
+namespace {
+void ocsp_chld_cb(struct ev_loop *loop, ev_child *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->handle_ocsp_complete();
+}
+} // namespace
+
+namespace {
+void thread_join_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
+ ev_break(loop);
+}
+} // namespace
+
+namespace {
+void serial_event_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
+ auto h = static_cast<ConnectionHandler *>(w->data);
+
+ h->handle_serial_event();
+}
+} // namespace
+
+ConnectionHandler::ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen)
+ :
+#ifdef ENABLE_HTTP3
+ quic_ipc_fd_(-1),
+#endif // ENABLE_HTTP3
+ gen_(gen),
+ single_worker_(nullptr),
+ loop_(loop),
+#ifdef HAVE_NEVERBLEED
+ nb_(nullptr),
+#endif // HAVE_NEVERBLEED
+ tls_ticket_key_memcached_get_retry_count_(0),
+ tls_ticket_key_memcached_fail_count_(0),
+ worker_round_robin_cnt_(get_config()->api.enabled ? 1 : 0),
+ graceful_shutdown_(false),
+ enable_acceptor_on_ocsp_completion_(false) {
+ ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
+ disable_acceptor_timer_.data = this;
+
+ ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.);
+ ocsp_timer_.data = this;
+
+ ev_io_init(&ocsp_.rev, ocsp_read_cb, -1, EV_READ);
+ ocsp_.rev.data = this;
+
+ ev_async_init(&thread_join_asyncev_, thread_join_async_cb);
+
+ ev_async_init(&serial_event_asyncev_, serial_event_async_cb);
+ serial_event_asyncev_.data = this;
+
+ ev_async_start(loop_, &serial_event_asyncev_);
+
+ ev_child_init(&ocsp_.chldev, ocsp_chld_cb, 0, 0);
+ ocsp_.chldev.data = this;
+
+ ocsp_.next = 0;
+ ocsp_.proc.rfd = -1;
+
+ reset_ocsp();
+}
+
+ConnectionHandler::~ConnectionHandler() {
+ ev_child_stop(loop_, &ocsp_.chldev);
+ ev_async_stop(loop_, &serial_event_asyncev_);
+ ev_async_stop(loop_, &thread_join_asyncev_);
+ ev_io_stop(loop_, &ocsp_.rev);
+ ev_timer_stop(loop_, &ocsp_timer_);
+ ev_timer_stop(loop_, &disable_acceptor_timer_);
+
+#ifdef ENABLE_HTTP3
+ for (auto ssl_ctx : quic_all_ssl_ctx_) {
+ if (ssl_ctx == nullptr) {
+ continue;
+ }
+
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+ delete tls_ctx_data;
+ SSL_CTX_free(ssl_ctx);
+ }
+#endif // ENABLE_HTTP3
+
+ for (auto ssl_ctx : all_ssl_ctx_) {
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+ delete tls_ctx_data;
+ SSL_CTX_free(ssl_ctx);
+ }
+
+ // Free workers before destroying ev_loop
+ workers_.clear();
+
+ for (auto loop : worker_loops_) {
+ ev_loop_destroy(loop);
+ }
+}
+
+void ConnectionHandler::set_ticket_keys_to_worker(
+ const std::shared_ptr<TicketKeys> &ticket_keys) {
+ for (auto &worker : workers_) {
+ worker->set_ticket_keys(ticket_keys);
+ }
+}
+
+void ConnectionHandler::worker_reopen_log_files() {
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+
+ wev.type = WorkerEventType::REOPEN_LOG;
+
+ worker->send(std::move(wev));
+ }
+}
+
+void ConnectionHandler::worker_replace_downstream(
+ std::shared_ptr<DownstreamConfig> downstreamconf) {
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+
+ wev.type = WorkerEventType::REPLACE_DOWNSTREAM;
+ wev.downstreamconf = downstreamconf;
+
+ worker->send(std::move(wev));
+ }
+}
+
+int ConnectionHandler::create_single_worker() {
+ cert_tree_ = tls::create_cert_lookup_tree();
+ auto sv_ssl_ctx = tls::setup_server_ssl_context(
+ all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get()
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+#endif // HAVE_NEVERBLEED
+ );
+
+#ifdef ENABLE_HTTP3
+ quic_cert_tree_ = tls::create_cert_lookup_tree();
+ auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
+ quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+#endif // ENABLE_HTTP3
+
+ auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ nb_
+#endif // HAVE_NEVERBLEED
+ );
+
+ if (cl_ssl_ctx) {
+ all_ssl_ctx_.push_back(cl_ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ SSL_CTX *session_cache_ssl_ctx = nullptr;
+ {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+ if (memcachedconf.tls) {
+ session_cache_ssl_ctx = tls::create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb_,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file,
+ memcachedconf.private_key_file);
+ all_ssl_ctx_.push_back(session_cache_ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+ }
+ }
+
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size());
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+
+#ifdef ENABLE_HTTP3
+ assert(cid_prefixes_.size() == 1);
+ const auto &cid_prefix = cid_prefixes_[0];
+#endif // ENABLE_HTTP3
+
+ single_worker_ = std::make_unique<Worker>(
+ loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
+#ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(),
+ cid_prefix.size(),
+# ifdef HAVE_LIBBPF
+ /* index = */ 0,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ ticket_keys_, this, config->conn.downstream);
+#ifdef HAVE_MRUBY
+ if (single_worker_->create_mruby_context() != 0) {
+ return -1;
+ }
+#endif // HAVE_MRUBY
+
+#ifdef ENABLE_HTTP3
+ if (single_worker_->setup_quic_server_socket() != 0) {
+ return -1;
+ }
+#endif // ENABLE_HTTP3
+
+ return 0;
+}
+
+int ConnectionHandler::create_worker_thread(size_t num) {
+#ifndef NOTHREADS
+ assert(workers_.size() == 0);
+
+ cert_tree_ = tls::create_cert_lookup_tree();
+ auto sv_ssl_ctx = tls::setup_server_ssl_context(
+ all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+
+# ifdef ENABLE_HTTP3
+ quic_cert_tree_ = tls::create_cert_lookup_tree();
+ auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
+ quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+# endif // ENABLE_HTTP3
+
+ auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
+# ifdef HAVE_NEVERBLEED
+ nb_
+# endif // HAVE_NEVERBLEED
+ );
+
+ if (cl_ssl_ctx) {
+ all_ssl_ctx_.push_back(cl_ssl_ctx);
+# ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+# endif // ENABLE_HTTP3
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+ auto &apiconf = config->api;
+
+# if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size());
+# endif // ENABLE_HTTP3 && HAVE_LIBBPF
+
+ // We have dedicated worker for API request processing.
+ if (apiconf.enabled) {
+ ++num;
+ }
+
+ SSL_CTX *session_cache_ssl_ctx = nullptr;
+ {
+ auto &memcachedconf = config->tls.session_cache.memcached;
+
+ if (memcachedconf.tls) {
+ session_cache_ssl_ctx = tls::create_ssl_client_context(
+# ifdef HAVE_NEVERBLEED
+ nb_,
+# endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file,
+ memcachedconf.private_key_file);
+ all_ssl_ctx_.push_back(session_cache_ssl_ctx);
+# ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+# endif // ENABLE_HTTP3
+ }
+ }
+
+# ifdef ENABLE_HTTP3
+ assert(cid_prefixes_.size() == num);
+# endif // ENABLE_HTTP3
+
+ for (size_t i = 0; i < num; ++i) {
+ auto loop = ev_loop_new(config->ev_loop_flags);
+
+# ifdef ENABLE_HTTP3
+ const auto &cid_prefix = cid_prefixes_[i];
+# endif // ENABLE_HTTP3
+
+ auto worker = std::make_unique<Worker>(
+ loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
+# ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(),
+ cid_prefix.size(),
+# ifdef HAVE_LIBBPF
+ i,
+# endif // HAVE_LIBBPF
+# endif // ENABLE_HTTP3
+ ticket_keys_, this, config->conn.downstream);
+# ifdef HAVE_MRUBY
+ if (worker->create_mruby_context() != 0) {
+ return -1;
+ }
+# endif // HAVE_MRUBY
+
+# ifdef ENABLE_HTTP3
+ if ((!apiconf.enabled || i != 0) &&
+ worker->setup_quic_server_socket() != 0) {
+ return -1;
+ }
+# endif // ENABLE_HTTP3
+
+ workers_.push_back(std::move(worker));
+ worker_loops_.push_back(loop);
+
+ LLOG(NOTICE, this) << "Created worker thread #" << workers_.size() - 1;
+ }
+
+ for (auto &worker : workers_) {
+ worker->run_async();
+ }
+
+#endif // NOTHREADS
+
+ return 0;
+}
+
+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 (single_worker_) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
+ }
+
+ for (auto &worker : workers_) {
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::GRACEFUL_SHUTDOWN;
+
+ worker->send(std::move(wev));
+ }
+
+#ifndef NOTHREADS
+ ev_async_start(loop_, &thread_join_asyncev_);
+
+ thread_join_fut_ = std::async(std::launch::async, [this]() {
+ (void)reopen_log_files(get_config()->logging);
+ join_worker();
+ ev_async_send(get_loop(), &thread_join_asyncev_);
+ delete_log_config();
+ });
+#endif // NOTHREADS
+}
+
+int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen,
+ const UpstreamAddr *faddr) {
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Accepted connection from "
+ << util::numeric_name(addr, addrlen) << ", fd=" << fd;
+ }
+
+ auto config = get_config();
+
+ if (single_worker_) {
+ auto &upstreamconf = config->conn.upstream;
+ if (single_worker_->get_worker_stat()->num_connections >=
+ upstreamconf.worker_connections) {
+
+ if (LOG_ENABLED(INFO)) {
+ LLOG(INFO, this) << "Too many connections >="
+ << upstreamconf.worker_connections;
+ }
+
+ close(fd);
+ return -1;
+ }
+
+ auto client =
+ tls::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr);
+ if (!client) {
+ LLOG(ERROR, this) << "ClientHandler creation failed";
+
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ Worker *worker;
+
+ if (faddr->alt_mode == UpstreamAltMode::API) {
+ worker = workers_[0].get();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Dispatch connection to API worker #0";
+ }
+ } else {
+ worker = workers_[worker_round_robin_cnt_].get();
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Dispatch connection to worker #" << worker_round_robin_cnt_;
+ }
+
+ if (++worker_round_robin_cnt_ == workers_.size()) {
+ auto &apiconf = config->api;
+
+ if (apiconf.enabled) {
+ worker_round_robin_cnt_ = 1;
+ } else {
+ worker_round_robin_cnt_ = 0;
+ }
+ }
+ }
+
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::NEW_CONNECTION;
+ wev.client_fd = fd;
+ memcpy(&wev.client_addr, addr, addrlen);
+ wev.client_addrlen = addrlen;
+ wev.faddr = faddr;
+
+ worker->send(std::move(wev));
+
+ return 0;
+}
+
+struct ev_loop *ConnectionHandler::get_loop() const { return loop_; }
+
+Worker *ConnectionHandler::get_single_worker() const {
+ return single_worker_.get();
+}
+
+void ConnectionHandler::add_acceptor(std::unique_ptr<AcceptHandler> h) {
+ acceptors_.push_back(std::move(h));
+}
+
+void ConnectionHandler::delete_acceptor() { acceptors_.clear(); }
+
+void ConnectionHandler::enable_acceptor() {
+ for (auto &a : acceptors_) {
+ a->enable();
+ }
+}
+
+void ConnectionHandler::disable_acceptor() {
+ for (auto &a : acceptors_) {
+ a->disable();
+ }
+}
+
+void ConnectionHandler::sleep_acceptor(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() {
+ for (auto &a : acceptors_) {
+ a->accept_connection();
+ }
+}
+
+void ConnectionHandler::set_ticket_keys(
+ std::shared_ptr<TicketKeys> ticket_keys) {
+ ticket_keys_ = std::move(ticket_keys);
+ if (single_worker_) {
+ single_worker_->set_ticket_keys(ticket_keys_);
+ }
+}
+
+const std::shared_ptr<TicketKeys> &ConnectionHandler::get_ticket_keys() const {
+ return ticket_keys_;
+}
+
+void ConnectionHandler::set_graceful_shutdown(bool f) {
+ graceful_shutdown_ = f;
+ if (single_worker_) {
+ single_worker_->set_graceful_shutdown(f);
+ }
+}
+
+bool ConnectionHandler::get_graceful_shutdown() const {
+ return graceful_shutdown_;
+}
+
+void ConnectionHandler::cancel_ocsp_update() {
+ enable_acceptor_on_ocsp_completion_ = false;
+ ev_timer_stop(loop_, &ocsp_timer_);
+
+ if (ocsp_.proc.pid == 0) {
+ return;
+ }
+
+ int rv;
+
+ rv = kill(ocsp_.proc.pid, SIGTERM);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send signal to OCSP query process: errno="
+ << error;
+ }
+
+ while ((rv = waitpid(ocsp_.proc.pid, nullptr, 0)) == -1 && errno == EINTR)
+ ;
+ if (rv == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Error occurred while we were waiting for the completion of "
+ "OCSP query process: errno="
+ << error;
+ }
+}
+
+// inspired by h2o_read_command function from h2o project:
+// https://github.com/h2o/h2o
+int ConnectionHandler::start_ocsp_update(const char *cert_file) {
+ int rv;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start ocsp update for " << cert_file;
+ }
+
+ assert(!ev_is_active(&ocsp_.rev));
+ assert(!ev_is_active(&ocsp_.chldev));
+
+ char *const argv[] = {
+ const_cast<char *>(
+ get_config()->tls.ocsp.fetch_ocsp_response_file.c_str()),
+ const_cast<char *>(cert_file), nullptr};
+
+ Process proc;
+ rv = exec_read_command(proc, argv);
+ if (rv != 0) {
+ return -1;
+ }
+
+ ocsp_.proc = proc;
+
+ ev_io_set(&ocsp_.rev, ocsp_.proc.rfd, EV_READ);
+ ev_io_start(loop_, &ocsp_.rev);
+
+ ev_child_set(&ocsp_.chldev, ocsp_.proc.pid, 0);
+ ev_child_start(loop_, &ocsp_.chldev);
+
+ return 0;
+}
+
+void ConnectionHandler::read_ocsp_chunk() {
+ std::array<uint8_t, 4_k> buf;
+ for (;;) {
+ ssize_t n;
+ while ((n = read(ocsp_.proc.rfd, buf.data(), buf.size())) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (n == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return;
+ }
+ auto error = errno;
+ LOG(WARN) << "Reading from ocsp query command failed: errno=" << error;
+ ocsp_.error = error;
+
+ break;
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ std::copy_n(std::begin(buf), n, std::back_inserter(ocsp_.resp));
+ }
+
+ ev_io_stop(loop_, &ocsp_.rev);
+}
+
+void ConnectionHandler::handle_ocsp_complete() {
+ ev_io_stop(loop_, &ocsp_.rev);
+ ev_child_stop(loop_, &ocsp_.chldev);
+
+ assert(ocsp_.next < all_ssl_ctx_.size());
+#ifdef ENABLE_HTTP3
+ assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size());
+#endif // ENABLE_HTTP3
+
+ auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ auto rstatus = ocsp_.chldev.rstatus;
+ auto status = WEXITSTATUS(rstatus);
+ if (ocsp_.error || !WIFEXITED(rstatus) || status != 0) {
+ LOG(WARN) << "ocsp query command for " << tls_ctx_data->cert_file
+ << " failed: error=" << ocsp_.error << ", rstatus=" << log::hex
+ << rstatus << log::dec << ", status=" << status;
+ ++ocsp_.next;
+ proceed_next_cert_ocsp();
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ocsp update for " << tls_ctx_data->cert_file
+ << " finished successfully";
+ }
+
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ if (tlsconf.ocsp.no_verify ||
+ tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(),
+ ocsp_.resp.size()) == 0) {
+#ifdef ENABLE_HTTP3
+ // We have list of SSL_CTX with the same certificate in
+ // quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in
+ // that case we get nullptr.
+ auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next];
+ if (quic_ssl_ctx) {
+# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ auto quic_tls_ctx_data = static_cast<tls::TLSContextData *>(
+ SSL_CTX_get_app_data(quic_ssl_ctx));
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ std::atomic_store_explicit(
+ &quic_tls_ctx_data->ocsp_data,
+ std::make_shared<std::vector<uint8_t>>(ocsp_.resp),
+ std::memory_order_release);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(quic_tls_ctx_data->mu);
+ quic_tls_ctx_data->ocsp_data =
+ std::make_shared<std::vector<uint8_t>>(ocsp_.resp);
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+# else // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_ocsp_response(quic_ssl_ctx, ocsp_.resp.data(),
+ ocsp_.resp.size());
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+#endif // ENABLE_HTTP3
+
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ std::atomic_store_explicit(
+ &tls_ctx_data->ocsp_data,
+ std::make_shared<std::vector<uint8_t>>(std::move(ocsp_.resp)),
+ std::memory_order_release);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(tls_ctx_data->mu);
+ tls_ctx_data->ocsp_data =
+ std::make_shared<std::vector<uint8_t>>(std::move(ocsp_.resp));
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+#else // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size());
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+
+ ++ocsp_.next;
+ proceed_next_cert_ocsp();
+}
+
+void ConnectionHandler::reset_ocsp() {
+ if (ocsp_.proc.rfd != -1) {
+ close(ocsp_.proc.rfd);
+ }
+
+ ocsp_.proc.rfd = -1;
+ ocsp_.proc.pid = 0;
+ ocsp_.error = 0;
+ ocsp_.resp = std::vector<uint8_t>();
+}
+
+void ConnectionHandler::proceed_next_cert_ocsp() {
+ for (;;) {
+ reset_ocsp();
+ if (ocsp_.next == all_ssl_ctx_.size()) {
+ ocsp_.next = 0;
+ // We have updated all ocsp response, and schedule next update.
+ ev_timer_set(&ocsp_timer_, get_config()->tls.ocsp.update_interval, 0.);
+ ev_timer_start(loop_, &ocsp_timer_);
+
+ if (enable_acceptor_on_ocsp_completion_) {
+ enable_acceptor_on_ocsp_completion_ = false;
+ enable_acceptor();
+ }
+
+ return;
+ }
+
+ auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
+ auto tls_ctx_data =
+ static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ // client SSL_CTX is also included in all_ssl_ctx_, but has no
+ // tls_ctx_data.
+ if (!tls_ctx_data) {
+ ++ocsp_.next;
+ continue;
+ }
+
+ auto cert_file = tls_ctx_data->cert_file;
+
+ if (start_ocsp_update(cert_file) != 0) {
+ ++ocsp_.next;
+ continue;
+ }
+
+ break;
+ }
+}
+
+void ConnectionHandler::set_tls_ticket_key_memcached_dispatcher(
+ std::unique_ptr<MemcachedDispatcher> dispatcher) {
+ tls_ticket_key_memcached_dispatcher_ = std::move(dispatcher);
+}
+
+MemcachedDispatcher *
+ConnectionHandler::get_tls_ticket_key_memcached_dispatcher() const {
+ return tls_ticket_key_memcached_dispatcher_.get();
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 3.2;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
+ if (++tls_ticket_key_memcached_get_retry_count_ >=
+ get_config()->tls.ticket.memcached.max_retry) {
+ LOG(WARN) << "Memcached: tls ticket get retry all failed "
+ << tls_ticket_key_memcached_get_retry_count_ << " times.";
+
+ on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ auto base_backoff = util::int_pow(
+ MULTIPLIER,
+ std::min(MAX_BACKOFF_EXP, tls_ticket_key_memcached_get_retry_count_));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto backoff = base_backoff + dist(gen_);
+
+ LOG(WARN)
+ << "Memcached: tls ticket get failed due to network error, retrying in "
+ << backoff << " seconds";
+
+ ev_timer_set(w, backoff, 0.);
+ ev_timer_start(loop_, w);
+}
+
+void ConnectionHandler::on_tls_ticket_key_not_found(ev_timer *w) {
+ tls_ticket_key_memcached_get_retry_count_ = 0;
+
+ if (++tls_ticket_key_memcached_fail_count_ >=
+ get_config()->tls.ticket.memcached.max_fail) {
+ LOG(WARN) << "Memcached: could not get tls ticket; disable tls ticket";
+
+ tls_ticket_key_memcached_fail_count_ = 0;
+
+ set_ticket_keys(nullptr);
+ set_ticket_keys_to_worker(nullptr);
+ }
+
+ LOG(WARN) << "Memcached: tls ticket get failed, schedule next";
+ schedule_next_tls_ticket_key_memcached_get(w);
+}
+
+void ConnectionHandler::on_tls_ticket_key_get_success(
+ const std::shared_ptr<TicketKeys> &ticket_keys, ev_timer *w) {
+ LOG(NOTICE) << "Memcached: tls ticket get success";
+
+ tls_ticket_key_memcached_get_retry_count_ = 0;
+ tls_ticket_key_memcached_fail_count_ = 0;
+
+ schedule_next_tls_ticket_key_memcached_get(w);
+
+ if (!ticket_keys || ticket_keys->keys.empty()) {
+ LOG(WARN) << "Memcached: tls ticket keys are empty; tls ticket disabled";
+ set_ticket_keys(nullptr);
+ set_ticket_keys_to_worker(nullptr);
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ticket keys get done";
+ LOG(INFO) << 0 << " enc+dec: "
+ << util::format_hex(ticket_keys->keys[0].data.name);
+ for (size_t i = 1; i < ticket_keys->keys.size(); ++i) {
+ auto &key = ticket_keys->keys[i];
+ LOG(INFO) << i << " dec: " << util::format_hex(key.data.name);
+ }
+ }
+
+ set_ticket_keys(ticket_keys);
+ set_ticket_keys_to_worker(ticket_keys);
+}
+
+void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(
+ ev_timer *w) {
+ ev_timer_set(w, get_config()->tls.ticket.memcached.interval, 0.);
+ ev_timer_start(loop_, w);
+}
+
+SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+ auto &memcachedconf = config->tls.ticket.memcached;
+
+ auto ssl_ctx = tls::create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb_,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file);
+
+ all_ssl_ctx_.push_back(ssl_ctx);
+#ifdef ENABLE_HTTP3
+ quic_all_ssl_ctx_.push_back(nullptr);
+#endif // ENABLE_HTTP3
+
+ return ssl_ctx;
+}
+
+#ifdef HAVE_NEVERBLEED
+void ConnectionHandler::set_neverbleed(neverbleed_t *nb) { nb_ = nb; }
+#endif // HAVE_NEVERBLEED
+
+void ConnectionHandler::handle_serial_event() {
+ std::vector<SerialEvent> q;
+ {
+ std::lock_guard<std::mutex> g(serial_event_mu_);
+ q.swap(serial_events_);
+ }
+
+ for (auto &sev : q) {
+ switch (sev.type) {
+ case SerialEventType::REPLACE_DOWNSTREAM:
+ // Mmake sure that none of worker uses
+ // get_config()->conn.downstream
+ mod_config()->conn.downstream = sev.downstreamconf;
+
+ if (single_worker_) {
+ single_worker_->replace_downstream_config(sev.downstreamconf);
+
+ break;
+ }
+
+ worker_replace_downstream(sev.downstreamconf);
+
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void ConnectionHandler::send_replace_downstream(
+ const std::shared_ptr<DownstreamConfig> &downstreamconf) {
+ send_serial_event(
+ SerialEvent(SerialEventType::REPLACE_DOWNSTREAM, downstreamconf));
+}
+
+void ConnectionHandler::send_serial_event(SerialEvent ev) {
+ {
+ std::lock_guard<std::mutex> g(serial_event_mu_);
+
+ serial_events_.push_back(std::move(ev));
+ }
+
+ ev_async_send(loop_, &serial_event_asyncev_);
+}
+
+SSL_CTX *ConnectionHandler::get_ssl_ctx(size_t idx) const {
+ return all_ssl_ctx_[idx];
+}
+
+const std::vector<SSL_CTX *> &
+ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const {
+ return indexed_ssl_ctx_[idx];
+}
+
+#ifdef ENABLE_HTTP3
+const std::vector<SSL_CTX *> &
+ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const {
+ return quic_indexed_ssl_ctx_[idx];
+}
+#endif // ENABLE_HTTP3
+
+void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) {
+ enable_acceptor_on_ocsp_completion_ = f;
+}
+
+#ifdef ENABLE_HTTP3
+int ConnectionHandler::forward_quic_packet(
+ const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *cid_prefix, const uint8_t *data, size_t datalen) {
+ assert(!get_config()->single_thread);
+
+ for (auto &worker : workers_) {
+ if (!std::equal(cid_prefix, cid_prefix + SHRPX_QUIC_CID_PREFIXLEN,
+ worker->get_cid_prefix())) {
+ continue;
+ }
+
+ WorkerEvent wev{};
+ wev.type = WorkerEventType::QUIC_PKT_FORWARD;
+ wev.quic_pkt = std::make_unique<QUICPacket>(faddr->index, remote_addr,
+ local_addr, pi, data, datalen);
+
+ worker->send(std::move(wev));
+
+ return 0;
+ }
+
+ return -1;
+}
+
+void ConnectionHandler::set_quic_keying_materials(
+ std::shared_ptr<QUICKeyingMaterials> qkms) {
+ quic_keying_materials_ = std::move(qkms);
+}
+
+const std::shared_ptr<QUICKeyingMaterials> &
+ConnectionHandler::get_quic_keying_materials() const {
+ return quic_keying_materials_;
+}
+
+void ConnectionHandler::set_cid_prefixes(
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes) {
+ cid_prefixes_ = cid_prefixes;
+}
+
+QUICLingeringWorkerProcess *
+ConnectionHandler::match_quic_lingering_worker_process_cid_prefix(
+ const uint8_t *dcid, size_t dcidlen) {
+ assert(dcidlen >= SHRPX_QUIC_CID_PREFIXLEN);
+
+ for (auto &lwps : quic_lingering_worker_processes_) {
+ for (auto &cid_prefix : lwps.cid_prefixes) {
+ if (std::equal(std::begin(cid_prefix), std::end(cid_prefix), dcid)) {
+ return &lwps;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+# ifdef HAVE_LIBBPF
+std::vector<BPFRef> &ConnectionHandler::get_quic_bpf_refs() {
+ return quic_bpf_refs_;
+}
+
+void ConnectionHandler::unload_bpf_objects() {
+ LOG(NOTICE) << "Unloading BPF objects";
+
+ for (auto &ref : quic_bpf_refs_) {
+ if (ref.obj == nullptr) {
+ continue;
+ }
+
+ bpf_object__close(ref.obj);
+
+ ref.obj = nullptr;
+ }
+}
+# endif // HAVE_LIBBPF
+
+void ConnectionHandler::set_quic_ipc_fd(int fd) { quic_ipc_fd_ = fd; }
+
+void ConnectionHandler::set_quic_lingering_worker_processes(
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps) {
+ quic_lingering_worker_processes_ = quic_lwps;
+}
+
+int ConnectionHandler::forward_quic_packet_to_lingering_worker_process(
+ QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ std::array<uint8_t, 512> header;
+
+ assert(header.size() >= 1 + 1 + 1 + 1 + sizeof(sockaddr_storage) * 2);
+ assert(remote_addr.len > 0);
+ assert(local_addr.len > 0);
+
+ auto p = header.data();
+
+ *p++ = static_cast<uint8_t>(QUICIPCType::DGRAM_FORWARD);
+ *p++ = static_cast<uint8_t>(remote_addr.len - 1);
+ p = std::copy_n(reinterpret_cast<const uint8_t *>(&remote_addr.su),
+ remote_addr.len, p);
+ *p++ = static_cast<uint8_t>(local_addr.len - 1);
+ p = std::copy_n(reinterpret_cast<const uint8_t *>(&local_addr.su),
+ local_addr.len, p);
+ *p++ = pi.ecn;
+
+ iovec msg_iov[] = {
+ {
+ .iov_base = header.data(),
+ .iov_len = static_cast<size_t>(p - header.data()),
+ },
+ {
+ .iov_base = const_cast<uint8_t *>(data),
+ .iov_len = datalen,
+ },
+ };
+
+ msghdr msg{};
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = array_size(msg_iov);
+
+ ssize_t nwrite;
+
+ while ((nwrite = sendmsg(quic_lwp->quic_ipc_fd, &msg, 0)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nwrite == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto error = errno;
+ LOG(ERROR) << "Failed to send QUIC IPC message: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ return 0;
+}
+
+int ConnectionHandler::quic_ipc_read() {
+ std::array<uint8_t, 65536> buf;
+
+ ssize_t nread;
+
+ while ((nread = recv(quic_ipc_fd_, buf.data(), buf.size(), 0)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nread == -1) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+
+ auto error = errno;
+ LOG(ERROR) << "Failed to read data from QUIC IPC channel: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ size_t len = 1 + 1 + 1 + 1;
+
+ // Wire format:
+ // TYPE(1) REMOTE_ADDRLEN(1) REMOTE_ADDR(N) LOCAL_ADDRLEN(1) LOCAL_ADDR(N)
+ // ECN(1) DGRAM_PAYLOAD(N)
+ //
+ // When encoding, REMOTE_ADDRLEN and LOCAL_ADDRLEN are decremented
+ // by 1.
+ if (static_cast<size_t>(nread) < len) {
+ return 0;
+ }
+
+ auto p = buf.data();
+ if (*p != static_cast<uint8_t>(QUICIPCType::DGRAM_FORWARD)) {
+ LOG(ERROR) << "Unknown QUICIPCType: " << static_cast<uint32_t>(*p);
+
+ return -1;
+ }
+
+ ++p;
+
+ auto pkt = std::make_unique<QUICPacket>();
+
+ auto remote_addrlen = static_cast<size_t>(*p++) + 1;
+ if (remote_addrlen > sizeof(sockaddr_storage)) {
+ LOG(ERROR) << "The length of remote address is too large: "
+ << remote_addrlen;
+
+ return -1;
+ }
+
+ len += remote_addrlen;
+
+ if (static_cast<size_t>(nread) < len) {
+ LOG(ERROR) << "Insufficient QUIC IPC message length";
+
+ return -1;
+ }
+
+ pkt->remote_addr.len = remote_addrlen;
+ memcpy(&pkt->remote_addr.su, p, remote_addrlen);
+
+ p += remote_addrlen;
+
+ auto local_addrlen = static_cast<size_t>(*p++) + 1;
+ if (local_addrlen > sizeof(sockaddr_storage)) {
+ LOG(ERROR) << "The length of local address is too large: " << local_addrlen;
+
+ return -1;
+ }
+
+ len += local_addrlen;
+
+ if (static_cast<size_t>(nread) < len) {
+ LOG(ERROR) << "Insufficient QUIC IPC message length";
+
+ return -1;
+ }
+
+ pkt->local_addr.len = local_addrlen;
+ memcpy(&pkt->local_addr.su, p, local_addrlen);
+
+ p += local_addrlen;
+
+ pkt->pi.ecn = *p++;
+
+ auto datalen = nread - (p - buf.data());
+
+ pkt->data.assign(p, p + datalen);
+
+ // At the moment, UpstreamAddr index is unknown.
+ pkt->upstream_addr_index = static_cast<size_t>(-1);
+
+ ngtcp2_version_cid vc;
+
+ auto rv = ngtcp2_pkt_decode_version_cid(&vc, p, datalen, SHRPX_QUIC_SCIDLEN);
+ if (rv < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_decode_version_cid: " << ngtcp2_strerror(rv);
+
+ return -1;
+ }
+
+ if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) {
+ LOG(ERROR) << "DCID length is invalid";
+ return -1;
+ }
+
+ if (single_worker_) {
+ auto faddr = single_worker_->find_quic_upstream_addr(pkt->local_addr);
+ if (faddr == nullptr) {
+ LOG(ERROR) << "No suitable upstream address found";
+
+ return 0;
+ }
+
+ auto quic_conn_handler = single_worker_->get_quic_connection_handler();
+
+ // Ignore return value
+ quic_conn_handler->handle_packet(faddr, pkt->remote_addr, pkt->local_addr,
+ pkt->pi, pkt->data.data(),
+ pkt->data.size());
+
+ return 0;
+ }
+
+ auto &qkm = quic_keying_materials_->keying_materials.front();
+
+ std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ for (auto &worker : workers_) {
+ if (!std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker->get_cid_prefix())) {
+ continue;
+ }
+
+ WorkerEvent wev{
+ .type = WorkerEventType::QUIC_PKT_FORWARD,
+ .quic_pkt = std::move(pkt),
+ };
+ worker->send(std::move(wev));
+
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No worker to match CID prefix";
+ }
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h
new file mode 100644
index 0000000..f3748ab
--- /dev/null
+++ b/src/shrpx_connection_handler.h
@@ -0,0 +1,322 @@
+/*
+ * 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>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+
+#include <mutex>
+#include <memory>
+#include <vector>
+#include <random>
+#ifndef NOTHREADS
+# include <future>
+#endif // NOTHREADS
+
+#ifdef HAVE_LIBBPF
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#ifdef HAVE_NEVERBLEED
+# include <neverbleed.h>
+#endif // HAVE_NEVERBLEED
+
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_config.h"
+#include "shrpx_exec.h"
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+class AcceptHandler;
+class Worker;
+struct WorkerStat;
+struct TicketKeys;
+class MemcachedDispatcher;
+struct UpstreamAddr;
+
+namespace tls {
+
+class CertLookupTree;
+
+} // namespace tls
+
+struct OCSPUpdateContext {
+ // ocsp response buffer
+ std::vector<uint8_t> resp;
+ // Process running fetch-ocsp-response script
+ Process proc;
+ // index to ConnectionHandler::all_ssl_ctx_, which points to next
+ // SSL_CTX to update ocsp response cache.
+ size_t next;
+ ev_child chldev;
+ ev_io rev;
+ // errno encountered while processing response
+ int error;
+};
+
+// SerialEvent is an event sent from Worker thread.
+enum class SerialEventType {
+ NONE,
+ REPLACE_DOWNSTREAM,
+};
+
+struct SerialEvent {
+ // ctor for event uses DownstreamConfig
+ SerialEvent(SerialEventType type,
+ const std::shared_ptr<DownstreamConfig> &downstreamconf)
+ : type(type), downstreamconf(downstreamconf) {}
+
+ SerialEventType type;
+ std::shared_ptr<DownstreamConfig> downstreamconf;
+};
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBBPF
+struct BPFRef {
+ bpf_object *obj;
+ bpf_map *reuseport_array;
+ bpf_map *cid_prefix_map;
+};
+# endif // HAVE_LIBBPF
+
+// QUIC IPC message type.
+enum class QUICIPCType {
+ NONE,
+ // Send forwarded QUIC UDP datagram and its metadata.
+ DGRAM_FORWARD,
+};
+
+// WorkerProcesses which are in graceful shutdown period.
+struct QUICLingeringWorkerProcess {
+ QUICLingeringWorkerProcess(
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes,
+ int quic_ipc_fd)
+ : cid_prefixes{std::move(cid_prefixes)}, quic_ipc_fd{quic_ipc_fd} {}
+
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+ // Socket to send QUIC IPC message to this worker process.
+ int quic_ipc_fd;
+};
+#endif // ENABLE_HTTP3
+
+class ConnectionHandler {
+public:
+ ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen);
+ ~ConnectionHandler();
+ int handle_connection(int fd, sockaddr *addr, int addrlen,
+ const UpstreamAddr *faddr);
+ // Creates Worker object for single threaded configuration.
+ int create_single_worker();
+ // Creates |num| Worker objects for multi threaded configuration.
+ // The |num| must be strictly more than 1.
+ int create_worker_thread(size_t num);
+ void
+ set_ticket_keys_to_worker(const std::shared_ptr<TicketKeys> &ticket_keys);
+ void worker_reopen_log_files();
+ void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
+ const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
+ struct ev_loop *get_loop() const;
+ Worker *get_single_worker() const;
+ void add_acceptor(std::unique_ptr<AcceptHandler> h);
+ void delete_acceptor();
+ void enable_acceptor();
+ void disable_acceptor();
+ void sleep_acceptor(ev_tstamp t);
+ void accept_pending_connection();
+ void graceful_shutdown_worker();
+ void set_graceful_shutdown(bool f);
+ bool get_graceful_shutdown() const;
+ void join_worker();
+
+ // Cancels ocsp update process
+ void cancel_ocsp_update();
+ // Starts ocsp update for certificate |cert_file|.
+ int start_ocsp_update(const char *cert_file);
+ // Reads incoming data from ocsp update process
+ void read_ocsp_chunk();
+ // Handles the completion of one ocsp update
+ void handle_ocsp_complete();
+ // Resets ocsp_;
+ void reset_ocsp();
+ // Proceeds to the next certificate's ocsp update. If all
+ // certificates' ocsp update has been done, schedule next ocsp
+ // update.
+ void proceed_next_cert_ocsp();
+
+ void set_tls_ticket_key_memcached_dispatcher(
+ std::unique_ptr<MemcachedDispatcher> dispatcher);
+
+ MemcachedDispatcher *get_tls_ticket_key_memcached_dispatcher() const;
+ void on_tls_ticket_key_network_error(ev_timer *w);
+ void on_tls_ticket_key_not_found(ev_timer *w);
+ void
+ on_tls_ticket_key_get_success(const std::shared_ptr<TicketKeys> &ticket_keys,
+ ev_timer *w);
+ void schedule_next_tls_ticket_key_memcached_get(ev_timer *w);
+ SSL_CTX *create_tls_ticket_key_memcached_ssl_ctx();
+ // Returns the SSL_CTX at all_ssl_ctx_[idx]. This does not perform
+ // array bound checking.
+ SSL_CTX *get_ssl_ctx(size_t idx) const;
+
+ const std::vector<SSL_CTX *> &get_indexed_ssl_ctx(size_t idx) const;
+#ifdef ENABLE_HTTP3
+ const std::vector<SSL_CTX *> &get_quic_indexed_ssl_ctx(size_t idx) const;
+
+ int forward_quic_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *cid_prefix, const uint8_t *data,
+ size_t datalen);
+
+ void set_quic_keying_materials(std::shared_ptr<QUICKeyingMaterials> qkms);
+ const std::shared_ptr<QUICKeyingMaterials> &get_quic_keying_materials() const;
+
+ void set_cid_prefixes(
+ const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ &cid_prefixes);
+
+ void set_quic_lingering_worker_processes(
+ const std::vector<QUICLingeringWorkerProcess> &quic_lwps);
+
+ // Return matching QUICLingeringWorkerProcess which has a CID prefix
+ // such that |dcid| starts with it. If no such
+ // QUICLingeringWorkerProcess, it returns nullptr.
+ QUICLingeringWorkerProcess *
+ match_quic_lingering_worker_process_cid_prefix(const uint8_t *dcid,
+ size_t dcidlen);
+
+ int forward_quic_packet_to_lingering_worker_process(
+ QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen);
+
+ void set_quic_ipc_fd(int fd);
+
+ int quic_ipc_read();
+
+# ifdef HAVE_LIBBPF
+ std::vector<BPFRef> &get_quic_bpf_refs();
+ void unload_bpf_objects();
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+
+#ifdef HAVE_NEVERBLEED
+ void set_neverbleed(neverbleed_t *nb);
+#endif // HAVE_NEVERBLEED
+
+ // Send SerialEvent SerialEventType::REPLACE_DOWNSTREAM to this
+ // object.
+ void send_replace_downstream(
+ const std::shared_ptr<DownstreamConfig> &downstreamconf);
+ // Internal function to send |ev| to this object.
+ void send_serial_event(SerialEvent ev);
+ // Handles SerialEvents received.
+ void handle_serial_event();
+ // Sends WorkerEvent to make them replace downstream.
+ void
+ worker_replace_downstream(std::shared_ptr<DownstreamConfig> downstreamconf);
+
+ void set_enable_acceptor_on_ocsp_completion(bool f);
+
+private:
+ // Stores all SSL_CTX objects.
+ std::vector<SSL_CTX *> all_ssl_ctx_;
+ // Stores all SSL_CTX objects in a way that its index is stored in
+ // cert_tree. The SSL_CTXs stored in the same index share the same
+ // hostname, but could have different signature algorithm. The
+ // selection among them are performed by hostname presented by SNI,
+ // and signature algorithm presented by client.
+ std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx_;
+#ifdef ENABLE_HTTP3
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes_;
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
+ lingering_cid_prefixes_;
+ int quic_ipc_fd_;
+ std::vector<QUICLingeringWorkerProcess> quic_lingering_worker_processes_;
+# ifdef HAVE_LIBBPF
+ std::vector<BPFRef> quic_bpf_refs_;
+# endif // HAVE_LIBBPF
+ std::shared_ptr<QUICKeyingMaterials> quic_keying_materials_;
+ std::vector<SSL_CTX *> quic_all_ssl_ctx_;
+ std::vector<std::vector<SSL_CTX *>> quic_indexed_ssl_ctx_;
+#endif // ENABLE_HTTP3
+ OCSPUpdateContext ocsp_;
+ std::mt19937 &gen_;
+ // ev_loop for each worker
+ std::vector<struct ev_loop *> worker_loops_;
+ // Worker instances when multi threaded mode (-nN, N >= 2) is used.
+ // If at least one frontend enables API request, we allocate 1
+ // additional worker dedicated to API request .
+ std::vector<std::unique_ptr<Worker>> workers_;
+ // mutex for serial event resive buffer handling
+ std::mutex serial_event_mu_;
+ // SerialEvent receive buffer
+ std::vector<SerialEvent> serial_events_;
+ // Worker instance used when single threaded mode (-n1) is used.
+ // Otherwise, nullptr and workers_ has instances of Worker instead.
+ std::unique_ptr<Worker> single_worker_;
+ std::unique_ptr<tls::CertLookupTree> cert_tree_;
+#ifdef ENABLE_HTTP3
+ std::unique_ptr<tls::CertLookupTree> quic_cert_tree_;
+#endif // ENABLE_HTTP3
+ std::unique_ptr<MemcachedDispatcher> tls_ticket_key_memcached_dispatcher_;
+ // Current TLS session ticket keys. Note that TLS connection does
+ // not refer to this field directly. They use TicketKeys object in
+ // Worker object.
+ std::shared_ptr<TicketKeys> ticket_keys_;
+ struct ev_loop *loop_;
+ std::vector<std::unique_ptr<AcceptHandler>> acceptors_;
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb_;
+#endif // HAVE_NEVERBLEED
+ ev_timer disable_acceptor_timer_;
+ ev_timer ocsp_timer_;
+ ev_async thread_join_asyncev_;
+ ev_async serial_event_asyncev_;
+#ifndef NOTHREADS
+ std::future<void> thread_join_fut_;
+#endif // NOTHREADS
+ size_t tls_ticket_key_memcached_get_retry_count_;
+ size_t tls_ticket_key_memcached_fail_count_;
+ unsigned int worker_round_robin_cnt_;
+ bool graceful_shutdown_;
+ // true if acceptors should be enabled after the initial ocsp update
+ // has finished.
+ bool enable_acceptor_on_ocsp_completion_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_HANDLER_H
diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc
new file mode 100644
index 0000000..f83ecb7
--- /dev/null
+++ b/src/shrpx_dns_resolver.cc
@@ -0,0 +1,353 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_dns_resolver.h"
+
+#include <cstring>
+#include <sys/time.h>
+
+#include "shrpx_log.h"
+#include "shrpx_connection.h"
+#include "shrpx_config.h"
+
+namespace shrpx {
+
+namespace {
+void sock_state_cb(void *data, int s, int read, int write) {
+ auto resolv = static_cast<DNSResolver *>(data);
+
+ if (resolv->get_status(nullptr) != DNSResolverStatus::RUNNING) {
+ return;
+ }
+
+ if (read) {
+ resolv->start_rev(s);
+ } else {
+ resolv->stop_rev(s);
+ }
+ if (write) {
+ resolv->start_wev(s);
+ } else {
+ resolv->stop_wev(s);
+ }
+}
+} // namespace
+
+namespace {
+void host_cb(void *arg, int status, int timeouts, hostent *hostent) {
+ auto resolv = static_cast<DNSResolver *>(arg);
+ resolv->on_result(status, hostent);
+}
+} // namespace
+
+namespace {
+void process_result(DNSResolver *resolv) {
+ auto cb = resolv->get_complete_cb();
+ if (!cb) {
+ return;
+ }
+ Address result;
+ auto status = resolv->get_status(&result);
+ switch (status) {
+ case DNSResolverStatus::OK:
+ case DNSResolverStatus::ERROR:
+ cb(status, &result);
+ break;
+ default:
+ break;
+ }
+ // resolv may be deleted here.
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_read(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_write(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_timeout();
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void stop_ev(struct ev_loop *loop,
+ const std::vector<std::unique_ptr<ev_io>> &evs) {
+ for (auto &w : evs) {
+ ev_io_stop(loop, w.get());
+ }
+}
+} // namespace
+
+DNSResolver::DNSResolver(struct ev_loop *loop)
+ : result_{},
+ loop_(loop),
+ channel_(nullptr),
+ family_(AF_UNSPEC),
+ status_(DNSResolverStatus::IDLE) {
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+DNSResolver::~DNSResolver() {
+ if (channel_) {
+ ares_destroy(channel_);
+ }
+
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+
+ ev_timer_stop(loop_, &timer_);
+}
+
+int DNSResolver::resolve(const StringRef &name, int family) {
+ if (status_ != DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start resolving host " << name << " in IPv"
+ << (family == AF_INET ? "4" : "6");
+ }
+
+ name_ = name;
+ family_ = family;
+
+ int rv;
+
+ auto &dnsconf = get_config()->dns;
+
+ ares_options opts{};
+ opts.sock_state_cb = sock_state_cb;
+ opts.sock_state_cb_data = this;
+ opts.timeout = static_cast<int>(dnsconf.timeout.lookup * 1000);
+ opts.tries = dnsconf.max_try;
+
+ auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES;
+
+ ares_channel chan;
+ rv = ares_init_options(&chan, &opts, optmask);
+ if (rv != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ares_init_options failed: " << ares_strerror(rv);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return -1;
+ }
+
+ channel_ = chan;
+ status_ = DNSResolverStatus::RUNNING;
+
+ ares_gethostbyname(channel_, name_.c_str(), family_, host_cb, this);
+ reset_timeout();
+
+ return 0;
+}
+
+int DNSResolver::on_read(int fd) { return handle_event(fd, ARES_SOCKET_BAD); }
+
+int DNSResolver::on_write(int fd) { return handle_event(ARES_SOCKET_BAD, fd); }
+
+int DNSResolver::on_timeout() {
+ return handle_event(ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+}
+
+int DNSResolver::handle_event(int rfd, int wfd) {
+ if (status_ == DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ ares_process_fd(channel_, rfd, wfd);
+
+ switch (status_) {
+ case DNSResolverStatus::RUNNING:
+ reset_timeout();
+ return 0;
+ case DNSResolverStatus::OK:
+ return 0;
+ case DNSResolverStatus::ERROR:
+ return -1;
+ default:
+ // Unreachable
+ assert(0);
+ abort();
+ }
+}
+
+void DNSResolver::reset_timeout() {
+ if (status_ != DNSResolverStatus::RUNNING) {
+ return;
+ }
+ timeval tvout;
+ auto tv = ares_timeout(channel_, nullptr, &tvout);
+ if (tv == nullptr) {
+ return;
+ }
+ // To avoid that timer_.repeat becomes 0, which makes ev_timer_again
+ // useless, add tiny fraction of time.
+ timer_.repeat = tv->tv_sec + tv->tv_usec / 1000000. + 1e-9;
+ ev_timer_again(loop_, &timer_);
+}
+
+DNSResolverStatus DNSResolver::get_status(Address *result) const {
+ if (status_ != DNSResolverStatus::OK) {
+ return status_;
+ }
+
+ if (result) {
+ memcpy(result, &result_, sizeof(result_));
+ }
+
+ return status_;
+}
+
+namespace {
+void start_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event, IOCb cb, void *data) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ return;
+ }
+ }
+ for (auto &w : evs) {
+ if (w->fd == -1) {
+ ev_io_set(w.get(), fd, event);
+ ev_io_start(loop, w.get());
+ return;
+ }
+ }
+
+ auto w = std::make_unique<ev_io>();
+ ev_io_init(w.get(), cb, fd, event);
+ w->data = data;
+ ev_io_start(loop, w.get());
+ evs.emplace_back(std::move(w));
+}
+} // namespace
+
+namespace {
+void stop_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ ev_io_stop(loop, w.get());
+ ev_io_set(w.get(), -1, event);
+ return;
+ }
+ }
+}
+} // namespace
+
+void DNSResolver::start_rev(int fd) {
+ start_ev(revs_, loop_, fd, EV_READ, readcb, this);
+}
+
+void DNSResolver::stop_rev(int fd) { stop_ev(revs_, loop_, fd, EV_READ); }
+
+void DNSResolver::start_wev(int fd) {
+ start_ev(wevs_, loop_, fd, EV_WRITE, writecb, this);
+}
+
+void DNSResolver::stop_wev(int fd) { stop_ev(wevs_, loop_, fd, EV_WRITE); }
+
+void DNSResolver::on_result(int status, hostent *hostent) {
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+ ev_timer_stop(loop_, &timer_);
+
+ if (status != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_
+ << " failed: " << ares_strerror(status);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ auto ap = *hostent->h_addr_list;
+ if (!ap) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_ << "failed: no address returned";
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ switch (hostent->h_addrtype) {
+ case AF_INET:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in);
+ result_.su.in = {};
+ result_.su.in.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_IN_SIN_LEN
+ result_.su.in.sin_len = sizeof(result_.su.in);
+#endif // HAVE_SOCKADDR_IN_SIN_LEN
+ memcpy(&result_.su.in.sin_addr, ap, sizeof(result_.su.in.sin_addr));
+ break;
+ case AF_INET6:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in6);
+ result_.su.in6 = {};
+ result_.su.in6.sin6_family = AF_INET6;
+#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN
+ result_.su.in6.sin6_len = sizeof(result_.su.in6);
+#endif // HAVE_SOCKADDR_IN6_SIN6_LEN
+ memcpy(&result_.su.in6.sin6_addr, ap, sizeof(result_.su.in6.sin6_addr));
+ break;
+ default:
+ assert(0);
+ }
+
+ if (status_ == DNSResolverStatus::OK) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << name_ << " -> "
+ << util::numeric_name(&result_.su.sa, result_.len);
+ }
+ return;
+ }
+
+ status_ = DNSResolverStatus::ERROR;
+}
+
+void DNSResolver::set_complete_cb(CompleteCb cb) {
+ completeCb_ = std::move(cb);
+}
+
+CompleteCb DNSResolver::get_complete_cb() const { return completeCb_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_dns_resolver.h b/src/shrpx_dns_resolver.h
new file mode 100644
index 0000000..e622f99
--- /dev/null
+++ b/src/shrpx_dns_resolver.h
@@ -0,0 +1,118 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_DNS_RESOLVER_H
+#define SHRPX_DNS_RESOLVER_H
+
+#include "shrpx.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <vector>
+
+#include <ev.h>
+#include <ares.h>
+
+#include "template.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+enum class DNSResolverStatus {
+ // Resolver is in initial status
+ IDLE,
+ // Resolver is currently resolving host name
+ RUNNING,
+ // Resolver successfully resolved host name
+ OK,
+ // Resolver failed to resolve host name
+ ERROR,
+};
+
+// Callback function called when host name lookup is finished.
+// |status| is either DNSResolverStatus::OK, or
+// DNSResolverStatus::ERROR. If |status| is DNSResolverStatus::OK,
+// |result| points to the resolved address. Note that port portion of
+// |result| is undefined, and must be initialized by application.
+// This callback function is not called if name lookup finishes in
+// DNSResolver::resolve() completely. In this case, application
+// should call DNSResolver::get_status() to get current status and
+// result. In other words, callback is called if get_status() returns
+// DNSResolverStatus::RUNNING.
+using CompleteCb =
+ std::function<void(DNSResolverStatus status, const Address *result)>;
+
+// DNSResolver is asynchronous name resolver, backed by c-ares
+// library.
+class DNSResolver {
+public:
+ DNSResolver(struct ev_loop *loop);
+ ~DNSResolver();
+
+ // Starts resolving hostname |name|.
+ int resolve(const StringRef &name, int family);
+ // Returns status. If status_ is DNSResolverStatus::SUCCESS &&
+ // |result| is not nullptr, |*result| is filled.
+ DNSResolverStatus get_status(Address *result) const;
+ // Sets callback function when name lookup finishes. The callback
+ // function is called in a way that it can destroy this DNSResolver.
+ void set_complete_cb(CompleteCb cb);
+ CompleteCb get_complete_cb() const;
+
+ // Calls these functions when read/write event occurred respectively.
+ int on_read(int fd);
+ int on_write(int fd);
+ int on_timeout();
+ // Calls this function when DNS query finished.
+ void on_result(int status, hostent *hostent);
+ void reset_timeout();
+
+ void start_rev(int fd);
+ void stop_rev(int fd);
+ void start_wev(int fd);
+ void stop_wev(int fd);
+
+private:
+ int handle_event(int rfd, int wfd);
+
+ std::vector<std::unique_ptr<ev_io>> revs_, wevs_;
+ Address result_;
+ CompleteCb completeCb_;
+ ev_timer timer_;
+ StringRef name_;
+ struct ev_loop *loop_;
+ // ares_channel is pointer type
+ ares_channel channel_;
+ // AF_INET or AF_INET6. AF_INET for A record lookup, and AF_INET6
+ // for AAAA record lookup.
+ int family_;
+ DNSResolverStatus status_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DNS_RESOLVER_H
diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc
new file mode 100644
index 0000000..57387ce
--- /dev/null
+++ b/src/shrpx_dns_tracker.cc
@@ -0,0 +1,328 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_dns_tracker.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "util.h"
+
+namespace shrpx {
+
+namespace {
+void gccb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto dns_tracker = static_cast<DNSTracker *>(w->data);
+ dns_tracker->gc();
+}
+} // namespace
+
+DNSTracker::DNSTracker(struct ev_loop *loop, int family)
+ : loop_(loop), family_(family) {
+ ev_timer_init(&gc_timer_, gccb, 0., 12_h);
+ gc_timer_.data = this;
+}
+
+DNSTracker::~DNSTracker() {
+ ev_timer_stop(loop_, &gc_timer_);
+
+ for (auto &p : ents_) {
+ auto &qlist = p.second.qlist;
+ while (!qlist.empty()) {
+ auto head = qlist.head;
+ qlist.remove(head);
+ head->status = DNSResolverStatus::ERROR;
+ head->in_qlist = false;
+ // TODO Not sure we should call callback here, or it is even be
+ // safe to do that.
+ }
+ }
+}
+
+ResolverEntry DNSTracker::make_entry(std::unique_ptr<DualDNSResolver> resolv,
+ ImmutableString host,
+ DNSResolverStatus status,
+ const Address *result) {
+ auto &dnsconf = get_config()->dns;
+
+ auto ent = ResolverEntry{};
+ ent.resolv = std::move(resolv);
+ ent.host = std::move(host);
+ ent.status = status;
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ break;
+ default:
+ break;
+ }
+ if (result) {
+ ent.result = *result;
+ }
+ return ent;
+}
+
+void DNSTracker::update_entry(ResolverEntry &ent,
+ std::unique_ptr<DualDNSResolver> resolv,
+ DNSResolverStatus status, const Address *result) {
+ auto &dnsconf = get_config()->dns;
+
+ ent.resolv = std::move(resolv);
+ ent.status = status;
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ break;
+ default:
+ break;
+ }
+ if (result) {
+ ent.result = *result;
+ }
+}
+
+DNSResolverStatus DNSTracker::resolve(Address *result, DNSQuery *dnsq) {
+ int rv;
+
+ auto it = ents_.find(dnsq->host);
+
+ if (it == std::end(ents_)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "DNS entry not found for " << dnsq->host;
+ }
+
+ auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
+ auto host_copy =
+ ImmutableString{std::begin(dnsq->host), std::end(dnsq->host)};
+ auto host = StringRef{host_copy};
+
+ rv = resolv->resolve(host);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::ERROR, nullptr));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::ERROR;
+ }
+
+ switch (resolv->get_status(result)) {
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::ERROR, nullptr));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << host << " -> "
+ << util::numeric_name(&result->su.sa, result->len);
+ }
+
+ ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
+ DNSResolverStatus::OK, result));
+
+ start_gc_timer();
+
+ return DNSResolverStatus::OK;
+ case DNSResolverStatus::RUNNING: {
+ auto p = ents_.emplace(host,
+ make_entry(std::move(resolv), std::move(host_copy),
+ DNSResolverStatus::RUNNING, nullptr));
+
+ start_gc_timer();
+
+ auto &ent = (*p.first).second;
+
+ add_to_qlist(ent, dnsq);
+
+ return DNSResolverStatus::RUNNING;
+ }
+ default:
+ assert(0);
+ }
+ }
+
+ auto &ent = (*it).second;
+
+ if (ent.status != DNSResolverStatus::RUNNING &&
+ ent.expiry < std::chrono::steady_clock::now()) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "DNS entry found for " << dnsq->host
+ << ", but it has been expired";
+ }
+
+ auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
+ auto host = StringRef{ent.host};
+
+ rv = resolv->resolve(host);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
+
+ return DNSResolverStatus::ERROR;
+ }
+
+ switch (resolv->get_status(result)) {
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << host;
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
+
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << host << " -> "
+ << util::numeric_name(&result->su.sa, result->len);
+ }
+
+ update_entry(ent, nullptr, DNSResolverStatus::OK, result);
+
+ return DNSResolverStatus::OK;
+ case DNSResolverStatus::RUNNING:
+ update_entry(ent, std::move(resolv), DNSResolverStatus::RUNNING, nullptr);
+ add_to_qlist(ent, dnsq);
+
+ return DNSResolverStatus::RUNNING;
+ default:
+ assert(0);
+ }
+ }
+
+ switch (ent.status) {
+ case DNSResolverStatus::RUNNING:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Waiting for name lookup complete for " << dnsq->host;
+ }
+ ent.qlist.append(dnsq);
+ dnsq->in_qlist = true;
+ return DNSResolverStatus::RUNNING;
+ case DNSResolverStatus::ERROR:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup failed for " << dnsq->host << " (cached)";
+ }
+ return DNSResolverStatus::ERROR;
+ case DNSResolverStatus::OK:
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded (cached): " << dnsq->host << " -> "
+ << util::numeric_name(&ent.result.su.sa, ent.result.len);
+ }
+ if (result) {
+ memcpy(result, &ent.result, sizeof(*result));
+ }
+ return DNSResolverStatus::OK;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) {
+ ent.resolv->set_complete_cb(
+ [&ent](DNSResolverStatus status, const Address *result) {
+ auto &qlist = ent.qlist;
+ while (!qlist.empty()) {
+ auto head = qlist.head;
+ qlist.remove(head);
+ head->status = status;
+ head->in_qlist = false;
+ auto cb = head->cb;
+ cb(status, result);
+ }
+
+ auto &dnsconf = get_config()->dns;
+
+ ent.resolv.reset();
+ ent.status = status;
+ ent.expiry = std::chrono::steady_clock::now() +
+ util::duration_from(dnsconf.timeout.cache);
+ if (ent.status == DNSResolverStatus::OK) {
+ ent.result = *result;
+ }
+ });
+ ent.qlist.append(dnsq);
+ dnsq->in_qlist = true;
+}
+
+void DNSTracker::cancel(DNSQuery *dnsq) {
+ if (!dnsq->in_qlist) {
+ return;
+ }
+
+ auto it = ents_.find(dnsq->host);
+ if (it == std::end(ents_)) {
+ return;
+ }
+
+ auto &ent = (*it).second;
+ ent.qlist.remove(dnsq);
+ dnsq->in_qlist = false;
+}
+
+void DNSTracker::start_gc_timer() {
+ if (ev_is_active(&gc_timer_)) {
+ return;
+ }
+
+ ev_timer_again(loop_, &gc_timer_);
+}
+
+void DNSTracker::gc() {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Starting removing expired DNS cache entries";
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ for (auto it = std::begin(ents_); it != std::end(ents_);) {
+ auto &ent = (*it).second;
+ if (ent.expiry >= now) {
+ ++it;
+ continue;
+ }
+
+ it = ents_.erase(it);
+ }
+
+ if (ents_.empty()) {
+ ev_timer_stop(loop_, &gc_timer_);
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_dns_tracker.h b/src/shrpx_dns_tracker.h
new file mode 100644
index 0000000..c7caac0
--- /dev/null
+++ b/src/shrpx_dns_tracker.h
@@ -0,0 +1,121 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_DNS_TRACKER_H
+#define SHRPX_DNS_TRACKER_H
+
+#include "shrpx.h"
+
+#include <map>
+#include <chrono>
+
+#include "shrpx_dual_dns_resolver.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct DNSQuery {
+ DNSQuery(StringRef host, CompleteCb cb)
+ : host(std::move(host)),
+ cb(std::move(cb)),
+ dlnext(nullptr),
+ dlprev(nullptr),
+ status(DNSResolverStatus::IDLE),
+ in_qlist(false) {}
+
+ // Host name we lookup for.
+ StringRef host;
+ // Callback function called when name lookup finished. This
+ // callback is not called if name lookup finishes within
+ // DNSTracker::resolve().
+ CompleteCb cb;
+ DNSQuery *dlnext, *dlprev;
+ DNSResolverStatus status;
+ // true if this object is in linked list ResolverEntry::qlist.
+ bool in_qlist;
+};
+
+struct ResolverEntry {
+ // Host name this entry lookups for.
+ ImmutableString host;
+ // DNS resolver. Only non-nullptr if status is
+ // DNSResolverStatus::RUNNING.
+ std::unique_ptr<DualDNSResolver> resolv;
+ // DNSQuery interested in this name lookup result. The result is
+ // notified to them all.
+ DList<DNSQuery> qlist;
+ // Use the same enum with DNSResolverStatus
+ DNSResolverStatus status;
+ // result and its expiry time
+ Address result;
+ // time point when cached result expires.
+ std::chrono::steady_clock::time_point expiry;
+};
+
+class DNSTracker {
+public:
+ DNSTracker(struct ev_loop *loop, int family);
+ ~DNSTracker();
+
+ // Lookups host name described in |dnsq|. If name lookup finishes
+ // within this function (either it came from /etc/hosts, host name
+ // is numeric, lookup result is cached, etc), it returns
+ // DNSResolverStatus::OK or DNSResolverStatus::ERROR. If lookup is
+ // successful, DNSResolverStatus::OK is returned, and |result| is
+ // filled. If lookup failed, DNSResolverStatus::ERROR is returned.
+ // If name lookup is being done background, it returns
+ // DNSResolverStatus::RUNNING. Its completion is notified by
+ // calling dnsq->cb.
+ DNSResolverStatus resolve(Address *result, DNSQuery *dnsq);
+ // Cancels name lookup requested by |dnsq|.
+ void cancel(DNSQuery *dnsq);
+ // Removes expired entries from ents_.
+ void gc();
+ // Starts GC timer.
+ void start_gc_timer();
+
+private:
+ ResolverEntry make_entry(std::unique_ptr<DualDNSResolver> resolv,
+ ImmutableString host, DNSResolverStatus status,
+ const Address *result);
+
+ void update_entry(ResolverEntry &ent, std::unique_ptr<DualDNSResolver> resolv,
+ DNSResolverStatus status, const Address *result);
+
+ void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq);
+
+ std::map<StringRef, ResolverEntry> ents_;
+ // Periodically iterates ents_, and removes expired entries to avoid
+ // excessive use of memory. Since only backend API can potentially
+ // increase memory consumption, interval could be very long.
+ ev_timer gc_timer_;
+ struct ev_loop *loop_;
+ // IP version preference.
+ int family_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DNS_TRACKER_H
diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc
new file mode 100644
index 0000000..9ea52b4
--- /dev/null
+++ b/src/shrpx_downstream.cc
@@ -0,0 +1,1189 @@
+/*
+ * 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 "url-parser/url_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 "shrpx_downstream_queue.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#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, MemchunkPool *mcpool,
+ int64_t stream_id)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ response_sent_body_length(0),
+ balloc_(1024, 1024),
+ req_(balloc_),
+ resp_(balloc_),
+ request_start_time_(std::chrono::high_resolution_clock::now()),
+ blocked_request_buf_(mcpool),
+ request_buf_(mcpool),
+ response_buf_(mcpool),
+ upstream_(upstream),
+ blocked_link_(nullptr),
+ addr_(nullptr),
+ num_retry_(0),
+ stream_id_(stream_id),
+ assoc_stream_id_(-1),
+ downstream_stream_id_(-1),
+ response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
+ affinity_cookie_(0),
+ request_state_(DownstreamState::INITIAL),
+ response_state_(DownstreamState::INITIAL),
+ dispatch_state_(DispatchState::NONE),
+ upgraded_(false),
+ chunked_request_(false),
+ chunked_response_(false),
+ expect_final_response_(false),
+ request_pending_(false),
+ request_header_sent_(false),
+ accesslog_written_(false),
+ new_affinity_cookie_(false),
+ blocked_request_data_eof_(false),
+ expect_100_continue_(false),
+ stop_reading_(false) {
+
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
+ timeoutconf.stream_read);
+ ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0.,
+ timeoutconf.stream_write);
+ ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0.,
+ timeoutconf.stream_read);
+ ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
+ timeoutconf.stream_write);
+
+ upstream_rtimer_.data = this;
+ upstream_wtimer_.data = this;
+ downstream_rtimer_.data = this;
+ downstream_wtimer_.data = this;
+
+ rcbufs_.reserve(32);
+#ifdef ENABLE_HTTP3
+ rcbufs3_.reserve(32);
+#endif // ENABLE_HTTP3
+}
+
+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_);
+
+#ifdef HAVE_MRUBY
+ auto handler = upstream_->get_client_handler();
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ mruby_ctx->delete_downstream(this);
+#endif // HAVE_MRUBY
+ }
+
+#ifdef HAVE_MRUBY
+ if (dconn_) {
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+ }
+#endif // HAVE_MRUBY
+
+ // DownstreamConnection may refer to this object. Delete it now
+ // explicitly.
+ dconn_.reset();
+
+#ifdef ENABLE_HTTP3
+ for (auto rcbuf : rcbufs3_) {
+ nghttp3_rcbuf_decref(rcbuf);
+ }
+#endif // ENABLE_HTTP3
+
+ for (auto rcbuf : rcbufs_) {
+ nghttp2_rcbuf_decref(rcbuf);
+ }
+
+ 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;
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+#endif // HAVE_MRUBY
+
+ dconn_->detach_downstream(this);
+
+ auto handler = dconn_->get_client_handler();
+
+ handler->pool_downstream_connection(
+ std::unique_ptr<DownstreamConnection>(dconn_.release()));
+}
+
+DownstreamConnection *Downstream::get_downstream_connection() {
+ return dconn_.get();
+}
+
+std::unique_ptr<DownstreamConnection> Downstream::pop_downstream_connection() {
+#ifdef HAVE_MRUBY
+ if (!dconn_) {
+ return nullptr;
+ }
+
+ const auto &group = dconn_->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ mruby_ctx->delete_downstream(this);
+ }
+#endif // HAVE_MRUBY
+
+ 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 HeaderRefs::value_type *
+search_header_linear_backwards(const HeaderRefs &headers,
+ const StringRef &name) {
+ for (auto it = headers.rbegin(); it != headers.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.name == name) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+} // namespace
+
+StringRef Downstream::assemble_request_cookie() {
+ size_t len = 0;
+
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE || kv.value.empty()) {
+ continue;
+ }
+
+ len += kv.value.size() + str_size("; ");
+ }
+
+ auto iov = make_byte_ref(balloc_, len + 1);
+ auto p = iov.base;
+
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE || kv.value.empty()) {
+ continue;
+ }
+
+ auto end = std::end(kv.value);
+ for (auto it = std::begin(kv.value) + kv.value.size();
+ it != std::begin(kv.value); --it) {
+ auto c = *(it - 1);
+ if (c == ' ' || c == ';') {
+ continue;
+ }
+ end = it;
+ break;
+ }
+
+ p = std::copy(std::begin(kv.value), end, p);
+ p = util::copy_lit(p, "; ");
+ }
+
+ // cut trailing "; "
+ if (p - iov.base >= 2) {
+ p -= 2;
+ }
+
+ return StringRef{iov.base, p};
+}
+
+uint32_t Downstream::find_affinity_cookie(const StringRef &name) {
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ auto end = std::find(it, std::end(kv.value), '=');
+ if (end == std::end(kv.value)) {
+ return 0;
+ }
+
+ if (!util::streq(name, StringRef{it, end})) {
+ it = std::find(it, std::end(kv.value), ';');
+ continue;
+ }
+
+ it = std::find(end + 1, std::end(kv.value), ';');
+ auto val = StringRef{end + 1, it};
+ if (val.size() != 8) {
+ return 0;
+ }
+ uint32_t h = 0;
+ for (auto c : val) {
+ auto n = util::hex_to_uint(c);
+ if (n == 256) {
+ return 0;
+ }
+ h <<= 4;
+ h += n;
+ }
+ affinity_cookie_ = h;
+ return h;
+ }
+ }
+ return 0;
+}
+
+size_t Downstream::count_crumble_request_cookie() {
+ size_t n = 0;
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ it = std::find(it, std::end(kv.value), ';');
+
+ ++n;
+ }
+ }
+ return n;
+}
+
+void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
+ for (auto &kv : req_.fs.headers()) {
+ if (kv.token != http2::HD_COOKIE) {
+ continue;
+ }
+
+ for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
+ if (*it == '\t' || *it == ' ' || *it == ';') {
+ ++it;
+ continue;
+ }
+
+ auto first = it;
+
+ it = std::find(it, std::end(kv.value), ';');
+
+ nva.push_back({(uint8_t *)"cookie", (uint8_t *)first, str_size("cookie"),
+ (size_t)(it - first),
+ (uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
+ NGHTTP2_NV_FLAG_NO_COPY_VALUE |
+ (kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
+ }
+ }
+}
+
+namespace {
+void add_header(size_t &sum, HeaderRefs &headers, const StringRef &name,
+ const StringRef &value, bool no_index, int32_t token) {
+ sum += name.size() + value.size();
+ headers.emplace_back(name, value, no_index, token);
+}
+} // namespace
+
+namespace {
+StringRef alloc_header_name(BlockAllocator &balloc, const StringRef &name) {
+ auto iov = make_byte_ref(balloc, name.size() + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(name), std::end(name), p);
+ util::inp_strlower(iov.base, p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+} // namespace
+
+namespace {
+void append_last_header_key(BlockAllocator &balloc, bool &key_prev, size_t &sum,
+ HeaderRefs &headers, const char *data, size_t len) {
+ assert(key_prev);
+ sum += len;
+ auto &item = headers.back();
+ auto name =
+ realloc_concat_string_ref(balloc, item.name, StringRef{data, len});
+
+ auto p = const_cast<uint8_t *>(name.byte());
+ util::inp_strlower(p + name.size() - len, p + name.size());
+
+ item.name = name;
+ item.token = http2::lookup_token(item.name);
+}
+} // namespace
+
+namespace {
+void append_last_header_value(BlockAllocator &balloc, bool &key_prev,
+ size_t &sum, HeaderRefs &headers,
+ const char *data, size_t len) {
+ key_prev = false;
+ sum += len;
+ auto &item = headers.back();
+ item.value =
+ realloc_concat_string_ref(balloc, item.value, StringRef{data, len});
+}
+} // namespace
+
+int FieldStore::parse_content_length() {
+ content_length = -1;
+
+ for (auto &kv : headers_) {
+ if (kv.token != http2::HD_CONTENT_LENGTH) {
+ continue;
+ }
+
+ auto len = util::parse_uint(kv.value);
+ if (len == -1) {
+ return -1;
+ }
+ if (content_length != -1) {
+ return -1;
+ }
+ content_length = len;
+ }
+ return 0;
+}
+
+const HeaderRefs::value_type *FieldStore::header(int32_t token) const {
+ for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.token == token) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+
+HeaderRefs::value_type *FieldStore::header(int32_t token) {
+ for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
+ auto &kv = *it;
+ if (kv.token == token) {
+ return &kv;
+ }
+ }
+ return nullptr;
+}
+
+const HeaderRefs::value_type *FieldStore::header(const StringRef &name) const {
+ return search_header_linear_backwards(headers_, name);
+}
+
+void FieldStore::add_header_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token) {
+ shrpx::add_header(buffer_size_, headers_, name, value, no_index, token);
+}
+
+void FieldStore::alloc_add_header_name(const StringRef &name) {
+ auto name_ref = alloc_header_name(balloc_, name);
+ auto token = http2::lookup_token(name_ref);
+ add_header_token(name_ref, StringRef{}, false, token);
+ header_key_prev_ = true;
+}
+
+void FieldStore::append_last_header_key(const char *data, size_t len) {
+ shrpx::append_last_header_key(balloc_, header_key_prev_, buffer_size_,
+ headers_, data, len);
+}
+
+void FieldStore::append_last_header_value(const char *data, size_t len) {
+ shrpx::append_last_header_value(balloc_, header_key_prev_, buffer_size_,
+ headers_, data, len);
+}
+
+void FieldStore::clear_headers() {
+ headers_.clear();
+ header_key_prev_ = false;
+}
+
+void FieldStore::add_trailer_token(const StringRef &name,
+ const StringRef &value, bool no_index,
+ int32_t token) {
+ // Header size limit should be applied to all header and trailer
+ // fields combined.
+ shrpx::add_header(buffer_size_, trailers_, name, value, no_index, token);
+}
+
+void FieldStore::alloc_add_trailer_name(const StringRef &name) {
+ auto name_ref = alloc_header_name(balloc_, name);
+ auto token = http2::lookup_token(name_ref);
+ add_trailer_token(name_ref, StringRef{}, false, token);
+ trailer_key_prev_ = true;
+}
+
+void FieldStore::append_last_trailer_key(const char *data, size_t len) {
+ shrpx::append_last_header_key(balloc_, trailer_key_prev_, buffer_size_,
+ trailers_, data, len);
+}
+
+void FieldStore::append_last_trailer_value(const char *data, size_t len) {
+ shrpx::append_last_header_value(balloc_, trailer_key_prev_, buffer_size_,
+ trailers_, data, len);
+}
+
+void FieldStore::erase_content_length_and_transfer_encoding() {
+ for (auto &kv : headers_) {
+ switch (kv.token) {
+ case http2::HD_CONTENT_LENGTH:
+ case http2::HD_TRANSFER_ENCODING:
+ kv.name = StringRef{};
+ kv.token = -1;
+ break;
+ }
+ }
+}
+
+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_;
+}
+
+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(int64_t stream_id) { stream_id_ = stream_id; }
+
+int64_t Downstream::get_stream_id() const { return stream_id_; }
+
+void Downstream::set_request_state(DownstreamState state) {
+ request_state_ = state;
+}
+
+DownstreamState 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::request_buf_full() {
+ auto handler = upstream_->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+ auto worker = handler->get_worker();
+
+ // We don't check buffer size here for API endpoint.
+ if (faddr->alt_mode == UpstreamAltMode::API) {
+ return false;
+ }
+
+ if (dconn_) {
+ auto &downstreamconf = *worker->get_downstream_config();
+ return blocked_request_buf_.rleft() + request_buf_.rleft() >=
+ downstreamconf.request_buffer_size;
+ }
+
+ 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) {
+ req_.recv_body_length += datalen;
+
+ if (!dconn_ && !request_header_sent_) {
+ blocked_request_buf_.append(data, datalen);
+ req_.unconsumed_body_length += datalen;
+ return 0;
+ }
+
+ // 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;
+ }
+ if (dconn_->push_upload_data_chunk(data, datalen) != 0) {
+ return -1;
+ }
+
+ req_.unconsumed_body_length += datalen;
+
+ return 0;
+}
+
+int Downstream::end_upload_data() {
+ if (!dconn_ && !request_header_sent_) {
+ blocked_request_data_eof_ = true;
+ return 0;
+ }
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ return dconn_->end_upload_data();
+}
+
+void Downstream::rewrite_location_response_header(
+ const StringRef &upstream_scheme) {
+ auto hd = resp_.fs.header(http2::HD_LOCATION);
+ if (!hd) {
+ return;
+ }
+
+ if (request_downstream_host_.empty() || req_.authority.empty()) {
+ return;
+ }
+
+ http_parser_url u{};
+ auto rv = http_parser_parse_url(hd->value.c_str(), hd->value.size(), 0, &u);
+ if (rv != 0) {
+ return;
+ }
+
+ auto new_uri = http2::rewrite_location_uri(balloc_, hd->value, u,
+ request_downstream_host_,
+ req_.authority, upstream_scheme);
+
+ if (new_uri.empty()) {
+ return;
+ }
+
+ hd->value = new_uri;
+}
+
+bool Downstream::get_chunked_response() const { return chunked_response_; }
+
+void Downstream::set_chunked_response(bool f) { chunked_response_ = f; }
+
+int Downstream::on_read() {
+ if (!dconn_) {
+ DLOG(INFO, this) << "dconn_ is NULL";
+ return -1;
+ }
+ return dconn_->on_read();
+}
+
+void Downstream::set_response_state(DownstreamState state) {
+ response_state_ = state;
+}
+
+DownstreamState Downstream::get_response_state() const {
+ return response_state_;
+}
+
+DefaultMemchunks *Downstream::get_response_buf() { return &response_buf_; }
+
+bool Downstream::response_buf_full() {
+ if (dconn_) {
+ auto handler = upstream_->get_client_handler();
+ auto worker = handler->get_worker();
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ return response_buf_.rleft() >= downstreamconf.response_buffer_size;
+ }
+
+ return false;
+}
+
+bool Downstream::validate_request_recv_body_length() const {
+ if (req_.fs.content_length == -1) {
+ return true;
+ }
+
+ if (req_.fs.content_length != req_.recv_body_length) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "request invalid bodylen: content-length="
+ << req_.fs.content_length
+ << ", received=" << req_.recv_body_length;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool Downstream::validate_response_recv_body_length() const {
+ if (!expect_response_body() || resp_.fs.content_length == -1) {
+ return true;
+ }
+
+ if (resp_.fs.content_length != resp_.recv_body_length) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, this) << "response invalid bodylen: content-length="
+ << resp_.fs.content_length
+ << ", received=" << resp_.recv_body_length;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void Downstream::check_upgrade_fulfilled_http2() {
+ // This handles nonzero req_.connect_proto and h1 frontend requests
+ // WebSocket upgrade.
+ upgraded_ = (req_.method == HTTP_CONNECT ||
+ req_.connect_proto == ConnectProto::WEBSOCKET) &&
+ resp_.http_status / 100 == 2;
+}
+
+void Downstream::check_upgrade_fulfilled_http1() {
+ if (req_.method == HTTP_CONNECT) {
+ if (req_.connect_proto == ConnectProto::WEBSOCKET) {
+ if (resp_.http_status != 101) {
+ return;
+ }
+
+ // This is done for HTTP/2 frontend only.
+ auto accept = resp_.fs.header(http2::HD_SEC_WEBSOCKET_ACCEPT);
+ if (!accept) {
+ return;
+ }
+
+ std::array<uint8_t, base64::encode_length(20)> accept_buf;
+ auto expected =
+ http2::make_websocket_accept_token(accept_buf.data(), ws_key_);
+
+ upgraded_ = expected != "" && expected == accept->value;
+ } else {
+ upgraded_ = resp_.http_status / 100 == 2;
+ }
+
+ return;
+ }
+
+ if (resp_.http_status == 101) {
+ // TODO Do more strict checking for upgrade headers
+ upgraded_ = req_.upgrade_request;
+
+ return;
+ }
+}
+
+void Downstream::inspect_http2_request() {
+ if (req_.method == HTTP_CONNECT) {
+ req_.upgrade_request = true;
+ }
+}
+
+void Downstream::inspect_http1_request() {
+ if (req_.method == HTTP_CONNECT) {
+ req_.upgrade_request = true;
+ } else if (req_.http_minor > 0) {
+ auto upgrade = req_.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ const auto &val = upgrade->value;
+ // TODO Perform more strict checking for upgrade headers
+ if (util::streq_l(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(),
+ val.size())) {
+ req_.http2_upgrade_seen = true;
+ } else {
+ req_.upgrade_request = true;
+
+ // TODO Should we check Sec-WebSocket-Key, and
+ // Sec-WebSocket-Version as well?
+ if (util::strieq_l("websocket", val)) {
+ req_.connect_proto = ConnectProto::WEBSOCKET;
+ }
+ }
+ }
+ }
+ auto transfer_encoding = req_.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding) {
+ req_.fs.content_length = -1;
+ }
+
+ auto expect = req_.fs.header(http2::HD_EXPECT);
+ expect_100_continue_ =
+ expect &&
+ util::strieq(expect->value, StringRef::from_lit("100-continue"));
+}
+
+void Downstream::inspect_http1_response() {
+ auto transfer_encoding = resp_.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding) {
+ resp_.fs.content_length = -1;
+ }
+}
+
+void Downstream::reset_response() {
+ resp_.http_status = 0;
+ resp_.http_major = 1;
+ resp_.http_minor = 1;
+}
+
+bool Downstream::get_non_final_response() const {
+ return !upgraded_ && resp_.http_status / 100 == 1;
+}
+
+bool Downstream::supports_non_final_response() const {
+ return req_.http_major == 3 || req_.http_major == 2 ||
+ (req_.http_major == 1 && req_.http_minor == 1);
+}
+
+bool Downstream::get_upgraded() const { return upgraded_; }
+
+bool Downstream::get_http2_upgrade_request() const {
+ return req_.http2_upgrade_seen && req_.fs.header(http2::HD_HTTP2_SETTINGS) &&
+ response_state_ == DownstreamState::INITIAL;
+}
+
+StringRef Downstream::get_http2_settings() const {
+ auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS);
+ if (!http2_settings) {
+ return StringRef{};
+ }
+ return http2_settings->value;
+}
+
+void Downstream::set_downstream_stream_id(int64_t stream_id) {
+ downstream_stream_id_ = stream_id;
+}
+
+int64_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_;
+}
+
+bool Downstream::expect_response_body() const {
+ return !resp_.headers_only &&
+ http2::expect_response_body(req_.method, resp_.http_status);
+}
+
+bool Downstream::expect_response_trailer() const {
+ // In HTTP/2, if final response HEADERS does not bear END_STREAM it
+ // is possible trailer fields might come, regardless of request
+ // method or status code.
+ return !resp_.headers_only &&
+ (resp_.http_major == 3 || resp_.http_major == 2);
+}
+
+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()->http2.timeout.stream_read == 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();
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ if (timeoutconf.stream_write != 0.) {
+ reset_timer(loop, &upstream_wtimer_);
+ }
+ if (timeoutconf.stream_read != 0.) {
+ try_reset_timer(loop, &upstream_rtimer_);
+ }
+}
+
+void Downstream::ensure_upstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ ensure_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::disable_upstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &upstream_rtimer_);
+}
+
+void Downstream::disable_upstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::reset_downstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 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();
+ auto &timeoutconf = get_config()->http2.timeout;
+
+ if (timeoutconf.stream_write != 0.) {
+ reset_timer(loop, &downstream_wtimer_);
+ }
+ if (timeoutconf.stream_read != 0.) {
+ try_reset_timer(loop, &downstream_rtimer_);
+ }
+}
+
+void Downstream::ensure_downstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ ensure_timer(loop, &downstream_wtimer_);
+}
+
+void Downstream::disable_downstream_rtimer() {
+ if (get_config()->http2.timeout.stream_read == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &downstream_rtimer_);
+}
+
+void Downstream::disable_downstream_wtimer() {
+ if (get_config()->http2.timeout.stream_write == 0.) {
+ return;
+ }
+ auto loop = upstream_->get_client_handler()->get_loop();
+ disable_timer(loop, &downstream_wtimer_);
+}
+
+bool Downstream::accesslog_ready() const {
+ return !accesslog_written_ && resp_.http_status > 0;
+}
+
+void Downstream::add_retry() { ++num_retry_; }
+
+bool Downstream::no_more_retry() const { return num_retry_ > 50; }
+
+void Downstream::set_request_downstream_host(const StringRef &host) {
+ request_downstream_host_ = host;
+}
+
+void Downstream::set_request_pending(bool f) { request_pending_ = f; }
+
+bool Downstream::get_request_pending() const { return request_pending_; }
+
+void Downstream::set_request_header_sent(bool f) { request_header_sent_ = f; }
+
+bool Downstream::get_request_header_sent() const {
+ return request_header_sent_;
+}
+
+bool Downstream::request_submission_ready() const {
+ return (request_state_ == DownstreamState::HEADER_COMPLETE ||
+ request_state_ == DownstreamState::MSG_COMPLETE) &&
+ (request_pending_ || !request_header_sent_) &&
+ response_state_ == DownstreamState::INITIAL;
+}
+
+DispatchState Downstream::get_dispatch_state() const { return dispatch_state_; }
+
+void Downstream::set_dispatch_state(DispatchState s) { dispatch_state_ = s; }
+
+void Downstream::attach_blocked_link(BlockedLink *l) {
+ assert(!blocked_link_);
+
+ l->downstream = this;
+ blocked_link_ = l;
+}
+
+BlockedLink *Downstream::detach_blocked_link() {
+ auto link = blocked_link_;
+ blocked_link_ = nullptr;
+ return link;
+}
+
+bool Downstream::can_detach_downstream_connection() const {
+ // We should check request and response buffer. If request buffer
+ // is not empty, then we might leave downstream connection in weird
+ // state, especially for HTTP/1.1
+ return dconn_ && response_state_ == DownstreamState::MSG_COMPLETE &&
+ request_state_ == DownstreamState::MSG_COMPLETE && !upgraded_ &&
+ !resp_.connection_close && request_buf_.rleft() == 0;
+}
+
+DefaultMemchunks Downstream::pop_response_buf() {
+ return std::move(response_buf_);
+}
+
+void Downstream::set_assoc_stream_id(int64_t stream_id) {
+ assoc_stream_id_ = stream_id;
+}
+
+int64_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
+
+BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
+
+void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
+ nghttp2_rcbuf_incref(rcbuf);
+ rcbufs_.push_back(rcbuf);
+}
+
+#ifdef ENABLE_HTTP3
+void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) {
+ nghttp3_rcbuf_incref(rcbuf);
+ rcbufs3_.push_back(rcbuf);
+}
+#endif // ENABLE_HTTP3
+
+void Downstream::set_downstream_addr_group(
+ const std::shared_ptr<DownstreamAddrGroup> &group) {
+ group_ = group;
+}
+
+void Downstream::set_addr(const DownstreamAddr *addr) { addr_ = addr; }
+
+const DownstreamAddr *Downstream::get_addr() const { return addr_; }
+
+void Downstream::set_accesslog_written(bool f) { accesslog_written_ = f; }
+
+void Downstream::renew_affinity_cookie(uint32_t h) {
+ affinity_cookie_ = h;
+ new_affinity_cookie_ = true;
+}
+
+uint32_t Downstream::get_affinity_cookie_to_send() const {
+ if (new_affinity_cookie_) {
+ return affinity_cookie_;
+ }
+ return 0;
+}
+
+DefaultMemchunks *Downstream::get_blocked_request_buf() {
+ return &blocked_request_buf_;
+}
+
+bool Downstream::get_blocked_request_data_eof() const {
+ return blocked_request_data_eof_;
+}
+
+void Downstream::set_blocked_request_data_eof(bool f) {
+ blocked_request_data_eof_ = f;
+}
+
+void Downstream::set_ws_key(const StringRef &key) { ws_key_ = key; }
+
+bool Downstream::get_expect_100_continue() const {
+ return expect_100_continue_;
+}
+
+bool Downstream::get_stop_reading() const { return stop_reading_; }
+
+void Downstream::set_stop_reading(bool f) { stop_reading_ = f; }
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h
new file mode 100644
index 0000000..146cae5
--- /dev/null
+++ b/src/shrpx_downstream.h
@@ -0,0 +1,628 @@
+/*
+ * 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 <cinttypes>
+#include <vector>
+#include <string>
+#include <memory>
+#include <chrono>
+#include <algorithm>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <nghttp3/nghttp3.h>
+#endif // ENABLE_HTTP3
+
+#include "llhttp.h"
+
+#include "shrpx_io_control.h"
+#include "shrpx_log_config.h"
+#include "http2.h"
+#include "memchunk.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+struct BlockedLink;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+
+class FieldStore {
+public:
+ FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity)
+ : content_length(-1),
+ balloc_(balloc),
+ buffer_size_(0),
+ header_key_prev_(false),
+ trailer_key_prev_(false) {
+ headers_.reserve(headers_initial_capacity);
+ }
+
+ const HeaderRefs &headers() const { return headers_; }
+ const HeaderRefs &trailers() const { return trailers_; }
+
+ HeaderRefs &headers() { return headers_; }
+ HeaderRefs &trailers() { return trailers_; }
+
+ const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
+ size_t buffer_size() const { return buffer_size_; }
+
+ size_t num_fields() const { return headers_.size() + trailers_.size(); }
+
+ // Returns pointer to the header field 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.
+ const HeaderRefs::value_type *header(int32_t token) const;
+ HeaderRefs::value_type *header(int32_t token);
+ // Returns pointer to the header field with the name |name|. If no
+ // such header is found, returns nullptr.
+ const HeaderRefs::value_type *header(const StringRef &name) const;
+
+ void add_header_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token);
+
+ // Adds header field name |name|. First, the copy of header field
+ // name pointed by name.c_str() of length name.size() is made, and
+ // stored.
+ void alloc_add_header_name(const StringRef &name);
+
+ void append_last_header_key(const char *data, size_t len);
+ void append_last_header_value(const char *data, size_t len);
+
+ bool header_key_prev() const { return header_key_prev_; }
+
+ // Parses content-length, and records it in the field. If there are
+ // multiple Content-Length, returns -1.
+ int parse_content_length();
+
+ // Empties headers.
+ void clear_headers();
+
+ void add_trailer_token(const StringRef &name, const StringRef &value,
+ bool no_index, int32_t token);
+
+ // Adds trailer field name |name|. First, the copy of trailer field
+ // name pointed by name.c_str() of length name.size() is made, and
+ // stored.
+ void alloc_add_trailer_name(const StringRef &name);
+
+ void append_last_trailer_key(const char *data, size_t len);
+ void append_last_trailer_value(const char *data, size_t len);
+
+ bool trailer_key_prev() const { return trailer_key_prev_; }
+
+ // erase_content_length_and_transfer_encoding erases content-length
+ // and transfer-encoding header fields.
+ void erase_content_length_and_transfer_encoding();
+
+ // content-length, -1 if it is unknown.
+ int64_t content_length;
+
+private:
+ BlockAllocator &balloc_;
+ HeaderRefs headers_;
+ // trailer fields. For HTTP/1.1, trailer fields are only included
+ // with chunked encoding. For HTTP/2, there is no such limit.
+ HeaderRefs trailers_;
+ // Sum of the length of name and value in headers_ and trailers_.
+ // This could also be increased by add_extra_buffer_size() to take
+ // into account for request URI in case of HTTP/1.x request.
+ size_t buffer_size_;
+ bool header_key_prev_;
+ bool trailer_key_prev_;
+};
+
+// Protocols allowed in HTTP/2 :protocol header field.
+enum class ConnectProto {
+ NONE,
+ WEBSOCKET,
+};
+
+struct Request {
+ Request(BlockAllocator &balloc)
+ : fs(balloc, 16),
+ recv_body_length(0),
+ unconsumed_body_length(0),
+ method(-1),
+ http_major(1),
+ http_minor(1),
+ connect_proto(ConnectProto::NONE),
+ upgrade_request(false),
+ http2_upgrade_seen(false),
+ connection_close(false),
+ http2_expect_body(false),
+ no_authority(false),
+ forwarded_once(false) {}
+
+ void consume(size_t len) {
+ assert(unconsumed_body_length >= len);
+ unconsumed_body_length -= len;
+ }
+
+ bool regular_connect_method() const {
+ return method == HTTP_CONNECT && connect_proto == ConnectProto::NONE;
+ }
+
+ bool extended_connect_method() const {
+ return connect_proto != ConnectProto::NONE;
+ }
+
+ FieldStore fs;
+ // Timestamp when all request header fields are received.
+ std::shared_ptr<Timestamp> tstamp;
+ // Request scheme. For HTTP/2, this is :scheme header field value.
+ // For HTTP/1.1, this is deduced from URI or connection.
+ StringRef scheme;
+ // Request authority. This is HTTP/2 :authority header field value
+ // or host header field value. We may deduce it from absolute-form
+ // HTTP/1 request. We also store authority-form HTTP/1 request.
+ // This could be empty if request comes from HTTP/1.0 without Host
+ // header field and origin-form.
+ StringRef authority;
+ // Request path, including query component. For HTTP/1.1, this is
+ // request-target. For HTTP/2, this is :path header field value.
+ // For CONNECT request, this is empty.
+ StringRef path;
+ // This is original authority which cannot be changed by per-pattern
+ // mruby script.
+ StringRef orig_authority;
+ // This is original path which cannot be changed by per-pattern
+ // mruby script.
+ StringRef orig_path;
+ // the length of request body received so far
+ int64_t recv_body_length;
+ // The number of bytes not consumed by the application yet.
+ size_t unconsumed_body_length;
+ int method;
+ // HTTP major and minor version
+ int http_major, http_minor;
+ // connect_proto specified in HTTP/2 :protocol pseudo header field
+ // which enables extended CONNECT method. This field is also set if
+ // WebSocket upgrade is requested in h1 frontend for convenience.
+ ConnectProto connect_proto;
+ // Returns true if the request is HTTP upgrade (HTTP Upgrade or
+ // CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2
+ // Upgrade, check get_http2_upgrade_request().
+ bool upgrade_request;
+ // true if h2c is seen in Upgrade header field.
+ bool http2_upgrade_seen;
+ bool connection_close;
+ // true if this is HTTP/2, and request body is expected. Note that
+ // we don't take into account HTTP method here.
+ bool http2_expect_body;
+ // true if request does not have any information about authority.
+ // This happens when: For HTTP/2 request, :authority is missing.
+ // For HTTP/1 request, origin or asterisk form is used.
+ bool no_authority;
+ // true if backend selection is done for request once.
+ // orig_authority and orig_path have the authority and path which
+ // are used for the first backend selection.
+ bool forwarded_once;
+};
+
+struct Response {
+ Response(BlockAllocator &balloc)
+ : fs(balloc, 32),
+ recv_body_length(0),
+ unconsumed_body_length(0),
+ http_status(0),
+ http_major(1),
+ http_minor(1),
+ connection_close(false),
+ headers_only(false) {}
+
+ void consume(size_t len) {
+ assert(unconsumed_body_length >= len);
+ unconsumed_body_length -= len;
+ }
+
+ // returns true if a resource denoted by scheme, authority, and path
+ // has already been pushed.
+ bool is_resource_pushed(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path) const {
+ if (!pushed_resources) {
+ return false;
+ }
+ return std::find(std::begin(*pushed_resources), std::end(*pushed_resources),
+ std::make_tuple(scheme, authority, path)) !=
+ std::end(*pushed_resources);
+ }
+
+ // remember that a resource denoted by scheme, authority, and path
+ // is pushed.
+ void resource_pushed(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path) {
+ if (!pushed_resources) {
+ pushed_resources = std::make_unique<
+ std::vector<std::tuple<StringRef, StringRef, StringRef>>>();
+ }
+ pushed_resources->emplace_back(scheme, authority, path);
+ }
+
+ FieldStore fs;
+ // array of the tuple of scheme, authority, and path of pushed
+ // resource. This is required because RFC 8297 says that server
+ // typically includes header fields appeared in non-final response
+ // header fields in final response header fields. Without checking
+ // that a particular resource has already been pushed, or not, we
+ // end up pushing the same resource at least twice. It is unknown
+ // that we should use more complex data structure (e.g., std::set)
+ // to find the resources faster.
+ std::unique_ptr<std::vector<std::tuple<StringRef, StringRef, StringRef>>>
+ pushed_resources;
+ // the length of response body received so far
+ int64_t recv_body_length;
+ // The number of bytes not consumed by the application yet. This is
+ // mainly for HTTP/2 backend.
+ size_t unconsumed_body_length;
+ // HTTP status code
+ unsigned int http_status;
+ int http_major, http_minor;
+ bool connection_close;
+ // true if response only consists of HEADERS, and it bears
+ // END_STREAM. This is used to tell Http2Upstream that it can send
+ // response with single HEADERS with END_STREAM flag only.
+ bool headers_only;
+};
+
+enum class DownstreamState {
+ INITIAL,
+ HEADER_COMPLETE,
+ MSG_COMPLETE,
+ STREAM_CLOSED,
+ CONNECT_FAIL,
+ MSG_RESET,
+ // header contains invalid header field. We can safely send error
+ // response (502) to a client.
+ MSG_BAD_HEADER,
+ // header fields in HTTP/1 request exceed the configuration limit.
+ // This state is only transitioned from INITIAL state, and solely
+ // used to signal 431 status code to the client.
+ HTTP1_REQUEST_HEADER_TOO_LARGE,
+};
+
+enum class DispatchState {
+ NONE,
+ PENDING,
+ BLOCKED,
+ ACTIVE,
+ FAILURE,
+};
+
+class Downstream {
+public:
+ Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id);
+ ~Downstream();
+ void reset_upstream(Upstream *upstream);
+ Upstream *get_upstream() const;
+ void set_stream_id(int64_t stream_id);
+ int64_t get_stream_id() const;
+ void set_assoc_stream_id(int64_t stream_id);
+ int64_t get_assoc_stream_id() 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(int64_t stream_id);
+ int64_t get_downstream_stream_id() const;
+
+ int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+ void detach_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 in
+ // h1 backend. This should not depend on inspect_http1_response().
+ void check_upgrade_fulfilled_http1();
+ // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
+ // h2 backend.
+ void check_upgrade_fulfilled_http2();
+ // Returns true if the upgrade is succeeded as a result of the call
+ // check_upgrade_fulfilled_http*(). HTTP/2 Upgrade is excluded.
+ 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.
+ StringRef get_http2_settings() const;
+
+ // downstream request API
+ const Request &request() const { return req_; }
+ Request &request() { return req_; }
+
+ // Count number of crumbled cookies
+ size_t count_crumble_request_cookie();
+ // Crumbles (split cookie by ";") in request_headers_ and adds them
+ // in |nva|. Headers::no_index is inherited.
+ void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
+ // Assembles request cookies. The opposite operation against
+ // crumble_request_cookie().
+ StringRef assemble_request_cookie();
+
+ 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;
+ int push_request_headers();
+ bool get_chunked_request() const;
+ void set_chunked_request(bool f);
+ int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+ int end_upload_data();
+ // Validates that received request body length and content-length
+ // matches.
+ bool validate_request_recv_body_length() const;
+ void set_request_downstream_host(const StringRef &host);
+ bool expect_response_body() const;
+ bool expect_response_trailer() const;
+ void set_request_state(DownstreamState state);
+ DownstreamState get_request_state() const;
+ DefaultMemchunks *get_request_buf();
+ void set_request_pending(bool f);
+ bool get_request_pending() const;
+ void set_request_header_sent(bool f);
+ bool get_request_header_sent() const;
+ // Returns true if request is ready to be submitted to downstream.
+ // When sending pending request, get_request_pending() should be
+ // checked too because this function may return true when
+ // get_request_pending() returns false.
+ bool request_submission_ready() const;
+
+ DefaultMemchunks *get_blocked_request_buf();
+ bool get_blocked_request_data_eof() const;
+ void set_blocked_request_data_eof(bool f);
+
+ // downstream response API
+ const Response &response() const { return resp_; }
+ Response &response() { return resp_; }
+
+ // Rewrites the location response header field.
+ void rewrite_location_response_header(const StringRef &upstream_scheme);
+
+ bool get_chunked_response() const;
+ void set_chunked_response(bool f);
+
+ void set_response_state(DownstreamState state);
+ DownstreamState get_response_state() const;
+ DefaultMemchunks *get_response_buf();
+ bool response_buf_full();
+ // Validates that received response body length and content-length
+ // matches.
+ bool validate_response_recv_body_length() 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();
+ // True if the response is non-final (1xx status code). Note that
+ // if connection was upgraded, 101 status code is treated as final.
+ bool get_non_final_response() const;
+ // True if protocol version used by client supports non final
+ // response. Only HTTP/1.1 and HTTP/2 clients support it.
+ bool supports_non_final_response() const;
+ void set_expect_final_response(bool f);
+ bool get_expect_final_response() const;
+
+ // Call this method when there is incoming data in downstream
+ // connection.
+ int on_read();
+
+ // 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;
+
+ DispatchState get_dispatch_state() const;
+ void set_dispatch_state(DispatchState s);
+
+ void attach_blocked_link(BlockedLink *l);
+ BlockedLink *detach_blocked_link();
+
+ // Returns true if downstream_connection can be detached and reused.
+ bool can_detach_downstream_connection() const;
+
+ DefaultMemchunks pop_response_buf();
+
+ BlockAllocator &get_block_allocator();
+
+ void add_rcbuf(nghttp2_rcbuf *rcbuf);
+#ifdef ENABLE_HTTP3
+ void add_rcbuf(nghttp3_rcbuf *rcbuf);
+#endif // ENABLE_HTTP3
+
+ void
+ set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
+ void set_addr(const DownstreamAddr *addr);
+
+ const DownstreamAddr *get_addr() const;
+
+ void set_accesslog_written(bool f);
+
+ // Finds affinity cookie from request header fields. The name of
+ // cookie is given in |name|. If an affinity cookie is found, it is
+ // assigned to a member function, and is returned. If it is not
+ // found, or is malformed, returns 0.
+ uint32_t find_affinity_cookie(const StringRef &name);
+ // Set |h| as affinity cookie.
+ void renew_affinity_cookie(uint32_t h);
+ // Returns affinity cookie to send. If it does not need to be sent,
+ // for example, because the value is retrieved from a request header
+ // field, returns 0.
+ uint32_t get_affinity_cookie_to_send() const;
+
+ void set_ws_key(const StringRef &key);
+
+ bool get_expect_100_continue() const;
+
+ bool get_stop_reading() const;
+ void set_stop_reading(bool f);
+
+ enum {
+ EVENT_ERROR = 0x1,
+ EVENT_TIMEOUT = 0x2,
+ };
+
+ Downstream *dlnext, *dlprev;
+
+ // the length of response body sent to upstream client
+ int64_t response_sent_body_length;
+
+private:
+ BlockAllocator balloc_;
+
+ std::vector<nghttp2_rcbuf *> rcbufs_;
+#ifdef ENABLE_HTTP3
+ std::vector<nghttp3_rcbuf *> rcbufs3_;
+#endif // ENABLE_HTTP3
+
+ Request req_;
+ Response resp_;
+
+ std::chrono::high_resolution_clock::time_point request_start_time_;
+
+ // host we requested to downstream. This is used to rewrite
+ // location header field to decide the location should be rewritten
+ // or not.
+ StringRef request_downstream_host_;
+
+ // Data arrived in frontend before sending header fields to backend
+ // are stored in this buffer.
+ DefaultMemchunks blocked_request_buf_;
+ DefaultMemchunks request_buf_;
+ DefaultMemchunks response_buf_;
+
+ // The Sec-WebSocket-Key field sent to the peer. This field is used
+ // if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2.
+ StringRef ws_key_;
+
+ ev_timer upstream_rtimer_;
+ ev_timer upstream_wtimer_;
+
+ ev_timer downstream_rtimer_;
+ ev_timer downstream_wtimer_;
+
+ Upstream *upstream_;
+ std::unique_ptr<DownstreamConnection> dconn_;
+
+ // only used by HTTP/2 upstream
+ BlockedLink *blocked_link_;
+ // The backend address used to fulfill this request. These are for
+ // logging purpose.
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ const DownstreamAddr *addr_;
+ // How many times we tried in backend connection
+ size_t num_retry_;
+ // The stream ID in frontend connection
+ int64_t stream_id_;
+ // The associated stream ID in frontend connection if this is pushed
+ // stream.
+ int64_t assoc_stream_id_;
+ // stream ID in backend connection
+ int64_t downstream_stream_id_;
+ // RST_STREAM error_code from downstream HTTP2 connection
+ uint32_t response_rst_stream_error_code_;
+ // An affinity cookie value.
+ uint32_t affinity_cookie_;
+ // request state
+ DownstreamState request_state_;
+ // response state
+ DownstreamState response_state_;
+ // only used by HTTP/2 upstream
+ DispatchState dispatch_state_;
+ // true if the connection is upgraded (HTTP Upgrade or CONNECT),
+ // excluding upgrade to HTTP/2.
+ bool upgraded_;
+ // true if backend request uses chunked transfer-encoding
+ bool chunked_request_;
+ // true if response to client uses chunked transfer-encoding
+ bool chunked_response_;
+ // true if we have not got final response code
+ bool expect_final_response_;
+ // true if downstream request is pending because backend connection
+ // has not been established or should be checked before use;
+ // currently used only with HTTP/2 connection.
+ bool request_pending_;
+ // true if downstream request header is considered to be sent.
+ bool request_header_sent_;
+ // true if access.log has been written.
+ bool accesslog_written_;
+ // true if affinity cookie is generated for this request.
+ bool new_affinity_cookie_;
+ // true if eof is received from client before sending header fields
+ // to backend.
+ bool blocked_request_data_eof_;
+ // true if request contains "expect: 100-continue" header field.
+ bool expect_100_continue_;
+ bool stop_reading_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_H
diff --git a/src/shrpx_downstream_connection.cc b/src/shrpx_downstream_connection.cc
new file mode 100644
index 0000000..16a2d6f
--- /dev/null
+++ b/src/shrpx_downstream_connection.cc
@@ -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 "shrpx_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+DownstreamConnection::DownstreamConnection()
+ : 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_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h
new file mode 100644
index 0000000..8efdcbe
--- /dev/null
+++ b/src/shrpx_downstream_connection.h
@@ -0,0 +1,81 @@
+/*
+ * 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 <memory>
+
+#include "shrpx_io_control.h"
+
+namespace shrpx {
+
+class ClientHandler;
+class Upstream;
+class Downstream;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+
+class DownstreamConnection {
+public:
+ DownstreamConnection();
+ 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 *upstream) = 0;
+
+ // true if this object is poolable.
+ virtual bool poolable() const = 0;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const = 0;
+ virtual DownstreamAddr *get_addr() const = 0;
+
+ void set_client_handler(ClientHandler *client_handler);
+ ClientHandler *get_client_handler();
+ Downstream *get_downstream();
+
+protected:
+ 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
index 0000000..0ee66b6
--- /dev/null
+++ b/src/shrpx_downstream_connection_pool.cc
@@ -0,0 +1,66 @@
+/*
+ * 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() { remove_all(); }
+
+void DownstreamConnectionPool::remove_all() {
+ for (auto dconn : pool_) {
+ delete dconn;
+ }
+
+ pool_.clear();
+}
+
+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 it = std::begin(pool_);
+ auto dconn = std::unique_ptr<DownstreamConnection>(*it);
+ pool_.erase(it);
+
+ 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
index 0000000..34dc30d
--- /dev/null
+++ b/src/shrpx_downstream_connection_pool.h
@@ -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_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);
+ void remove_all();
+
+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
index 0000000..f8906e8
--- /dev/null
+++ b/src/shrpx_downstream_queue.cc
@@ -0,0 +1,175 @@
+/*
+ * 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(ImmutableString &&key)
+ : key(std::move(key)), 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() {
+ dlist_delete_all(downstreams_);
+ for (auto &p : host_entries_) {
+ auto &ent = p.second;
+ dlist_delete_all(ent.blocked);
+ }
+}
+
+void DownstreamQueue::add_pending(std::unique_ptr<Downstream> downstream) {
+ downstream->set_dispatch_state(DispatchState::PENDING);
+ downstreams_.append(downstream.release());
+}
+
+void DownstreamQueue::mark_failure(Downstream *downstream) {
+ downstream->set_dispatch_state(DispatchState::FAILURE);
+}
+
+DownstreamQueue::HostEntry &
+DownstreamQueue::find_host_entry(const StringRef &host) {
+ auto itr = host_entries_.find(host);
+ if (itr == std::end(host_entries_)) {
+ auto key = ImmutableString{std::begin(host), std::end(host)};
+ auto key_ref = StringRef{key};
+#ifdef HAVE_STD_MAP_EMPLACE
+ std::tie(itr, std::ignore) =
+ host_entries_.emplace(key_ref, HostEntry(std::move(key)));
+#else // !HAVE_STD_MAP_EMPLACE
+ // for g++-4.7
+ std::tie(itr, std::ignore) = host_entries_.insert(
+ std::make_pair(key_ref, HostEntry(std::move(key))));
+#endif // !HAVE_STD_MAP_EMPLACE
+ }
+ return (*itr).second;
+}
+
+StringRef DownstreamQueue::make_host_key(const StringRef &host) const {
+ return unified_host_ ? StringRef{} : host;
+}
+
+StringRef DownstreamQueue::make_host_key(Downstream *downstream) const {
+ return make_host_key(downstream->request().authority);
+}
+
+void DownstreamQueue::mark_active(Downstream *downstream) {
+ auto &ent = find_host_entry(make_host_key(downstream));
+ ++ent.num_active;
+
+ downstream->set_dispatch_state(DispatchState::ACTIVE);
+}
+
+void DownstreamQueue::mark_blocked(Downstream *downstream) {
+ auto &ent = find_host_entry(make_host_key(downstream));
+
+ downstream->set_dispatch_state(DispatchState::BLOCKED);
+
+ auto link = new BlockedLink{};
+ downstream->attach_blocked_link(link);
+ ent.blocked.append(link);
+}
+
+bool DownstreamQueue::can_activate(const StringRef &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 {
+bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
+ DownstreamQueue::HostEntryMap &host_entries,
+ const StringRef &host) {
+ if (ent.blocked.empty() && ent.num_active == 0) {
+ host_entries.erase(host);
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
+ bool next_blocked) {
+ // Delete downstream when this function returns.
+ auto delptr = std::unique_ptr<Downstream>(downstream);
+
+ downstreams_.remove(downstream);
+
+ auto host = make_host_key(downstream);
+ auto &ent = find_host_entry(host);
+
+ if (downstream->get_dispatch_state() == DispatchState::ACTIVE) {
+ --ent.num_active;
+ } else {
+ // For those downstreams deleted while in blocked state
+ auto link = downstream->detach_blocked_link();
+ if (link) {
+ ent.blocked.remove(link);
+ delete link;
+ }
+ }
+
+ if (remove_host_entry_if_empty(ent, host_entries_, host)) {
+ return nullptr;
+ }
+
+ if (!next_blocked || ent.num_active >= conn_max_per_host_) {
+ return nullptr;
+ }
+
+ auto link = ent.blocked.head;
+
+ if (!link) {
+ return nullptr;
+ }
+
+ auto next_downstream = link->downstream;
+ auto link2 = next_downstream->detach_blocked_link();
+ // This is required with --disable-assert.
+ (void)link2;
+ assert(link2 == link);
+ ent.blocked.remove(link);
+ delete link;
+ remove_host_entry_if_empty(ent, host_entries_, host);
+
+ return next_downstream;
+}
+
+Downstream *DownstreamQueue::get_downstreams() const {
+ return downstreams_.head;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_queue.h b/src/shrpx_downstream_queue.h
new file mode 100644
index 0000000..a5b980f
--- /dev/null
+++ b/src/shrpx_downstream_queue.h
@@ -0,0 +1,116 @@
+/*
+ * 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 <cinttypes>
+#include <map>
+#include <set>
+#include <memory>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+// Link entry in HostEntry.blocked and downstream because downstream
+// could be deleted in anytime and we'd like to find Downstream in
+// O(1). Downstream has field to link back to this object.
+struct BlockedLink {
+ Downstream *downstream;
+ BlockedLink *dlnext, *dlprev;
+};
+
+class DownstreamQueue {
+public:
+ struct HostEntry {
+ HostEntry(ImmutableString &&key);
+
+ HostEntry(HostEntry &&) = default;
+ HostEntry &operator=(HostEntry &&) = default;
+
+ HostEntry(const HostEntry &) = delete;
+ HostEntry &operator=(const HostEntry &) = delete;
+
+ // Key that associates this object
+ ImmutableString key;
+ // Set of stream ID that blocked by conn_max_per_host_.
+ DList<BlockedLink> blocked;
+ // The number of connections currently made to this host.
+ size_t num_active;
+ };
+
+ using HostEntryMap = std::map<StringRef, HostEntry>;
+
+ // conn_max_per_host == 0 means no limit for downstream connection.
+ DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true);
+ ~DownstreamQueue();
+ // Add |downstream| to this queue. This is entry point for
+ // Downstream object.
+ void add_pending(std::unique_ptr<Downstream> downstream);
+ // Set |downstream| to failure state, which means that downstream
+ // failed to connect to backend.
+ void mark_failure(Downstream *downstream);
+ // Set |downstream| to active state, which means that downstream
+ // connection has started.
+ void mark_active(Downstream *downstream);
+ // Set |downstream| to blocked state, which means that download
+ // connection was blocked because conn_max_per_host_ limit.
+ void mark_blocked(Downstream *downstream);
+ // Returns true if we can make downstream connection to given
+ // |host|.
+ bool can_activate(const StringRef &host) const;
+ // Removes and frees |downstream| object. If |downstream| is in
+ // DispatchState::ACTIVE, and |next_blocked| is true, this function
+ // may return Downstream object with the same target host in
+ // DispatchState::BLOCKED if its connection is now not blocked by
+ // conn_max_per_host_ limit.
+ Downstream *remove_and_get_blocked(Downstream *downstream,
+ bool next_blocked = true);
+ Downstream *get_downstreams() const;
+ HostEntry &find_host_entry(const StringRef &host);
+ StringRef make_host_key(const StringRef &host) const;
+ StringRef make_host_key(Downstream *downstream) const;
+
+private:
+ // Per target host structure to keep track of the number of
+ // connections to the same host.
+ HostEntryMap host_entries_;
+ DList<Downstream> downstreams_;
+ // Maximum number of concurrent connections to the same host.
+ size_t conn_max_per_host_;
+ // 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
index 0000000..6100b18
--- /dev/null
+++ b/src/shrpx_downstream_test.cc
@@ -0,0 +1,231 @@
+/*
+ * 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_field_store_append_last_header(void) {
+ BlockAllocator balloc(16, 16);
+ FieldStore fs(balloc, 0);
+ fs.alloc_add_header_name(StringRef::from_lit("alpha"));
+ auto bravo = StringRef::from_lit("BRAVO");
+ fs.append_last_header_key(bravo.c_str(), bravo.size());
+ // Add more characters so that relloc occurs
+ auto golf = StringRef::from_lit("golF0123456789");
+ fs.append_last_header_key(golf.c_str(), golf.size());
+
+ auto charlie = StringRef::from_lit("Charlie");
+ fs.append_last_header_value(charlie.c_str(), charlie.size());
+ auto delta = StringRef::from_lit("deltA");
+ fs.append_last_header_value(delta.c_str(), delta.size());
+ // Add more characters so that relloc occurs
+ auto echo = StringRef::from_lit("echo0123456789");
+ fs.append_last_header_value(echo.c_str(), echo.size());
+
+ fs.add_header_token(StringRef::from_lit("echo"),
+ StringRef::from_lit("foxtrot"), false, -1);
+
+ auto ans =
+ HeaderRefs{{StringRef::from_lit("alphabravogolf0123456789"),
+ StringRef::from_lit("CharliedeltAecho0123456789")},
+ {StringRef::from_lit("echo"), StringRef::from_lit("foxtrot")}};
+ CU_ASSERT(ans == fs.headers());
+}
+
+void test_downstream_field_store_header(void) {
+ BlockAllocator balloc(16, 16);
+ FieldStore fs(balloc, 0);
+ fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"),
+ false, -1);
+ fs.add_header_token(StringRef::from_lit(":authority"),
+ StringRef::from_lit("1"), false, http2::HD__AUTHORITY);
+ fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("2"), false,
+ http2::HD_CONTENT_LENGTH);
+
+ // By token
+ CU_ASSERT(HeaderRef(StringRef{":authority"}, StringRef{"1"}) ==
+ *fs.header(http2::HD__AUTHORITY));
+ CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
+
+ // By name
+ CU_ASSERT(HeaderRef(StringRef{"alpha"}, StringRef{"0"}) ==
+ *fs.header(StringRef::from_lit("alpha")));
+ CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo")));
+}
+
+void test_downstream_crumble_request_cookie(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+ req.fs.add_header_token(StringRef::from_lit(":method"),
+ StringRef::from_lit("get"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit(":path"),
+ StringRef::from_lit("/"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("alpha; bravo; ; ;; charlie;;"),
+ true, http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit(";delta"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("echo"), false, http2::HD_COOKIE);
+
+ std::vector<nghttp2_nv> nva;
+ d.crumble_request_cookie(nva);
+
+ auto num_cookies = d.count_crumble_request_cookie();
+
+ CU_ASSERT(5 == nva.size());
+ CU_ASSERT(5 == num_cookies);
+
+ HeaderRefs cookies;
+ std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
+ [](const nghttp2_nv &nv) {
+ return HeaderRef(StringRef{nv.name, nv.namelen},
+ StringRef{nv.value, nv.valuelen},
+ nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
+ });
+
+ HeaderRefs ans = {
+ {StringRef::from_lit("cookie"), StringRef::from_lit("alpha")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("bravo")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("charlie")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("delta")},
+ {StringRef::from_lit("cookie"), StringRef::from_lit("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, nullptr, 0);
+ auto &req = d.request();
+
+ req.fs.add_header_token(StringRef::from_lit(":method"),
+ StringRef::from_lit("get"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit(":path"),
+ StringRef::from_lit("/"), false, -1);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("alpha"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("bravo;"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("charlie; "), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("delta;;"), false,
+ http2::HD_COOKIE);
+ CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie());
+}
+
+void test_downstream_rewrite_location_response_header(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+ auto &resp = d.response();
+ d.set_request_downstream_host(StringRef::from_lit("localhost2"));
+ req.authority = StringRef::from_lit("localhost:8443");
+ resp.fs.add_header_token(StringRef::from_lit("location"),
+ StringRef::from_lit("http://localhost2:3000/"),
+ false, http2::HD_LOCATION);
+ d.rewrite_location_response_header(StringRef::from_lit("https"));
+ auto location = resp.fs.header(http2::HD_LOCATION);
+ CU_ASSERT("https://localhost:8443/" == (*location).value);
+}
+
+void test_downstream_supports_non_final_response(void) {
+ Downstream d(nullptr, nullptr, 0);
+ auto &req = d.request();
+
+ req.http_major = 3;
+ req.http_minor = 0;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 1;
+ req.http_minor = 1;
+
+ CU_ASSERT(d.supports_non_final_response());
+
+ req.http_major = 1;
+ req.http_minor = 0;
+
+ CU_ASSERT(!d.supports_non_final_response());
+
+ req.http_major = 0;
+ req.http_minor = 9;
+
+ CU_ASSERT(!d.supports_non_final_response());
+}
+
+void test_downstream_find_affinity_cookie(void) {
+ Downstream d(nullptr, nullptr, 0);
+
+ auto &req = d.request();
+ req.fs.add_header_token(StringRef::from_lit("cookie"), StringRef{}, false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("a=b;;c=d"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("599"), false,
+ http2::HD_CONTENT_LENGTH);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("lb=deadbeef;LB=f1f2f3f4"), false,
+ http2::HD_COOKIE);
+ req.fs.add_header_token(StringRef::from_lit("cookie"),
+ StringRef::from_lit("short=e1e2e3e"), false,
+ http2::HD_COOKIE);
+
+ uint32_t aff;
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("lb"));
+
+ CU_ASSERT(0xdeadbeef == aff);
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("LB"));
+
+ CU_ASSERT(0xf1f2f3f4 == aff);
+
+ aff = d.find_affinity_cookie(StringRef::from_lit("short"));
+
+ CU_ASSERT(0 == aff);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h
new file mode 100644
index 0000000..ef06ea3
--- /dev/null
+++ b/src/shrpx_downstream_test.h
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_downstream_field_store_append_last_header(void);
+void test_downstream_field_store_header(void);
+void test_downstream_crumble_request_cookie(void);
+void test_downstream_assemble_request_cookie(void);
+void test_downstream_rewrite_location_response_header(void);
+void test_downstream_supports_non_final_response(void);
+void test_downstream_find_affinity_cookie(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_TEST_H
diff --git a/src/shrpx_dual_dns_resolver.cc b/src/shrpx_dual_dns_resolver.cc
new file mode 100644
index 0000000..8c6c5c9
--- /dev/null
+++ b/src/shrpx_dual_dns_resolver.cc
@@ -0,0 +1,93 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_dual_dns_resolver.h"
+
+namespace shrpx {
+
+DualDNSResolver::DualDNSResolver(struct ev_loop *loop, int family)
+ : family_(family), resolv4_(loop), resolv6_(loop) {
+ auto cb = [this](DNSResolverStatus, const Address *) {
+ Address result;
+
+ auto status = this->get_status(&result);
+ switch (status) {
+ case DNSResolverStatus::ERROR:
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ return;
+ }
+
+ auto cb = this->get_complete_cb();
+ cb(status, &result);
+ };
+
+ if (family_ == AF_UNSPEC || family_ == AF_INET) {
+ resolv4_.set_complete_cb(cb);
+ }
+ if (family_ == AF_UNSPEC || family_ == AF_INET6) {
+ resolv6_.set_complete_cb(cb);
+ }
+}
+
+int DualDNSResolver::resolve(const StringRef &host) {
+ int rv4 = 0, rv6 = 0;
+ if (family_ == AF_UNSPEC || family_ == AF_INET) {
+ rv4 = resolv4_.resolve(host, AF_INET);
+ }
+ if (family_ == AF_UNSPEC || family_ == AF_INET6) {
+ rv6 = resolv6_.resolve(host, AF_INET6);
+ }
+
+ if (rv4 != 0 && rv6 != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+CompleteCb DualDNSResolver::get_complete_cb() const { return complete_cb_; }
+
+void DualDNSResolver::set_complete_cb(CompleteCb cb) { complete_cb_ = cb; }
+
+DNSResolverStatus DualDNSResolver::get_status(Address *result) const {
+ auto rv6 = resolv6_.get_status(result);
+ if (rv6 == DNSResolverStatus::OK) {
+ return DNSResolverStatus::OK;
+ }
+ auto rv4 = resolv4_.get_status(result);
+ if (rv4 == DNSResolverStatus::OK) {
+ return DNSResolverStatus::OK;
+ }
+ if (rv4 == DNSResolverStatus::RUNNING || rv6 == DNSResolverStatus::RUNNING) {
+ return DNSResolverStatus::RUNNING;
+ }
+ if (rv4 == DNSResolverStatus::ERROR || rv6 == DNSResolverStatus::ERROR) {
+ return DNSResolverStatus::ERROR;
+ }
+ return DNSResolverStatus::IDLE;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_dual_dns_resolver.h b/src/shrpx_dual_dns_resolver.h
new file mode 100644
index 0000000..98065da
--- /dev/null
+++ b/src/shrpx_dual_dns_resolver.h
@@ -0,0 +1,69 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_DUAL_DNS_RESOLVER_H
+#define SHRPX_DUAL_DNS_RESOLVER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+#include "shrpx_dns_resolver.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// DualDNSResolver performs name resolution for both A and AAAA
+// records at the same time. The first successful return (or if we
+// have both successful results, prefer to AAAA) is chosen. This is
+// wrapper around 2 DNSResolver inside. resolve(), get_status(), and
+// how CompleteCb is called have the same semantics with DNSResolver.
+class DualDNSResolver {
+public:
+ // |family| controls IP version preference. If |family| ==
+ // AF_UNSPEC, bot A and AAAA lookups are performed. If |family| ==
+ // AF_INET, only A lookup is performed. If |family| == AF_INET6,
+ // only AAAA lookup is performed.
+ DualDNSResolver(struct ev_loop *loop, int family);
+
+ // Resolves |host|. |host| must be NULL-terminated string.
+ int resolve(const StringRef &host);
+ CompleteCb get_complete_cb() const;
+ void set_complete_cb(CompleteCb cb);
+ DNSResolverStatus get_status(Address *result) const;
+
+private:
+ // IP version preference.
+ int family_;
+ // For A record
+ DNSResolver resolv4_;
+ // For AAAA record
+ DNSResolver resolv6_;
+ CompleteCb complete_cb_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DUAL_DNS_RESOLVER_H
diff --git a/src/shrpx_error.h b/src/shrpx_error.h
new file mode 100644
index 0000000..c89611f
--- /dev/null
+++ b/src/shrpx_error.h
@@ -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_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,
+ SHRPX_ERR_DCONN_CANCELED = -103,
+ SHRPX_ERR_RETRY = -104,
+ SHRPX_ERR_TLS_REQUIRED = -105,
+ SHRPX_ERR_SEND_BLOCKED = -106,
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ERROR_H
diff --git a/src/shrpx_exec.cc b/src/shrpx_exec.cc
new file mode 100644
index 0000000..d5cd091
--- /dev/null
+++ b/src/shrpx_exec.cc
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_exec.h"
+
+#include <cerrno>
+
+#include "shrpx_signal.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+// inspired by h2o_read_command function from h2o project:
+// https://github.com/h2o/h2o
+int exec_read_command(Process &proc, char *const argv[]) {
+ int rv;
+ int pfd[2];
+
+#ifdef O_CLOEXEC
+ if (pipe2(pfd, O_CLOEXEC) == -1) {
+ return -1;
+ }
+#else // !O_CLOEXEC
+ if (pipe(pfd) == -1) {
+ return -1;
+ }
+ util::make_socket_closeonexec(pfd[0]);
+ util::make_socket_closeonexec(pfd[1]);
+#endif // !O_CLOEXEC
+
+ auto closer = defer([&pfd]() {
+ if (pfd[0] != -1) {
+ close(pfd[0]);
+ }
+
+ if (pfd[1] != -1) {
+ close(pfd[1]);
+ }
+ });
+
+ sigset_t oldset;
+
+ rv = shrpx_signal_block_all(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Blocking all signals failed: errno=" << error;
+
+ return -1;
+ }
+
+ auto pid = fork();
+
+ if (pid == 0) {
+ // This is multithreaded program, and we are allowed to use only
+ // async-signal-safe functions here.
+
+ // child process
+ shrpx_signal_unset_worker_proc_ign_handler();
+
+ rv = shrpx_signal_unblock_all();
+ if (rv != 0) {
+ static constexpr char msg[] = "Unblocking all signals failed\n";
+ while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR)
+ ;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ dup2(pfd[1], 1);
+ close(pfd[0]);
+
+ rv = execv(argv[0], argv);
+ if (rv == -1) {
+ static constexpr char msg[] = "Could not execute command\n";
+ while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR)
+ ;
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+ // unreachable
+ }
+
+ // parent process
+ if (pid == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Could not execute command: " << argv[0]
+ << ", fork() failed, errno=" << error;
+ }
+
+ rv = shrpx_signal_set(&oldset);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Restoring all signals failed: errno=" << error;
+
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ if (pid == -1) {
+ return -1;
+ }
+
+ close(pfd[1]);
+ pfd[1] = -1;
+
+ util::make_socket_nonblocking(pfd[0]);
+
+ proc.pid = pid;
+ proc.rfd = pfd[0];
+
+ pfd[0] = -1;
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_exec.h b/src/shrpx_exec.h
new file mode 100644
index 0000000..2d8a770
--- /dev/null
+++ b/src/shrpx_exec.h
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_EXEC_H
+#define SHRPX_EXEC_H
+
+#include "unistd.h"
+
+namespace shrpx {
+
+struct Process {
+ pid_t pid;
+ // fd to read from process
+ int rfd;
+};
+
+// Executes command |argv| after forking current process. The command
+// should not expect to read from stdin. Parent process can read the
+// stdout from command using proc.rfd. On success, this function
+// returns 0, and process information is stored in |proc|. Otherwise,
+// returns -1.
+int exec_read_command(Process &proc, char *const argv[]);
+
+} // namespace shrpx
+
+#endif // SHRPX_EXEC_H
diff --git a/src/shrpx_health_monitor_downstream_connection.cc b/src/shrpx_health_monitor_downstream_connection.cc
new file mode 100644
index 0000000..89e5396
--- /dev/null
+++ b/src/shrpx_health_monitor_downstream_connection.cc
@@ -0,0 +1,116 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_health_monitor_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+HealthMonitorDownstreamConnection::HealthMonitorDownstreamConnection() {}
+
+HealthMonitorDownstreamConnection::~HealthMonitorDownstreamConnection() {}
+
+int HealthMonitorDownstreamConnection::attach_downstream(
+ Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::detach_downstream(
+ Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int HealthMonitorDownstreamConnection::push_request_headers() {
+ downstream_->set_request_header_sent(true);
+ auto src = downstream_->get_blocked_request_buf();
+ auto dest = downstream_->get_request_buf();
+ src->remove(*dest);
+
+ return 0;
+}
+
+int HealthMonitorDownstreamConnection::push_upload_data_chunk(
+ const uint8_t *data, size_t datalen) {
+ return 0;
+}
+
+int HealthMonitorDownstreamConnection::end_upload_data() {
+ auto upstream = downstream_->get_upstream();
+ auto &resp = downstream_->response();
+
+ resp.http_status = 200;
+
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ StringRef::from_lit("0"), false,
+ http2::HD_CONTENT_LENGTH);
+
+ if (upstream->send_reply(downstream_, nullptr, 0) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int HealthMonitorDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ return 0;
+}
+
+void HealthMonitorDownstreamConnection::force_resume_read() {}
+
+int HealthMonitorDownstreamConnection::on_read() { return 0; }
+
+int HealthMonitorDownstreamConnection::on_write() { return 0; }
+
+void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *upstream) {
+}
+
+bool HealthMonitorDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+HealthMonitorDownstreamConnection::get_downstream_addr_group() const {
+ static std::shared_ptr<DownstreamAddrGroup> s;
+ return s;
+}
+
+DownstreamAddr *HealthMonitorDownstreamConnection::get_addr() const {
+ return nullptr;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_health_monitor_downstream_connection.h b/src/shrpx_health_monitor_downstream_connection.h
new file mode 100644
index 0000000..c0eb633
--- /dev/null
+++ b/src/shrpx_health_monitor_downstream_connection.h
@@ -0,0 +1,64 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+class Worker;
+
+class HealthMonitorDownstreamConnection : public DownstreamConnection {
+public:
+ HealthMonitorDownstreamConnection();
+ virtual ~HealthMonitorDownstreamConnection();
+ 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);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc
new file mode 100644
index 0000000..ad32dc9
--- /dev/null
+++ b/src/shrpx_http.cc
@@ -0,0 +1,280 @@
+/*
+ * 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 "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace http {
+
+StringRef create_error_html(BlockAllocator &balloc, unsigned int http_status) {
+ auto &httpconf = get_config()->http;
+
+ const auto &error_pages = httpconf.error_pages;
+ for (const auto &page : error_pages) {
+ if (page.http_status == 0 || page.http_status == http_status) {
+ return StringRef{std::begin(page.content), std::end(page.content)};
+ }
+ }
+
+ auto status_string = http2::stringify_status(balloc, http_status);
+ auto reason_phrase = http2::get_reason_phrase(http_status);
+
+ return concat_string_ref(
+ balloc, StringRef::from_lit(R"(<!DOCTYPE html><html lang="en"><title>)"),
+ status_string, StringRef::from_lit(" "), reason_phrase,
+ StringRef::from_lit("</title><body><h1>"), status_string,
+ StringRef::from_lit(" "), reason_phrase,
+ StringRef::from_lit("</h1><footer>"), httpconf.server_name,
+ StringRef::from_lit("</footer></body></html>"));
+}
+
+StringRef create_forwarded(BlockAllocator &balloc, int params,
+ const StringRef &node_by, const StringRef &node_for,
+ const StringRef &host, const StringRef &proto) {
+ size_t len = 0;
+ if ((params & FORWARDED_BY) && !node_by.empty()) {
+ len += str_size("by=\"") + node_by.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_FOR) && !node_for.empty()) {
+ len += str_size("for=\"") + node_for.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_HOST) && !host.empty()) {
+ len += str_size("host=\"") + host.size() + str_size("\";");
+ }
+ if ((params & FORWARDED_PROTO) && !proto.empty()) {
+ len += str_size("proto=") + proto.size() + str_size(";");
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if ((params & FORWARDED_BY) && !node_by.empty()) {
+ // This must be quoted-string unless it is obfuscated version
+ // (which starts with "_") or some special value (e.g.,
+ // "localhost" for UNIX domain socket), since ':' is not allowed
+ // in token. ':' is used to separate host and port.
+ if (node_by[0] == '_' || node_by[0] == 'l') {
+ p = util::copy_lit(p, "by=");
+ p = std::copy(std::begin(node_by), std::end(node_by), p);
+ p = util::copy_lit(p, ";");
+ } else {
+ p = util::copy_lit(p, "by=\"");
+ p = std::copy(std::begin(node_by), std::end(node_by), p);
+ p = util::copy_lit(p, "\";");
+ }
+ }
+ if ((params & FORWARDED_FOR) && !node_for.empty()) {
+ // We only quote IPv6 literal address only, which starts with '['.
+ if (node_for[0] == '[') {
+ p = util::copy_lit(p, "for=\"");
+ p = std::copy(std::begin(node_for), std::end(node_for), p);
+ p = util::copy_lit(p, "\";");
+ } else {
+ p = util::copy_lit(p, "for=");
+ p = std::copy(std::begin(node_for), std::end(node_for), p);
+ p = util::copy_lit(p, ";");
+ }
+ }
+ if ((params & FORWARDED_HOST) && !host.empty()) {
+ // Just be quoted to skip checking characters.
+ p = util::copy_lit(p, "host=\"");
+ p = std::copy(std::begin(host), std::end(host), p);
+ p = util::copy_lit(p, "\";");
+ }
+ if ((params & FORWARDED_PROTO) && !proto.empty()) {
+ // Scheme production rule only allow characters which are all in
+ // token.
+ p = util::copy_lit(p, "proto=");
+ p = std::copy(std::begin(proto), std::end(proto), p);
+ *p++ = ';';
+ }
+
+ if (iov.base == p) {
+ return StringRef{};
+ }
+
+ --p;
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+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;
+ auto redact = util::strieq_l("authorization", StringRef{p, np});
+ p = np;
+ np = strchr(p, '\n');
+ if (!np) {
+ if (redact) {
+ nhdrs.append(": <redacted>");
+ } else {
+ nhdrs.append(p);
+ }
+ break;
+ }
+ if (redact) {
+ nhdrs.append(": <redacted>\n");
+ } else {
+ 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);
+}
+
+StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
+ uint32_t affinity_cookie,
+ const StringRef &path, bool secure) {
+ static constexpr auto PATH_PREFIX = StringRef::from_lit("; Path=");
+ static constexpr auto SECURE = StringRef::from_lit("; Secure");
+ // <name>=<value>[; Path=<path>][; Secure]
+ size_t len = name.size() + 1 + 8;
+
+ if (!path.empty()) {
+ len += PATH_PREFIX.size() + path.size();
+ }
+ if (secure) {
+ len += SECURE.size();
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ p = std::copy(std::begin(name), std::end(name), p);
+ *p++ = '=';
+ affinity_cookie = htonl(affinity_cookie);
+ p = util::format_hex(p,
+ StringRef{reinterpret_cast<uint8_t *>(&affinity_cookie),
+ reinterpret_cast<uint8_t *>(&affinity_cookie) +
+ sizeof(affinity_cookie)});
+ if (!path.empty()) {
+ p = std::copy(std::begin(PATH_PREFIX), std::end(PATH_PREFIX), p);
+ p = std::copy(std::begin(path), std::end(path), p);
+ }
+ if (secure) {
+ p = std::copy(std::begin(SECURE), std::end(SECURE), p);
+ }
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
+ const StringRef &scheme) {
+ switch (secure) {
+ case SessionAffinityCookieSecure::AUTO:
+ return scheme == "https";
+ case SessionAffinityCookieSecure::YES:
+ return true;
+ default:
+ return false;
+ }
+}
+
+StringRef create_altsvc_header_value(BlockAllocator &balloc,
+ const std::vector<AltSvc> &altsvcs) {
+ // <PROTOID>="<HOST>:<SERVICE>"; <PARAMS>
+ size_t len = 0;
+
+ if (altsvcs.empty()) {
+ return StringRef{};
+ }
+
+ for (auto &altsvc : altsvcs) {
+ len += util::percent_encode_tokenlen(altsvc.protocol_id);
+ len += str_size("=\"");
+ len += util::quote_stringlen(altsvc.host);
+ len += str_size(":");
+ len += altsvc.service.size();
+ len += str_size("\"");
+ if (!altsvc.params.empty()) {
+ len += str_size("; ");
+ len += altsvc.params.size();
+ }
+ }
+
+ // ", " between items.
+ len += (altsvcs.size() - 1) * 2;
+
+ // We will write additional ", " at the end, and cut it later.
+ auto iov = make_byte_ref(balloc, len + 2);
+ auto p = iov.base;
+
+ for (auto &altsvc : altsvcs) {
+ p = util::percent_encode_token(p, altsvc.protocol_id);
+ p = util::copy_lit(p, "=\"");
+ p = util::quote_string(p, altsvc.host);
+ *p++ = ':';
+ p = std::copy(std::begin(altsvc.service), std::end(altsvc.service), p);
+ *p++ = '"';
+ if (!altsvc.params.empty()) {
+ p = util::copy_lit(p, "; ");
+ p = std::copy(std::begin(altsvc.params), std::end(altsvc.params), p);
+ }
+ p = util::copy_lit(p, ", ");
+ }
+
+ p -= 2;
+ *p = '\0';
+
+ assert(static_cast<size_t>(p - iov.base) == len);
+
+ return StringRef{iov.base, p};
+}
+
+bool check_http_scheme(const StringRef &scheme, bool encrypted) {
+ return encrypted ? scheme == "https" : scheme == "http";
+}
+
+} // namespace http
+
+} // namespace shrpx
diff --git a/src/shrpx_http.h b/src/shrpx_http.h
new file mode 100644
index 0000000..18935d8
--- /dev/null
+++ b/src/shrpx_http.h
@@ -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_HTTP_H
+#define SHRPX_HTTP_H
+
+#include "shrpx.h"
+
+#include <string>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_config.h"
+#include "util.h"
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace http {
+
+StringRef create_error_html(BlockAllocator &balloc, unsigned int status_code);
+
+template <typename OutputIt>
+OutputIt create_via_header_value(OutputIt dst, int major, int minor) {
+ *dst++ = static_cast<char>(major + '0');
+ if (major < 2) {
+ *dst++ = '.';
+ *dst++ = static_cast<char>(minor + '0');
+ }
+ return util::copy_lit(dst, " nghttpx");
+}
+
+// Returns generated RFC 7239 Forwarded header field value. The
+// |params| is bitwise-OR of zero or more of shrpx_forwarded_param
+// defined in shrpx_config.h.
+StringRef create_forwarded(BlockAllocator &balloc, int params,
+ const StringRef &node_by, const StringRef &node_for,
+ const StringRef &host, const StringRef &proto);
+
+// 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);
+
+// Creates set-cookie-string for cookie based affinity. If |path| is
+// not empty, "; <path>" is added. If |secure| is true, "; Secure" is
+// added.
+StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
+ uint32_t affinity_cookie,
+ const StringRef &path, bool secure);
+
+// Returns true if |secure| indicates that Secure attribute should be
+// set.
+bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
+ const StringRef &scheme);
+
+// Returns RFC 7838 alt-svc header field value.
+StringRef create_altsvc_header_value(BlockAllocator &balloc,
+ const std::vector<AltSvc> &altsvcs);
+
+// Returns true if either of the following conditions holds:
+// - scheme is https and encrypted is true
+// - scheme is http and encrypted is false
+// Otherwise returns false.
+bool check_http_scheme(const StringRef &scheme, bool encrypted);
+
+} // 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
index 0000000..ee48799
--- /dev/null
+++ b/src/shrpx_http2_downstream_connection.cc
@@ -0,0 +1,621 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include "llhttp.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.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+Http2DownstreamConnection::Http2DownstreamConnection(Http2Session *http2session)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ 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() == DownstreamState::STREAM_CLOSED &&
+ downstream_->get_upgraded()) {
+ // For upgraded connection, send NO_ERROR. Should we consider
+ // request states other than DownstreamState::STREAM_CLOSED ?
+ error_code = NGHTTP2_NO_ERROR;
+ } else {
+ error_code = NGHTTP2_INTERNAL_ERROR;
+ }
+
+ if (http2session_->get_state() == Http2SessionState::CONNECTED &&
+ downstream_->get_downstream_stream_id() != -1) {
+ submit_rst_stream(downstream_, error_code);
+
+ auto &resp = downstream_->response();
+
+ http2session_->consume(downstream_->get_downstream_stream_id(),
+ resp.unconsumed_body_length);
+
+ resp.unconsumed_body_length = 0;
+
+ http2session_->signal_write();
+ }
+ }
+ http2session_->remove_downstream_connection(this);
+
+ 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);
+ http2session_->signal_write();
+
+ downstream_ = downstream;
+ downstream_->reset_downstream_rtimer();
+
+ auto &req = downstream_->request();
+
+ // HTTP/2 disables HTTP Upgrade.
+ if (req.method != HTTP_CONNECT && req.connect_proto == ConnectProto::NONE) {
+ req.upgrade_request = false;
+ }
+
+ return 0;
+}
+
+void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+
+ auto &resp = downstream_->response();
+
+ if (downstream_->get_downstream_stream_id() != -1) {
+ if (submit_rst_stream(downstream) == 0) {
+ http2session_->signal_write();
+ }
+
+ http2session_->consume(downstream_->get_downstream_stream_id(),
+ resp.unconsumed_body_length);
+
+ resp.unconsumed_body_length = 0;
+
+ 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() == Http2SessionState::CONNECTED &&
+ downstream->get_downstream_stream_id() != -1) {
+ switch (downstream->get_response_state()) {
+ case DownstreamState::MSG_RESET:
+ case DownstreamState::MSG_BAD_HEADER:
+ case DownstreamState::MSG_COMPLETE:
+ break;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream
+ << ", stream_id="
+ << downstream->get_downstream_stream_id()
+ << ", error_code=" << error_code;
+ }
+ 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) {
+ int rv;
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+ if (!sd || !sd->dconn) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ auto dconn = sd->dconn;
+ 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;
+ }
+ const auto &req = downstream->request();
+ auto input = downstream->get_request_buf();
+
+ auto nread = std::min(input->rleft(), length);
+ auto input_empty = input->rleft() == nread;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (input_empty &&
+ downstream->get_request_state() == DownstreamState::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.
+ (!req.upgrade_request ||
+ (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE &&
+ !downstream->get_upgraded()))) {
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ const auto &trailers = req.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(trailers.size());
+ http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+ }
+ }
+
+ 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 (!downstream_) {
+ return 0;
+ }
+ if (!http2session_->can_push_request(downstream_)) {
+ // 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.
+ downstream_->set_request_pending(true);
+ http2session_->start_checking_connection();
+ return 0;
+ }
+
+ downstream_->set_request_pending(false);
+
+ const auto &req = downstream_->request();
+
+ if (req.connect_proto != ConnectProto::NONE &&
+ !http2session_->get_allow_connect_proto()) {
+ return -1;
+ }
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+ auto &http2conf = config->http2;
+
+ auto no_host_rewrite = httpconf.no_host_rewrite || config->http2_proxy ||
+ req.regular_connect_method();
+
+ // http2session_ has already in CONNECTED state, so we can get
+ // addr_idx here.
+ const auto &downstream_hostport = http2session_->get_addr()->hostport;
+
+ // For HTTP/1.0 request, there is no authority in request. In that
+ // case, we use backend server's host nonetheless.
+ auto authority = StringRef(downstream_hostport);
+
+ if (no_host_rewrite && !req.authority.empty()) {
+ authority = req.authority;
+ }
+
+ downstream_->set_request_downstream_host(authority);
+
+ size_t num_cookies = 0;
+ if (!http2conf.no_cookie_crumbling) {
+ num_cookies = downstream_->count_crumble_request_cookie();
+ }
+
+ // 11 means:
+ // 1. :method
+ // 2. :scheme
+ // 3. :path
+ // 4. :authority (or host)
+ // 5. :protocol (optional)
+ // 6. via (optional)
+ // 7. x-forwarded-for (optional)
+ // 8. x-forwarded-proto (optional)
+ // 9. te (optional)
+ // 10. forwarded (optional)
+ // 11. early-data (optional)
+ auto nva = std::vector<nghttp2_nv>();
+ nva.reserve(req.fs.headers().size() + 11 + num_cookies +
+ httpconf.add_request_headers.size());
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET) {
+ nva.push_back(http2::make_nv_ll(":method", "CONNECT"));
+ nva.push_back(http2::make_nv_ll(":protocol", "websocket"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(
+ ":method", http2::to_method_string(req.method)));
+ }
+
+ if (!req.regular_connect_method()) {
+ assert(!req.scheme.empty());
+
+ auto addr = http2session_->get_addr();
+ assert(addr);
+ // We will handle more protocol scheme upgrade in the future.
+ if (addr->tls && addr->upgrade_scheme && req.scheme == "http") {
+ nva.push_back(http2::make_nv_ll(":scheme", "https"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme));
+ }
+
+ if (req.method == HTTP_OPTIONS && req.path.empty()) {
+ nva.push_back(http2::make_nv_ll(":path", "*"));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
+ }
+
+ if (!req.no_authority || req.connect_proto != ConnectProto::NONE) {
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy("host", authority));
+ }
+ } else {
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+ }
+
+ auto &fwdconf = httpconf.forwarded;
+ auto &xffconf = httpconf.xff;
+ auto &xfpconf = httpconf.xfp;
+ auto &earlydataconf = httpconf.early_data;
+
+ uint32_t build_flags =
+ (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
+ (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
+ (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
+ (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
+ http2::HDOP_STRIP_SEC_WEBSOCKET_KEY;
+
+ http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
+
+ if (!http2conf.no_cookie_crumbling) {
+ downstream_->crumble_request_cookie(nva);
+ }
+
+ auto upstream = downstream_->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto conn = handler->get_connection();
+
+ if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) {
+ nva.push_back(http2::make_nv_ll("early-data", "1"));
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ auto fwd =
+ fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
+
+ if (fwdconf.params) {
+ auto params = fwdconf.params;
+
+ if (config->http2_proxy || req.regular_connect_method()) {
+ params &= ~FORWARDED_PROTO;
+ }
+
+ auto value = http::create_forwarded(
+ balloc, params, handler->get_forwarded_by(),
+ handler->get_forwarded_for(), req.authority, req.scheme);
+
+ if (fwd || !value.empty()) {
+ if (fwd) {
+ if (value.empty()) {
+ value = fwd->value;
+ } else {
+ value = concat_string_ref(balloc, fwd->value,
+ StringRef::from_lit(", "), value);
+ }
+ }
+
+ nva.push_back(http2::make_nv_ls_nocopy("forwarded", value));
+ }
+ } else if (fwd) {
+ nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
+ }
+
+ auto xff = xffconf.strip_incoming ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_FOR);
+
+ if (xffconf.add) {
+ StringRef xff_value;
+ const auto &addr = upstream->get_client_handler()->get_ipaddr();
+ if (xff) {
+ xff_value = concat_string_ref(balloc, xff->value,
+ StringRef::from_lit(", "), addr);
+ } else {
+ xff_value = addr;
+ }
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value));
+ } else if (xff) {
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
+ }
+
+ if (!config->http2_proxy && !req.regular_connect_method()) {
+ auto xfp = xfpconf.strip_incoming
+ ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_PROTO);
+
+ if (xfpconf.add) {
+ StringRef xfp_value;
+ // We use same protocol with :scheme header field
+ if (xfp) {
+ xfp_value = concat_string_ref(balloc, xfp->value,
+ StringRef::from_lit(", "), req.scheme);
+ } else {
+ xfp_value = req.scheme;
+ }
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp_value));
+ } else if (xfp) {
+ nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp->value));
+ }
+ }
+
+ auto via = req.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ size_t vialen = 16;
+ if (via) {
+ vialen += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, vialen + 1);
+ auto p = iov.base;
+
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, req.http_major, req.http_minor);
+ *p = '\0';
+
+ nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ auto te = req.fs.header(http2::HD_TE);
+ // HTTP/1 upstream request can contain keyword other than
+ // "trailers". We just forward "trailers".
+ // TODO more strict handling required here.
+ if (te && http2::contains_trailers(te->value)) {
+ nva.push_back(http2::make_nv_ll("te", "trailers"));
+ }
+
+ for (auto &p : httpconf.add_request_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (util::streq_l("authorization", nv.name, nv.namelen)) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST
+ << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ DCLOG(INFO, this) << "HTTP request headers\n" << ss.str();
+ }
+
+ auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
+
+ nghttp2_data_provider *data_prdptr = nullptr;
+ nghttp2_data_provider data_prd;
+
+ // Add body as long as transfer-encoding is given even if
+ // req.fs.content_length == 0 to forward trailer fields.
+ if (req.method == HTTP_CONNECT || req.connect_proto != ConnectProto::NONE ||
+ transfer_encoding || req.fs.content_length > 0 || req.http2_expect_body) {
+ // Request-body is expected.
+ data_prd = {{}, http2_data_read_callback};
+ data_prdptr = &data_prd;
+ }
+
+ rv = http2session_->submit_request(this, nva.data(), nva.size(), data_prdptr);
+ if (rv != 0) {
+ DCLOG(FATAL, this) << "nghttp2_submit_request() failed";
+ return -1;
+ }
+
+ if (data_prdptr) {
+ downstream_->reset_downstream_wtimer();
+ }
+
+ http2session_->signal_write();
+ return 0;
+}
+
+int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (!downstream_->get_request_header_sent()) {
+ auto output = downstream_->get_blocked_request_buf();
+ auto &req = downstream_->request();
+ output->append(data, datalen);
+ req.unconsumed_body_length += datalen;
+ return 0;
+ }
+
+ 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() {
+ if (!downstream_->get_request_header_sent()) {
+ downstream_->set_blocked_request_data_eof(true);
+ return 0;
+ }
+
+ 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() != Http2SessionState::CONNECTED) {
+ return 0;
+ }
+
+ if (!downstream_ || downstream_->get_downstream_stream_id() == -1) {
+ return 0;
+ }
+
+ if (consumed > 0) {
+ rv = http2session_->consume(downstream_->get_downstream_stream_id(),
+ consumed);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto &resp = downstream_->response();
+
+ resp.unconsumed_body_length -= 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_timeout() {
+ if (!downstream_) {
+ return 0;
+ }
+
+ return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR);
+}
+
+const std::shared_ptr<DownstreamAddrGroup> &
+Http2DownstreamConnection::get_downstream_addr_group() const {
+ return http2session_->get_downstream_addr_group();
+}
+
+DownstreamAddr *Http2DownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h
new file mode 100644
index 0000000..0fc7d91
--- /dev/null
+++ b/src/shrpx_http2_downstream_connection.h
@@ -0,0 +1,88 @@
+/*
+ * 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(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) {}
+
+ // This object is not poolable because we don't have facility to
+ // migrate to another Http2Session object.
+ virtual bool poolable() const { return false; }
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ 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);
+
+ Http2DownstreamConnection *dlnext, *dlprev;
+
+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
index 0000000..f58ed2f
--- /dev/null
+++ b/src/shrpx_http2_session.cc
@@ -0,0 +1,2426 @@
+/*
+ * 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>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_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_tls.h"
+#include "shrpx_http.h"
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "tls.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr ev_tstamp CONNCHK_TIMEOUT = 5.;
+constexpr ev_tstamp CONNCHK_PING_TIMEOUT = 1.;
+} // namespace
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 32_k;
+} // namespace
+
+namespace {
+void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+
+ ev_timer_stop(loop, w);
+
+ switch (http2session->get_connection_check_state()) {
+ case ConnectionCheck::STARTED:
+ // ping timeout; disconnect
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "ping timeout";
+ }
+
+ delete http2session;
+
+ return;
+ default:
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "connection check required";
+ }
+ http2session->set_connection_check_state(ConnectionCheck::REQUIRED);
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "SETTINGS timeout";
+ }
+
+ downstream_failure(http2session->get_addr(), http2session->get_raddr());
+
+ if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
+ delete http2session;
+
+ 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 (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Timeout";
+ }
+
+ http2session->on_timeout();
+
+ delete http2session;
+}
+} // 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);
+ rv = http2session->do_read();
+ if (rv != 0) {
+ delete http2session;
+
+ return;
+ }
+ http2session->connection_alive();
+}
+} // 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);
+ rv = http2session->do_write();
+ if (rv != 0) {
+ delete http2session;
+
+ return;
+ }
+ http2session->reset_connection_check_timer_if_not_checking();
+}
+} // namespace
+
+namespace {
+void initiate_connection_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+ ev_timer_stop(loop, w);
+ if (http2session->initiate_connection() != 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Could not initiate backend connection";
+ }
+
+ delete http2session;
+
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+ auto http2session = static_cast<Http2Session *>(w->data);
+ http2session->check_retire();
+}
+} // namespace
+
+Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ Worker *worker,
+ const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr)
+ : dlnext(nullptr),
+ dlprev(nullptr),
+ conn_(loop, -1, nullptr, worker->get_mcpool(),
+ group->shared_addr->timeout.write, group->shared_addr->timeout.read,
+ {}, {}, writecb, readcb, timeoutcb, this,
+ get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP2),
+ wb_(worker->get_mcpool()),
+ worker_(worker),
+ ssl_ctx_(ssl_ctx),
+ group_(group),
+ addr_(addr),
+ session_(nullptr),
+ raddr_(nullptr),
+ state_(Http2SessionState::DISCONNECTED),
+ connection_check_state_(ConnectionCheck::NONE),
+ freelist_zone_(FreelistZone::NONE),
+ settings_recved_(false),
+ allow_connect_proto_(false) {
+ read_ = write_ = &Http2Session::noop;
+
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ // We will reuse this many times, so use repeat timeout value. The
+ // timeout value is set later.
+ ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 0.);
+
+ connchk_timer_.data = this;
+
+ // SETTINGS ACK timeout is 10 seconds for now. We will reuse this
+ // many times, so use repeat timeout value.
+ ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.);
+
+ settings_timer_.data = this;
+
+ ev_timer_init(&initiate_connection_timer_, initiate_connection_cb, 0., 0.);
+ initiate_connection_timer_.data = this;
+
+ ev_prepare_init(&prep_, prepare_cb);
+ prep_.data = this;
+ ev_prepare_start(loop, &prep_);
+}
+
+Http2Session::~Http2Session() {
+ exclude_from_scheduling();
+ disconnect(should_hard_fail());
+}
+
+int Http2Session::disconnect(bool hard) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Disconnecting";
+ }
+ nghttp2_session_del(session_);
+ session_ = nullptr;
+
+ wb_.reset();
+
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+ dns_tracker->cancel(dns_query_.get());
+ }
+
+ conn_.rlimit.stopw();
+ conn_.wlimit.stopw();
+
+ ev_prepare_stop(conn_.loop, &prep_);
+
+ ev_timer_stop(conn_.loop, &initiate_connection_timer_);
+ ev_timer_stop(conn_.loop, &settings_timer_);
+ ev_timer_stop(conn_.loop, &connchk_timer_);
+
+ read_ = write_ = &Http2Session::noop;
+
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ conn_.disconnect();
+
+ if (proxy_htp_) {
+ proxy_htp_.reset();
+ }
+
+ connection_check_state_ = ConnectionCheck::NONE;
+ state_ = Http2SessionState::DISCONNECTED;
+
+ // When deleting Http2DownstreamConnection, it calls this object's
+ // remove_downstream_connection(). The multiple
+ // Http2DownstreamConnection objects belong to the same
+ // ClientHandler object if upstream is h2. So be careful when you
+ // delete ClientHandler here.
+ //
+ // We allow creating new pending Http2DownstreamConnection with this
+ // object. Upstream::on_downstream_reset() may add
+ // Http2DownstreamConnection to another Http2Session.
+
+ for (auto dc = dconns_.head; dc;) {
+ auto next = dc->dlnext;
+ auto downstream = dc->get_downstream();
+ auto upstream = downstream->get_upstream();
+
+ // Failure is allowed only for HTTP/1 upstream where upstream is
+ // not shared by multiple Downstreams.
+ if (upstream->on_downstream_reset(downstream, hard) != 0) {
+ delete upstream->get_client_handler();
+ }
+
+ // dc was deleted
+ dc = next;
+ }
+
+ auto streams = std::move(streams_);
+ for (auto s = streams.head; s;) {
+ auto next = s->dlnext;
+ delete s;
+ s = next;
+ }
+
+ return 0;
+}
+
+int Http2Session::resolve_name() {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host, [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *resolved_addr_ = *result;
+ util::set_port(*this->resolved_addr_, this->addr_->port);
+ }
+
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ delete this;
+ }
+ });
+ resolved_addr_ = std::make_unique<Address>();
+ auto dns_tracker = worker_->get_dns_tracker();
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ return -1;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ state_ = Http2SessionState::RESOLVING_NAME;
+ return 0;
+ case DNSResolverStatus::OK:
+ util::set_port(*resolved_addr_, addr_->port);
+ return 0;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ nullptr, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ nullptr, // llhttp_data_cb on_header_field;
+ nullptr, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ nullptr, // llhttp_data_cb on_body;
+ nullptr, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+int Http2Session::initiate_connection() {
+ int rv = 0;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+
+ if (state_ == Http2SessionState::DISCONNECTED ||
+ state_ == Http2SessionState::RESOLVING_NAME) {
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return -1;
+ }
+ }
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ const auto &proxy = get_config()->downstream_http_proxy;
+ if (!proxy.host.empty() && state_ == Http2SessionState::DISCONNECTED) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connecting to the proxy " << proxy.host << ":"
+ << proxy.port;
+ }
+
+ conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this) << "Backend proxy socket() failed; addr="
+ << util::to_numeric_addr(&proxy.addr)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ rv = connect(conn_.fd, &proxy.addr.su.sa, proxy.addr.len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this) << "Backend proxy connect() failed; addr="
+ << util::to_numeric_addr(&proxy.addr)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+
+ return -1;
+ }
+
+ raddr_ = &proxy.addr;
+
+ worker_blocker->on_success();
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ write_ = &Http2Session::connected;
+
+ on_read_ = &Http2Session::downstream_read_proxy;
+ on_write_ = &Http2Session::downstream_connect_proxy;
+
+ proxy_htp_ = std::make_unique<llhttp_t>();
+ llhttp_init(proxy_htp_.get(), HTTP_RESPONSE, &htp_hooks);
+ proxy_htp_->data = this;
+
+ state_ = Http2SessionState::PROXY_CONNECTING;
+
+ return 0;
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED ||
+ state_ == Http2SessionState::PROXY_CONNECTED ||
+ state_ == Http2SessionState::RESOLVING_NAME) {
+ if (LOG_ENABLED(INFO)) {
+ if (state_ != Http2SessionState::RESOLVING_NAME) {
+ SSLOG(INFO, this) << "Connecting to downstream server";
+ }
+ }
+ if (addr_->tls) {
+ assert(ssl_ctx_);
+
+ if (state_ != Http2SessionState::RESOLVING_NAME) {
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ tls::setup_downstream_http2_alpn(ssl);
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+
+ if (!util::numeric_host(sni_name.c_str())) {
+ // 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.c_str());
+ }
+
+ auto tls_session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (tls_session) {
+ SSL_set_session(conn_.tls.ssl, tls_session);
+ SSL_SESSION_free(tls_session);
+ }
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ if (addr_->dns) {
+ rv = resolve_name();
+ if (rv != 0) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ return 0;
+ }
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+ }
+
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ if (dns_query_->status == DNSResolverStatus::ERROR) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ assert(dns_query_->status == DNSResolverStatus::OK);
+ state_ = Http2SessionState::DISCONNECTED;
+ dns_query_.reset();
+ raddr_ = resolved_addr_.get();
+ }
+
+ // If state_ == Http2SessionState::PROXY_CONNECTED, we have
+ // connected to the proxy using conn_.fd and tunnel has been
+ // established.
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ assert(conn_.fd == -1);
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd,
+ // TODO maybe not thread-safe?
+ const_cast<sockaddr *>(&raddr_->su.sa), raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ }
+
+ conn_.prepare_client_handshake();
+ } else {
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ // Without TLS and proxy.
+ if (addr_->dns) {
+ rv = resolve_name();
+ if (rv != 0) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ return 0;
+ }
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+ }
+
+ if (state_ == Http2SessionState::RESOLVING_NAME) {
+ if (dns_query_->status == DNSResolverStatus::ERROR) {
+ downstream_failure(addr_, nullptr);
+ return -1;
+ }
+ assert(dns_query_->status == DNSResolverStatus::OK);
+ state_ = Http2SessionState::DISCONNECTED;
+ dns_query_.reset();
+ raddr_ = resolved_addr_.get();
+ }
+
+ if (state_ == Http2SessionState::DISCONNECTED) {
+ // Without TLS and proxy.
+ assert(conn_.fd == -1);
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ worker_blocker->on_failure();
+ return -1;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd, const_cast<sockaddr *>(&raddr_->su.sa),
+ raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ SSLOG(WARN, this)
+ << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ }
+ }
+
+ // We have been already connected when no TLS and proxy is used.
+ if (state_ == Http2SessionState::PROXY_CONNECTED) {
+ on_read_ = &Http2Session::read_noop;
+ on_write_ = &Http2Session::write_noop;
+
+ return connected();
+ }
+
+ write_ = &Http2Session::connected;
+
+ state_ = Http2SessionState::CONNECTING;
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+ }
+
+ // Unreachable
+ assert(0);
+
+ return 0;
+}
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ auto http2session = static_cast<Http2Session *>(htp->data);
+
+ // We only read HTTP header part. If tunneling succeeds, response
+ // body is a different protocol (HTTP/2 in this case), we don't read
+ // them here.
+
+ // We just check status code here
+ if (htp->status_code / 100 == 2) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session) << "Tunneling success";
+ }
+ http2session->set_state(Http2SessionState::PROXY_CONNECTED);
+
+ return HPE_PAUSED;
+ }
+
+ SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code;
+ http2session->set_state(Http2SessionState::PROXY_FAILED);
+
+ return HPE_PAUSED;
+}
+} // namespace
+
+int Http2Session::downstream_read_proxy(const uint8_t *data, size_t datalen) {
+ auto htperr = llhttp_execute(proxy_htp_.get(),
+ reinterpret_cast<const char *>(data), datalen);
+ if (htperr == HPE_PAUSED) {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTED:
+ // Initiate SSL/TLS handshake through established tunnel.
+ if (initiate_connection() != 0) {
+ return -1;
+ }
+ return 0;
+ case Http2SessionState::PROXY_FAILED:
+ return -1;
+ default:
+ break;
+ }
+ // should not be here
+ assert(0);
+ }
+
+ if (htperr != HPE_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::downstream_connect_proxy() {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connected to the proxy";
+ }
+
+ std::string req = "CONNECT ";
+ req.append(addr_->hostport.c_str(), addr_->hostport.size());
+ if (addr_->port == 80 || addr_->port == 443) {
+ req += ':';
+ req += util::utos(addr_->port);
+ }
+ req += " HTTP/1.1\r\nHost: ";
+ req += addr_->host;
+ req += "\r\n";
+ const auto &proxy = get_config()->downstream_http_proxy;
+ if (!proxy.userinfo.empty()) {
+ req += "Proxy-Authorization: Basic ";
+ req += base64::encode(std::begin(proxy.userinfo), std::end(proxy.userinfo));
+ req += "\r\n";
+ }
+ req += "\r\n";
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "HTTP proxy request headers\n" << req;
+ }
+ wb_.append(req);
+
+ on_write_ = &Http2Session::write_noop;
+
+ signal_write();
+ return 0;
+}
+
+void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
+ dconns_.append(dconn);
+ ++addr_->num_dconn;
+}
+
+void Http2Session::remove_downstream_connection(
+ Http2DownstreamConnection *dconn) {
+ --addr_->num_dconn;
+ dconns_.remove(dconn);
+ dconn->detach_stream_data();
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Remove downstream";
+ }
+
+ if (freelist_zone_ == FreelistZone::NONE && !max_concurrency_reached()) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+
+ add_to_extra_freelist();
+ }
+}
+
+void Http2Session::remove_stream_data(StreamData *sd) {
+ streams_.remove(sd);
+ if (sd->dconn) {
+ sd->dconn->detach_stream_data();
+ }
+ delete sd;
+}
+
+int Http2Session::submit_request(Http2DownstreamConnection *dconn,
+ const nghttp2_nv *nva, size_t nvlen,
+ const nghttp2_data_provider *data_prd) {
+ assert(state_ == Http2SessionState::CONNECTED);
+ auto sd = std::make_unique<StreamData>();
+ sd->dlnext = sd->dlprev = nullptr;
+ // 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_.append(sd.release());
+
+ return 0;
+}
+
+int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
+ assert(state_ == Http2SessionState::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;
+}
+
+nghttp2_session *Http2Session::get_session() const { return session_; }
+
+int Http2Session::resume_data(Http2DownstreamConnection *dconn) {
+ assert(state_ == Http2SessionState::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();
+ auto upstream = downstream->get_upstream();
+
+ if (downstream->get_downstream_stream_id() % 2 == 0 &&
+ downstream->get_request_state() == DownstreamState::INITIAL) {
+ // Downstream is canceled in backend before it is submitted in
+ // frontend session.
+
+ // This will avoid to send RST_STREAM to backend
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ upstream->cancel_premature_downstream(downstream);
+ } else {
+ if (downstream->get_upgraded() && downstream->get_response_state() ==
+ DownstreamState::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(DownstreamState::MSG_COMPLETE);
+ } else if (error_code == NGHTTP2_NO_ERROR) {
+ switch (downstream->get_response_state()) {
+ case DownstreamState::MSG_COMPLETE:
+ case DownstreamState::MSG_BAD_HEADER:
+ break;
+ default:
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ } else if (downstream->get_response_state() !=
+ DownstreamState::MSG_BAD_HEADER) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET &&
+ downstream->get_response_rst_stream_error_code() ==
+ NGHTTP2_NO_ERROR) {
+ downstream->set_response_rst_stream_error_code(error_code);
+ }
+ 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() {
+ auto &downstreamconf = get_config()->http2.downstream;
+
+ ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.);
+ ev_timer_start(conn_.loop, &settings_timer_);
+}
+
+void Http2Session::stop_settings_timer() {
+ ev_timer_stop(conn_.loop, &settings_timer_);
+}
+
+namespace {
+int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ 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();
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
+ !downstream->get_expect_final_response();
+
+ if (resp.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.response_header_field_buffer ||
+ resp.fs.num_fields() >= httpconf.max_response_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too large or many header field size="
+ << resp.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << resp.fs.num_fields() + 1;
+ }
+
+ if (trailer) {
+ // We don't care trailer part exceeds header size limit; just
+ // discard it.
+ return 0;
+ }
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (trailer) {
+ // just store header fields for trailer part
+ resp.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len},
+ no_index, token);
+ return 0;
+ }
+
+ resp.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+ auto promised_sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, promised_stream_id));
+ if (!promised_sd || !promised_sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto promised_downstream = promised_sd->dconn->get_downstream();
+
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ assert(promised_downstream);
+
+ auto &promised_req = promised_downstream->request();
+
+ // We use request header limit for PUSH_PROMISE
+ if (promised_req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ promised_req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too large or many header field size="
+ << promised_req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << promised_req.fs.num_fields() + 1;
+ }
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ promised_downstream->add_rcbuf(name);
+ promised_downstream->add_rcbuf(value);
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ promised_req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len},
+ flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+
+ return 0;
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_invalid_header_callback2(nghttp2_session *session,
+ const nghttp2_frame *frame, nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, 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;
+ }
+
+ int32_t stream_id;
+
+ if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ stream_id = frame->push_promise.promised_stream_id;
+ } else {
+ stream_id = frame->hd.stream_id;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ SSLOG(INFO, http2session)
+ << "Invalid header field for stream_id=" << stream_id
+ << " in frame type=" << static_cast<uint32_t>(frame->hd.type)
+ << ": name=[" << StringRef{namebuf.base, namebuf.len} << "], value=["
+ << StringRef{valuebuf.base, valuebuf.len} << "]";
+ }
+
+ http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ auto http2session = static_cast<Http2Session *>(user_data);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS: {
+ if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
+ frame->headers.cat != NGHTTP2_HCAT_PUSH_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;
+ }
+ return 0;
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto downstream = sd->dconn->get_downstream();
+
+ assert(downstream);
+ assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
+
+ if (http2session->handle_downstream_push_promise(downstream,
+ promised_stream_id) != 0) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ 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 handler = upstream->get_client_handler();
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &nva = resp.fs.headers();
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ downstream->set_expect_final_response(false);
+
+ auto status = resp.fs.header(http2::HD__STATUS);
+ // libnghttp2 guarantees this exists and can be parsed
+ assert(status);
+ auto status_code = http2::parse_http_status_code(status->value);
+
+ resp.http_status = status_code;
+ resp.http_major = 2;
+ resp.http_minor = 0;
+
+ downstream->set_downstream_addr_group(
+ http2session->get_downstream_addr_group());
+ downstream->set_addr(http2session->get_addr());
+
+ 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(DownstreamState::MSG_RESET);
+ }
+
+ return 0;
+ }
+
+ downstream->set_response_state(DownstreamState::HEADER_COMPLETE);
+ downstream->check_upgrade_fulfilled_http2();
+
+ if (downstream->get_upgraded()) {
+ resp.connection_close = true;
+ // On upgrade success, both ends can send data
+ if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) {
+ // If resume_read fails, just drop connection. Not ideal.
+ delete handler;
+ return -1;
+ }
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session)
+ << "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
+ }
+ } else {
+ auto content_length = resp.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp2 guarantees this can be parsed
+ resp.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ if (resp.fs.content_length == -1 && downstream->expect_response_body()) {
+ // Here we have response body but Content-Length is not known in
+ // advance.
+ if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) {
+ // We simply close connection for pre-HTTP/1.1 in this case.
+ resp.connection_close = true;
+ } else {
+ // Otherwise, use chunked encoding to keep upstream connection
+ // open. In HTTP2, we are supposed not to receive
+ // transfer-encoding.
+ resp.fs.add_header_token(StringRef::from_lit("transfer-encoding"),
+ StringRef::from_lit("chunked"), false,
+ http2::HD_TRANSFER_ENCODING);
+ downstream->set_chunked_response(true);
+ }
+ }
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ resp.headers_only = true;
+ }
+
+ if (loggingconf.access.write_early && downstream->accesslog_ready()) {
+ handler->write_accesslog(downstream);
+ downstream->set_accesslog_written(true);
+ }
+
+ rv = upstream->on_downstream_header_complete(downstream);
+ if (rv != 0) {
+ // Handling early return (in other words, response was hijacked by
+ // mruby scripting).
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_CANCEL);
+ } else {
+ http2session->submit_rst_stream(frame->hd.stream_id,
+ NGHTTP2_INTERNAL_ERROR);
+ downstream->set_response_state(DownstreamState::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) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+ 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(DownstreamState::MSG_RESET);
+
+ } else if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+
+ downstream->disable_downstream_rtimer();
+
+ if (downstream->get_response_state() ==
+ DownstreamState::HEADER_COMPLETE) {
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ rv = upstream->on_downstream_body_complete(downstream);
+
+ if (rv != 0) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ }
+ }
+
+ call_downstream_readcb(http2session, downstream);
+ return 0;
+ }
+ case NGHTTP2_HEADERS: {
+ 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 (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
+ frame->headers.cat == NGHTTP2_HCAT_PUSH_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;
+ }
+ }
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_downstream_rtimer();
+
+ if (downstream->get_response_state() ==
+ DownstreamState::HEADER_COMPLETE) {
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->on_downstream_body_complete(downstream);
+
+ if (rv != 0) {
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ }
+ }
+ } else {
+ downstream->reset_downstream_rtimer();
+ }
+
+ // This may delete downstream
+ call_downstream_readcb(http2session, downstream);
+
+ return 0;
+ }
+ 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();
+ downstream->set_response_rst_stream_error_code(
+ frame->rst_stream.error_code);
+ call_downstream_readcb(http2session, downstream);
+ }
+ return 0;
+ }
+ case NGHTTP2_SETTINGS: {
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ http2session->on_settings_received(frame);
+ return 0;
+ }
+
+ http2session->stop_settings_timer();
+
+ auto addr = http2session->get_addr();
+ auto &connect_blocker = addr->connect_blocker;
+
+ connect_blocker->on_success();
+
+ return 0;
+ }
+ case NGHTTP2_PING:
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "PING ACK received";
+ }
+ http2session->connection_alive();
+ }
+ return 0;
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, http2session)
+ << "Received downstream PUSH_PROMISE stream_id="
+ << frame->hd.stream_id
+ << ", promised_stream_id=" << promised_stream_id;
+ }
+
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd || !sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ auto downstream = sd->dconn->get_downstream();
+
+ assert(downstream);
+ assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
+
+ auto promised_sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, promised_stream_id));
+ if (!promised_sd || !promised_sd->dconn) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ auto promised_downstream = promised_sd->dconn->get_downstream();
+
+ assert(promised_downstream);
+
+ if (http2session->handle_downstream_push_promise_complete(
+ downstream, promised_downstream) != 0) {
+ http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
+ return 0;
+ }
+
+ return 0;
+ }
+ case NGHTTP2_GOAWAY:
+ if (LOG_ENABLED(INFO)) {
+ auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+ frame->goaway.opaque_data_len);
+
+ SSLOG(INFO, http2session)
+ << "GOAWAY received: last-stream-id=" << frame->goaway.last_stream_id
+ << ", error_code=" << frame->goaway.error_code
+ << ", debug_data=" << debug_data;
+ }
+ return 0;
+ default:
+ 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->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();
+
+ auto &resp = downstream->response();
+
+ resp.recv_body_length += len;
+ resp.unconsumed_body_length += 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(DownstreamState::MSG_RESET);
+ }
+
+ 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) {
+ 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 (frame->hd.type == NGHTTP2_HEADERS &&
+ frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ downstream->set_request_header_sent(true);
+ auto src = downstream->get_blocked_request_buf();
+ if (src->rleft()) {
+ auto dest = downstream->get_request_buf();
+ src->remove(*dest);
+ if (http2session->resume_data(sd->dconn) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ downstream->ensure_downstream_wtimer();
+ }
+ }
+
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ 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 ||
+ lib_error_code == NGHTTP2_ERR_STREAM_CLOSED ||
+ lib_error_code == NGHTTP2_ERR_STREAM_CLOSING) {
+ return 0;
+ }
+
+ auto sd = static_cast<StreamData *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!sd) {
+ return 0;
+ }
+ if (!sd->dconn) {
+ return 0;
+ }
+ auto downstream = sd->dconn->get_downstream();
+
+ if (lib_error_code == NGHTTP2_ERR_START_STREAM_NOT_ALLOWED) {
+ // Migrate to another downstream connection.
+ auto upstream = downstream->get_upstream();
+
+ if (upstream->on_downstream_reset(downstream, false)) {
+ // This should be done for h1 upstream only. Deleting
+ // ClientHandler for h2 upstream may lead to crash.
+ delete upstream->get_client_handler();
+ }
+
+ return 0;
+ }
+
+ // To avoid stream hanging around, flag DownstreamState::MSG_RESET.
+ downstream->set_response_state(DownstreamState::MSG_RESET);
+ call_downstream_readcb(http2session, downstream);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto PADDING = std::array<uint8_t, 256>{};
+} // namespace
+
+namespace {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, 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 == nullptr) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ auto dconn = sd->dconn;
+ auto downstream = dconn->get_downstream();
+ auto input = downstream->get_request_buf();
+ auto wb = http2session->get_request_buf();
+
+ size_t padlen = 0;
+
+ wb->append(framehd, 9);
+ if (frame->data.padlen > 0) {
+ padlen = frame->data.padlen - 1;
+ wb->append(static_cast<uint8_t>(padlen));
+ }
+
+ input->remove(*wb, length);
+
+ wb->append(PADDING.data(), padlen);
+
+ if (input->rleft() == 0) {
+ downstream->disable_downstream_wtimer();
+ } else {
+ downstream->reset_downstream_wtimer();
+ }
+
+ if (length > 0) {
+ // This is important because it will handle flow control
+ // stuff.
+ if (downstream->get_upstream()->resume_read(SHRPX_NO_BUFFER, downstream,
+ length) != 0) {
+ // In this case, downstream may be deleted.
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ // Here sd->dconn could be nullptr, because
+ // Upstream::resume_read() may delete downstream which will delete
+ // dconn. Is this still really true?
+ }
+
+ return 0;
+}
+} // namespace
+
+nghttp2_session_callbacks *create_http2_downstream_callbacks() {
+ int rv;
+ nghttp2_session_callbacks *callbacks;
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+
+ if (rv != 0) {
+ return nullptr;
+ }
+
+ 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_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ callbacks, on_invalid_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_callback);
+
+ if (get_config()->padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, http::select_padding_callback);
+ }
+
+ return callbacks;
+}
+
+int Http2Session::connection_made() {
+ int rv;
+
+ state_ = Http2SessionState::CONNECTED;
+
+ on_write_ = &Http2Session::downstream_write;
+ on_read_ = &Http2Session::downstream_read;
+
+ if (addr_->tls) {
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ if (!next_proto) {
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+
+ auto proto = StringRef{next_proto, next_proto_len};
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
+ }
+ if (!util::check_h2_is_selected(proto)) {
+ downstream_failure(addr_, raddr_);
+ return -1;
+ }
+ }
+
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks,
+ this, http2conf.downstream.option);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ std::array<nghttp2_settings_entry, 5> entry;
+ size_t nentry = 3;
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = http2conf.downstream.max_concurrent_streams;
+
+ entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ entry[1].value = http2conf.downstream.window_size;
+
+ entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[2].value = 1;
+
+ if (http2conf.no_server_push || config->http2_proxy) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ entry[nentry].value = 0;
+ ++nentry;
+ }
+
+ if (http2conf.downstream.decoder_dynamic_table_size !=
+ NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[nentry].value = http2conf.downstream.decoder_dynamic_table_size;
+ ++nentry;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+ nentry);
+ if (rv != 0) {
+ return -1;
+ }
+
+ rv = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0,
+ http2conf.downstream.connection_window_size);
+ if (rv != 0) {
+ return -1;
+ }
+
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+
+ submit_pending_requests();
+
+ signal_write();
+ return 0;
+}
+
+int Http2Session::do_read() { return read_(*this); }
+int Http2Session::do_write() { return write_(*this); }
+
+int Http2Session::on_read(const uint8_t *data, size_t datalen) {
+ return on_read_(*this, data, datalen);
+}
+
+int Http2Session::on_write() { return on_write_(*this); }
+
+int Http2Session::downstream_read(const uint8_t *data, size_t datalen) {
+ ssize_t rv;
+
+ rv = nghttp2_session_mem_recv(session_, data, datalen);
+ if (rv < 0) {
+ SSLOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ 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() {
+ 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;
+ }
+ wb_.append(data, datalen);
+
+ if (wb_.rleft() >= MAX_BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ 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() {
+ switch (state_) {
+ case Http2SessionState::DISCONNECTED:
+ if (!ev_is_active(&initiate_connection_timer_)) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start connecting to backend server";
+ }
+ // Since the timer is set to 0., these will feed 2 events. We
+ // will stop the timer in the initiate_connection_timer_ to void
+ // 2nd event.
+ ev_timer_start(conn_.loop, &initiate_connection_timer_);
+ ev_feed_event(conn_.loop, &initiate_connection_timer_, 0);
+ }
+ break;
+ case Http2SessionState::CONNECTED:
+ conn_.wlimit.startw();
+ break;
+ default:
+ break;
+ }
+}
+
+struct ev_loop *Http2Session::get_loop() const { return conn_.loop; }
+
+ev_io *Http2Session::get_wev() { return &conn_.wev; }
+
+Http2SessionState Http2Session::get_state() const { return state_; }
+
+void Http2Session::set_state(Http2SessionState 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 Downstream *downstream) const {
+ auto &req = downstream->request();
+ return state_ == Http2SessionState::CONNECTED &&
+ connection_check_state_ == ConnectionCheck::NONE &&
+ (req.connect_proto == ConnectProto::NONE || settings_recved_);
+}
+
+void Http2Session::start_checking_connection() {
+ if (state_ != Http2SessionState::CONNECTED ||
+ connection_check_state_ != ConnectionCheck::REQUIRED) {
+ return;
+ }
+ connection_check_state_ = ConnectionCheck::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, nullptr);
+
+ // set ping timeout and start timer again
+ reset_connection_check_timer(CONNCHK_PING_TIMEOUT);
+
+ signal_write();
+}
+
+void Http2Session::reset_connection_check_timer(ev_tstamp t) {
+ connchk_timer_.repeat = t;
+ ev_timer_again(conn_.loop, &connchk_timer_);
+}
+
+void Http2Session::reset_connection_check_timer_if_not_checking() {
+ if (connection_check_state_ != ConnectionCheck::NONE) {
+ return;
+ }
+
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+}
+
+void Http2Session::connection_alive() {
+ reset_connection_check_timer(CONNCHK_TIMEOUT);
+
+ if (connection_check_state_ == ConnectionCheck::NONE) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connection alive";
+ }
+
+ connection_check_state_ = ConnectionCheck::NONE;
+
+ submit_pending_requests();
+}
+
+void Http2Session::submit_pending_requests() {
+ for (auto dconn = dconns_.head; dconn; dconn = dconn->dlnext) {
+ auto downstream = dconn->get_downstream();
+
+ if (!downstream->get_request_pending() ||
+ !downstream->request_submission_ready()) {
+ continue;
+ }
+
+ auto &req = downstream->request();
+ if (req.connect_proto != ConnectProto::NONE && !settings_recved_) {
+ continue;
+ }
+
+ auto upstream = downstream->get_upstream();
+
+ if (dconn->push_request_headers() != 0) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "backend request failed";
+ }
+
+ upstream->on_downstream_abort_request(downstream, 400);
+
+ continue;
+ }
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
+ }
+}
+
+void Http2Session::set_connection_check_state(ConnectionCheck state) {
+ connection_check_state_ = state;
+}
+
+ConnectionCheck Http2Session::get_connection_check_state() const {
+ return connection_check_state_;
+}
+
+int Http2Session::noop() { return 0; }
+
+int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; }
+
+int Http2Session::write_noop() { return 0; }
+
+int Http2Session::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ SSLOG(WARN, this) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_)
+ << ": errno=" << sock_error;
+
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Connection established";
+ }
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = group_->shared_addr->timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ read_ = &Http2Session::read_clear;
+ write_ = &Http2Session::write_clear;
+
+ if (state_ == Http2SessionState::PROXY_CONNECTING) {
+ return do_write();
+ }
+
+ if (conn_.tls.ssl) {
+ read_ = &Http2Session::tls_handshake;
+ write_ = &Http2Session::tls_handshake;
+
+ return do_write();
+ }
+
+ if (connection_made() != 0) {
+ state_ = Http2SessionState::CONNECT_FAILING;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return write_clear();
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int Http2Session::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(iov.data(), iov.size());
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ // We may have pending data in receive buffer which may
+ // contain part of response body. So keep reading. Invoke
+ // read event to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ write_ = &Http2Session::write_void;
+ break;
+ }
+
+ wb_.drain(nwrite);
+ continue;
+ }
+
+ 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() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto rv = conn_.tls_handshake();
+
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ downstream_failure(addr_, raddr_);
+
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ read_ = &Http2Session::read_tls;
+ write_ = &Http2Session::write_tls;
+
+ if (connection_made() != 0) {
+ state_ = Http2SessionState::CONNECT_FAILING;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http2Session::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return write_tls();
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int Http2Session::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ // We may have pending data in receive buffer which may
+ // contain part of response body. So keep reading. Invoke
+ // read event to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ write_ = &Http2Session::write_void;
+ break;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+ if (wb_.rleft() == 0) {
+ conn_.start_tls_write_idle();
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int Http2Session::write_void() {
+ conn_.wlimit.stopw();
+ return 0;
+}
+
+bool Http2Session::should_hard_fail() const {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTING:
+ case Http2SessionState::PROXY_FAILED:
+ return true;
+ case Http2SessionState::DISCONNECTED: {
+ const auto &proxy = get_config()->downstream_http_proxy;
+ return !proxy.host.empty();
+ }
+ default:
+ return false;
+ }
+}
+
+DownstreamAddr *Http2Session::get_addr() const { return addr_; }
+
+int Http2Session::handle_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ auto upstream = downstream->get_upstream();
+ if (!upstream->push_enabled()) {
+ return -1;
+ }
+
+ auto promised_downstream =
+ upstream->on_downstream_push_promise(downstream, promised_stream_id);
+ if (!promised_downstream) {
+ return -1;
+ }
+
+ // Now we have Downstream object for pushed stream.
+ // promised_downstream->get_stream() still returns 0.
+
+ auto handler = upstream->get_client_handler();
+
+ auto promised_dconn = std::make_unique<Http2DownstreamConnection>(this);
+ promised_dconn->set_client_handler(handler);
+
+ auto ptr = promised_dconn.get();
+
+ if (promised_downstream->attach_downstream_connection(
+ std::move(promised_dconn)) != 0) {
+ return -1;
+ }
+
+ auto promised_sd = std::make_unique<StreamData>();
+
+ nghttp2_session_set_stream_user_data(session_, promised_stream_id,
+ promised_sd.get());
+
+ ptr->attach_stream_data(promised_sd.get());
+ streams_.append(promised_sd.release());
+
+ return 0;
+}
+
+int Http2Session::handle_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ auto &promised_req = promised_downstream->request();
+
+ auto &promised_balloc = promised_downstream->get_block_allocator();
+
+ auto authority = promised_req.fs.header(http2::HD__AUTHORITY);
+ auto path = promised_req.fs.header(http2::HD__PATH);
+ auto method = promised_req.fs.header(http2::HD__METHOD);
+ auto scheme = promised_req.fs.header(http2::HD__SCHEME);
+
+ if (!authority) {
+ authority = promised_req.fs.header(http2::HD_HOST);
+ }
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Unrecognized method: " << method->value;
+ }
+
+ return -1;
+ }
+
+ // TODO Rewrite authority if we enabled rewrite host. But we
+ // really don't know how to rewrite host. Should we use the same
+ // host in associated stream?
+ if (authority) {
+ promised_req.authority = authority->value;
+ }
+ promised_req.method = method_token;
+ // libnghttp2 ensures that we don't have CONNECT method in
+ // PUSH_PROMISE, and guarantees that :scheme exists.
+ if (scheme) {
+ promised_req.scheme = scheme->value;
+ }
+
+ // For server-wide OPTIONS request, path is empty.
+ if (method_token != HTTP_OPTIONS || path->value != "*") {
+ promised_req.path = http2::rewrite_clean_path(promised_balloc, path->value);
+ }
+
+ promised_downstream->inspect_http2_request();
+
+ auto upstream = promised_downstream->get_upstream();
+
+ promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ promised_downstream->set_request_header_sent(true);
+
+ if (upstream->on_downstream_push_promise_complete(downstream,
+ promised_downstream) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+size_t Http2Session::get_num_dconns() const { return dconns_.size(); }
+
+bool Http2Session::max_concurrency_reached(size_t extra) const {
+ if (!session_) {
+ return dconns_.size() + extra >= 100;
+ }
+
+ // If session does not allow further requests, it effectively means
+ // that maximum concurrency is reached.
+ return !nghttp2_session_check_request_allowed(session_) ||
+ dconns_.size() + extra >=
+ nghttp2_session_get_remote_settings(
+ session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
+}
+
+const std::shared_ptr<DownstreamAddrGroup> &
+Http2Session::get_downstream_addr_group() const {
+ return group_;
+}
+
+void Http2Session::add_to_extra_freelist() {
+ if (freelist_zone_ != FreelistZone::NONE) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+
+ freelist_zone_ = FreelistZone::EXTRA;
+ addr_->http2_extra_freelist.append(this);
+}
+
+void Http2Session::remove_from_freelist() {
+ switch (freelist_zone_) {
+ case FreelistZone::NONE:
+ return;
+ case FreelistZone::EXTRA:
+ if (LOG_ENABLED(INFO)) {
+ SSLOG(INFO, this) << "Remove from http2_extra_freelist, addr=" << addr_
+ << ", freelist.size="
+ << addr_->http2_extra_freelist.size();
+ }
+ addr_->http2_extra_freelist.remove(this);
+ break;
+ case FreelistZone::GONE:
+ return;
+ }
+
+ freelist_zone_ = FreelistZone::NONE;
+}
+
+void Http2Session::exclude_from_scheduling() {
+ remove_from_freelist();
+ freelist_zone_ = FreelistZone::GONE;
+}
+
+DefaultMemchunks *Http2Session::get_request_buf() { return &wb_; }
+
+void Http2Session::on_timeout() {
+ switch (state_) {
+ case Http2SessionState::PROXY_CONNECTING: {
+ auto worker_blocker = worker_->get_connect_blocker();
+ worker_blocker->on_failure();
+ break;
+ }
+ case Http2SessionState::CONNECTING:
+ SSLOG(WARN, this) << "Connect time out; addr="
+ << util::to_numeric_addr(raddr_);
+
+ downstream_failure(addr_, raddr_);
+ break;
+ default:
+ break;
+ }
+}
+
+void Http2Session::check_retire() {
+ if (!group_->retired) {
+ return;
+ }
+
+ ev_prepare_stop(conn_.loop, &prep_);
+
+ if (!session_) {
+ return;
+ }
+
+ 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);
+
+ signal_write();
+}
+
+const Address *Http2Session::get_raddr() const { return raddr_; }
+
+void Http2Session::on_settings_received(const nghttp2_frame *frame) {
+ // TODO This effectively disallows nghttpx to change its behaviour
+ // based on the 2nd SETTINGS.
+ if (settings_recved_) {
+ return;
+ }
+
+ settings_recved_ = true;
+
+ for (size_t i = 0; i < frame->settings.niv; ++i) {
+ auto &ent = frame->settings.iv[i];
+ if (ent.settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) {
+ allow_connect_proto_ = true;
+ break;
+ }
+ }
+
+ submit_pending_requests();
+}
+
+bool Http2Session::get_allow_connect_proto() const {
+ return allow_connect_proto_;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h
new file mode 100644
index 0000000..31b2545
--- /dev/null
+++ b/src/shrpx_http2_session.h
@@ -0,0 +1,296 @@
+/*
+ * 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 <unordered_set>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "llhttp.h"
+
+#include "shrpx_connection.h"
+#include "buffer.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Http2DownstreamConnection;
+class Worker;
+class Downstream;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+struct DNSQuery;
+
+struct StreamData {
+ StreamData *dlnext, *dlprev;
+ Http2DownstreamConnection *dconn;
+};
+
+enum class FreelistZone {
+ // Http2Session object is not linked in any freelist.
+ NONE,
+ // Http2Session object is linked in address scope
+ // http2_extra_freelist.
+ EXTRA,
+ // Http2Session object is about to be deleted, and it does not
+ // belong to any linked list.
+ GONE
+};
+
+enum class Http2SessionState {
+ // 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,
+ // Resolving host name
+ RESOLVING_NAME,
+};
+
+enum class ConnectionCheck {
+ // Connection checking is not required
+ NONE,
+ // Connection checking is required
+ REQUIRED,
+ // Connection checking has been started
+ STARTED,
+};
+
+class Http2Session {
+public:
+ Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr);
+ ~Http2Session();
+
+ // If hard is true, all pending requests are abandoned and
+ // associated ClientHandlers will be deleted.
+ int disconnect(bool hard = false);
+ int initiate_connection();
+ int resolve_name();
+
+ void add_downstream_connection(Http2DownstreamConnection *dconn);
+ void remove_downstream_connection(Http2DownstreamConnection *dconn);
+
+ void remove_stream_data(StreamData *sd);
+
+ int submit_request(Http2DownstreamConnection *dconn, 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 terminate_session(uint32_t error_code);
+
+ nghttp2_session *get_session() const;
+
+ int resume_data(Http2DownstreamConnection *dconn);
+
+ int connection_made();
+
+ int do_read();
+ int do_write();
+
+ int on_read(const uint8_t *data, size_t datalen);
+ int on_write();
+
+ int connected();
+ int read_clear();
+ int write_clear();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+ // This is a special write function which just stop write event
+ // watcher.
+ int write_void();
+
+ int downstream_read_proxy(const uint8_t *data, size_t datalen);
+ int downstream_connect_proxy();
+
+ int downstream_read(const uint8_t *data, size_t datalen);
+ int downstream_write();
+
+ int noop();
+ int read_noop(const uint8_t *data, size_t datalen);
+ int write_noop();
+
+ void signal_write();
+
+ struct ev_loop *get_loop() const;
+
+ ev_io *get_wev();
+
+ Http2SessionState get_state() const;
+ void set_state(Http2SessionState 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 Downstream *downstream) const;
+ // Initiates the connection checking if downstream connection has
+ // been established and connection checking is required.
+ void start_checking_connection();
+ // Resets connection check timer to timeout |t|. After timeout, we
+ // require connection checking. If connection checking is already
+ // enabled, this timeout is for PING ACK timeout.
+ void reset_connection_check_timer(ev_tstamp t);
+ void reset_connection_check_timer_if_not_checking();
+ // Signals that connection is alive. Internally
+ // reset_connection_check_timer() is called.
+ void connection_alive();
+ // Change connection check state.
+ void set_connection_check_state(ConnectionCheck state);
+ ConnectionCheck get_connection_check_state() const;
+
+ bool should_hard_fail() const;
+
+ void submit_pending_requests();
+
+ DownstreamAddr *get_addr() const;
+
+ const std::shared_ptr<DownstreamAddrGroup> &get_downstream_addr_group() const;
+
+ int handle_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ int handle_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+
+ // Returns number of downstream connections, including pushed
+ // streams.
+ size_t get_num_dconns() const;
+
+ // Adds to group scope http2_avail_freelist.
+ void add_to_avail_freelist();
+ // Adds to address scope http2_extra_freelist.
+ void add_to_extra_freelist();
+
+ // Removes this object from any freelist. If this object is not
+ // linked from any freelist, this function does nothing.
+ void remove_from_freelist();
+
+ // Removes this object form any freelist, and marks this object as
+ // not schedulable.
+ void exclude_from_scheduling();
+
+ // Returns true if the maximum concurrency is reached. In other
+ // words, the number of currently participated streams in this
+ // session is equal or greater than the max concurrent streams limit
+ // advertised by server. If |extra| is nonzero, it is added to the
+ // number of current concurrent streams when comparing against
+ // server initiated concurrency limit.
+ bool max_concurrency_reached(size_t extra = 0) const;
+
+ DefaultMemchunks *get_request_buf();
+
+ void on_timeout();
+
+ // This is called periodically using ev_prepare watcher, and if
+ // group_ is retired (backend has been replaced), send GOAWAY to
+ // shutdown the connection.
+ void check_retire();
+
+ // Returns address used to connect to backend. Could be nullptr.
+ const Address *get_raddr() const;
+
+ // This is called when SETTINGS frame without ACK flag set is
+ // received.
+ void on_settings_received(const nghttp2_frame *frame);
+
+ bool get_allow_connect_proto() const;
+
+ using ReadBuf = Buffer<8_k>;
+
+ Http2Session *dlnext, *dlprev;
+
+private:
+ Connection conn_;
+ DefaultMemchunks wb_;
+ ev_timer settings_timer_;
+ // This timer has 2 purpose: when it first timeout, set
+ // connection_check_state_ = ConnectionCheck::REQUIRED. After
+ // connection check has started, this timer is started again and
+ // traps PING ACK timeout.
+ ev_timer connchk_timer_;
+ // timer to initiate connection. usually, this fires immediately.
+ ev_timer initiate_connection_timer_;
+ ev_prepare prep_;
+ DList<Http2DownstreamConnection> dconns_;
+ DList<StreamData> streams_;
+ std::function<int(Http2Session &)> read_, write_;
+ std::function<int(Http2Session &, const uint8_t *, size_t)> on_read_;
+ std::function<int(Http2Session &)> on_write_;
+ // Used to parse the response from HTTP proxy
+ std::unique_ptr<llhttp_t> proxy_htp_;
+ Worker *worker_;
+ // NULL if no TLS is configured
+ SSL_CTX *ssl_ctx_;
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ nghttp2_session *session_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr,
+ // resolved_addr_.get(), or HTTP proxy's address structure.
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ Http2SessionState state_;
+ ConnectionCheck connection_check_state_;
+ FreelistZone freelist_zone_;
+ // true if SETTINGS without ACK is received from peer.
+ bool settings_recved_;
+ // true if peer enables RFC 8441 CONNECT protocol.
+ bool allow_connect_proto_;
+};
+
+nghttp2_session_callbacks *create_http2_downstream_callbacks();
+
+} // 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
index 0000000..c9f8a8c
--- /dev/null
+++ b/src/shrpx_http2_upstream.cc
@@ -0,0 +1,2404 @@
+/*
+ * 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.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "app_helper.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 32_k;
+} // namespace
+
+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 = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ return 0;
+ }
+
+ auto &req = downstream->request();
+
+ upstream->consume(stream_id, req.unconsumed_body_length);
+
+ req.unconsumed_body_length = 0;
+
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ upstream->remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+ downstream->set_request_state(DownstreamState::STREAM_CLOSED);
+
+ // 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 &balloc = http->get_downstream()->get_block_allocator();
+
+ auto http2_settings = http->get_downstream()->get_http2_settings();
+ http2_settings = util::to_base64(balloc, http2_settings);
+
+ auto settings_payload = base64::decode(balloc, std::begin(http2_settings),
+ std::end(http2_settings));
+
+ rv = nghttp2_session_upgrade2(
+ session_, settings_payload.byte(), settings_payload.size(),
+ http->get_downstream()->request().method == HTTP_HEAD, nullptr);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, 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);
+
+ auto ptr = downstream.get();
+
+ nghttp2_session_set_stream_user_data(session_, 1, ptr);
+ downstream_queue_.add_pending(std::move(downstream));
+ downstream_queue_.mark_active(ptr);
+
+ // TODO This might not be necessary
+ handler_->stop_read_timer();
+
+ 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_callback2(nghttp2_session *session, const nghttp2_frame *frame,
+ nghttp2_rcbuf *name, nghttp2_rcbuf *value,
+ uint8_t flags, void *user_data) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+ auto config = get_config();
+
+ if (config->http2.upstream.debug.frame_debug) {
+ verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
+ valuebuf.base, valuebuf.len, flags, user_data);
+ }
+ if (frame->hd.type != NGHTTP2_HEADERS) {
+ return 0;
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ auto &req = downstream->request();
+
+ auto &httpconf = config->http;
+
+ if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large or many header field size="
+ << req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << req.fs.num_fields() + 1;
+ }
+
+ // just ignore header fields if this is trailer part.
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+ return 0;
+ }
+
+ if (upstream->error_reply(downstream, 431) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ return 0;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+ // just store header fields for trailer part
+ req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+
+ req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_invalid_header_callback2(nghttp2_session *session,
+ const nghttp2_frame *frame, nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value, uint8_t flags,
+ void *user_data) {
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ auto namebuf = nghttp2_rcbuf_get_buf(name);
+ auto valuebuf = nghttp2_rcbuf_get_buf(value);
+
+ ULOG(INFO, upstream) << "Invalid header field for stream_id="
+ << frame->hd.stream_id << ": name=["
+ << StringRef{namebuf.base, namebuf.len} << "], value=["
+ << StringRef{valuebuf.base, valuebuf.len} << "]";
+ }
+
+ upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+} // 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;
+ }
+
+ upstream->on_start_request(frame);
+
+ return 0;
+}
+} // namespace
+
+void Http2Upstream::on_start_request(const nghttp2_frame *frame) {
+ auto downstream = std::make_unique<Downstream>(this, handler_->get_mcpool(),
+ frame->hd.stream_id);
+ nghttp2_session_set_stream_user_data(session_, frame->hd.stream_id,
+ downstream.get());
+
+ downstream->reset_upstream_rtimer();
+
+ handler_->repeat_read_timer();
+
+ auto &req = downstream->request();
+
+ // Although, we deprecated minor version from HTTP/2, we supply
+ // minor version 0 to use via header field in a conventional way.
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ add_pending_downstream(std::move(downstream));
+
+ ++num_requests_;
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+ if (httpconf.max_requests <= num_requests_) {
+ start_graceful_shutdown();
+ }
+}
+
+int Http2Upstream::on_request_headers(Downstream *downstream,
+ const nghttp2_frame *frame) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ auto &req = downstream->request();
+ req.tstamp = lgconf->tstamp;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ auto &nva = req.fs.headers();
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (nv.name == "authorization") {
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+ }
+ ULOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+ }
+
+ auto config = get_config();
+ auto &dump = config->http2.upstream.debug.dump;
+
+ if (dump.request_header) {
+ http2::dump_nv(dump.request_header, nva);
+ }
+
+ auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp2 guarantees this can be parsed
+ req.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ // presence of mandatory header fields are guaranteed by libnghttp2.
+ auto authority = req.fs.header(http2::HD__AUTHORITY);
+ auto path = req.fs.header(http2::HD__PATH);
+ auto method = req.fs.header(http2::HD__METHOD);
+ auto scheme = req.fs.header(http2::HD__SCHEME);
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (error_reply(downstream, 501) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+ auto faddr = handler_->get_upstream_addr();
+
+ // For HTTP/2 proxy, we require :authority.
+ if (method_token != HTTP_CONNECT && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
+ rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+ return 0;
+ }
+
+ req.method = method_token;
+ if (scheme) {
+ req.scheme = scheme->value;
+ }
+
+ // nghttp2 library guarantees either :authority or host exist
+ if (!authority) {
+ req.no_authority = true;
+ authority = req.fs.header(http2::HD_HOST);
+ }
+
+ if (authority) {
+ req.authority = authority->value;
+ }
+
+ if (path) {
+ if (method_token == HTTP_OPTIONS &&
+ path->value == StringRef::from_lit("*")) {
+ // Server-wide OPTIONS request. Path is empty.
+ } else if (config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ req.path = path->value;
+ } else {
+ req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
+ path->value);
+ }
+ }
+
+ auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
+ if (connect_proto) {
+ if (connect_proto->value != "websocket") {
+ if (error_reply(downstream, 400) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+ req.connect_proto = ConnectProto::WEBSOCKET;
+ }
+
+ if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+ req.http2_expect_body = true;
+ } else if (req.fs.content_length == -1) {
+ // If END_STREAM flag is set to HEADERS frame, we are sure that
+ // content-length is 0.
+ req.fs.content_length = 0;
+ }
+
+ downstream->inspect_http2_request();
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, handler_->get_ssl() != nullptr)) {
+ if (error_reply(downstream, 400) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ start_downstream(downstream);
+
+ return 0;
+}
+
+void Http2Upstream::start_downstream(Downstream *downstream) {
+ if (downstream_queue_.can_activate(downstream->request().authority)) {
+ initiate_downstream(downstream);
+ return;
+ }
+
+ downstream_queue_.mark_blocked(downstream);
+}
+
+void Http2Upstream::initiate_downstream(Downstream *downstream) {
+ int rv;
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = redirect_to_https(downstream);
+ } else {
+ rv = error_reply(downstream, 502);
+ }
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+
+ if (error_reply(downstream, 502) != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ downstream_queue_.mark_active(downstream);
+
+ auto &req = downstream->request();
+ if (!req.http2_expect_body) {
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ return;
+}
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ if (get_config()->http2.upstream.debug.frame_debug) {
+ verbose_on_frame_recv_callback(session, frame, user_data);
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_HEADERS: {
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (!downstream) {
+ return 0;
+ }
+
+ if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+ downstream->reset_upstream_rtimer();
+
+ handler->stop_read_timer();
+
+ return upstream->on_request_headers(downstream, frame);
+ }
+
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_SETTINGS:
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ return 0;
+ }
+ upstream->stop_settings_timer();
+ return 0;
+ 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;
+ }
+ return 0;
+ default:
+ 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 = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ 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) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ 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()->http2.upstream.debug.frame_debug) {
+ verbose_on_frame_send_callback(session, frame, user_data);
+ }
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ case NGHTTP2_HEADERS: {
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+ return 0;
+ }
+ // RST_STREAM if request is still incomplete.
+ auto stream_id = frame->hd.stream_id;
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ if (!downstream) {
+ return 0;
+ }
+
+ // For tunneling, issue RST_STREAM to finish the stream.
+ if (downstream->get_upgraded() ||
+ nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Send RST_STREAM to "
+ << (downstream->get_upgraded() ? "tunneled " : "")
+ << "stream stream_id=" << downstream->get_stream_id()
+ << " to finish off incomplete request";
+ }
+
+ upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
+ }
+
+ return 0;
+ }
+ case NGHTTP2_SETTINGS:
+ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ upstream->start_settings_timer();
+ }
+ return 0;
+ case NGHTTP2_PUSH_PROMISE: {
+ auto promised_stream_id = frame->push_promise.promised_stream_id;
+
+ if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) {
+ // In case of push from backend, downstream object was already
+ // created.
+ return 0;
+ }
+
+ auto promised_downstream = std::make_unique<Downstream>(
+ upstream, handler->get_mcpool(), promised_stream_id);
+ auto &req = promised_downstream->request();
+
+ // As long as we use nghttp2_session_mem_send(), setting stream
+ // user data here should not fail. This is because this callback
+ // is called just after frame was serialized. So no worries about
+ // hanging Downstream.
+ nghttp2_session_set_stream_user_data(session, promised_stream_id,
+ promised_downstream.get());
+
+ promised_downstream->set_assoc_stream_id(frame->hd.stream_id);
+ promised_downstream->disable_upstream_rtimer();
+
+ req.http_major = 2;
+ req.http_minor = 0;
+
+ req.fs.content_length = 0;
+ req.http2_expect_body = false;
+
+ auto &promised_balloc = promised_downstream->get_block_allocator();
+
+ for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
+ auto &nv = frame->push_promise.nva[i];
+
+ auto name =
+ make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen});
+ auto value =
+ make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen});
+
+ auto token = http2::lookup_token(nv.name, nv.namelen);
+ switch (token) {
+ case http2::HD__METHOD:
+ req.method = http2::lookup_method_token(value);
+ break;
+ case http2::HD__SCHEME:
+ req.scheme = value;
+ break;
+ case http2::HD__AUTHORITY:
+ req.authority = value;
+ break;
+ case http2::HD__PATH:
+ req.path = http2::rewrite_clean_path(promised_balloc, value);
+ break;
+ }
+ req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
+ token);
+ }
+
+ promised_downstream->inspect_http2_request();
+
+ promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+
+ // a bit weird but start_downstream() expects that given
+ // downstream is in pending queue.
+ auto ptr = promised_downstream.get();
+ upstream->add_pending_downstream(std::move(promised_downstream));
+
+#ifdef HAVE_MRUBY
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(ptr) != 0) {
+ if (upstream->error_reply(ptr, 500) != 0) {
+ upstream->rst_stream(ptr, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ upstream->start_downstream(ptr);
+
+ return 0;
+ }
+ 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;
+ }
+ return 0;
+ default:
+ 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 &&
+ lib_error_code != NGHTTP2_ERR_STREAM_CLOSED &&
+ lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) {
+ // To avoid stream hanging around, issue RST_STREAM.
+ auto downstream = static_cast<Downstream *>(
+ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+ if (downstream) {
+ upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto PADDING = std::array<uint8_t, 256>{};
+} // namespace
+
+namespace {
+int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ 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();
+
+ auto wb = upstream->get_response_buf();
+
+ size_t padlen = 0;
+
+ wb->append(framehd, 9);
+ if (frame->data.padlen > 0) {
+ padlen = frame->data.padlen - 1;
+ wb->append(static_cast<uint8_t>(padlen));
+ }
+
+ body->remove(*wb, length);
+
+ wb->append(PADDING.data(), padlen);
+
+ if (body->rleft() == 0) {
+ downstream->disable_upstream_wtimer();
+ } else {
+ downstream->reset_upstream_wtimer();
+ }
+
+ if (length > 0 && downstream->resume_read(SHRPX_NO_BUFFER, length) != 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ // We have to add length here, so that we can log this amount of
+ // data transferred.
+ downstream->response_sent_body_length += length;
+
+ auto max_buffer_size = upstream->get_max_buffer_size();
+
+ return wb->rleft() >= max_buffer_size ? NGHTTP2_ERR_PAUSE : 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() {
+ auto worker = handler_->get_worker();
+
+ if (!worker->get_graceful_shutdown()) {
+ return;
+ }
+
+ ev_prepare_stop(handler_->get_loop(), &prep_);
+
+ start_graceful_shutdown();
+}
+
+void Http2Upstream::start_graceful_shutdown() {
+ int rv;
+ if (ev_is_active(&shutdown_timer_)) {
+ return;
+ }
+
+ 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_);
+}
+
+nghttp2_session_callbacks *create_http2_upstream_callbacks() {
+ int rv;
+ nghttp2_session_callbacks *callbacks;
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+
+ if (rv != 0) {
+ return nullptr;
+ }
+
+ 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_callback2(callbacks,
+ on_header_callback2);
+
+ nghttp2_session_callbacks_set_on_invalid_header_callback2(
+ callbacks, on_invalid_header_callback2);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_send_data_callback(callbacks,
+ send_data_callback);
+
+ auto config = get_config();
+
+ if (config->padding) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, http::select_padding_callback);
+ }
+
+ if (config->http2.upstream.debug.frame_debug) {
+ nghttp2_session_callbacks_set_error_callback2(callbacks,
+ verbose_error_callback);
+ }
+
+ return callbacks;
+}
+
+namespace {
+size_t downstream_queue_size(Worker *worker) {
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ if (get_config()->http2_proxy) {
+ return downstreamconf.connections_per_host;
+ }
+
+ return downstreamconf.connections_per_frontend;
+}
+} // namespace
+
+Http2Upstream::Http2Upstream(ClientHandler *handler)
+ : wb_(handler->get_worker()->get_mcpool()),
+ downstream_queue_(downstream_queue_size(handler->get_worker()),
+ !get_config()->http2_proxy),
+ handler_(handler),
+ session_(nullptr),
+ max_buffer_size_(MAX_BUFFER_SIZE),
+ num_requests_(0) {
+ int rv;
+
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ auto faddr = handler_->get_upstream_addr();
+
+ rv =
+ nghttp2_session_server_new2(&session_, http2conf.upstream.callbacks, this,
+ faddr->alt_mode != UpstreamAltMode::NONE
+ ? http2conf.upstream.alt_mode_option
+ : http2conf.upstream.option);
+
+ assert(rv == 0);
+
+ flow_control_ = true;
+
+ // TODO Maybe call from outside?
+ std::array<nghttp2_settings_entry, 5> entry;
+ size_t nentry = 3;
+
+ entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ entry[0].value = http2conf.upstream.max_concurrent_streams;
+
+ entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ entry[1].value = (1u << 31) - 1;
+ } else {
+ entry[1].value = http2conf.upstream.window_size;
+ }
+
+ entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ entry[2].value = 1;
+
+ if (!config->http2_proxy) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ entry[nentry].value = 1;
+ ++nentry;
+ }
+
+ if (http2conf.upstream.decoder_dynamic_table_size !=
+ NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ entry[nentry].value = http2conf.upstream.decoder_dynamic_table_size;
+ ++nentry;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+ nentry);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: "
+ << nghttp2_strerror(rv);
+ }
+
+ auto window_size = faddr->alt_mode != UpstreamAltMode::NONE
+ ? std::numeric_limits<int32_t>::max()
+ : http2conf.upstream.optimize_window_size
+ ? std::min(http2conf.upstream.connection_window_size,
+ NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE)
+ : http2conf.upstream.connection_window_size;
+
+ rv = nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
+ window_size);
+
+ if (rv != 0) {
+ ULOG(ERROR, this)
+ << "nghttp2_session_set_local_window_size() returned error: "
+ << nghttp2_strerror(rv);
+ }
+
+ // We wait for SETTINGS ACK at least 10 seconds.
+ ev_timer_init(&settings_timer_, settings_timeout_cb,
+ http2conf.upstream.timeout.settings, 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_);
+
+#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+ if (http2conf.upstream.optimize_write_buffer_size) {
+ auto conn = handler_->get_connection();
+ conn->tls_dyn_rec_warmup_threshold = 0;
+
+ uint32_t pollout_thres = 1;
+ rv = setsockopt(conn->fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &pollout_thres,
+ static_cast<socklen_t>(sizeof(pollout_thres)));
+
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ auto error = errno;
+ LOG(INFO) << "setsockopt(TCP_NOTSENT_LOWAT, " << pollout_thres
+ << ") failed: errno=" << error;
+ }
+ }
+ }
+#endif // defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
+
+ handler_->reset_upstream_read_timeout(
+ config->conn.upstream.timeout.http2_read);
+
+ 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();
+ auto rlimit = handler_->get_rlimit();
+
+ if (rb->rleft()) {
+ rv = nghttp2_session_mem_recv(session_, rb->pos(), rb->rleft());
+ if (rv < 0) {
+ if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
+ ULOG(ERROR, this) << "nghttp2_session_mem_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();
+ rlimit->startw();
+ }
+
+ 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() {
+ int rv;
+ auto config = get_config();
+ auto &http2conf = config->http2;
+
+ if ((http2conf.upstream.optimize_write_buffer_size ||
+ http2conf.upstream.optimize_window_size) &&
+ handler_->get_ssl()) {
+ auto conn = handler_->get_connection();
+ TCPHint hint;
+ rv = conn->get_tcp_hint(&hint);
+ if (rv == 0) {
+ if (http2conf.upstream.optimize_write_buffer_size) {
+ max_buffer_size_ = std::min(MAX_BUFFER_SIZE, hint.write_buffer_size);
+ }
+
+ if (http2conf.upstream.optimize_window_size) {
+ auto faddr = handler_->get_upstream_addr();
+ if (faddr->alt_mode == UpstreamAltMode::NONE) {
+ auto window_size = std::min(http2conf.upstream.connection_window_size,
+ static_cast<int32_t>(hint.rwin * 2));
+
+ rv = nghttp2_session_set_local_window_size(
+ session_, NGHTTP2_FLAG_NONE, 0, window_size);
+ if (rv != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this)
+ << "nghttp2_session_set_local_window_size() with window_size="
+ << window_size << " failed: " << nghttp2_strerror(rv);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (;;) {
+ if (wb_.rleft() >= max_buffer_size_) {
+ return 0;
+ }
+
+ 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;
+ }
+ wb_.append(data, datalen);
+ }
+
+ 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_response_state() == DownstreamState::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() ==
+ DownstreamState::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) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+ return 0;
+ }
+ 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);
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // 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 rv;
+ }
+ 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();
+ }
+
+ // 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 will be deleted in on_stream_close_callback.
+ if (downstream->get_response_state() == DownstreamState::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(DownstreamState::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() !=
+ DownstreamState::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";
+ }
+ }
+
+ // 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() == DownstreamState::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() == DownstreamState::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) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ if (error_reply(downstream, status) != 0) {
+ return -1;
+ }
+ }
+ downstream->set_response_state(DownstreamState::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);
+ return -1;
+ }
+ 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) {
+ int rv;
+ auto downstream = static_cast<Downstream *>(source->ptr);
+ auto body = downstream->get_response_buf();
+ assert(body);
+ auto upstream = static_cast<Http2Upstream *>(user_data);
+
+ const auto &resp = downstream->response();
+
+ auto nread = std::min(body->rleft(), length);
+
+ auto max_buffer_size = upstream->get_max_buffer_size();
+
+ auto buffer = upstream->get_response_buf();
+
+ if (max_buffer_size <
+ std::min(nread, static_cast<size_t>(256)) + 9 + buffer->rleft()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Buffer is almost full. Skip write DATA";
+ }
+ return NGHTTP2_ERR_PAUSE;
+ }
+
+ nread = std::min(nread, max_buffer_size - 9 - buffer->rleft());
+
+ auto body_empty = body->rleft() == nread;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (body_empty &&
+ downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ if (!downstream->get_upgraded()) {
+ const auto &trailers = resp.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp2_nv> nva;
+ nva.reserve(trailers.size());
+ http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
+ nva.size());
+ if (rv != 0) {
+ if (nghttp2_is_fatal(rv)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ }
+ }
+ }
+ }
+ }
+
+ if (nread == 0 && ((*data_flags) & NGHTTP2_DATA_FLAG_EOF) == 0) {
+ downstream->disable_upstream_wtimer();
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ return nread;
+}
+} // namespace
+
+int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ int rv;
+
+ nghttp2_data_provider data_prd, *data_prd_ptr = nullptr;
+
+ if (bodylen) {
+ data_prd.source.ptr = downstream;
+ data_prd.read_callback = downstream_data_read_callback;
+ data_prd_ptr = &data_prd;
+ }
+
+ const auto &resp = downstream->response();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ const auto &headers = resp.fs.headers();
+ auto nva = std::vector<nghttp2_nv>();
+ // 2 for :status and server
+ nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
+
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ for (auto &kv : headers) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ switch (kv.token) {
+ case http2::HD_CONNECTION:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ }
+ nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", config->http.server_name));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_prd_ptr);
+ if (nghttp2_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ auto buf = downstream->get_response_buf();
+
+ buf->append(body, bodylen);
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ if (data_prd_ptr) {
+ downstream->reset_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+int Http2Upstream::error_reply(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+ resp.http_status = status_code;
+ auto body = downstream->get_response_buf();
+ body->append(html);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ nghttp2_data_provider data_prd;
+ data_prd.source.ptr = downstream;
+ data_prd.read_callback = downstream_data_read_callback;
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+
+ auto response_status = http2::stringify_status(balloc, status_code);
+ auto content_length = util::make_string_ref_uint(balloc, html.size());
+ auto date = make_string_ref(balloc, lgconf->tstamp->time_http);
+
+ auto nva = std::array<nghttp2_nv, 5>{
+ {http2::make_nv_ls_nocopy(":status", response_status),
+ http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
+ http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
+ http2::make_nv_ls_nocopy("content-length", content_length),
+ http2::make_nv_ls_nocopy("date", date)}};
+
+ 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;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ 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);
+ }
+
+ nghttp2_session_set_stream_user_data(session_, downstream->get_stream_id(),
+ nullptr);
+
+ auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream);
+
+ if (next_downstream) {
+ initiate_downstream(next_downstream);
+ }
+
+ if (downstream_queue_.get_downstreams() == nullptr) {
+ // There is no downstream at the moment. Start idle timer now.
+ handler_->repeat_read_timer();
+ }
+}
+
+// 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;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ 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";
+ }
+ }
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(req.scheme);
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ auto dconn = downstream->get_downstream_connection();
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto &http2conf = config->http2;
+
+ // We need some conditions that must be fulfilled to initiate server
+ // push.
+ //
+ // * Server push is disabled for http2 proxy or client proxy, since
+ // incoming headers are mixed origins. We don't know how to
+ // reliably determine the authority yet.
+ //
+ // * We need non-final response or 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 (!http2conf.no_server_push &&
+ nghttp2_session_get_remote_settings(session_,
+ NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
+ !config->http2_proxy && (downstream->get_stream_id() % 2) &&
+ resp.fs.header(http2::HD_LINK) &&
+ (downstream->get_non_final_response() || resp.http_status == 200) &&
+ (req.method == HTTP_GET || req.method == HTTP_POST)) {
+
+ if (prepare_push_promise(downstream) != 0) {
+ // Continue to send response even if push was failed.
+ }
+ }
+
+ auto nva = std::vector<nghttp2_nv>();
+ // 6 means :status and possible server, via, x-http2-push, alt-svc,
+ // and set-cookie (for affinity cookie) header field.
+ nva.reserve(resp.fs.headers().size() + 6 +
+ httpconf.add_response_headers.size());
+
+ if (downstream->get_non_final_response()) {
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ 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);
+
+ resp.fs.clear_headers();
+
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp2_submit_headers() failed";
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
+ StringRef response_status;
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) {
+ response_status = http2::stringify_status(balloc, 200);
+ striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
+ } else {
+ response_status = http2::stringify_status(balloc, resp.http_status);
+ }
+
+ nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
+
+ http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value));
+ }
+ }
+
+ if (!req.regular_connect_method() || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto dconn = downstream->get_downstream_connection();
+ assert(dconn);
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ nva.push_back(http2::make_nv_ls_nocopy("set-cookie", cookie_str));
+ }
+ }
+
+ if (!resp.fs.header(http2::HD_ALT_SVC)) {
+ // We won't change or alter alt-svc from backend for now
+ if (!httpconf.http2_altsvc_header_value.empty()) {
+ nva.push_back(http2::make_nv_ls_nocopy(
+ "alt-svc", httpconf.http2_altsvc_header_value));
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ // we don't create more than 16 bytes in
+ // http::create_via_header_value.
+ size_t len = 16;
+ if (via) {
+ len += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
+ *p = '\0';
+
+ nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http2::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (downstream->get_stream_id() % 2 == 0) {
+ // This header field is basically for human on client side to
+ // figure out that the resource is pushed.
+ nva.push_back(http2::make_nv_ll("x-http2-push", "1"));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ if (http2conf.upstream.debug.dump.response_header) {
+ http2::dump_nv(http2conf.upstream.debug.dump.response_header, nva.data(),
+ nva.size());
+ }
+
+ auto priority = resp.fs.header(http2::HD_PRIORITY);
+ if (priority) {
+ nghttp2_extpri extpri;
+
+ if (nghttp2_session_get_extpri_stream_priority(
+ session_, &extpri, downstream->get_stream_id()) == 0 &&
+ nghttp2_extpri_parse_priority(&extpri, priority->value.byte(),
+ priority->value.size()) == 0) {
+ rv = nghttp2_session_change_extpri_stream_priority(
+ session_, downstream->get_stream_id(), &extpri,
+ /* ignore_client_signal = */ 1);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp2_session_change_extpri_stream_priority: "
+ << nghttp2_strerror(rv);
+ }
+ }
+ }
+
+ 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() ||
+ downstream->expect_response_trailer()) {
+ 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;
+ }
+
+ if (data_prdptr) {
+ downstream->reset_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(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";
+ }
+
+ auto &resp = downstream->response();
+
+ if (!downstream->validate_response_recv_body_length()) {
+ rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+ resp.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()) {
+ if (consume(downstream->get_stream_id(), consumed) != 0) {
+ return -1;
+ }
+
+ auto &req = downstream->request();
+
+ req.consume(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::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ int rv;
+
+ rv = redirect_to_https(downstream);
+ if (rv != 0) {
+ return -1;
+ }
+
+ handler_->signal_write();
+ return 0;
+}
+
+int Http2Upstream::redirect_to_https(Downstream *downstream) {
+ auto &req = downstream->request();
+ if (req.regular_connect_method() || req.scheme != "http") {
+ return error_reply(downstream, 400);
+ }
+
+ auto authority = util::extract_host(req.authority);
+ if (authority.empty()) {
+ return error_reply(downstream, 400);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ StringRef loc;
+ if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ req.path);
+ } else {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ StringRef::from_lit(":"),
+ httpconf.redirect_https_port, req.path);
+ }
+
+ auto &resp = downstream->response();
+ resp.http_status = 308;
+ resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
+ http2::HD_LOCATION);
+
+ return send_reply(downstream, nullptr, 0);
+}
+
+int Http2Upstream::consume(int32_t stream_id, size_t len) {
+ int rv;
+
+ auto faddr = handler_->get_upstream_addr();
+
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ return 0;
+ }
+
+ 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 << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\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_INTERNAL_ERROR);
+ handler_->signal_write();
+
+ return 0;
+}
+
+void Http2Upstream::on_handler_delete() {
+ for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) {
+ if (d->get_dispatch_state() == DispatchState::ACTIVE &&
+ d->accesslog_ready()) {
+ handler_->write_accesslog(d);
+ }
+ }
+}
+
+int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+
+ if (downstream->get_dispatch_state() != DispatchState::ACTIVE) {
+ // This is error condition when we failed push_request_headers()
+ // in initiate_downstream(). Otherwise, we have
+ // DispatchState::ACTIVE state, or we did not set
+ // DownstreamConnection.
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ if (!downstream->request_submission_ready()) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // We have got all response body already. Send it off.
+ downstream->pop_downstream_connection();
+ return 0;
+ }
+ // pushed stream is handled here
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ downstream->pop_downstream_connection();
+
+ downstream->add_retry();
+
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ rv = 0;
+
+ if (no_retry || downstream->no_more_retry()) {
+ goto fail;
+ }
+
+ // downstream connection is clean; we can retry with new
+ // downstream connection.
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = on_downstream_abort_request(downstream, 502);
+ }
+ if (rv != 0) {
+ rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+ }
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http2Upstream::prepare_push_promise(Downstream *downstream) {
+ int rv;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto base = http2::get_pure_path_component(req.path);
+ if (base.empty()) {
+ return 0;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.headers()) {
+ if (kv.token != http2::HD_LINK) {
+ continue;
+ }
+ for (auto &link : http2::parse_link_header(kv.value)) {
+ StringRef scheme, authority, path;
+
+ rv = http2::construct_push_component(balloc, scheme, authority, path,
+ base, link.uri);
+ if (rv != 0) {
+ continue;
+ }
+
+ if (scheme.empty()) {
+ scheme = req.scheme;
+ }
+
+ if (authority.empty()) {
+ authority = req.authority;
+ }
+
+ if (resp.is_resource_pushed(scheme, authority, path)) {
+ continue;
+ }
+
+ rv = submit_push_promise(scheme, authority, path, downstream);
+ if (rv != 0) {
+ return -1;
+ }
+
+ resp.resource_pushed(scheme, authority, path);
+ }
+ }
+ return 0;
+}
+
+int Http2Upstream::submit_push_promise(const StringRef &scheme,
+ const StringRef &authority,
+ const StringRef &path,
+ Downstream *downstream) {
+ const auto &req = downstream->request();
+
+ std::vector<nghttp2_nv> nva;
+ // 4 for :method, :scheme, :path and :authority
+ nva.reserve(4 + req.fs.headers().size());
+
+ // just use "GET" for now
+ nva.push_back(http2::make_nv_ll(":method", "GET"));
+ nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
+ nva.push_back(http2::make_nv_ls_nocopy(":path", path));
+ nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
+
+ for (auto &kv : req.fs.headers()) {
+ switch (kv.token) {
+ // TODO generate referer
+ case http2::HD__AUTHORITY:
+ case http2::HD__SCHEME:
+ case http2::HD__METHOD:
+ case http2::HD__PATH:
+ continue;
+ 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_nocopy(kv.name, kv.value, kv.no_index));
+ break;
+ }
+ }
+
+ auto promised_stream_id = nghttp2_submit_push_promise(
+ session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
+ nva.size(), nullptr);
+
+ if (promised_stream_id < 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
+ << nghttp2_strerror(promised_stream_id);
+ }
+ if (nghttp2_is_fatal(promised_stream_id)) {
+ return -1;
+ }
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ ULOG(INFO, this) << "HTTP push request headers. promised_stream_id="
+ << promised_stream_id << "\n"
+ << ss.str();
+ }
+
+ return 0;
+}
+
+bool Http2Upstream::push_enabled() const {
+ auto config = get_config();
+ return !(config->http2.no_server_push ||
+ nghttp2_session_get_remote_settings(
+ session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
+ config->http2_proxy);
+}
+
+int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ int rv;
+
+ if (uri.empty() || !push_enabled() ||
+ (downstream->get_stream_id() % 2) == 0) {
+ return 0;
+ }
+
+ const auto &req = downstream->request();
+
+ auto base = http2::get_pure_path_component(req.path);
+ if (base.empty()) {
+ return -1;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ StringRef scheme, authority, path;
+
+ rv = http2::construct_push_component(balloc, scheme, authority, path, base,
+ uri);
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (scheme.empty()) {
+ scheme = req.scheme;
+ }
+
+ if (authority.empty()) {
+ authority = req.authority;
+ }
+
+ auto &resp = downstream->response();
+
+ if (resp.is_resource_pushed(scheme, authority, path)) {
+ return 0;
+ }
+
+ rv = submit_push_promise(scheme, authority, path, downstream);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ resp.resource_pushed(scheme, authority, path);
+
+ return 0;
+}
+
+int Http2Upstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ if (iovcnt == 0 || wb_.rleft() == 0) {
+ return 0;
+ }
+
+ return wb_.riovec(iov, iovcnt);
+}
+
+void Http2Upstream::response_drain(size_t n) { wb_.drain(n); }
+
+bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; }
+
+DefaultMemchunks *Http2Upstream::get_response_buf() { return &wb_; }
+
+Downstream *
+Http2Upstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ // promised_stream_id is for backend HTTP/2 session, not for
+ // frontend.
+ auto promised_downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
+ auto &promised_req = promised_downstream->request();
+
+ promised_downstream->set_downstream_stream_id(promised_stream_id);
+ // Set associated stream in frontend
+ promised_downstream->set_assoc_stream_id(downstream->get_stream_id());
+
+ promised_downstream->disable_upstream_rtimer();
+
+ promised_req.http_major = 2;
+ promised_req.http_minor = 0;
+
+ promised_req.fs.content_length = 0;
+ promised_req.http2_expect_body = false;
+
+ auto ptr = promised_downstream.get();
+ add_pending_downstream(std::move(promised_downstream));
+ downstream_queue_.mark_active(ptr);
+
+ return ptr;
+}
+
+int Http2Upstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ std::vector<nghttp2_nv> nva;
+
+ const auto &promised_req = promised_downstream->request();
+ const auto &headers = promised_req.fs.headers();
+
+ nva.reserve(headers.size());
+
+ for (auto &kv : headers) {
+ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+ }
+
+ auto promised_stream_id = nghttp2_submit_push_promise(
+ session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
+ nva.size(), promised_downstream);
+ if (promised_stream_id < 0) {
+ return -1;
+ }
+
+ promised_downstream->set_stream_id(promised_stream_id);
+
+ return 0;
+}
+
+void Http2Upstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Remove premature promised stream "
+ << promised_downstream;
+ }
+ downstream_queue_.remove_and_get_blocked(promised_downstream, false);
+}
+
+size_t Http2Upstream::get_max_buffer_size() const { return max_buffer_size_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h
new file mode 100644
index 0000000..739c6ff
--- /dev/null
+++ b/src/shrpx_http2_upstream.h
@@ -0,0 +1,150 @@
+/*
+ * 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"
+#include "buffer.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 int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ 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);
+
+ 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(Downstream *downstream, bool no_retry);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ 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(Downstream *downstream);
+
+ void submit_goaway();
+ void check_shutdown();
+ // Starts graceful shutdown period.
+ void start_graceful_shutdown();
+
+ int prepare_push_promise(Downstream *downstream);
+ int submit_push_promise(const StringRef &scheme, const StringRef &authority,
+ const StringRef &path, Downstream *downstream);
+
+ // Called when new request has started.
+ void on_start_request(const nghttp2_frame *frame);
+ int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);
+
+ DefaultMemchunks *get_response_buf();
+
+ size_t get_max_buffer_size() const;
+
+ int redirect_to_https(Downstream *downstream);
+
+private:
+ DefaultMemchunks wb_;
+ std::unique_ptr<HttpsUpstream> pre_upstream_;
+ DownstreamQueue downstream_queue_;
+ ev_timer settings_timer_;
+ ev_timer shutdown_timer_;
+ ev_prepare prep_;
+ ClientHandler *handler_;
+ nghttp2_session *session_;
+ size_t max_buffer_size_;
+ // The number of requests seen so far.
+ size_t num_requests_;
+ bool flow_control_;
+};
+
+nghttp2_session_callbacks *create_http2_upstream_callbacks();
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_UPSTREAM_H
diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc
new file mode 100644
index 0000000..f8ae1ce
--- /dev/null
+++ b/src/shrpx_http3_upstream.cc
@@ -0,0 +1,2906 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_http3_upstream.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/udp.h>
+
+#include <cstdio>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_log.h"
+#include "shrpx_quic.h"
+#include "shrpx_worker.h"
+#include "shrpx_http.h"
+#include "shrpx_connection_handler.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http3.h"
+#include "util.h"
+#include "ssl_compat.h"
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+
+ if (upstream->handle_expiry() != 0 || upstream->on_write() != 0) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+ auto handler = upstream->get_client_handler();
+
+ delete handler;
+}
+} // namespace
+
+namespace {
+void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+
+ if (upstream->submit_goaway() != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revent) {
+ auto upstream = static_cast<Http3Upstream *>(w->data);
+ auto handler = upstream->get_client_handler();
+
+ if (upstream->check_shutdown() != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+size_t downstream_queue_size(Worker *worker) {
+ auto &downstreamconf = *worker->get_downstream_config();
+
+ if (get_config()->http2_proxy) {
+ return downstreamconf.connections_per_host;
+ }
+
+ return downstreamconf.connections_per_frontend;
+}
+} // namespace
+
+namespace {
+ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto conn = static_cast<Connection *>(conn_ref->user_data);
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
+ return upstream->get_conn();
+}
+} // namespace
+
+Http3Upstream::Http3Upstream(ClientHandler *handler)
+ : handler_{handler},
+ qlog_fd_{-1},
+ hashed_scid_{},
+ conn_{nullptr},
+ httpconn_{nullptr},
+ downstream_queue_{downstream_queue_size(handler->get_worker()),
+ !get_config()->http2_proxy},
+ retry_close_{false},
+ tx_{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]),
+ } {
+ auto conn = handler_->get_connection();
+ conn->conn_ref.get_conn = shrpx::get_conn;
+
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+
+ ngtcp2_ccerr_default(&last_error_);
+
+ ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 0., 0.);
+ shutdown_timer_.data = this;
+
+ ev_prepare_init(&prep_, prepare_cb);
+ prep_.data = this;
+ ev_prepare_start(handler_->get_loop(), &prep_);
+}
+
+Http3Upstream::~Http3Upstream() {
+ auto loop = handler_->get_loop();
+
+ ev_prepare_stop(loop, &prep_);
+ ev_timer_stop(loop, &shutdown_timer_);
+ ev_timer_stop(loop, &timer_);
+
+ nghttp3_conn_del(httpconn_);
+
+ ngtcp2_conn_del(conn_);
+
+ if (qlog_fd_ != -1) {
+ close(qlog_fd_);
+ }
+}
+
+namespace {
+void log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+ std::array<char, 4096> buf;
+
+ va_start(ap, fmt);
+ auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap);
+ va_end(ap);
+
+ if (static_cast<size_t>(nwrite) >= buf.size()) {
+ nwrite = buf.size() - 1;
+ }
+
+ buf[nwrite++] = '\n';
+
+ while (write(fileno(stderr), buf.data(), nwrite) == -1 && errno == EINTR)
+ ;
+}
+} // namespace
+
+namespace {
+void qlog_write(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->qlog_write(data, datalen, flags & NGTCP2_QLOG_WRITE_FLAG_FIN);
+}
+} // namespace
+
+void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) {
+ assert(qlog_fd_ != -1);
+
+ while (write(qlog_fd_, data, datalen) == -1 && errno == EINTR)
+ ;
+
+ if (fin) {
+ close(qlog_fd_);
+ qlog_fd_ = -1;
+ }
+}
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ util::random_bytes(dest, dest + destlen,
+ *static_cast<std::mt19937 *>(rand_ctx->native_handle));
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto worker = handler->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ if (generate_quic_connection_id(*cid, cidlen, worker->get_cid_prefix(),
+ qkm.id, qkm.cid_encryption_key.data()) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (generate_quic_stateless_reset_token(token, *cid, qkm.secret.data(),
+ qkm.secret.size()) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ auto quic_connection_handler = worker->get_quic_connection_handler();
+
+ quic_connection_handler->add_connection_id(*cid, handler);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto worker = handler->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ quic_conn_handler->remove_connection_id(*cid);
+
+ return 0;
+}
+} // namespace
+
+void Http3Upstream::http_begin_request_headers(int64_t stream_id) {
+ auto downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), stream_id);
+ nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get());
+
+ downstream->reset_upstream_rtimer();
+
+ handler_->repeat_read_timer();
+
+ auto &req = downstream->request();
+ req.http_major = 3;
+ req.http_minor = 0;
+
+ add_pending_downstream(std::move(downstream));
+}
+
+void Http3Upstream::add_pending_downstream(
+ std::unique_ptr<Downstream> downstream) {
+ downstream_queue_.add_pending(std::move(downstream));
+}
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ assert(httpconn_);
+
+ auto nconsumed = nghttp3_conn_read_stream(
+ httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_read_stream: "
+ << nghttp3_strerror(nconsumed);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr,
+ 0);
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+
+ return 0;
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (upstream->stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ break;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+ }
+ break;
+ default:
+ ULOG(ERROR, this) << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::acked_stream_data_offset(int64_t stream_id,
+ uint64_t datalen) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_add_ack_offset: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->extend_max_stream_data(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::extend_max_stream_data(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_unblock_stream: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->extend_max_remote_streams_bidi(max_streams);
+
+ return 0;
+}
+} // namespace
+
+void Http3Upstream::extend_max_remote_streams_bidi(uint64_t max_streams) {
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams);
+}
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_shutdown_stream_read(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_shutdown_stream_read(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_shutdown_stream_read: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_shutdown_stream_read(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::handshake_completed() {
+ handler_->set_alpn_from_conn();
+
+ auto alpn = handler_->get_alpn();
+ if (alpn.empty()) {
+ ULOG(ERROR, this) << "NO ALPN was negotiated";
+ return -1;
+ }
+
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ return send_new_token(&path->remote);
+}
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ const ngtcp2_path *old_path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (res != NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ||
+ !(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ if (upstream->send_new_token(&path->remote) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::send_new_token(const ngtcp2_addr *remote_addr) {
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN + 1> token;
+ size_t tokenlen;
+
+ auto worker = handler_->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ if (generate_token(token.data(), tokenlen, remote_addr->addr,
+ remote_addr->addrlen, qkm.secret.data(),
+ qkm.secret.size()) != 0) {
+ return -1;
+ }
+
+ assert(tokenlen == NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN);
+
+ token[tokenlen++] = qkm.id;
+
+ auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_submit_new_token: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int recv_tx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+ void *user_data) {
+ if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ if (upstream->setup_httpconn() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_hd &initial_hd,
+ const ngtcp2_cid *odcid, const uint8_t *token,
+ size_t tokenlen, ngtcp2_token_type token_type) {
+ int rv;
+
+ auto worker = handler_->get_worker();
+ auto conn_handler = worker->get_connection_handler();
+
+ auto callbacks = ngtcp2_callbacks{
+ nullptr, // client_initial
+ ngtcp2_crypto_recv_client_initial_cb,
+ ngtcp2_crypto_recv_crypto_data_cb,
+ shrpx::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ shrpx::recv_stream_data,
+ shrpx::acked_stream_data_offset,
+ nullptr, // stream_open
+ shrpx::stream_close,
+ nullptr, // recv_stateless_reset
+ nullptr, // recv_retry
+ nullptr, // extend_max_local_streams_bidi
+ nullptr, // extend_max_local_streams_uni
+ rand,
+ get_new_connection_id,
+ remove_connection_id,
+ ngtcp2_crypto_update_key_cb,
+ shrpx::path_validation,
+ nullptr, // select_preferred_addr
+ shrpx::stream_reset,
+ shrpx::extend_max_remote_streams_bidi,
+ nullptr, // extend_max_remote_streams_uni
+ shrpx::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ shrpx::stream_stop_sending,
+ nullptr, // version_negotiation
+ nullptr, // recv_rx_key
+ shrpx::recv_tx_key,
+ };
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+ auto &http3conf = config->http3;
+
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ ngtcp2_cid scid;
+
+ if (generate_quic_connection_id(scid, SHRPX_QUIC_SCIDLEN,
+ worker->get_cid_prefix(), qkm.id,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ if (quicconf.upstream.debug.log) {
+ settings.log_printf = log_printf;
+ }
+
+ if (!quicconf.upstream.qlog.dir.empty()) {
+ auto fd = open_qlog_file(quicconf.upstream.qlog.dir, scid);
+ if (fd != -1) {
+ qlog_fd_ = fd;
+ settings.qlog_write = shrpx::qlog_write;
+ }
+ }
+
+ settings.initial_ts = quic_timestamp();
+ settings.initial_rtt = static_cast<ngtcp2_tstamp>(
+ quicconf.upstream.initial_rtt * NGTCP2_SECONDS);
+ settings.cc_algo = quicconf.upstream.congestion_controller;
+ settings.max_window = http3conf.upstream.max_connection_window_size;
+ settings.max_stream_window = http3conf.upstream.max_window_size;
+ settings.max_tx_udp_payload_size = SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE;
+ settings.rand_ctx.native_handle = &worker->get_randgen();
+ settings.token = token;
+ settings.tokenlen = tokenlen;
+ settings.token_type = token_type;
+ settings.initial_pkt_num = std::uniform_int_distribution<uint32_t>(
+ 0, std::numeric_limits<int32_t>::max())(worker->get_randgen());
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_streams_bidi = http3conf.upstream.max_concurrent_streams;
+ // The minimum number of unidirectional streams required for HTTP/3.
+ params.initial_max_streams_uni = 3;
+ params.initial_max_data = http3conf.upstream.connection_window_size;
+ params.initial_max_stream_data_bidi_remote = http3conf.upstream.window_size;
+ params.initial_max_stream_data_uni = http3conf.upstream.window_size;
+ params.max_idle_timeout = static_cast<ngtcp2_tstamp>(
+ quicconf.upstream.timeout.idle * NGTCP2_SECONDS);
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ if (quicconf.upstream.early_data) {
+ ngtcp2_transport_params early_data_params;
+
+ ngtcp2_transport_params_default(&early_data_params);
+
+ early_data_params.initial_max_stream_data_bidi_local =
+ params.initial_max_stream_data_bidi_local;
+ early_data_params.initial_max_stream_data_bidi_remote =
+ params.initial_max_stream_data_bidi_remote;
+ early_data_params.initial_max_stream_data_uni =
+ params.initial_max_stream_data_uni;
+ early_data_params.initial_max_data = params.initial_max_data;
+ early_data_params.initial_max_streams_bidi =
+ params.initial_max_streams_bidi;
+ early_data_params.initial_max_streams_uni = params.initial_max_streams_uni;
+
+ // TODO include HTTP/3 SETTINGS
+
+ std::array<uint8_t, 128> quic_early_data_ctx;
+
+ auto quic_early_data_ctxlen = ngtcp2_transport_params_encode(
+ quic_early_data_ctx.data(), quic_early_data_ctx.size(),
+ &early_data_params);
+
+ assert(quic_early_data_ctxlen > 0);
+ assert(static_cast<size_t>(quic_early_data_ctxlen) <=
+ quic_early_data_ctx.size());
+
+ if (SSL_set_quic_early_data_context(handler_->get_ssl(),
+ quic_early_data_ctx.data(),
+ quic_early_data_ctxlen) != 1) {
+ ULOG(ERROR, this) << "SSL_set_quic_early_data_context failed";
+ return -1;
+ }
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ if (odcid) {
+ params.original_dcid = *odcid;
+ params.retry_scid = initial_hd.dcid;
+ params.retry_scid_present = 1;
+ } else {
+ params.original_dcid = initial_hd.dcid;
+ }
+
+ params.original_dcid_present = 1;
+
+ rv = generate_quic_stateless_reset_token(
+ params.stateless_reset_token, scid, qkm.secret.data(), qkm.secret.size());
+ if (rv != 0) {
+ ULOG(ERROR, this) << "generate_quic_stateless_reset_token failed";
+ return -1;
+ }
+ params.stateless_reset_token_present = 1;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ static_cast<socklen_t>(remote_addr.len),
+ },
+ const_cast<UpstreamAddr *>(faddr),
+ };
+
+ rv = ngtcp2_conn_server_new(&conn_, &initial_hd.scid, &scid, &path,
+ initial_hd.version, &callbacks, &settings,
+ &params, nullptr, this);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(conn_, handler_->get_ssl());
+
+ auto quic_connection_handler = worker->get_quic_connection_handler();
+
+ if (generate_quic_hashed_connection_id(hashed_scid_, remote_addr, local_addr,
+ initial_hd.dcid) != 0) {
+ return -1;
+ }
+
+ quic_connection_handler->add_connection_id(hashed_scid_, handler_);
+ quic_connection_handler->add_connection_id(scid, handler_);
+
+ return 0;
+}
+
+int Http3Upstream::on_read() { return 0; }
+
+int Http3Upstream::on_write() {
+ int rv;
+
+ if (tx_.send_blocked) {
+ rv = send_blocked_packet();
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ handler_->get_connection()->wlimit.stopw();
+
+ if (write_streams() != 0) {
+ return -1;
+ }
+
+ if (httpconn_ && nghttp3_conn_is_drained(httpconn_)) {
+ return -1;
+ }
+
+ reset_timer();
+
+ return 0;
+}
+
+int Http3Upstream::write_streams() {
+ std::array<nghttp3_vec, 16> vec;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+#ifdef UDP_SEGMENT
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_);
+#endif // UDP_SEGMENT
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ ngtcp2_pkt_info pi, prev_pi;
+ uint8_t *bufpos = tx_.data.get();
+ ngtcp2_path_storage ps, prev_ps;
+ size_t pktcnt = 0;
+ int rv;
+ size_t gso_size = 0;
+ auto ts = quic_timestamp();
+
+ ngtcp2_path_storage_zero(&ps);
+ ngtcp2_path_storage_zero(&prev_ps);
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ nghttp3_ssize sveccnt = 0;
+
+ if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
+ sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
+ vec.data(), vec.size());
+ if (sveccnt < 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_writev_stream: "
+ << nghttp3_strerror(sveccnt);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt),
+ nullptr, 0);
+ return handle_error();
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ conn_, &ps.path, &pi, bufpos, max_udp_payload_size, &ndatalen, flags,
+ stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ nghttp3_conn_block_stream(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ nghttp3_conn_shutdown_stream_write(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ULOG(ERROR, this)
+ << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ ULOG(ERROR, this) << "ngtcp2_conn_writev_stream: "
+ << ngtcp2_strerror(nwrite);
+
+ ngtcp2_ccerr_set_liberr(&last_error_, nwrite, nullptr, 0);
+
+ return handle_error();
+ } else if (ndatalen >= 0) {
+ rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_add_write_offset: "
+ << nghttp3_strerror(rv);
+ ngtcp2_ccerr_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ }
+
+ if (nwrite == 0) {
+ if (bufpos - tx_.data.get()) {
+ auto faddr = static_cast<UpstreamAddr *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, prev_ps.path.remote.addr,
+ prev_ps.path.remote.addrlen, prev_ps.path.local.addr,
+ prev_ps.path.local.addrlen, prev_pi, data, datalen,
+ gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local,
+ prev_pi, data, datalen, gso_size);
+
+ signal_write_upstream_addr(faddr);
+ }
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+#ifdef UDP_SEGMENT
+ if (pktcnt == 0) {
+ ngtcp2_path_copy(&prev_ps.path, &ps.path);
+ prev_pi = pi;
+ gso_size = nwrite;
+ } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) ||
+ prev_pi.ecn != pi.ecn ||
+ static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto faddr = static_cast<UpstreamAddr *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data - nwrite;
+
+ rv = send_packet(faddr, prev_ps.path.remote.addr,
+ prev_ps.path.remote.addrlen, prev_ps.path.local.addr,
+ prev_ps.path.local.addrlen, prev_pi, data, datalen,
+ gso_size);
+ switch (rv) {
+ case SHRPX_ERR_SEND_BLOCKED:
+ on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local, prev_pi,
+ data, datalen, gso_size);
+
+ on_send_blocked(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote, ps.path.local, pi, bufpos - nwrite,
+ nwrite, 0);
+
+ signal_write_upstream_addr(faddr);
+
+ break;
+ default: {
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = bufpos - nwrite;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ nwrite, 0);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data,
+ nwrite, 0);
+
+ signal_write_upstream_addr(faddr);
+ }
+ }
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ datalen, gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen,
+ gso_size);
+
+ signal_write_upstream_addr(faddr);
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+#else // !UDP_SEGMENT
+ auto faddr = static_cast<UpstreamAddr *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen,
+ ps.path.local.addr, ps.path.local.addrlen, pi, data,
+ datalen, 0);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen,
+ 0);
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ signal_write_upstream_addr(faddr);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+
+ return 0;
+ }
+
+ bufpos = tx_.data.get();
+#endif // !UDP_SEGMENT
+ }
+
+ return 0;
+}
+
+int Http3Upstream::on_timeout(Downstream *downstream) { return 0; }
+
+int Http3Upstream::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 Http3Upstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ assert(0);
+ abort();
+}
+
+namespace {
+uint64_t
+infer_upstream_shutdown_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:
+ return NGHTTP3_H3_NO_ERROR;
+ case NGHTTP2_REFUSED_STREAM:
+ return NGHTTP3_H3_REQUEST_REJECTED;
+ default:
+ return NGHTTP3_H3_INTERNAL_ERROR;
+ }
+}
+} // namespace
+
+int Http3Upstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (downstream->get_response_state() == DownstreamState::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.
+ shutdown_stream(downstream,
+ infer_upstream_shutdown_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() ==
+ DownstreamState::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) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+ return 0;
+ }
+ 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);
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+ }
+
+ handler_->signal_write();
+
+ // At this point, downstream may be deleted.
+
+ return 0;
+}
+
+int Http3Upstream::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 rv;
+ }
+ return 0;
+}
+
+int Http3Upstream::downstream_eof(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
+ }
+
+ // 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 will be deleted in on_stream_close_callback.
+ if (downstream->get_response_state() == DownstreamState::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(DownstreamState::MSG_COMPLETE);
+
+ // For tunneled connection, MSG_COMPLETE signals
+ // downstream_read_data_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.
+ if (on_downstream_body_complete(downstream) != 0) {
+ return -1;
+ }
+ } else if (downstream->get_response_state() !=
+ DownstreamState::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 Http3Upstream::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";
+ }
+ }
+
+ // 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() == DownstreamState::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()) {
+ shutdown_stream(downstream, NGHTTP3_H3_NO_ERROR);
+ }
+ } else {
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ if (downstream->get_upgraded()) {
+ if (on_downstream_body_complete(downstream) != 0) {
+ return -1;
+ }
+ } else {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ } else {
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ if (error_reply(downstream, status) != 0) {
+ return -1;
+ }
+ }
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ }
+ handler_->signal_write();
+ // At this point, downstream may be deleted.
+ return 0;
+}
+
+ClientHandler *Http3Upstream::get_client_handler() const { return handler_; }
+
+namespace {
+nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn,
+ int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags,
+ void *conn_user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(conn_user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ assert(downstream);
+
+ auto body = downstream->get_response_buf();
+
+ assert(body);
+
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE &&
+ body->rleft_mark() == 0) {
+ downstream->disable_upstream_wtimer();
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ veccnt = body->riovec_mark(reinterpret_cast<struct iovec *>(vec), veccnt);
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE &&
+ body->rleft_mark() == 0) {
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+ }
+
+ assert((*pflags & NGHTTP3_DATA_FLAG_EOF) || veccnt);
+
+ downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt);
+
+ if ((*pflags & NGHTTP3_DATA_FLAG_EOF) &&
+ upstream->shutdown_stream_read(stream_id, NGHTTP3_H3_NO_ERROR) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return veccnt;
+}
+} // namespace
+
+int Http3Upstream::on_downstream_header_complete(Downstream *downstream) {
+ int rv;
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ 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";
+ }
+ }
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(req.scheme);
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ auto dconn = downstream->get_downstream_connection();
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ // Returning -1 will signal deletion of dconn.
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto nva = std::vector<nghttp3_nv>();
+ // 4 means :status and possible server, via, and set-cookie (for
+ // affinity cookie) header field.
+ nva.reserve(resp.fs.headers().size() + 4 +
+ httpconf.add_response_headers.size());
+
+ if (downstream->get_non_final_response()) {
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ rv = nghttp3_conn_submit_info(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size());
+
+ resp.fs.clear_headers();
+
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_info() failed";
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
+ StringRef response_status;
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) {
+ response_status = http2::stringify_status(balloc, 200);
+ striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
+ } else {
+ response_status = http2::stringify_status(balloc, resp.http_status);
+ }
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", httpconf.server_name));
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", (*server).value));
+ }
+ }
+
+ if (!req.regular_connect_method() || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto dconn = downstream->get_downstream_connection();
+ assert(dconn);
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ nva.push_back(http3::make_nv_ls_nocopy("set-cookie", cookie_str));
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ nva.push_back(http3::make_nv_ls_nocopy("via", (*via).value));
+ }
+ } else {
+ // we don't create more than 16 bytes in
+ // http::create_via_header_value.
+ size_t len = 16;
+ if (via) {
+ len += via->value.size() + 2;
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+ if (via) {
+ p = std::copy(std::begin(via->value), std::end(via->value), p);
+ p = util::copy_lit(p, ", ");
+ }
+ p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
+ *p = '\0';
+
+ nva.push_back(http3::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http3::make_nv_nocopy(p.name, p.value));
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(downstream, nva);
+ }
+
+ auto priority = resp.fs.header(http2::HD_PRIORITY);
+ if (priority) {
+ nghttp3_pri pri;
+
+ if (nghttp3_conn_get_stream_priority(httpconn_, &pri,
+ downstream->get_stream_id()) == 0 &&
+ nghttp3_pri_parse_priority(&pri, priority->value.byte(),
+ priority->value.size()) == 0) {
+ rv = nghttp3_conn_set_server_stream_priority(
+ httpconn_, downstream->get_stream_id(), &pri);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_set_server_stream_priority: "
+ << nghttp3_strerror(rv);
+ }
+ }
+ }
+
+ nghttp3_data_reader data_read;
+ data_read.read_data = downstream_read_data_callback;
+
+ nghttp3_data_reader *data_readptr;
+
+ if (downstream->expect_response_body() ||
+ downstream->expect_response_trailer()) {
+ data_readptr = &data_read;
+ } else {
+ data_readptr = nullptr;
+ }
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_readptr);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed";
+ return -1;
+ }
+
+ if (data_readptr) {
+ downstream->reset_upstream_wtimer();
+ } else if (shutdown_stream_read(downstream->get_stream_id(),
+ NGHTTP3_H3_NO_ERROR) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::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) {
+ nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id());
+
+ downstream->ensure_upstream_wtimer();
+ }
+
+ return 0;
+}
+
+int Http3Upstream::on_downstream_body_complete(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ auto &resp = downstream->response();
+
+ if (!downstream->validate_response_recv_body_length()) {
+ shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR);
+ resp.connection_close = true;
+ return 0;
+ }
+
+ if (!downstream->get_upgraded()) {
+ const auto &trailers = resp.fs.trailers();
+ if (!trailers.empty()) {
+ std::vector<nghttp3_nv> nva;
+ nva.reserve(trailers.size());
+ http3::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
+ if (!nva.empty()) {
+ auto rv = nghttp3_conn_submit_trailers(
+ httpconn_, downstream->get_stream_id(), nva.data(), nva.size());
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_trailers() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+ }
+ }
+ }
+
+ nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id());
+ downstream->ensure_upstream_wtimer();
+
+ return 0;
+}
+
+void Http3Upstream::on_handler_delete() {
+ for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) {
+ if (d->get_dispatch_state() == DispatchState::ACTIVE &&
+ d->accesslog_ready()) {
+ handler_->write_accesslog(d);
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ std::vector<ngtcp2_cid> scids(ngtcp2_conn_get_scid(conn_, nullptr) + 1);
+ ngtcp2_conn_get_scid(conn_, scids.data());
+ scids.back() = hashed_scid_;
+
+ for (auto &cid : scids) {
+ quic_conn_handler->remove_connection_id(cid);
+ }
+
+ if (retry_close_ || last_error_.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) {
+ return;
+ }
+
+ // If this is not idle close, send CONNECTION_CLOSE.
+ if (!ngtcp2_conn_in_closing_period(conn_) &&
+ !ngtcp2_conn_in_draining_period(conn_)) {
+ ngtcp2_path_storage ps;
+ ngtcp2_pkt_info pi;
+ conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN);
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_ccerr ccerr;
+ ngtcp2_ccerr_default(&ccerr);
+
+ if (worker->get_graceful_shutdown() &&
+ !ngtcp2_conn_get_handshake_completed(conn_)) {
+ ccerr.error_code = NGTCP2_CONNECTION_REFUSED;
+ }
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), &ccerr,
+ quic_timestamp());
+ if (nwrite < 0) {
+ if (nwrite != NGTCP2_ERR_INVALID_STATE) {
+ ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite);
+ }
+
+ return;
+ }
+
+ conn_close_.resize(nwrite);
+
+ send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
+ ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
+ }
+
+ auto d =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_) * 3) / NGTCP2_SECONDS;
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Enter close-wait period " << d << "s with "
+ << conn_close_.size() << " bytes sentinel packet";
+ }
+
+ auto cw = std::make_unique<CloseWait>(worker, std::move(scids),
+ std::move(conn_close_), d);
+
+ quic_conn_handler->add_close_wait(cw.release());
+}
+
+int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+
+ if (downstream->get_dispatch_state() != DispatchState::ACTIVE) {
+ // This is error condition when we failed push_request_headers()
+ // in initiate_downstream(). Otherwise, we have
+ // DispatchState::ACTIVE state, or we did not set
+ // DownstreamConnection.
+ downstream->pop_downstream_connection();
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ if (!downstream->request_submission_ready()) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // We have got all response body already. Send it off.
+ downstream->pop_downstream_connection();
+ return 0;
+ }
+ // pushed stream is handled here
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ downstream->pop_downstream_connection();
+
+ downstream->add_retry();
+
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ rv = 0;
+
+ if (no_retry || downstream->no_more_retry()) {
+ goto fail;
+ }
+
+ // downstream connection is clean; we can retry with new
+ // downstream connection.
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ assert(0);
+ abort();
+ }
+
+ rv = on_downstream_abort_request(downstream, 502);
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+void Http3Upstream::pause_read(IOCtrlReason reason) {}
+
+int Http3Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) {
+ consume(downstream->get_stream_id(), consumed);
+
+ auto &req = downstream->request();
+
+ req.consume(consumed);
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ int rv;
+
+ nghttp3_data_reader data_read, *data_read_ptr = nullptr;
+
+ if (bodylen) {
+ data_read.read_data = downstream_read_data_callback;
+ data_read_ptr = &data_read;
+ }
+
+ const auto &resp = downstream->response();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ const auto &headers = resp.fs.headers();
+ auto nva = std::vector<nghttp3_nv>();
+ // 2 for :status and server
+ nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
+
+ auto response_status = http2::stringify_status(balloc, resp.http_status);
+
+ nva.push_back(http3::make_nv_ls_nocopy(":status", response_status));
+
+ for (auto &kv : headers) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ switch (kv.token) {
+ case http2::HD_CONNECTION:
+ case http2::HD_KEEP_ALIVE:
+ case http2::HD_PROXY_CONNECTION:
+ case http2::HD_TE:
+ case http2::HD_TRANSFER_ENCODING:
+ case http2::HD_UPGRADE:
+ continue;
+ }
+ nva.push_back(http3::make_nv_nocopy(kv.name, kv.value, kv.no_index));
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ nva.push_back(http3::make_nv_ls_nocopy("server", config->http.server_name));
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ nva.push_back(http3::make_nv_nocopy(p.name, p.value));
+ }
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), data_read_ptr);
+ if (nghttp3_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ auto buf = downstream->get_response_buf();
+
+ buf->append(body, bodylen);
+
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ if (data_read_ptr) {
+ downstream->reset_upstream_wtimer();
+ }
+
+ if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) !=
+ 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ return 0;
+}
+
+int Http3Upstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ return 0;
+}
+
+void Http3Upstream::response_drain(size_t n) {}
+
+bool Http3Upstream::response_empty() const { return false; }
+
+Downstream *
+Http3Upstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ return nullptr;
+}
+
+int Http3Upstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ return 0;
+}
+
+bool Http3Upstream::push_enabled() const { return false; }
+
+void Http3Upstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {}
+
+int Http3Upstream::on_read(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ static_cast<socklen_t>(local_addr.len),
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ static_cast<socklen_t>(remote_addr.len),
+ },
+ const_cast<UpstreamAddr *>(faddr),
+ };
+
+ rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp());
+ if (rv != 0) {
+ switch (rv) {
+ case NGTCP2_ERR_DRAINING:
+ return -1;
+ case NGTCP2_ERR_RETRY: {
+ auto worker = handler_->get_worker();
+ auto quic_conn_handler = worker->get_quic_connection_handler();
+
+ if (worker->get_graceful_shutdown()) {
+ ngtcp2_ccerr_set_transport_error(&last_error_,
+ NGTCP2_CONNECTION_REFUSED, nullptr, 0);
+
+ return handle_error();
+ }
+
+ ngtcp2_version_cid vc;
+
+ rv =
+ ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN);
+ if (rv != 0) {
+ return -1;
+ }
+
+ retry_close_ = true;
+
+ quic_conn_handler->send_retry(handler_->get_upstream_addr(), vc.version,
+ vc.dcid, vc.dcidlen, vc.scid, vc.scidlen,
+ remote_addr, local_addr, datalen * 3);
+
+ return -1;
+ }
+ case NGTCP2_ERR_CRYPTO:
+ if (!last_error_.error_code) {
+ ngtcp2_ccerr_set_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ }
+ break;
+ case NGTCP2_ERR_DROP_CONN:
+ return -1;
+ default:
+ if (!last_error_.error_code) {
+ ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0);
+ }
+ }
+
+ ULOG(ERROR, this) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv);
+
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Http3Upstream::send_packet(const UpstreamAddr *faddr,
+ const sockaddr *remote_sa, size_t remote_salen,
+ const sockaddr *local_sa, size_t local_salen,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen, size_t gso_size) {
+ auto rv = quic_send_packet(faddr, remote_sa, remote_salen, local_sa,
+ local_salen, pi, data, datalen, gso_size);
+ switch (rv) {
+ case 0:
+ return 0;
+ // With GSO, sendmsg may fail with EINVAL if UDP payload is too
+ // large.
+ case -EINVAL:
+ case -EMSGSIZE:
+ // Let the packet lost.
+ break;
+ case -EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case -EWOULDBLOCK:
+#endif // EAGAIN != EWOULDBLOCK
+ return SHRPX_ERR_SEND_BLOCKED;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+void Http3Upstream::on_send_blocked(const UpstreamAddr *faddr,
+ const ngtcp2_addr &remote_addr,
+ const ngtcp2_addr &local_addr,
+ const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(tx_.num_blocked || !tx_.send_blocked);
+ assert(tx_.num_blocked < 2);
+
+ tx_.send_blocked = true;
+
+ auto &p = tx_.blocked[tx_.num_blocked++];
+
+ memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen);
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.local_addr.len = local_addr.addrlen;
+ p.remote_addr.len = remote_addr.addrlen;
+ p.faddr = faddr;
+ p.pi = pi;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+int Http3Upstream::send_blocked_packet() {
+ int rv;
+
+ assert(tx_.send_blocked);
+
+ for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) {
+ auto &p = tx_.blocked[tx_.num_blocked_sent];
+
+ rv = send_packet(p.faddr, &p.remote_addr.su.sa, p.remote_addr.len,
+ &p.local_addr.su.sa, p.local_addr.len, p.pi, p.data,
+ p.datalen, p.gso_size);
+ if (rv == SHRPX_ERR_SEND_BLOCKED) {
+ signal_write_upstream_addr(p.faddr);
+
+ return 0;
+ }
+ }
+
+ tx_.send_blocked = false;
+ tx_.num_blocked = 0;
+ tx_.num_blocked_sent = 0;
+
+ return 0;
+}
+
+void Http3Upstream::signal_write_upstream_addr(const UpstreamAddr *faddr) {
+ auto conn = handler_->get_connection();
+
+ if (faddr->fd != conn->wev.fd) {
+ if (ev_is_active(&conn->wev)) {
+ ev_io_stop(handler_->get_loop(), &conn->wev);
+ }
+
+ ev_io_set(&conn->wev, faddr->fd, EV_WRITE);
+ }
+
+ conn->wlimit.startw();
+}
+
+int Http3Upstream::handle_error() {
+ if (ngtcp2_conn_in_closing_period(conn_) ||
+ ngtcp2_conn_in_draining_period(conn_)) {
+ return -1;
+ }
+
+ ngtcp2_path_storage ps;
+ ngtcp2_pkt_info pi;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ auto ts = quic_timestamp();
+
+ conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN);
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(),
+ &last_error_, ts);
+ if (nwrite < 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ conn_close_.resize(nwrite);
+
+ if (nwrite == 0) {
+ return -1;
+ }
+
+ send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
+ ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
+ ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
+
+ return -1;
+}
+
+int Http3Upstream::handle_expiry() {
+ int rv;
+
+ auto ts = quic_timestamp();
+
+ rv = ngtcp2_conn_handle_expiry(conn_, ts);
+ if (rv != 0) {
+ if (rv == NGTCP2_ERR_IDLE_CLOSE) {
+ ULOG(INFO, this) << "Idle connection timeout";
+ } else {
+ ULOG(ERROR, this) << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv);
+ }
+ ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0);
+ return handle_error();
+ }
+
+ return 0;
+}
+
+void Http3Upstream::reset_timer() {
+ auto ts = quic_timestamp();
+ auto expiry_ts = ngtcp2_conn_get_expiry(conn_);
+ auto loop = handler_->get_loop();
+
+ if (expiry_ts <= ts) {
+ ev_feed_event(loop, &timer_, EV_TIMER);
+ return;
+ }
+
+ timer_.repeat = static_cast<ev_tstamp>(expiry_ts - ts) / NGTCP2_SECONDS;
+
+ ev_timer_again(loop, &timer_);
+}
+
+namespace {
+int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
+ size_t nconsumed, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ upstream->consume(stream_id, nconsumed);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ assert(downstream);
+
+ if (upstream->http_acked_stream_data(downstream, datalen) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_acked_stream_data(Downstream *downstream,
+ uint64_t datalen) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Stream " << downstream->get_stream_id() << " "
+ << datalen << " bytes acknowledged";
+ }
+
+ auto body = downstream->get_response_buf();
+ auto drained = body->drain_mark(datalen);
+ (void)drained;
+
+ assert(datalen == drained);
+
+ if (downstream->resume_read(SHRPX_NO_BUFFER, datalen) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id,
+ void *user_data, void *stream_user_data) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ return 0;
+ }
+
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ upstream->http_begin_request_headers(stream_id);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_recv_request_header(downstream, token, name, value, flags,
+ /* trailer = */ false) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_request_trailer(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_recv_request_header(downstream, token, name, value, flags,
+ /* trailer = */ true) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_recv_request_header(Downstream *downstream,
+ int32_t h3token,
+ nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ bool trailer) {
+ auto namebuf = nghttp3_rcbuf_get_buf(name);
+ auto valuebuf = nghttp3_rcbuf_get_buf(value);
+ auto &req = downstream->request();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
+ httpconf.request_header_field_buffer ||
+ req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ downstream->set_stop_reading(true);
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Too large or many header field size="
+ << req.fs.buffer_size() + namebuf.len + valuebuf.len
+ << ", num=" << req.fs.num_fields() + 1;
+ }
+
+ // just ignore if this is a trailer part.
+ if (trailer) {
+ return 0;
+ }
+
+ if (error_reply(downstream, 431) != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ auto token = http2::lookup_token(namebuf.base, namebuf.len);
+ auto no_index = flags & NGHTTP3_NV_FLAG_NEVER_INDEX;
+
+ downstream->add_rcbuf(name);
+ downstream->add_rcbuf(value);
+
+ if (trailer) {
+ req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+ }
+
+ req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
+ StringRef{valuebuf.base, valuebuf.len}, no_index,
+ token);
+ return 0;
+}
+
+namespace {
+int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
+ void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto handler = upstream->get_client_handler();
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_end_request_headers(downstream, fin) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ downstream->reset_upstream_rtimer();
+ handler->stop_read_timer();
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ auto &req = downstream->request();
+ req.tstamp = lgconf->tstamp;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ auto &nva = req.fs.headers();
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ if (nv.name == "authorization") {
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+ }
+ ULOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+ }
+
+ auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
+ if (content_length) {
+ // libnghttp3 guarantees this can be parsed
+ req.fs.content_length = util::parse_uint(content_length->value);
+ }
+
+ // presence of mandatory header fields are guaranteed by libnghttp3.
+ auto authority = req.fs.header(http2::HD__AUTHORITY);
+ auto path = req.fs.header(http2::HD__PATH);
+ auto method = req.fs.header(http2::HD__METHOD);
+ auto scheme = req.fs.header(http2::HD__SCHEME);
+
+ auto method_token = http2::lookup_method_token(method->value);
+ if (method_token == -1) {
+ if (error_reply(downstream, 501) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ auto faddr = handler_->get_upstream_addr();
+
+ auto config = get_config();
+
+ // For HTTP/2 proxy, we require :authority.
+ if (method_token != HTTP_CONNECT && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
+ shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR);
+ return 0;
+ }
+
+ req.method = method_token;
+ if (scheme) {
+ req.scheme = scheme->value;
+ }
+
+ // nghttp2 library guarantees either :authority or host exist
+ if (!authority) {
+ req.no_authority = true;
+ authority = req.fs.header(http2::HD_HOST);
+ }
+
+ if (authority) {
+ req.authority = authority->value;
+ }
+
+ if (path) {
+ if (method_token == HTTP_OPTIONS &&
+ path->value == StringRef::from_lit("*")) {
+ // Server-wide OPTIONS request. Path is empty.
+ } else if (config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ req.path = path->value;
+ } else {
+ req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
+ path->value);
+ }
+ }
+
+ auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
+ if (connect_proto) {
+ if (connect_proto->value != "websocket") {
+ if (error_reply(downstream, 400) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+ req.connect_proto = ConnectProto::WEBSOCKET;
+ }
+
+ if (!fin) {
+ req.http2_expect_body = true;
+ } else if (req.fs.content_length == -1) {
+ req.fs.content_length = 0;
+ }
+
+ downstream->inspect_http2_request();
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, /* encrypted = */ true)) {
+ if (error_reply(downstream, 400) != 0) {
+ return -1;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ return -1;
+ }
+ return 0;
+ }
+#endif // HAVE_MRUBY
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ start_downstream(downstream);
+
+ return 0;
+}
+
+void Http3Upstream::start_downstream(Downstream *downstream) {
+ if (downstream_queue_.can_activate(downstream->request().authority)) {
+ initiate_downstream(downstream);
+ return;
+ }
+
+ downstream_queue_.mark_blocked(downstream);
+}
+
+void Http3Upstream::initiate_downstream(Downstream *downstream) {
+ int rv;
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream);
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ assert(0);
+ abort();
+ }
+
+ rv = error_reply(downstream, 502);
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ rv = downstream->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &mruby_ctx = group->shared_addr->mruby_ctx;
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ if (error_reply(downstream, 500) != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+ if (rv != 0) {
+
+ if (error_reply(downstream, 502) != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ downstream_queue_.mark_failure(downstream);
+
+ return;
+ }
+
+ downstream_queue_.mark_active(downstream);
+
+ auto &req = downstream->request();
+ if (!req.http2_expect_body) {
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ }
+}
+
+namespace {
+int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (upstream->http_recv_data(downstream, data, datalen) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_recv_data(Downstream *downstream, const uint8_t *data,
+ size_t datalen) {
+ downstream->reset_upstream_rtimer();
+
+ if (downstream->push_upload_data_chunk(data, datalen) != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+
+ consume(downstream->get_stream_id(), datalen);
+
+ return 0;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream || downstream->get_stop_reading()) {
+ return 0;
+ }
+
+ if (upstream->http_end_stream(downstream) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_end_stream(Downstream *downstream) {
+ downstream->disable_upstream_rtimer();
+
+ if (downstream->end_upload_data() != 0) {
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+
+ return 0;
+}
+
+namespace {
+int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *conn_user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(conn_user_data);
+ auto downstream = static_cast<Downstream *>(stream_user_data);
+
+ if (!downstream) {
+ return 0;
+ }
+
+ if (upstream->http_stream_close(downstream, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_stream_close(Downstream *downstream,
+ uint64_t app_error_code) {
+ auto stream_id = downstream->get_stream_id();
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Stream stream_id=" << stream_id
+ << " is being closed with app_error_code="
+ << app_error_code;
+
+ auto body = downstream->get_response_buf();
+
+ ULOG(INFO, this) << "response unacked_left=" << body->rleft()
+ << " not_sent=" << body->rleft_mark();
+ }
+
+ auto &req = downstream->request();
+
+ consume(stream_id, req.unconsumed_body_length);
+
+ req.unconsumed_body_length = 0;
+
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+ downstream->set_request_state(DownstreamState::STREAM_CLOSED);
+
+ // At this point, downstream read may be paused.
+
+ // If shrpx_downstream::push_request_headers() failed, the
+ // error is handled here.
+ remove_downstream(downstream);
+ // downstream was deleted
+
+ return 0;
+}
+
+namespace {
+int http_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_stop_sending(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv =
+ ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_read: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto upstream = static_cast<Http3Upstream *>(user_data);
+
+ if (upstream->http_reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Http3Upstream::http_reset_stream(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv =
+ ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, app_error_code);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_write: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::setup_httpconn() {
+ int rv;
+
+ if (ngtcp2_conn_get_streams_uni_left(conn_) < 3) {
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ shrpx::http_acked_stream_data,
+ shrpx::http_stream_close,
+ shrpx::http_recv_data,
+ http_deferred_consume,
+ shrpx::http_begin_request_headers,
+ shrpx::http_recv_request_header,
+ shrpx::http_end_request_headers,
+ nullptr, // begin_trailers
+ shrpx::http_recv_request_trailer,
+ nullptr, // end_trailers
+ shrpx::http_stop_sending,
+ shrpx::http_end_stream,
+ shrpx::http_reset_stream,
+ };
+
+ auto config = get_config();
+
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = 4_k;
+
+ if (!config->http2_proxy) {
+ settings.enable_connect_protocol = 1;
+ }
+
+ auto mem = nghttp3_mem_default();
+
+ rv = nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_server_new: " << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ auto params = ngtcp2_conn_get_local_transport_params(conn_);
+
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_,
+ params->initial_max_streams_bidi);
+
+ int64_t ctrl_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_bind_control_stream: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ if (rv != 0) {
+ ULOG(ERROR, this) << "nghttp3_conn_bind_qpack_streams: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::error_reply(Downstream *downstream,
+ unsigned int status_code) {
+ int rv;
+ auto &resp = downstream->response();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+ resp.http_status = status_code;
+ auto body = downstream->get_response_buf();
+ body->append(html);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ nghttp3_data_reader data_read;
+ data_read.read_data = downstream_read_data_callback;
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+
+ auto response_status = http2::stringify_status(balloc, status_code);
+ auto content_length = util::make_string_ref_uint(balloc, html.size());
+ auto date = make_string_ref(balloc, lgconf->tstamp->time_http);
+
+ auto nva = std::array<nghttp3_nv, 5>{
+ {http3::make_nv_ls_nocopy(":status", response_status),
+ http3::make_nv_ll("content-type", "text/html; charset=UTF-8"),
+ http3::make_nv_ls_nocopy("server", get_config()->http.server_name),
+ http3::make_nv_ls_nocopy("content-length", content_length),
+ http3::make_nv_ls_nocopy("date", date)}};
+
+ rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(),
+ nva.data(), nva.size(), &data_read);
+ if (nghttp3_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ downstream->reset_upstream_wtimer();
+
+ if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) !=
+ 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::shutdown_stream(Downstream *downstream,
+ uint64_t app_error_code) {
+ auto stream_id = downstream->get_stream_id();
+
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Shutdown stream_id=" << stream_id
+ << " with app_error_code=" << app_error_code;
+ }
+
+ auto rv = ngtcp2_conn_shutdown_stream(conn_, 0, stream_id, app_error_code);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream() failed: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+int Http3Upstream::shutdown_stream_read(int64_t stream_id,
+ uint64_t app_error_code) {
+ auto rv = ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id,
+ NGHTTP3_H3_NO_ERROR);
+ if (ngtcp2_err_is_fatal(rv)) {
+ ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream_read: "
+ << ngtcp2_strerror(rv);
+ return -1;
+ }
+
+ return 0;
+}
+
+void Http3Upstream::consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+}
+
+void Http3Upstream::remove_downstream(Downstream *downstream) {
+ if (downstream->accesslog_ready()) {
+ handler_->write_accesslog(downstream);
+ }
+
+ nghttp3_conn_set_stream_user_data(httpconn_, downstream->get_stream_id(),
+ nullptr);
+
+ auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream);
+
+ if (next_downstream) {
+ initiate_downstream(next_downstream);
+ }
+
+ if (downstream_queue_.get_downstreams() == nullptr) {
+ // There is no downstream at the moment. Start idle timer now.
+ handler_->repeat_read_timer();
+ }
+}
+
+void Http3Upstream::log_response_headers(
+ Downstream *downstream, const std::vector<nghttp3_nv> &nva) const {
+ std::stringstream ss;
+ for (auto &nv : nva) {
+ ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
+ << StringRef{nv.value, nv.valuelen} << "\n";
+ }
+ ULOG(INFO, this) << "HTTP response headers. stream_id="
+ << downstream->get_stream_id() << "\n"
+ << ss.str();
+}
+
+int Http3Upstream::check_shutdown() {
+ auto worker = handler_->get_worker();
+
+ if (!worker->get_graceful_shutdown()) {
+ return 0;
+ }
+
+ ev_prepare_stop(handler_->get_loop(), &prep_);
+
+ return start_graceful_shutdown();
+}
+
+int Http3Upstream::start_graceful_shutdown() {
+ int rv;
+
+ if (ev_is_active(&shutdown_timer_)) {
+ return 0;
+ }
+
+ if (!httpconn_) {
+ return -1;
+ }
+
+ rv = nghttp3_conn_submit_shutdown_notice(httpconn_);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_submit_shutdown_notice: "
+ << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ handler_->signal_write();
+
+ auto t = ngtcp2_conn_get_pto(conn_);
+
+ ev_timer_set(&shutdown_timer_, static_cast<ev_tstamp>(t * 3) / NGTCP2_SECONDS,
+ 0.);
+ ev_timer_start(handler_->get_loop(), &shutdown_timer_);
+
+ return 0;
+}
+
+int Http3Upstream::submit_goaway() {
+ int rv;
+
+ rv = nghttp3_conn_shutdown(httpconn_);
+ if (rv != 0) {
+ ULOG(FATAL, this) << "nghttp3_conn_shutdown: " << nghttp3_strerror(rv);
+ return -1;
+ }
+
+ handler_->signal_write();
+
+ return 0;
+}
+
+int Http3Upstream::open_qlog_file(const StringRef &dir,
+ const ngtcp2_cid &scid) const {
+ std::array<char, sizeof("20141115T125824.741+0900")> buf;
+
+ auto path = dir.str();
+ path += '/';
+ path +=
+ util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now());
+ path += '-';
+ path += util::format_hex(scid.data, scid.datalen);
+ path += ".sqlog";
+
+ int fd;
+
+#ifdef O_CLOEXEC
+ while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
+ errno == EINTR)
+ ;
+#else // !O_CLOEXEC
+ while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (fd != -1) {
+ util::make_socket_closeonexec(fd);
+ }
+#endif // !O_CLOEXEC
+
+ if (fd == -1) {
+ auto error = errno;
+ ULOG(ERROR, this) << "Failed to open qlog file " << path
+ << ": errno=" << error;
+ return -1;
+ }
+
+ return fd;
+}
+
+ngtcp2_conn *Http3Upstream::get_conn() const { return conn_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h
new file mode 100644
index 0000000..89dfc17
--- /dev/null
+++ b/src/shrpx_http3_upstream.h
@@ -0,0 +1,193 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_HTTP3_UPSTREAM_H
+#define SHRPX_HTTP3_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream_queue.h"
+#include "quic.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct UpstreamAddr;
+
+class Http3Upstream : public Upstream {
+public:
+ Http3Upstream(ClientHandler *handler);
+ virtual ~Http3Upstream();
+
+ 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 int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ 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);
+ virtual ClientHandler *get_client_handler() const;
+
+ 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(Downstream *downstream, bool no_retry);
+
+ virtual void pause_read(IOCtrlReason reason);
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ int init(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &initial_hd,
+ const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+ ngtcp2_token_type token_type);
+
+ int on_read(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+
+ int write_streams();
+
+ int handle_error();
+
+ int handle_expiry();
+ void reset_timer();
+
+ int setup_httpconn();
+ void add_pending_downstream(std::unique_ptr<Downstream> downstream);
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
+ int extend_max_stream_data(int64_t stream_id);
+ void extend_max_remote_streams_bidi(uint64_t max_streams);
+ int error_reply(Downstream *downstream, unsigned int status_code);
+ void http_begin_request_headers(int64_t stream_id);
+ int http_recv_request_header(Downstream *downstream, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value,
+ uint8_t flags, bool trailer);
+ int http_end_request_headers(Downstream *downstream, int fin);
+ int http_end_stream(Downstream *downstream);
+ void start_downstream(Downstream *downstream);
+ void initiate_downstream(Downstream *downstream);
+ int shutdown_stream(Downstream *downstream, uint64_t app_error_code);
+ int shutdown_stream_read(int64_t stream_id, uint64_t app_error_code);
+ int http_stream_close(Downstream *downstream, uint64_t app_error_code);
+ void consume(int64_t stream_id, size_t nconsumed);
+ void remove_downstream(Downstream *downstream);
+ int stream_close(int64_t stream_id, uint64_t app_error_code);
+ void log_response_headers(Downstream *downstream,
+ const std::vector<nghttp3_nv> &nva) const;
+ int http_acked_stream_data(Downstream *downstream, uint64_t datalen);
+ int http_shutdown_stream_read(int64_t stream_id);
+ int http_reset_stream(int64_t stream_id, uint64_t app_error_code);
+ int http_stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int http_recv_data(Downstream *downstream, const uint8_t *data,
+ size_t datalen);
+ int handshake_completed();
+ int check_shutdown();
+ int start_graceful_shutdown();
+ int submit_goaway();
+ int send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+
+ void qlog_write(const void *data, size_t datalen, bool fin);
+ int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const;
+
+ void on_send_blocked(const UpstreamAddr *faddr,
+ const ngtcp2_addr &remote_addr,
+ const ngtcp2_addr &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+ int send_blocked_packet();
+ void signal_write_upstream_addr(const UpstreamAddr *faddr);
+
+ ngtcp2_conn *get_conn() const;
+
+ int send_new_token(const ngtcp2_addr *remote_addr);
+
+private:
+ ClientHandler *handler_;
+ ev_timer timer_;
+ ev_timer shutdown_timer_;
+ ev_prepare prep_;
+ int qlog_fd_;
+ ngtcp2_cid hashed_scid_;
+ ngtcp2_conn *conn_;
+ ngtcp2_ccerr last_error_;
+ nghttp3_conn *httpconn_;
+ DownstreamQueue downstream_queue_;
+ bool retry_close_;
+ std::vector<uint8_t> conn_close_;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ const UpstreamAddr *faddr;
+ Address local_addr;
+ Address remote_addr;
+ ngtcp2_pkt_info pi;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP3_UPSTREAM_H
diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
new file mode 100644
index 0000000..4164b8b
--- /dev/null
+++ b/src/shrpx_http_downstream_connection.cc
@@ -0,0 +1,1617 @@
+/*
+ * 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 <openssl/rand.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_log_config.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "http2.h"
+#include "util.h"
+#include "ssl_compat.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 (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ 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();
+ auto &resp = downstream->response();
+
+ // Do this so that dconn is not pooled
+ resp.connection_close = true;
+
+ if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void retry_downstream_connection(Downstream *downstream,
+ unsigned int status_code) {
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ assert(!downstream->get_request_header_sent());
+
+ downstream->add_retry();
+
+ if (downstream->no_more_retry()) {
+ delete handler;
+ return;
+ }
+
+ downstream->pop_downstream_connection();
+ auto buf = downstream->get_request_buf();
+ buf->reset();
+
+ int rv;
+
+ for (;;) {
+ auto ndconn = handler->get_downstream_connection(rv, downstream);
+ if (!ndconn) {
+ break;
+ }
+ if (downstream->attach_downstream_connection(std::move(ndconn)) != 0) {
+ continue;
+ }
+ if (downstream->push_request_headers() == 0) {
+ return;
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = upstream->on_downstream_abort_request(downstream, status_code);
+ }
+
+ if (rv != 0) {
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+ auto addr = dconn->get_addr();
+ auto raddr = dconn->get_raddr();
+
+ DCLOG(WARN, dconn) << "Connect time out; addr="
+ << util::to_numeric_addr(raddr);
+
+ downstream_failure(addr, raddr);
+
+ auto downstream = dconn->get_downstream();
+
+ retry_downstream_connection(downstream, 504);
+}
+} // namespace
+
+namespace {
+void backend_retry(Downstream *downstream) {
+ retry_downstream_connection(downstream, 502);
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ 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();
+
+ rv = upstream->downstream_read(dconn);
+ if (rv != 0) {
+ if (rv == SHRPX_ERR_RETRY) {
+ backend_retry(downstream);
+ return;
+ }
+
+ delete handler;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ 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();
+
+ rv = upstream->downstream_write(dconn);
+ if (rv == SHRPX_ERR_RETRY) {
+ backend_retry(downstream);
+ return;
+ }
+
+ if (rv != 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();
+ if (dconn->connected() != 0) {
+ backend_retry(downstream);
+ return;
+ }
+ writecb(loop, w, revents);
+}
+} // namespace
+
+HttpDownstreamConnection::HttpDownstreamConnection(
+ const std::shared_ptr<DownstreamAddrGroup> &group, DownstreamAddr *addr,
+ struct ev_loop *loop, Worker *worker)
+ : conn_(loop, -1, nullptr, worker->get_mcpool(),
+ group->shared_addr->timeout.write, group->shared_addr->timeout.read,
+ {}, {}, connectcb, readcb, connect_timeoutcb, this,
+ get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP1),
+ on_read_(&HttpDownstreamConnection::noop),
+ on_write_(&HttpDownstreamConnection::noop),
+ signal_write_(&HttpDownstreamConnection::noop),
+ worker_(worker),
+ ssl_ctx_(worker->get_cl_ssl_ctx()),
+ group_(group),
+ addr_(addr),
+ raddr_(nullptr),
+ ioctrl_(&conn_.rlimit),
+ response_htp_{0},
+ first_write_done_(false),
+ reusable_(true),
+ request_header_written_(false) {}
+
+HttpDownstreamConnection::~HttpDownstreamConnection() {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Deleted";
+ }
+
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+ dns_tracker->cancel(dns_query_.get());
+ }
+}
+
+int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
+ int rv;
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ rv = initiate_connection();
+ if (rv != 0) {
+ downstream_ = nullptr;
+ return rv;
+ }
+
+ return 0;
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp);
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdrs_completecb(llhttp_t *htp);
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len);
+int htp_msg_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begincb, // llhttp_cb on_message_begin;
+ nullptr, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_bodycb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+int HttpDownstreamConnection::initiate_connection() {
+ int rv;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this)
+ << "Worker wide backend connection was blocked temporarily";
+ }
+ return SHRPX_ERR_NETWORK;
+ }
+
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ if (conn_.fd == -1) {
+ auto check_dns_result = dns_query_.get() != nullptr;
+
+ if (check_dns_result) {
+ assert(addr_->dns);
+ }
+
+ auto &connect_blocker = addr_->connect_blocker;
+
+ if (connect_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Backend server " << addr_->host << ":"
+ << addr_->port << " was not available temporarily";
+ }
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ Address *raddr;
+
+ if (addr_->dns) {
+ if (!check_dns_result) {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host,
+ [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *this->resolved_addr_ = *result;
+ }
+
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ // This callback destroys |this|.
+ auto downstream = this->downstream_;
+ backend_retry(downstream);
+ }
+ });
+
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ if (!resolved_addr_) {
+ resolved_addr_ = std::make_unique<Address>();
+ }
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ downstream_failure(addr_, nullptr);
+ return SHRPX_ERR_NETWORK;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ return 0;
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (dns_query_->status) {
+ case DNSResolverStatus::ERROR:
+ dns_query_.reset();
+ downstream_failure(addr_, nullptr);
+ return SHRPX_ERR_NETWORK;
+ case DNSResolverStatus::OK:
+ dns_query_.reset();
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ raddr = resolved_addr_.get();
+ util::set_port(*resolved_addr_, addr_->port);
+ } else {
+ raddr = &addr_->addr;
+ }
+
+ conn_.fd = util::create_nonblock_socket(raddr->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ DCLOG(WARN, this) << "socket() failed; addr="
+ << util::to_numeric_addr(raddr) << ", errno=" << error;
+
+ worker_blocker->on_failure();
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ worker_blocker->on_success();
+
+ rv = connect(conn_.fd, &raddr->su.sa, raddr->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ DCLOG(WARN, this) << "connect() failed; addr="
+ << util::to_numeric_addr(raddr) << ", errno=" << error;
+
+ downstream_failure(addr_, raddr);
+
+ return SHRPX_ERR_NETWORK;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Connecting to downstream server";
+ }
+
+ raddr_ = raddr;
+
+ if (addr_->tls) {
+ assert(ssl_ctx_);
+
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ tls::setup_downstream_http1_alpn(ssl);
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+ if (!util::numeric_host(sni_name.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ conn_.wlimit.startw();
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+ } else {
+ // we may set read timer cb to idle_timeoutcb. Reset again.
+ ev_set_cb(&conn_.rt, timeoutcb);
+ if (conn_.read_timeout < group_->shared_addr->timeout.read) {
+ conn_.read_timeout = group_->shared_addr->timeout.read;
+ conn_.last_read = std::chrono::steady_clock::now();
+ } else {
+ conn_.again_rt(group_->shared_addr->timeout.read);
+ }
+
+ ev_set_cb(&conn_.rev, readcb);
+
+ on_write_ = &HttpDownstreamConnection::write_first;
+ first_write_done_ = false;
+ request_header_written_ = false;
+ }
+
+ llhttp_init(&response_htp_, HTTP_RESPONSE, &htp_hooks);
+ response_htp_.data = downstream_;
+
+ return 0;
+}
+
+int HttpDownstreamConnection::push_request_headers() {
+ if (request_header_written_) {
+ signal_write();
+ return 0;
+ }
+
+ const auto &downstream_hostport = addr_->hostport;
+ const auto &req = downstream_->request();
+
+ auto &balloc = downstream_->get_block_allocator();
+
+ auto connect_method = req.regular_connect_method();
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ request_header_written_ = true;
+
+ // For HTTP/1.0 request, there is no authority in request. In that
+ // case, we use backend server's host nonetheless.
+ auto authority = StringRef(downstream_hostport);
+ auto no_host_rewrite =
+ httpconf.no_host_rewrite || config->http2_proxy || connect_method;
+
+ if (no_host_rewrite && !req.authority.empty()) {
+ authority = req.authority;
+ }
+
+ downstream_->set_request_downstream_host(authority);
+
+ auto buf = downstream_->get_request_buf();
+
+ // Assume that method and request path do not contain \r\n.
+ auto meth = http2::to_method_string(
+ req.connect_proto == ConnectProto::WEBSOCKET ? HTTP_GET : req.method);
+ buf->append(meth);
+ buf->append(' ');
+
+ if (connect_method) {
+ buf->append(authority);
+ } else if (config->http2_proxy) {
+ // Construct absolute-form request target because we are going to
+ // send a request to a HTTP/1 proxy.
+ assert(!req.scheme.empty());
+ buf->append(req.scheme);
+ buf->append("://");
+ buf->append(authority);
+ buf->append(req.path);
+ } else if (req.method == HTTP_OPTIONS && req.path.empty()) {
+ // Server-wide OPTIONS
+ buf->append("*");
+ } else {
+ buf->append(req.path);
+ }
+ buf->append(" HTTP/1.1\r\nHost: ");
+ buf->append(authority);
+ buf->append("\r\n");
+
+ auto &fwdconf = httpconf.forwarded;
+ auto &xffconf = httpconf.xff;
+ auto &xfpconf = httpconf.xfp;
+ auto &earlydataconf = httpconf.early_data;
+
+ uint32_t build_flags =
+ (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
+ (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
+ (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
+ (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
+ ((req.http_major == 3 || req.http_major == 2)
+ ? http2::HDOP_STRIP_SEC_WEBSOCKET_KEY
+ : 0);
+
+ http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
+
+ auto cookie = downstream_->assemble_request_cookie();
+ if (!cookie.empty()) {
+ buf->append("Cookie: ");
+ buf->append(cookie);
+ buf->append("\r\n");
+ }
+
+ // set transfer-encoding only when content-length is unknown and
+ // request body is expected.
+ if (req.method != HTTP_CONNECT && req.http2_expect_body &&
+ req.fs.content_length == -1) {
+ downstream_->set_chunked_request(true);
+ buf->append("Transfer-Encoding: chunked\r\n");
+ }
+
+ if (req.connect_proto == ConnectProto::WEBSOCKET) {
+ if (req.http_major == 3 || req.http_major == 2) {
+ std::array<uint8_t, 16> nonce;
+ if (RAND_bytes(nonce.data(), nonce.size()) != 1) {
+ return -1;
+ }
+ auto iov = make_byte_ref(balloc, base64::encode_length(nonce.size()) + 1);
+ auto p = base64::encode(std::begin(nonce), std::end(nonce), iov.base);
+ *p = '\0';
+ auto key = StringRef{iov.base, p};
+ downstream_->set_ws_key(key);
+
+ buf->append("Sec-Websocket-Key: ");
+ buf->append(key);
+ buf->append("\r\n");
+ }
+
+ buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
+ } else if (!connect_method && req.upgrade_request) {
+ auto connection = req.fs.header(http2::HD_CONNECTION);
+ if (connection) {
+ buf->append("Connection: ");
+ buf->append((*connection).value);
+ buf->append("\r\n");
+ }
+
+ auto upgrade = req.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ buf->append("Upgrade: ");
+ buf->append((*upgrade).value);
+ buf->append("\r\n");
+ }
+ } else if (req.connection_close) {
+ buf->append("Connection: close\r\n");
+ }
+
+ auto upstream = downstream_->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ auto conn = handler->get_connection();
+
+ if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) {
+ buf->append("Early-Data: 1\r\n");
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ auto fwd =
+ fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
+
+ if (fwdconf.params) {
+ auto params = fwdconf.params;
+
+ if (config->http2_proxy || connect_method) {
+ params &= ~FORWARDED_PROTO;
+ }
+
+ auto value = http::create_forwarded(
+ balloc, params, handler->get_forwarded_by(),
+ handler->get_forwarded_for(), req.authority, req.scheme);
+
+ if (fwd || !value.empty()) {
+ buf->append("Forwarded: ");
+ if (fwd) {
+ buf->append(fwd->value);
+
+ if (!value.empty()) {
+ buf->append(", ");
+ }
+ }
+ buf->append(value);
+ buf->append("\r\n");
+ }
+ } else if (fwd) {
+ buf->append("Forwarded: ");
+ buf->append(fwd->value);
+ buf->append("\r\n");
+ }
+
+ auto xff = xffconf.strip_incoming ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_FOR);
+
+ if (xffconf.add) {
+ buf->append("X-Forwarded-For: ");
+ if (xff) {
+ buf->append((*xff).value);
+ buf->append(", ");
+ }
+ buf->append(client_handler_->get_ipaddr());
+ buf->append("\r\n");
+ } else if (xff) {
+ buf->append("X-Forwarded-For: ");
+ buf->append((*xff).value);
+ buf->append("\r\n");
+ }
+ if (!config->http2_proxy && !connect_method) {
+ auto xfp = xfpconf.strip_incoming
+ ? nullptr
+ : req.fs.header(http2::HD_X_FORWARDED_PROTO);
+
+ if (xfpconf.add) {
+ buf->append("X-Forwarded-Proto: ");
+ if (xfp) {
+ buf->append((*xfp).value);
+ buf->append(", ");
+ }
+ assert(!req.scheme.empty());
+ buf->append(req.scheme);
+ buf->append("\r\n");
+ } else if (xfp) {
+ buf->append("X-Forwarded-Proto: ");
+ buf->append((*xfp).value);
+ buf->append("\r\n");
+ }
+ }
+ auto via = req.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ buf->append("Via: ");
+ buf->append((*via).value);
+ buf->append("\r\n");
+ }
+ } else {
+ buf->append("Via: ");
+ if (via) {
+ buf->append((*via).value);
+ buf->append(", ");
+ }
+ std::array<char, 16> viabuf;
+ auto end = http::create_via_header_value(viabuf.data(), req.http_major,
+ req.http_minor);
+ buf->append(viabuf.data(), end - viabuf.data());
+ buf->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_request_headers) {
+ buf->append(p.name);
+ buf->append(": ");
+ buf->append(p.value);
+ buf->append("\r\n");
+ }
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ std::string nhdrs;
+ for (auto chunk = buf->head; chunk; chunk = chunk->next) {
+ nhdrs.append(chunk->pos, chunk->last);
+ }
+ if (log_config()->errorlog_tty) {
+ nhdrs = http::colorizeHeaders(nhdrs.c_str());
+ }
+ DCLOG(INFO, this) << "HTTP request headers. stream_id="
+ << downstream_->get_stream_id() << "\n"
+ << nhdrs;
+ }
+
+ // Don't call signal_write() if we anticipate request body. We call
+ // signal_write() when we received request body chunk, and it
+ // enables us to send headers and data in one writev system call.
+ if (req.method == HTTP_CONNECT ||
+ downstream_->get_blocked_request_buf()->rleft() ||
+ (!req.http2_expect_body && req.fs.content_length == 0) ||
+ downstream_->get_expect_100_continue()) {
+ signal_write();
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::process_blocked_request_buf() {
+ auto src = downstream_->get_blocked_request_buf();
+
+ if (src->rleft()) {
+ auto dest = downstream_->get_request_buf();
+ auto chunked = downstream_->get_chunked_request();
+ if (chunked) {
+ auto chunk_size_hex = util::utox(src->rleft());
+ dest->append(chunk_size_hex);
+ dest->append("\r\n");
+ }
+
+ src->copy(*dest);
+
+ if (chunked) {
+ dest->append("\r\n");
+ }
+ }
+
+ if (downstream_->get_blocked_request_data_eof() &&
+ downstream_->get_chunked_request()) {
+ end_upload_data_chunk();
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ if (!downstream_->get_request_header_sent()) {
+ auto output = downstream_->get_blocked_request_buf();
+ auto &req = downstream_->request();
+ output->append(data, datalen);
+ req.unconsumed_body_length += datalen;
+ if (request_header_written_) {
+ signal_write();
+ }
+ return 0;
+ }
+
+ 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);
+ 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_request_header_sent()) {
+ downstream_->set_blocked_request_data_eof(true);
+ if (request_header_written_) {
+ signal_write();
+ }
+ return 0;
+ }
+
+ signal_write();
+
+ if (!downstream_->get_chunked_request()) {
+ return 0;
+ }
+
+ end_upload_data_chunk();
+
+ return 0;
+}
+
+void HttpDownstreamConnection::end_upload_data_chunk() {
+ const auto &req = downstream_->request();
+
+ auto output = downstream_->get_request_buf();
+ const auto &trailers = req.fs.trailers();
+ if (trailers.empty()) {
+ output->append("0\r\n\r\n");
+ } else {
+ output->append("0\r\n");
+ http2::build_http1_headers_from_headers(output, trailers,
+ http2::HDOP_STRIP_ALL);
+ output->append("\r\n");
+ }
+}
+
+namespace {
+void remove_from_pool(HttpDownstreamConnection *dconn) {
+ auto addr = dconn->get_addr();
+ auto &dconn_pool = addr->dconn_pool;
+ dconn_pool->remove_downstream_connection(dconn);
+}
+} // namespace
+
+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";
+ }
+
+ remove_from_pool(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 (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Idle connection timeout";
+ }
+
+ remove_from_pool(dconn);
+ // dconn was deleted
+}
+} // namespace
+
+void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+
+ ev_set_cb(&conn_.rev, idle_readcb);
+ ioctrl_.force_resume_read();
+
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ ev_set_cb(&conn_.rt, idle_timeoutcb);
+ if (conn_.read_timeout < downstreamconf.timeout.idle_read) {
+ conn_.read_timeout = downstreamconf.timeout.idle_read;
+ conn_.last_read = std::chrono::steady_clock::now();
+ } else {
+ conn_.again_rt(downstreamconf.timeout.idle_read);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+}
+
+void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
+ ioctrl_.pause_read(reason);
+}
+
+int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ auto &downstreamconf = *worker_->get_downstream_config();
+
+ if (downstream_->get_response_buf()->rleft() <=
+ downstreamconf.request_buffer_size / 2) {
+ ioctrl_.resume_read(reason);
+ }
+
+ return 0;
+}
+
+void HttpDownstreamConnection::force_resume_read() {
+ ioctrl_.force_resume_read();
+}
+
+namespace {
+int htp_msg_begincb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+
+ if (downstream->get_response_state() != DownstreamState::INITIAL) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ int rv;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.headers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+
+ if (kv.token == http2::HD_TRANSFER_ENCODING &&
+ !http2::check_transfer_encoding(kv.value)) {
+ return -1;
+ }
+ }
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ resp.http_status = htp->status_code;
+ resp.http_major = htp->http_major;
+ resp.http_minor = htp->http_minor;
+
+ if (resp.http_major > 1 || req.http_minor > 1) {
+ resp.http_major = 1;
+ resp.http_minor = 1;
+ return -1;
+ }
+
+ auto dconn = downstream->get_downstream_connection();
+
+ downstream->set_downstream_addr_group(dconn->get_downstream_addr_group());
+ downstream->set_addr(dconn->get_addr());
+
+ // Server MUST NOT send Transfer-Encoding with a status code 1xx or
+ // 204. Also server MUST NOT send Transfer-Encoding with a status
+ // code 2xx to a CONNECT request. Same holds true with
+ // Content-Length.
+ if (resp.http_status == 204) {
+ if (resp.fs.header(http2::HD_TRANSFER_ENCODING)) {
+ return -1;
+ }
+ // Some server send content-length: 0 for 204. Until they get
+ // fixed, we accept, but ignore it.
+
+ // Calling parse_content_length() detects duplicated
+ // content-length header fields.
+ if (resp.fs.parse_content_length() != 0) {
+ return -1;
+ }
+ if (resp.fs.content_length == 0) {
+ resp.fs.erase_content_length_and_transfer_encoding();
+ } else if (resp.fs.content_length != -1) {
+ return -1;
+ }
+ } else if (resp.http_status / 100 == 1 ||
+ (resp.http_status / 100 == 2 && req.method == HTTP_CONNECT)) {
+ // Server MUST NOT send Content-Length and Transfer-Encoding in
+ // these responses.
+ resp.fs.erase_content_length_and_transfer_encoding();
+ } else if (resp.fs.parse_content_length() != 0) {
+ downstream->set_response_state(DownstreamState::MSG_BAD_HEADER);
+ return -1;
+ }
+
+ // Check upgrade before processing non-final response, since if
+ // upgrade succeeded, 101 response is treated as final in nghttpx.
+ downstream->check_upgrade_fulfilled_http1();
+
+ if (downstream->get_non_final_response()) {
+ // Reset content-length because we reuse same Downstream for the
+ // next response.
+ resp.fs.content_length = -1;
+ // 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;
+ }
+
+ // Ignore response body for non-final response.
+ return 1;
+ }
+
+ resp.connection_close = !llhttp_should_keep_alive(htp);
+ downstream->set_response_state(DownstreamState::HEADER_COMPLETE);
+ downstream->inspect_http1_response();
+
+ if (htp->flags & F_CHUNKED) {
+ downstream->set_chunked_response(true);
+ }
+
+ auto transfer_encoding = resp.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding && !downstream->get_chunked_response()) {
+ resp.connection_close = true;
+ }
+
+ if (downstream->get_upgraded()) {
+ // content-length must be ignored for upgraded connection.
+ resp.fs.content_length = -1;
+ resp.connection_close = true;
+ // transfer-encoding not applied to upgraded connection
+ downstream->set_chunked_response(false);
+ } else if (http2::legacy_http1(req.http_major, req.http_minor)) {
+ if (resp.fs.content_length == -1) {
+ resp.connection_close = true;
+ }
+ downstream->set_chunked_response(false);
+ } else if (!downstream->expect_response_body()) {
+ downstream->set_chunked_response(false);
+ }
+
+ if (loggingconf.access.write_early && downstream->accesslog_ready()) {
+ handler->write_accesslog(downstream);
+ downstream->set_accesslog_written(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_NO_BUFFER, downstream, 0) != 0) {
+ return -1;
+ }
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "HTTP upgrade success. stream_id="
+ << downstream->get_stream_id();
+ }
+ }
+
+ // 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
+ // https://tools.ietf.org/html/rfc7230#section-3.3
+
+ // TODO It seems that the cases other than HEAD are handled by
+ // llhttp. Need test.
+ return !http2::expect_response_body(req.method, resp.http_status);
+}
+} // namespace
+
+namespace {
+int ensure_header_field_buffer(const Downstream *downstream,
+ const HttpConfig &httpconf, size_t len) {
+ auto &resp = downstream->response();
+
+ if (resp.fs.buffer_size() + len > httpconf.response_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "Too large header header field size="
+ << resp.fs.buffer_size() + len;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int ensure_max_header_fields(const Downstream *downstream,
+ const HttpConfig &httpconf) {
+ auto &resp = downstream->response();
+
+ if (resp.fs.num_fields() >= httpconf.max_response_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream)
+ << "Too many header field num=" << resp.fs.num_fields() + 1;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ if (resp.fs.header_key_prev()) {
+ resp.fs.append_last_header_key(data, len);
+ } else {
+ if (ensure_max_header_fields(downstream, httpconf) != 0) {
+ return -1;
+ }
+ resp.fs.alloc_add_header_name(StringRef{data, len});
+ }
+ } else {
+ // trailer part
+ if (resp.fs.trailer_key_prev()) {
+ resp.fs.append_last_trailer_key(data, len);
+ } else {
+ if (ensure_max_header_fields(downstream, httpconf) != 0) {
+ // Could not ignore this trailer field easily, since we may
+ // get its value in htp_hdr_valcb, and it will be added to
+ // wrong place or crash if trailer fields are currently empty.
+ return -1;
+ }
+ resp.fs.alloc_add_trailer_name(StringRef{data, len});
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &httpconf = get_config()->http;
+
+ if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ resp.fs.append_last_header_value(data, len);
+ } else {
+ resp.fs.append_last_trailer_value(data, len);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+
+ resp.recv_body_length += len;
+
+ return downstream->get_upstream()->on_downstream_body(
+ downstream, reinterpret_cast<const uint8_t *>(data), len, true);
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ auto downstream = static_cast<Downstream *>(htp->data);
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : resp.fs.trailers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ // llhttp does not treat "200 connection established" response
+ // against CONNECT request, and in that case, this function is not
+ // called. But if HTTP Upgrade is made (e.g., WebSocket), this
+ // function is called, and llhttp_execute() returns just after that.
+ if (downstream->get_upgraded()) {
+ return 0;
+ }
+
+ if (downstream->get_non_final_response()) {
+ downstream->reset_response();
+
+ return 0;
+ }
+
+ downstream->set_response_state(DownstreamState::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
+
+int HttpDownstreamConnection::write_first() {
+ int rv;
+
+ process_blocked_request_buf();
+
+ if (conn_.tls.ssl) {
+ rv = write_tls();
+ } else {
+ rv = write_clear();
+ }
+
+ if (rv != 0) {
+ return SHRPX_ERR_RETRY;
+ }
+
+ if (conn_.tls.ssl) {
+ on_write_ = &HttpDownstreamConnection::write_tls;
+ } else {
+ on_write_ = &HttpDownstreamConnection::write_clear;
+ }
+
+ first_write_done_ = true;
+ downstream_->set_request_header_sent(true);
+
+ auto buf = downstream_->get_blocked_request_buf();
+ buf->reset();
+
+ // upstream->resume_read() might be called in
+ // write_tls()/write_clear(), but before blocked_request_buf_ is
+ // reset. So upstream read might still be blocked. Let's do it
+ // again here.
+ auto input = downstream_->get_request_buf();
+ if (input->rleft() == 0) {
+ auto upstream = downstream_->get_upstream();
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 16_k> buf;
+ int rv;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
+ auto htperr = llhttp_finish(&response_htp_);
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP response ended prematurely: "
+ << llhttp_errno_name(htperr);
+ }
+
+ return -1;
+ }
+ }
+
+ return nread;
+ }
+
+ rv = process_input(buf.data(), nread);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!ev_is_active(&conn_.rev)) {
+ return 0;
+ }
+ }
+}
+
+int HttpDownstreamConnection::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ 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) {
+ if (!first_write_done_) {
+ return nwrite;
+ }
+ // We may have pending data in receive buffer which may contain
+ // part of response body. So keep reading. Invoke read event
+ // to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ on_write_ = &HttpDownstreamConnection::noop;
+ reusable_ = false;
+ break;
+ }
+
+ input->drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (input->rleft() == 0) {
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::tls_handshake() {
+ ERR_clear_error();
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ auto rv = conn_.tls_handshake();
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ downstream_failure(addr_, raddr_);
+
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ auto &connect_blocker = addr_->connect_blocker;
+
+ signal_write_ = &HttpDownstreamConnection::actual_signal_write;
+
+ connect_blocker->on_success();
+
+ ev_set_cb(&conn_.rt, timeoutcb);
+ ev_set_cb(&conn_.wt, timeoutcb);
+
+ on_read_ = &HttpDownstreamConnection::read_tls;
+ on_write_ = &HttpDownstreamConnection::write_first;
+
+ // TODO Check negotiated ALPN
+
+ return on_write();
+}
+
+int HttpDownstreamConnection::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ std::array<uint8_t, 16_k> buf;
+ int rv;
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
+ auto htperr = llhttp_finish(&response_htp_);
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP response ended prematurely: "
+ << llhttp_errno_name(htperr);
+ }
+
+ return -1;
+ }
+ }
+
+ return nread;
+ }
+
+ rv = process_input(buf.data(), nread);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (!ev_is_active(&conn_.rev)) {
+ return 0;
+ }
+ }
+}
+
+int HttpDownstreamConnection::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto upstream = downstream_->get_upstream();
+ auto input = downstream_->get_request_buf();
+
+ struct iovec iov;
+
+ while (input->rleft() > 0) {
+ auto iovcnt = input->riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ if (!first_write_done_) {
+ return nwrite;
+ }
+ // We may have pending data in receive buffer which may contain
+ // part of response body. So keep reading. Invoke read event
+ // to get read(2) error just in case.
+ ev_feed_event(conn_.loop, &conn_.rev, EV_READ);
+ on_write_ = &HttpDownstreamConnection::noop;
+ reusable_ = false;
+ break;
+ }
+
+ input->drain(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (input->rleft() == 0) {
+ auto &req = downstream_->request();
+
+ upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+ req.unconsumed_body_length);
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::process_input(const uint8_t *data,
+ size_t datalen) {
+ int rv;
+
+ if (downstream_->get_upgraded()) {
+ // For upgraded connection, just pass data to the upstream.
+ rv = downstream_->get_upstream()->on_downstream_body(downstream_, data,
+ datalen, true);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+
+ return 0;
+ }
+
+ auto htperr = llhttp_execute(&response_htp_,
+ reinterpret_cast<const char *>(data), datalen);
+ auto nproc =
+ htperr == HPE_OK
+ ? datalen
+ : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
+ llhttp_get_error_pos(&response_htp_)) -
+ data);
+
+ if (htperr != HPE_OK &&
+ (!downstream_->get_upgraded() || htperr != HPE_PAUSED_UPGRADE)) {
+ // Handling early return (in other words, response was hijacked by
+ // mruby scripting).
+ if (downstream_->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return SHRPX_ERR_DCONN_CANCELED;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "HTTP parser failure: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&response_htp_);
+ }
+
+ return -1;
+ }
+
+ if (downstream_->get_upgraded()) {
+ if (nproc < datalen) {
+ // Data from data + nproc are for upgraded protocol.
+ rv = downstream_->get_upstream()->on_downstream_body(
+ downstream_, data + nproc, datalen - nproc, true);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ if (downstream_->response_buf_full()) {
+ downstream_->pause_read(SHRPX_NO_BUFFER);
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpDownstreamConnection::connected() {
+ auto &connect_blocker = addr_->connect_blocker;
+
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ conn_.wlimit.stopw();
+
+ DCLOG(WARN, this) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_)
+ << ": errno=" << sock_error;
+
+ downstream_failure(addr_, raddr_);
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Connected to downstream host";
+ }
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = group_->shared_addr->timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ ev_set_cb(&conn_.wev, writecb);
+
+ if (conn_.tls.ssl) {
+ on_read_ = &HttpDownstreamConnection::tls_handshake;
+ on_write_ = &HttpDownstreamConnection::tls_handshake;
+
+ return 0;
+ }
+
+ signal_write_ = &HttpDownstreamConnection::actual_signal_write;
+
+ connect_blocker->on_success();
+
+ ev_set_cb(&conn_.rt, timeoutcb);
+ ev_set_cb(&conn_.wt, timeoutcb);
+
+ on_read_ = &HttpDownstreamConnection::read_clear;
+ on_write_ = &HttpDownstreamConnection::write_first;
+
+ return 0;
+}
+
+int HttpDownstreamConnection::on_read() { return on_read_(*this); }
+
+int HttpDownstreamConnection::on_write() { return on_write_(*this); }
+
+void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+void HttpDownstreamConnection::signal_write() { signal_write_(*this); }
+
+int HttpDownstreamConnection::actual_signal_write() {
+ ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE);
+ return 0;
+}
+
+int HttpDownstreamConnection::noop() { return 0; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+HttpDownstreamConnection::get_downstream_addr_group() const {
+ return group_;
+}
+
+DownstreamAddr *HttpDownstreamConnection::get_addr() const { return addr_; }
+
+bool HttpDownstreamConnection::poolable() const {
+ return !group_->retired && reusable_;
+}
+
+const Address *HttpDownstreamConnection::get_raddr() const { return raddr_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h
new file mode 100644
index 0000000..a453f0d
--- /dev/null
+++ b/src/shrpx_http_downstream_connection.h
@@ -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_HTTP_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include "llhttp.h"
+
+#include "shrpx_downstream_connection.h"
+#include "shrpx_io_control.h"
+#include "shrpx_connection.h"
+
+namespace shrpx {
+
+class DownstreamConnectionPool;
+class Worker;
+struct DownstreamAddrGroup;
+struct DownstreamAddr;
+struct DNSQuery;
+
+class HttpDownstreamConnection : public DownstreamConnection {
+public:
+ HttpDownstreamConnection(const std::shared_ptr<DownstreamAddrGroup> &group,
+ DownstreamAddr *addr, struct ev_loop *loop,
+ Worker *worker);
+ 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();
+ void end_upload_data_chunk();
+
+ 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 bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+ int initiate_connection();
+
+ int write_first();
+ int read_clear();
+ int write_clear();
+ int read_tls();
+ int write_tls();
+
+ int process_input(const uint8_t *data, size_t datalen);
+ int tls_handshake();
+
+ int connected();
+ void signal_write();
+ int actual_signal_write();
+
+ // Returns address used to connect to backend. Could be nullptr.
+ const Address *get_raddr() const;
+
+ int noop();
+
+ int process_blocked_request_buf();
+
+private:
+ Connection conn_;
+ std::function<int(HttpDownstreamConnection &)> on_read_, on_write_,
+ signal_write_;
+ Worker *worker_;
+ // nullptr if TLS is not used.
+ SSL_CTX *ssl_ctx_;
+ std::shared_ptr<DownstreamAddrGroup> group_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr, or
+ // resolved_addr_.get().
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ IOControl ioctrl_;
+ llhttp_t response_htp_;
+ // true if first write succeeded.
+ bool first_write_done_;
+ // true if this object can be reused
+ bool reusable_;
+ // true if request header is written to request buffer.
+ bool request_header_written_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http_test.cc b/src/shrpx_http_test.cc
new file mode 100644
index 0000000..3ace870
--- /dev/null
+++ b/src/shrpx_http_test.cc
@@ -0,0 +1,168 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_http.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_http_create_forwarded(void) {
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
+ "proto=https" ==
+ http::create_forwarded(balloc,
+ FORWARDED_BY | FORWARDED_FOR |
+ FORWARDED_HOST | FORWARDED_PROTO,
+ StringRef::from_lit("example.com:3000"),
+ StringRef::from_lit("[::1]"),
+ StringRef::from_lit("www.example.com"),
+ StringRef::from_lit("https")));
+
+ CU_ASSERT("for=192.168.0.1" ==
+ http::create_forwarded(
+ balloc, FORWARDED_FOR, StringRef::from_lit("alpha"),
+ StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("bravo"), StringRef::from_lit("charlie")));
+
+ CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
+ http::create_forwarded(
+ balloc, FORWARDED_BY | FORWARDED_FOR,
+ StringRef::from_lit("_hidden"), StringRef::from_lit("[::1]"),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+
+ CU_ASSERT("by=\"[::1]\";for=_hidden" ==
+ http::create_forwarded(
+ balloc, FORWARDED_BY | FORWARDED_FOR,
+ StringRef::from_lit("[::1]"), StringRef::from_lit("_hidden"),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+
+ CU_ASSERT("" ==
+ http::create_forwarded(
+ balloc,
+ FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
+ StringRef::from_lit(""), StringRef::from_lit(""),
+ StringRef::from_lit(""), StringRef::from_lit("")));
+}
+
+void test_shrpx_http_create_via_header_value(void) {
+ std::array<char, 16> buf;
+
+ auto end = http::create_via_header_value(std::begin(buf), 1, 1);
+
+ CU_ASSERT(("1.1 nghttpx" == StringRef{std::begin(buf), end}));
+
+ std::fill(std::begin(buf), std::end(buf), '\0');
+
+ end = http::create_via_header_value(std::begin(buf), 2, 0);
+
+ CU_ASSERT(("2 nghttpx" == StringRef{std::begin(buf), end}));
+}
+
+void test_shrpx_http_create_affinity_cookie(void) {
+ BlockAllocator balloc(1024, 1024);
+ StringRef c;
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("cookie-val"),
+ 0xf1e2d3c4u, StringRef{}, false);
+
+ CU_ASSERT("cookie-val=f1e2d3c4" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("alpha"),
+ 0x00000000u, StringRef{}, true);
+
+ CU_ASSERT("alpha=00000000; Secure" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("bravo"),
+ 0x01111111u, StringRef::from_lit("bar"),
+ false);
+
+ CU_ASSERT("bravo=01111111; Path=bar" == c);
+
+ c = http::create_affinity_cookie(balloc, StringRef::from_lit("charlie"),
+ 0x01111111u, StringRef::from_lit("bar"),
+ true);
+
+ CU_ASSERT("charlie=01111111; Path=bar; Secure" == c);
+}
+
+void test_shrpx_http_create_altsvc_header_value(void) {
+ {
+ BlockAllocator balloc(1024, 1024);
+ std::vector<AltSvc> altsvcs{
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3"),
+ .host = StringRef::from_lit("127.0.0.1"),
+ .service = StringRef::from_lit("443"),
+ .params = StringRef::from_lit("ma=3600"),
+ },
+ };
+
+ CU_ASSERT(R"(h3="127.0.0.1:443"; ma=3600)" ==
+ http::create_altsvc_header_value(balloc, altsvcs));
+ }
+
+ {
+ BlockAllocator balloc(1024, 1024);
+ std::vector<AltSvc> altsvcs{
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3"),
+ .service = StringRef::from_lit("443"),
+ .params = StringRef::from_lit("ma=3600"),
+ },
+ AltSvc{
+ .protocol_id = StringRef::from_lit("h3%"),
+ .host = StringRef::from_lit("\"foo\""),
+ .service = StringRef::from_lit("4433"),
+ },
+ };
+
+ CU_ASSERT(R"(h3=":443"; ma=3600, h3%25="\"foo\":4433")" ==
+ http::create_altsvc_header_value(balloc, altsvcs));
+ }
+}
+
+void test_shrpx_http_check_http_scheme(void) {
+ CU_ASSERT(http::check_http_scheme(StringRef::from_lit("https"), true));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("https"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("http"), true));
+ CU_ASSERT(http::check_http_scheme(StringRef::from_lit("http"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), true));
+ CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), false));
+ CU_ASSERT(!http::check_http_scheme(StringRef{}, true));
+ CU_ASSERT(!http::check_http_scheme(StringRef{}, false));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http_test.h b/src/shrpx_http_test.h
new file mode 100644
index 0000000..d50ab53
--- /dev/null
+++ b/src/shrpx_http_test.h
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_TEST_H
+#define SHRPX_HTTP_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_http_create_forwarded(void);
+void test_shrpx_http_create_via_header_value(void);
+void test_shrpx_http_create_affinity_cookie(void);
+void test_shrpx_http_create_altsvc_header_value(void);
+void test_shrpx_http_check_http_scheme(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_TEST_H
diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc
new file mode 100644
index 0000000..49d2088
--- /dev/null
+++ b/src/shrpx_https_upstream.cc
@@ -0,0 +1,1582 @@
+/*
+ * 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_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+#include "base64.h"
+#include "url-parser/url_parser.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+int htp_msg_begin(llhttp_t *htp);
+int htp_uricb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdrs_completecb(llhttp_t *htp);
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len);
+int htp_msg_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begin, // llhttp_cb on_message_begin;
+ htp_uricb, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_bodycb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+HttpsUpstream::HttpsUpstream(ClientHandler *handler)
+ : handler_(handler),
+ current_header_length_(0),
+ ioctrl_(handler->get_rlimit()),
+ num_requests_(0) {
+ llhttp_init(&htp_, HTTP_REQUEST, &htp_hooks);
+ htp_.data = this;
+}
+
+HttpsUpstream::~HttpsUpstream() {}
+
+void HttpsUpstream::reset_current_header_length() {
+ current_header_length_ = 0;
+}
+
+void HttpsUpstream::on_start_request() {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP request started";
+ }
+ reset_current_header_length();
+
+ auto downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
+
+ attach_downstream(std::move(downstream));
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.read;
+
+ handler_->repeat_read_timer();
+
+ ++num_requests_;
+}
+
+namespace {
+int htp_msg_begin(llhttp_t *htp) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ upstream->on_start_request();
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_uricb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ // We happen to have the same value for method token.
+ req.method = htp->method;
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large URI size="
+ << req.fs.buffer_size() + len;
+ }
+ assert(downstream->get_request_state() == DownstreamState::INITIAL);
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too long request URI");
+ return HPE_USER;
+ }
+
+ req.fs.add_extra_buffer_size(len);
+
+ if (req.method == HTTP_CONNECT) {
+ req.authority =
+ concat_string_ref(balloc, req.authority, StringRef{data, len});
+ } else {
+ req.path = concat_string_ref(balloc, req.path, StringRef{data, len});
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &httpconf = get_config()->http;
+
+ if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ if (req.fs.header_key_prev()) {
+ req.fs.append_last_header_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_header_name(StringRef{data, len});
+ }
+ } else {
+ // trailer part
+ if (req.fs.trailer_key_prev()) {
+ req.fs.append_last_trailer_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_trailer_name(StringRef{data, len});
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ req.fs.append_last_header_value(data, len);
+ } else {
+ req.fs.append_last_trailer_value(data, len);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req,
+ const StringRef &uri,
+ http_parser_url &u) {
+ assert(u.field_set & (1 << UF_HOST));
+
+ // As per https://tools.ietf.org/html/rfc7230#section-5.4, we
+ // rewrite host header field with authority component.
+ auto authority = util::get_uri_field(uri.c_str(), u, UF_HOST);
+ // TODO properly check IPv6 numeric address
+ auto ipv6 = std::find(std::begin(authority), std::end(authority), ':') !=
+ std::end(authority);
+ auto authoritylen = authority.size();
+ if (ipv6) {
+ authoritylen += 2;
+ }
+ if (u.field_set & (1 << UF_PORT)) {
+ authoritylen += 1 + str_size("65535");
+ }
+ if (authoritylen > authority.size()) {
+ auto iovec = make_byte_ref(balloc, authoritylen + 1);
+ auto p = iovec.base;
+ if (ipv6) {
+ *p++ = '[';
+ }
+ p = std::copy(std::begin(authority), std::end(authority), p);
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ if (u.field_set & (1 << UF_PORT)) {
+ *p++ = ':';
+ p = util::utos(p, u.port);
+ }
+ *p = '\0';
+
+ req.authority = StringRef{iovec.base, p};
+ } else {
+ req.authority = authority;
+ }
+
+ req.scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+
+ StringRef path;
+ if (u.field_set & (1 << UF_PATH)) {
+ path = util::get_uri_field(uri.c_str(), u, UF_PATH);
+ } else if (req.method == HTTP_OPTIONS) {
+ // 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
+ req.path = StringRef::from_lit("");
+ // we ignore query component here
+ return;
+ } else {
+ path = StringRef::from_lit("/");
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ auto &fdata = u.field_data[UF_QUERY];
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto q = util::get_uri_field(uri.c_str(), u, UF_QUERY);
+ path = StringRef{std::begin(path), std::end(q)};
+ } else {
+ path = concat_string_ref(balloc, path, StringRef::from_lit("?"),
+ StringRef{&uri[fdata.off], fdata.len});
+ }
+ }
+
+ req.path = http2::rewrite_clean_path(balloc, path);
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request headers completed";
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.headers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+
+ if (kv.token == http2::HD_TRANSFER_ENCODING &&
+ !http2::check_transfer_encoding(kv.value)) {
+ return -1;
+ }
+ }
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ req.tstamp = lgconf->tstamp;
+
+ req.http_major = htp->http_major;
+ req.http_minor = htp->http_minor;
+
+ req.connection_close = !llhttp_should_keep_alive(htp);
+
+ handler->stop_read_timer();
+
+ auto method = req.method;
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ ss << http2::to_method_string(method) << " "
+ << (method == HTTP_CONNECT ? req.authority : req.path) << " "
+ << "HTTP/" << req.http_major << "." << req.http_minor << "\n";
+
+ for (const auto &kv : req.fs.headers()) {
+ if (kv.name == "authorization") {
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n";
+ }
+
+ ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
+ }
+
+ // set content-length if method is not CONNECT, and no
+ // transfer-encoding is given. If transfer-encoding is given, leave
+ // req.fs.content_length to -1.
+ if (method != HTTP_CONNECT && !req.fs.header(http2::HD_TRANSFER_ENCODING)) {
+ // llhttp sets 0 to htp->content_length if there is no
+ // content-length header field. If we don't have both
+ // transfer-encoding and content-length header field, we assume
+ // that there is no request body.
+ req.fs.content_length = htp->content_length;
+ }
+
+ auto host = req.fs.header(http2::HD_HOST);
+
+ if (req.http_major > 1 || req.http_minor > 1) {
+ req.http_major = 1;
+ req.http_minor = 1;
+ return -1;
+ }
+
+ if (req.http_major == 1 && req.http_minor == 1 && !host) {
+ return -1;
+ }
+
+ if (host) {
+ const auto &value = host->value;
+ // Not allow at least '"' or '\' in host. They are illegal in
+ // authority component, also they cause headaches when we put them
+ // in quoted-string.
+ if (std::find_if(std::begin(value), std::end(value), [](char c) {
+ return c == '"' || c == '\\';
+ }) != std::end(value)) {
+ return -1;
+ }
+ }
+
+ downstream->inspect_http1_request();
+
+ if (htp->flags & F_CHUNKED) {
+ downstream->set_chunked_request(true);
+ }
+
+ auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
+ if (transfer_encoding &&
+ http2::legacy_http1(req.http_major, req.http_minor)) {
+ return -1;
+ }
+
+ auto faddr = handler->get_upstream_addr();
+ auto config = get_config();
+
+ if (method != HTTP_CONNECT) {
+ http_parser_url u{};
+ rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u);
+ if (rv != 0) {
+ // Expect to respond with 400 bad request
+ return -1;
+ }
+ // checking UF_HOST could be redundant, but just in case ...
+ if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
+ req.no_authority = true;
+
+ if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) {
+ req.path = StringRef{};
+ } else {
+ req.path = http2::rewrite_clean_path(balloc, req.path);
+ }
+
+ if (host) {
+ req.authority = host->value;
+ }
+
+ if (handler->get_ssl()) {
+ req.scheme = StringRef::from_lit("https");
+ } else {
+ req.scheme = StringRef::from_lit("http");
+ }
+ } else {
+ rewrite_request_host_path_from_uri(balloc, req, req.path, u);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ auto &resp = downstream->response();
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, handler->get_ssl() != nullptr)) {
+ resp.http_status = 400;
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+#endif // HAVE_MRUBY
+
+ // mruby hook may change method value
+
+ if (req.no_authority && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ // Request URI should be absolute-form for client proxy mode
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler->get_downstream_connection(rv, downstream);
+
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ upstream->redirect_to_https(downstream);
+ }
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ if (downstream->attach_downstream_connection(std::move(dconn)) == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ // Normally, we forward expect: 100-continue to backend server,
+ // and let them decide whether responds with 100 Continue or not.
+ // For alternative mode, we have no backend, so just send 100
+ // Continue here to make the client happy.
+ if (downstream->get_expect_100_continue()) {
+ auto output = downstream->get_response_buf();
+ constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n");
+ output->append(res);
+ handler->signal_write();
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(llhttp_t *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) {
+ // Ignore error if response has been completed. We will end up in
+ // htp_msg_completecb, and request will end gracefully.
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ llhttp_set_error_reason(htp, "could not process request body");
+ return HPE_USER;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request completed";
+ }
+ auto handler = upstream->get_client_handler();
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.trailers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // Here both response and request were completed. One of the
+ // reason why end_upload_data() failed is when we sent response
+ // in request phase hook. We only delete and proceed to the
+ // next request handling (if we don't close the connection). We
+ // first pause parser here just as we normally do, and call
+ // signal_write() to run on_write().
+ return HPE_PAUSED;
+ }
+ return -1;
+ }
+
+ if (handler->get_http2_upgrade_allowed() &&
+ downstream->get_http2_upgrade_request() &&
+ handler->perform_http2_upgrade(upstream) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";
+ }
+ }
+
+ // Stop further processing to complete this request
+ return HPE_PAUSED;
+}
+} // 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 rlimit = handler_->get_rlimit();
+ auto downstream = get_downstream();
+
+ if (rb->rleft() == 0 || handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ // downstream can be nullptr here, because it is initialized in the
+ // callback chain called by llhttp_execute()
+ if (downstream && downstream->get_upgraded()) {
+
+ auto rv = downstream->push_upload_data_chunk(rb->pos(), rb->rleft());
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ rb->reset();
+ rlimit->startw();
+
+ 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;
+ }
+
+ if (downstream) {
+ // To avoid reading next pipelined request
+ switch (downstream->get_request_state()) {
+ case DownstreamState::INITIAL:
+ case DownstreamState::HEADER_COMPLETE:
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ // llhttp_execute() does nothing once it entered error state.
+ auto htperr = llhttp_execute(&htp_, reinterpret_cast<const char *>(rb->pos()),
+ rb->rleft());
+
+ if (htperr == HPE_PAUSED_UPGRADE &&
+ rb->pos() ==
+ reinterpret_cast<const uint8_t *>(llhttp_get_error_pos(&htp_))) {
+ llhttp_resume_after_upgrade(&htp_);
+
+ htperr = llhttp_execute(&htp_, reinterpret_cast<const char *>(rb->pos()),
+ rb->rleft());
+ }
+
+ auto nread =
+ htperr == HPE_OK
+ ? rb->rleft()
+ : reinterpret_cast<const uint8_t *>(llhttp_get_error_pos(&htp_)) -
+ rb->pos();
+ rb->drain(nread);
+ rlimit->startw();
+
+ // 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();
+
+ if (htperr == HPE_PAUSED) {
+ // We may pause parser in htp_msg_completecb when both side are
+ // completed. Signal write, so that we can run on_write().
+ if (downstream &&
+ downstream->get_request_state() == DownstreamState::MSG_COMPLETE &&
+ downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ handler_->signal_write();
+ }
+ return 0;
+ }
+
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP parse failure: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&htp_);
+ }
+
+ if (downstream &&
+ downstream->get_response_state() != DownstreamState::INITIAL) {
+ handler_->set_should_close_after_write(true);
+ handler_->signal_write();
+ return 0;
+ }
+
+ unsigned int status_code;
+
+ if (htperr == HPE_INVALID_METHOD) {
+ status_code = 501;
+ } else if (downstream) {
+ status_code = downstream->response().http_status;
+ if (status_code == 0) {
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ status_code = 502;
+ } else if (downstream->get_request_state() ==
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE) {
+ status_code = 431;
+ } else {
+ status_code = 400;
+ }
+ }
+ } 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 output = downstream->get_response_buf();
+ const auto &resp = downstream->response();
+
+ if (output->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() == DownstreamState::MSG_COMPLETE) {
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ } else {
+ // Connection close
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ }
+ // We need this if response ends before request.
+ if (downstream->get_request_state() == DownstreamState::MSG_COMPLETE) {
+ delete_downstream();
+
+ if (handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.idle_read;
+
+ handler_->repeat_read_timer();
+
+ return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
+ } else {
+ // If the request is not complete, close the connection.
+ delete_downstream();
+
+ handler_->set_should_close_after_write(true);
+
+ return 0;
+ }
+ }
+
+ return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length);
+}
+
+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 (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.
+ llhttp_resume(&htp_);
+
+ auto conn = handler_->get_connection();
+ ev_feed_event(conn->loop, &conn->rev, EV_READ);
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+ int rv;
+
+ rv = downstream->on_read();
+
+ if (rv == SHRPX_ERR_EOF) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (rv < 0) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_BAD_HEADER) {
+ error_reply(502);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+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 rv;
+ }
+
+ 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() == DownstreamState::MSG_COMPLETE) {
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::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(DownstreamState::MSG_COMPLETE);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::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() != DownstreamState::INITIAL) {
+ return -1;
+ }
+
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ error_reply(status);
+
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto connection_close = false;
+
+ auto worker = handler_->get_worker();
+
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+ connection_close = true;
+ } else if (req.http_major <= 0 ||
+ (req.http_major == 1 && req.http_minor == 0)) {
+ connection_close = true;
+ } else {
+ auto c = resp.fs.header(http2::HD_CONNECTION);
+ if (c && util::strieq_l("close", c->value)) {
+ connection_close = true;
+ }
+ }
+
+ if (connection_close) {
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+ }
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, resp.http_status));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(resp.http_status));
+ output->append("\r\n");
+
+ for (auto &kv : resp.fs.headers()) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ http2::capitalize(output, kv.name);
+ output->append(": ");
+ output->append(kv.value);
+ output->append("\r\n");
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ output->append("Server: ");
+ output->append(config->http.server_name);
+ output->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ output->append(p.name);
+ output->append(": ");
+ output->append(p.value);
+ output->append("\r\n");
+ }
+
+ output->append("\r\n");
+
+ output->append(body, bodylen);
+
+ downstream->response_sent_body_length += bodylen;
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ return 0;
+}
+
+void HttpsUpstream::error_reply(unsigned int status_code) {
+ auto downstream = get_downstream();
+
+ if (!downstream) {
+ attach_downstream(
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 1));
+ downstream = get_downstream();
+ }
+
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+
+ resp.http_status = status_code;
+ // we are going to close connection for both frontend and backend in
+ // error condition. This is safest option.
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, status_code));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(status_code));
+ output->append("\r\nServer: ");
+ output->append(get_config()->http.server_name);
+ output->append("\r\nContent-Length: ");
+ std::array<uint8_t, NGHTTP2_MAX_UINT64_DIGITS> intbuf;
+ output->append(StringRef{std::begin(intbuf),
+ util::utos(std::begin(intbuf), html.size())});
+ output->append("\r\nDate: ");
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ output->append(lgconf->tstamp->time_http);
+ output->append("\r\nContent-Type: text/html; "
+ "charset=UTF-8\r\nConnection: close\r\n\r\n");
+ output->append(html);
+
+ downstream->response_sent_body_length += html.size();
+ downstream->set_response_state(DownstreamState::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";
+ }
+ }
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto dconn = downstream->get_downstream_connection();
+ // dconn might be nullptr if this is non-final response from mruby.
+
+ if (downstream->get_non_final_response() &&
+ !downstream->supports_non_final_response()) {
+ resp.fs.clear_headers();
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ assert(dconn);
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto connect_method = req.method == HTTP_CONNECT;
+
+ auto buf = downstream->get_response_buf();
+ buf->append("HTTP/");
+ buf->append('0' + req.http_major);
+ buf->append('.');
+ buf->append('0' + req.http_minor);
+ buf->append(' ');
+ if (req.connect_proto != ConnectProto::NONE && downstream->get_upgraded()) {
+ buf->append(http2::stringify_status(balloc, 101));
+ buf->append(' ');
+ buf->append(http2::get_reason_phrase(101));
+ } else {
+ buf->append(http2::stringify_status(balloc, resp.http_status));
+ buf->append(' ');
+ buf->append(http2::get_reason_phrase(resp.http_status));
+ }
+ buf->append("\r\n");
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(
+ get_client_handler()->get_upstream_scheme());
+ }
+
+ if (downstream->get_non_final_response()) {
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ resp.fs.clear_headers();
+
+ return 0;
+ }
+
+ auto build_flags = (http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA) |
+ (!http2::legacy_http1(req.http_major, req.http_minor)
+ ? 0
+ : http2::HDOP_STRIP_TRANSFER_ENCODING);
+
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(), build_flags);
+
+ auto worker = handler_->get_worker();
+
+ // after graceful shutdown commenced, add connection: close header
+ // field.
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.connection_close = true;
+ }
+
+ // We check downstream->get_response_connection_close() in case when
+ // the Content-Length is not available.
+ if (!req.connection_close && !resp.connection_close) {
+ if (req.http_major <= 0 || req.http_minor <= 0) {
+ // We add this header for HTTP/1.0 or HTTP/0.9 clients
+ buf->append("Connection: Keep-Alive\r\n");
+ }
+ } else if (!downstream->get_upgraded()) {
+ buf->append("Connection: close\r\n");
+ }
+
+ if (!connect_method && downstream->get_upgraded()) {
+ if (req.connect_proto == ConnectProto::WEBSOCKET &&
+ resp.http_status / 100 == 2) {
+ buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
+ auto key = req.fs.header(http2::HD_SEC_WEBSOCKET_KEY);
+ if (!key || key->value.size() != base64::encode_length(16)) {
+ return -1;
+ }
+ std::array<uint8_t, base64::encode_length(20)> out;
+ auto accept = http2::make_websocket_accept_token(out.data(), key->value);
+ if (accept.empty()) {
+ return -1;
+ }
+ buf->append("Sec-WebSocket-Accept: ");
+ buf->append(accept);
+ buf->append("\r\n");
+ } else {
+ auto connection = resp.fs.header(http2::HD_CONNECTION);
+ if (connection) {
+ buf->append("Connection: ");
+ buf->append((*connection).value);
+ buf->append("\r\n");
+ }
+
+ auto upgrade = resp.fs.header(http2::HD_UPGRADE);
+ if (upgrade) {
+ buf->append("Upgrade: ");
+ buf->append((*upgrade).value);
+ buf->append("\r\n");
+ }
+ }
+ }
+
+ if (!resp.fs.header(http2::HD_ALT_SVC)) {
+ // We won't change or alter alt-svc from backend for now
+ if (!httpconf.altsvcs.empty()) {
+ buf->append("Alt-Svc: ");
+ buf->append(httpconf.altsvc_header_value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ buf->append("Server: ");
+ buf->append(httpconf.server_name);
+ buf->append("\r\n");
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ buf->append("Server: ");
+ buf->append((*server).value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (req.method != HTTP_CONNECT || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ buf->append("Set-Cookie: ");
+ buf->append(cookie_str);
+ buf->append("\r\n");
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ buf->append("Via: ");
+ buf->append((*via).value);
+ buf->append("\r\n");
+ }
+ } else {
+ buf->append("Via: ");
+ if (via) {
+ buf->append((*via).value);
+ buf->append(", ");
+ }
+ std::array<char, 16> viabuf;
+ auto end = http::create_via_header_value(viabuf.data(), resp.http_major,
+ resp.http_minor);
+ buf->append(viabuf.data(), end - std::begin(viabuf));
+ buf->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ buf->append(p.name);
+ buf->append(": ");
+ buf->append(p.value);
+ buf->append("\r\n");
+ }
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ 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()) {
+ output->append(util::utox(len));
+ output->append("\r\n");
+ }
+ output->append(data, len);
+
+ downstream->response_sent_body_length += len;
+
+ if (downstream->get_chunked_response()) {
+ output->append("\r\n");
+ }
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ if (downstream->get_chunked_response()) {
+ auto output = downstream->get_response_buf();
+ const auto &trailers = resp.fs.trailers();
+ if (trailers.empty()) {
+ output->append("0\r\n\r\n");
+ } else {
+ output->append("0\r\n");
+ http2::build_http1_headers_from_headers(output, trailers,
+ http2::HDOP_STRIP_ALL);
+ output->append("\r\n");
+ }
+ }
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ if (!downstream->validate_response_recv_body_length()) {
+ resp.connection_close = true;
+ }
+
+ if (req.connection_close || resp.connection_close ||
+ // To avoid to stall upload body
+ downstream->get_request_state() != DownstreamState::MSG_COMPLETE) {
+ 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;
+}
+
+int HttpsUpstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ redirect_to_https(downstream);
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::redirect_to_https(Downstream *downstream) {
+ auto &req = downstream->request();
+ if (req.method == HTTP_CONNECT || req.scheme != "http" ||
+ req.authority.empty()) {
+ error_reply(400);
+ return 0;
+ }
+
+ auto authority = util::extract_host(req.authority);
+ if (authority.empty()) {
+ error_reply(400);
+ return 0;
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ StringRef loc;
+ if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ req.path);
+ } else {
+ loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
+ StringRef::from_lit(":"),
+ httpconf.redirect_https_port, req.path);
+ }
+
+ auto &resp = downstream->response();
+ resp.http_status = 308;
+ resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
+ http2::HD_LOCATION);
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+
+ return send_reply(downstream, nullptr, 0);
+}
+
+void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const {
+ std::string nhdrs;
+ for (auto chunk = buf->head; chunk; chunk = chunk->next) {
+ nhdrs.append(chunk->pos, chunk->last);
+ }
+ if (log_config()->errorlog_tty) {
+ nhdrs = http::colorizeHeaders(nhdrs.c_str());
+ }
+ ULOG(INFO, this) << "HTTP response headers\n" << nhdrs;
+}
+
+void HttpsUpstream::on_handler_delete() {
+ if (downstream_ && downstream_->accesslog_ready()) {
+ handler_->write_accesslog(downstream_.get());
+ }
+}
+
+int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ assert(downstream == downstream_.get());
+
+ downstream_->pop_downstream_connection();
+
+ if (!downstream_->request_submission_ready()) {
+ switch (downstream_->get_response_state()) {
+ case DownstreamState::MSG_COMPLETE:
+ // We have got all response body already. Send it off.
+ return 0;
+ case DownstreamState::INITIAL:
+ if (on_downstream_abort_request(downstream_.get(), 502) != 0) {
+ return -1;
+ }
+ return 0;
+ default:
+ break;
+ }
+ // Return error so that caller can delete handler
+ return -1;
+ }
+
+ downstream_->add_retry();
+
+ rv = 0;
+
+ if (no_retry || downstream_->no_more_retry()) {
+ goto fail;
+ }
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream_.get());
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream_->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream_->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = on_downstream_abort_request(downstream_.get(), 502);
+ }
+ if (rv != 0) {
+ return -1;
+ }
+ downstream_->pop_downstream_connection();
+
+ return 0;
+}
+
+int HttpsUpstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ return 0;
+}
+
+int HttpsUpstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ if (!downstream_) {
+ return 0;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->riovec(iov, iovcnt);
+}
+
+void HttpsUpstream::response_drain(size_t n) {
+ if (!downstream_) {
+ return;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ buf->drain(n);
+}
+
+bool HttpsUpstream::response_empty() const {
+ if (!downstream_) {
+ return true;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->rleft() == 0;
+}
+
+Downstream *
+HttpsUpstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ return nullptr;
+}
+
+int HttpsUpstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ return -1;
+}
+
+bool HttpsUpstream::push_enabled() const { return false; }
+
+void HttpsUpstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {}
+
+} // namespace shrpx
diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h
new file mode 100644
index 0000000..d85d2de
--- /dev/null
+++ b/src/shrpx_https_upstream.h
@@ -0,0 +1,113 @@
+/*
+ * 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 <cinttypes>
+#include <memory>
+
+#include "llhttp.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 int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream);
+ 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(Downstream *downstream, bool no_retry);
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen);
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri);
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const;
+ virtual void response_drain(size_t n);
+ virtual bool response_empty() const;
+
+ virtual Downstream *on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id);
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream);
+ virtual bool push_enabled() const;
+ virtual void cancel_premature_downstream(Downstream *promised_downstream);
+
+ void reset_current_header_length();
+ void log_response_headers(DefaultMemchunks *buf) const;
+ int redirect_to_https(Downstream *downstream);
+
+ // Called when new request has started.
+ void on_start_request();
+
+private:
+ ClientHandler *handler_;
+ llhttp_t htp_;
+ size_t current_header_length_;
+ std::unique_ptr<Downstream> downstream_;
+ IOControl ioctrl_;
+ // The number of requests seen so far.
+ size_t num_requests_;
+};
+
+} // 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
index 0000000..f43a257
--- /dev/null
+++ b/src/shrpx_io_control.cc
@@ -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
index 0000000..d427fcb
--- /dev/null
+++ b/src/shrpx_io_control.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <cinttypes>
+#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_live_check.cc b/src/shrpx_live_check.cc
new file mode 100644
index 0000000..9b932cd
--- /dev/null
+++ b/src/shrpx_live_check.cc
@@ -0,0 +1,792 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_live_check.h"
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+constexpr size_t MAX_BUFFER_SIZE = 4_k;
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ rv = live_check->do_read();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ int rv;
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ rv = live_check->do_write();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto live_check = static_cast<LiveCheck *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ live_check->on_failure();
+}
+} // namespace
+
+namespace {
+void backoff_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto live_check = static_cast<LiveCheck *>(w->data);
+
+ rv = live_check->initiate_connection();
+ if (rv != 0) {
+ live_check->on_failure();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto live_check = static_cast<LiveCheck *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SETTINGS timeout";
+ }
+
+ live_check->on_failure();
+}
+} // namespace
+
+LiveCheck::LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ DownstreamAddr *addr, std::mt19937 &gen)
+ : conn_(loop, -1, nullptr, worker->get_mcpool(),
+ worker->get_downstream_config()->timeout.write,
+ worker->get_downstream_config()->timeout.read, {}, {}, writecb,
+ readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
+ get_config()->tls.dyn_rec.idle_timeout, Proto::NONE),
+ wb_(worker->get_mcpool()),
+ gen_(gen),
+ read_(&LiveCheck::noop),
+ write_(&LiveCheck::noop),
+ worker_(worker),
+ ssl_ctx_(ssl_ctx),
+ addr_(addr),
+ session_(nullptr),
+ raddr_(nullptr),
+ success_count_(0),
+ fail_count_(0),
+ settings_ack_received_(false),
+ session_closing_(false) {
+ ev_timer_init(&backoff_timer_, backoff_timeoutcb, 0., 0.);
+ backoff_timer_.data = this;
+
+ // SETTINGS ACK must be received in a short timeout. Otherwise, we
+ // assume that connection is broken.
+ ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.);
+ settings_timer_.data = this;
+}
+
+LiveCheck::~LiveCheck() {
+ disconnect();
+
+ ev_timer_stop(conn_.loop, &backoff_timer_);
+}
+
+void LiveCheck::disconnect() {
+ if (dns_query_) {
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ dns_tracker->cancel(dns_query_.get());
+ }
+
+ dns_query_.reset();
+ // We can reuse resolved_addr_
+ raddr_ = nullptr;
+
+ conn_.rlimit.stopw();
+ conn_.wlimit.stopw();
+
+ ev_timer_stop(conn_.loop, &settings_timer_);
+
+ read_ = write_ = &LiveCheck::noop;
+
+ conn_.disconnect();
+
+ nghttp2_session_del(session_);
+ session_ = nullptr;
+
+ settings_ack_received_ = false;
+ session_closing_ = false;
+
+ wb_.reset();
+}
+
+// Use the similar backoff algorithm described in
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+namespace {
+constexpr size_t MAX_BACKOFF_EXP = 10;
+constexpr auto MULTIPLIER = 1.6;
+constexpr auto JITTER = 0.2;
+} // namespace
+
+void LiveCheck::schedule() {
+ auto base_backoff =
+ util::int_pow(MULTIPLIER, std::min(fail_count_, MAX_BACKOFF_EXP));
+ auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff,
+ JITTER * base_backoff);
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ auto backoff =
+ std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_));
+
+ ev_timer_set(&backoff_timer_, backoff, 0.);
+ ev_timer_start(conn_.loop, &backoff_timer_);
+}
+
+int LiveCheck::do_read() { return read_(*this); }
+
+int LiveCheck::do_write() { return write_(*this); }
+
+int LiveCheck::initiate_connection() {
+ int rv;
+
+ auto worker_blocker = worker_->get_connect_blocker();
+ if (worker_blocker->blocked()) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Worker wide backend connection was blocked temporarily";
+ }
+ return -1;
+ }
+
+ if (!dns_query_ && addr_->tls) {
+ assert(ssl_ctx_);
+
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+
+ switch (addr_->proto) {
+ case Proto::HTTP1:
+ tls::setup_downstream_http1_alpn(ssl);
+ break;
+ case Proto::HTTP2:
+ tls::setup_downstream_http2_alpn(ssl);
+ break;
+ default:
+ assert(0);
+ }
+
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &addr_->tls_session_cache;
+ }
+
+ if (addr_->dns) {
+ if (!dns_query_) {
+ auto dns_query = std::make_unique<DNSQuery>(
+ addr_->host, [this](DNSResolverStatus status, const Address *result) {
+ int rv;
+
+ if (status == DNSResolverStatus::OK) {
+ *this->resolved_addr_ = *result;
+ }
+ rv = this->initiate_connection();
+ if (rv != 0) {
+ this->on_failure();
+ }
+ });
+ auto dns_tracker = worker_->get_dns_tracker();
+
+ if (!resolved_addr_) {
+ resolved_addr_ = std::make_unique<Address>();
+ }
+
+ switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) {
+ case DNSResolverStatus::ERROR:
+ return -1;
+ case DNSResolverStatus::RUNNING:
+ dns_query_ = std::move(dns_query);
+ return 0;
+ case DNSResolverStatus::OK:
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (dns_query_->status) {
+ case DNSResolverStatus::ERROR:
+ dns_query_.reset();
+ return -1;
+ case DNSResolverStatus::OK:
+ dns_query_.reset();
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ util::set_port(*resolved_addr_, addr_->port);
+ raddr_ = resolved_addr_.get();
+ } else {
+ raddr_ = &addr_->addr;
+ }
+
+ conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+ return -1;
+ }
+
+ rv = connect(conn_.fd, &raddr_->su.sa, raddr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ LOG(WARN) << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
+ << ", errno=" << error;
+
+ close(conn_.fd);
+ conn_.fd = -1;
+
+ return -1;
+ }
+
+ if (addr_->tls) {
+ auto sni_name =
+ addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
+ if (!util::numeric_host(sni_name.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(addr_->tls_session_cache);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ write_ = &LiveCheck::connected;
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ conn_.wlimit.startw();
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ conn_.wt.repeat = downstreamconf.timeout.connect;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int LiveCheck::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Backend connect failed; addr="
+ << util::to_numeric_addr(raddr_) << ": errno=" << sock_error;
+ }
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Connection established";
+ }
+
+ auto &downstreamconf = *get_config()->conn.downstream;
+
+ // Reset timeout for write. Previously, we set timeout for connect.
+ conn_.wt.repeat = downstreamconf.timeout.write;
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ conn_.rlimit.startw();
+ conn_.again_rt();
+
+ if (conn_.tls.ssl) {
+ read_ = &LiveCheck::tls_handshake;
+ write_ = &LiveCheck::tls_handshake;
+
+ return do_write();
+ }
+
+ if (addr_->proto == Proto::HTTP2) {
+ // For HTTP/2, we try to read SETTINGS ACK from server to make
+ // sure it is really alive, and serving HTTP/2.
+ read_ = &LiveCheck::read_clear;
+ write_ = &LiveCheck::write_clear;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ on_success();
+
+ return 0;
+}
+
+int LiveCheck::tls_handshake() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ auto rv = conn_.tls_handshake();
+
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ }
+
+ if (!get_config()->tls.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
+ return -1;
+ }
+
+ // Check negotiated ALPN
+
+ const unsigned char *next_proto = nullptr;
+ unsigned int next_proto_len = 0;
+
+ SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+
+ auto proto = StringRef{next_proto, next_proto_len};
+
+ switch (addr_->proto) {
+ case Proto::HTTP1:
+ if (proto.empty() || proto == StringRef::from_lit("http/1.1")) {
+ break;
+ }
+ return -1;
+ case Proto::HTTP2:
+ if (util::check_h2_is_selected(proto)) {
+ // For HTTP/2, we try to read SETTINGS ACK from server to make
+ // sure it is really alive, and serving HTTP/2.
+ read_ = &LiveCheck::read_tls;
+ write_ = &LiveCheck::write_tls;
+
+ if (connection_made() != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ return -1;
+ default:
+ break;
+ }
+
+ on_success();
+
+ return 0;
+}
+
+int LiveCheck::read_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 4_k> buf;
+
+ ERR_clear_error();
+
+ for (;;) {
+ auto nread = conn_.read_tls(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int LiveCheck::write_tls() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ ERR_clear_error();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ return nwrite;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ if (wb_.rleft() == 0) {
+ conn_.start_tls_write_idle();
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (settings_ack_received_) {
+ on_success();
+ }
+
+ return 0;
+}
+
+int LiveCheck::read_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<uint8_t, 4_k> buf;
+
+ for (;;) {
+ auto nread = conn_.read_clear(buf.data(), buf.size());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return nread;
+ }
+
+ if (on_read(buf.data(), nread) != 0) {
+ return -1;
+ }
+ }
+}
+
+int LiveCheck::write_clear() {
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ struct iovec iov;
+
+ for (;;) {
+ if (wb_.rleft() > 0) {
+ auto iovcnt = wb_.riovec(&iov, 1);
+ if (iovcnt != 1) {
+ assert(0);
+ return -1;
+ }
+ auto nwrite = conn_.write_clear(iov.iov_base, iov.iov_len);
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (nwrite < 0) {
+ return nwrite;
+ }
+
+ wb_.drain(nwrite);
+
+ continue;
+ }
+
+ if (on_write() != 0) {
+ return -1;
+ }
+
+ if (wb_.rleft() == 0) {
+ break;
+ }
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ if (settings_ack_received_) {
+ on_success();
+ }
+
+ return 0;
+}
+
+int LiveCheck::on_read(const uint8_t *data, size_t len) {
+ ssize_t rv;
+
+ rv = nghttp2_session_mem_recv(session_, data, len);
+ if (rv < 0) {
+ LOG(ERROR) << "nghttp2_session_mem_recv() returned error: "
+ << nghttp2_strerror(rv);
+ return -1;
+ }
+
+ if (settings_ack_received_ && !session_closing_) {
+ session_closing_ = true;
+ rv = nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No more read/write for this session";
+ }
+
+ // If we have SETTINGS ACK already, we treat this success.
+ if (settings_ack_received_) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+int LiveCheck::on_write() {
+ for (;;) {
+ const uint8_t *data;
+ auto datalen = nghttp2_session_mem_send(session_, &data);
+
+ if (datalen < 0) {
+ LOG(ERROR) << "nghttp2_session_mem_send() returned error: "
+ << nghttp2_strerror(datalen);
+ return -1;
+ }
+ if (datalen == 0) {
+ break;
+ }
+ wb_.append(data, datalen);
+
+ if (wb_.rleft() >= MAX_BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ if (nghttp2_session_want_read(session_) == 0 &&
+ nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "No more read/write for this session";
+ }
+
+ if (settings_ack_received_) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+void LiveCheck::on_failure() {
+ ++fail_count_;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
+ << " failed " << fail_count_ << " time(s) in a row";
+ }
+
+ disconnect();
+
+ schedule();
+}
+
+void LiveCheck::on_success() {
+ ++success_count_;
+ fail_count_ = 0;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
+ << " succeeded " << success_count_ << " time(s) in a row";
+ }
+
+ if (success_count_ < addr_->rise) {
+ disconnect();
+
+ schedule();
+
+ return;
+ }
+
+ LOG(NOTICE) << util::to_numeric_addr(&addr_->addr) << " is considered online";
+
+ addr_->connect_blocker->online();
+
+ success_count_ = 0;
+ fail_count_ = 0;
+
+ disconnect();
+}
+
+int LiveCheck::noop() { return 0; }
+
+void LiveCheck::start_settings_timer() {
+ auto &downstreamconf = get_config()->http2.downstream;
+
+ ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.);
+ ev_timer_start(conn_.loop, &settings_timer_);
+}
+
+void LiveCheck::stop_settings_timer() {
+ ev_timer_stop(conn_.loop, &settings_timer_);
+}
+
+void LiveCheck::settings_ack_received() { settings_ack_received_ = true; }
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto live_check = static_cast<LiveCheck *>(user_data);
+
+ if (frame->hd.type != NGHTTP2_SETTINGS ||
+ (frame->hd.flags & NGHTTP2_FLAG_ACK)) {
+ return 0;
+ }
+
+ live_check->start_settings_timer();
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ void *user_data) {
+ auto live_check = static_cast<LiveCheck *>(user_data);
+
+ if (frame->hd.type != NGHTTP2_SETTINGS ||
+ (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+ return 0;
+ }
+
+ live_check->stop_settings_timer();
+ live_check->settings_ack_received();
+
+ return 0;
+}
+} // namespace
+
+int LiveCheck::connection_made() {
+ int rv;
+
+ nghttp2_session_callbacks *callbacks;
+ rv = nghttp2_session_callbacks_new(&callbacks);
+ if (rv != 0) {
+ return -1;
+ }
+
+ 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);
+
+ rv = nghttp2_session_client_new(&session_, callbacks, this);
+
+ nghttp2_session_callbacks_del(callbacks);
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, nullptr, 0);
+ if (rv != 0) {
+ return -1;
+ }
+
+ auto must_terminate =
+ addr_->tls && !nghttp2::tls::check_http2_requirement(conn_.tls.ssl);
+
+ if (must_terminate) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be negotiated.";
+ }
+
+ rv = nghttp2_session_terminate_session(session_,
+ NGHTTP2_INADEQUATE_SECURITY);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+
+ signal_write();
+
+ return 0;
+}
+
+void LiveCheck::signal_write() { conn_.wlimit.startw(); }
+
+} // namespace shrpx
diff --git a/src/shrpx_live_check.h b/src/shrpx_live_check.h
new file mode 100644
index 0000000..b65ecdc
--- /dev/null
+++ b/src/shrpx_live_check.h
@@ -0,0 +1,125 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_LIVE_CHECK_H
+#define SHRPX_LIVE_CHECK_H
+
+#include "shrpx.h"
+
+#include <functional>
+#include <random>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_connection.h"
+
+namespace shrpx {
+
+class Worker;
+struct DownstreamAddr;
+struct DNSQuery;
+
+class LiveCheck {
+public:
+ LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
+ DownstreamAddr *addr, std::mt19937 &gen);
+ ~LiveCheck();
+
+ void disconnect();
+
+ void on_success();
+ void on_failure();
+
+ int initiate_connection();
+
+ // Schedules next connection attempt
+ void schedule();
+
+ // Low level I/O operation callback; they are called from do_read()
+ // or do_write().
+ int noop();
+ int connected();
+ int tls_handshake();
+ int read_tls();
+ int write_tls();
+ int read_clear();
+ int write_clear();
+
+ int do_read();
+ int do_write();
+
+ // These functions are used to feed / extract data to
+ // nghttp2_session object.
+ int on_read(const uint8_t *data, size_t len);
+ int on_write();
+
+ // Call this function when HTTP/2 connection was established. We
+ // don't call this function for HTTP/1 at the moment.
+ int connection_made();
+
+ void start_settings_timer();
+ void stop_settings_timer();
+
+ // Call this function when SETTINGS ACK was received from server.
+ void settings_ack_received();
+
+ void signal_write();
+
+private:
+ Connection conn_;
+ DefaultMemchunks wb_;
+ std::mt19937 &gen_;
+ ev_timer backoff_timer_;
+ ev_timer settings_timer_;
+ std::function<int(LiveCheck &)> read_, write_;
+ Worker *worker_;
+ // nullptr if no TLS is configured
+ SSL_CTX *ssl_ctx_;
+ // Address of remote endpoint
+ DownstreamAddr *addr_;
+ nghttp2_session *session_;
+ // Actual remote address used to contact backend. This is initially
+ // nullptr, and may point to either &addr_->addr, or
+ // resolved_addr_.get().
+ const Address *raddr_;
+ // Resolved IP address if dns parameter is used
+ std::unique_ptr<Address> resolved_addr_;
+ std::unique_ptr<DNSQuery> dns_query_;
+ // The number of successful connect attempt in a row.
+ size_t success_count_;
+ // The number of unsuccessful connect attempt in a row.
+ size_t fail_count_;
+ // true when SETTINGS ACK has been received from server.
+ bool settings_ack_received_;
+ // true when GOAWAY has been queued.
+ bool session_closing_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_LIVE_CHECK_H
diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc
new file mode 100644
index 0000000..de5e09f
--- /dev/null
+++ b/src/shrpx_log.cc
@@ -0,0 +1,1008 @@
+/*
+ * 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"
+
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif // HAVE_SYSLOG_H
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif // HAVE_INTTYPES_H
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#include <sys/wait.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <iomanip>
+
+#include "shrpx_config.h"
+#include "shrpx_downstream.h"
+#include "shrpx_worker.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+constexpr StringRef SEVERITY_STR[] = {
+ StringRef::from_lit("INFO"), StringRef::from_lit("NOTICE"),
+ StringRef::from_lit("WARN"), StringRef::from_lit("ERROR"),
+ StringRef::from_lit("FATAL")};
+} // namespace
+
+namespace {
+constexpr 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
+
+#ifndef NOTHREADS
+# ifdef HAVE_THREAD_LOCAL
+namespace {
+thread_local LogBuffer logbuf_;
+} // namespace
+
+namespace {
+LogBuffer *get_logbuf() { return &logbuf_; }
+} // namespace
+# else // !HAVE_THREAD_LOCAL
+namespace {
+pthread_key_t lckey;
+pthread_once_t lckey_once = PTHREAD_ONCE_INIT;
+} // namespace
+
+namespace {
+void make_key() { pthread_key_create(&lckey, nullptr); }
+} // namespace
+
+LogBuffer *get_logbuf() {
+ pthread_once(&lckey_once, make_key);
+ auto buf = static_cast<LogBuffer *>(pthread_getspecific(lckey));
+ if (!buf) {
+ buf = new LogBuffer();
+ pthread_setspecific(lckey, buf);
+ }
+ return buf;
+}
+# endif // !HAVE_THREAD_LOCAL
+#else // NOTHREADS
+namespace {
+LogBuffer *get_logbuf() {
+ static LogBuffer logbuf;
+ return &logbuf;
+}
+} // namespace
+#endif // NOTHREADS
+
+int Log::severity_thres_ = NOTICE;
+
+void Log::set_severity_level(int severity) { severity_thres_ = severity; }
+
+int Log::get_severity_level_by_name(const StringRef &name) {
+ for (size_t i = 0, max = array_size(SEVERITY_STR); i < max; ++i) {
+ if (name == SEVERITY_STR[i]) {
+ return i;
+ }
+ }
+ 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)
+ : buf_(*get_logbuf()),
+ begin_(buf_.data()),
+ end_(begin_ + buf_.size()),
+ last_(begin_),
+ filename_(filename),
+ flags_(0),
+ severity_(severity),
+ linenum_(linenum),
+ full_(false) {}
+
+Log::~Log() {
+ int rv;
+ auto config = get_config();
+
+ if (!config) {
+ return;
+ }
+
+ auto lgconf = log_config();
+
+ auto &errorconf = config->logging.error;
+
+ if (!log_enabled(severity_) ||
+ (lgconf->errorlog_fd == -1 && !errorconf.syslog)) {
+ return;
+ }
+
+ if (errorconf.syslog) {
+ if (severity_ == NOTICE) {
+ syslog(severity_to_syslog_level(severity_), "[%s] %.*s",
+ SEVERITY_STR[severity_].c_str(), static_cast<int>(rleft()),
+ begin_);
+ } else {
+ syslog(severity_to_syslog_level(severity_), "[%s] %.*s (%s:%d)",
+ SEVERITY_STR[severity_].c_str(), static_cast<int>(rleft()), begin_,
+ filename_, linenum_);
+ }
+
+ return;
+ }
+
+ char buf[4_k];
+ auto tty = lgconf->errorlog_tty;
+
+ lgconf->update_tstamp_millis(std::chrono::system_clock::now());
+
+ // Error log format: <datetime> <main-pid> <current-pid>
+ // <thread-id> <level> (<filename>:<line>) <msg>
+ rv = snprintf(buf, sizeof(buf), "%s %d %d %s %s%s%s (%s:%d) %.*s\n",
+ lgconf->tstamp->time_iso8601.c_str(), config->pid, lgconf->pid,
+ lgconf->thread_id.c_str(), tty ? SEVERITY_COLOR[severity_] : "",
+ SEVERITY_STR[severity_].c_str(), tty ? "\033[0m" : "",
+ filename_, linenum_, static_cast<int>(rleft()), begin_);
+
+ if (rv < 0) {
+ return;
+ }
+
+ auto nwrite = std::min(static_cast<size_t>(rv), sizeof(buf) - 1);
+
+ while (write(lgconf->errorlog_fd, buf, nwrite) == -1 && errno == EINTR)
+ ;
+}
+
+Log &Log::operator<<(const std::string &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(const StringRef &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(const char *s) {
+ write_seq(s, s + strlen(s));
+ return *this;
+}
+
+Log &Log::operator<<(const ImmutableString &s) {
+ write_seq(std::begin(s), std::end(s));
+ return *this;
+}
+
+Log &Log::operator<<(long long n) {
+ if (n >= 0) {
+ return *this << static_cast<uint64_t>(n);
+ }
+
+ if (flags_ & fmt_hex) {
+ write_hex(n);
+ return *this;
+ }
+
+ if (full_) {
+ return *this;
+ }
+
+ n *= -1;
+
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ if (wleft() < 1 /* sign */ + nlen) {
+ full_ = true;
+ return *this;
+ }
+ *last_++ = '-';
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n /= 10) {
+ *p-- = (n % 10) + '0';
+ }
+ return *this;
+}
+
+Log &Log::operator<<(unsigned long long n) {
+ if (flags_ & fmt_hex) {
+ write_hex(n);
+ return *this;
+ }
+
+ if (full_) {
+ return *this;
+ }
+
+ if (n == 0) {
+ *last_++ = '0';
+ update_full();
+ return *this;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ if (wleft() < nlen) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n /= 10) {
+ *p-- = (n % 10) + '0';
+ }
+ return *this;
+}
+
+Log &Log::operator<<(double n) {
+ if (full_) {
+ return *this;
+ }
+
+ auto left = wleft();
+ auto rv = snprintf(reinterpret_cast<char *>(last_), left, "%.9f", n);
+ if (rv > static_cast<int>(left)) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += rv;
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(long double n) {
+ if (full_) {
+ return *this;
+ }
+
+ auto left = wleft();
+ auto rv = snprintf(reinterpret_cast<char *>(last_), left, "%.9Lf", n);
+ if (rv > static_cast<int>(left)) {
+ full_ = true;
+ return *this;
+ }
+
+ last_ += rv;
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(bool n) {
+ if (full_) {
+ return *this;
+ }
+
+ *last_++ = n ? '1' : '0';
+ update_full();
+
+ return *this;
+}
+
+Log &Log::operator<<(const void *p) {
+ if (full_) {
+ return *this;
+ }
+
+ write_hex(reinterpret_cast<uintptr_t>(p));
+
+ return *this;
+}
+
+namespace log {
+void hex(Log &log) { log.set_flags(Log::fmt_hex); };
+
+void dec(Log &log) { log.set_flags(Log::fmt_dec); };
+} // namespace log
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy(const char *src, size_t srclen,
+ OutputIterator d_first,
+ OutputIterator d_last) {
+ auto nwrite =
+ std::min(static_cast<size_t>(std::distance(d_first, d_last)), srclen);
+ return std::make_pair(std::copy_n(src, nwrite, d_first), d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy(const char *src, OutputIterator d_first, OutputIterator d_last) {
+ return copy(src, strlen(src), d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy(const StringRef &src, OutputIterator d_first, OutputIterator d_last) {
+ return copy(src.c_str(), src.size(), d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <size_t N, typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_l(const char (&src)[N], OutputIterator d_first, OutputIterator d_last) {
+ return copy(src, N - 1, d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy(char c, OutputIterator d_first,
+ OutputIterator d_last) {
+ if (d_first == d_last) {
+ return std::make_pair(d_last, d_last);
+ }
+ *d_first++ = c;
+ return std::make_pair(d_first, d_last);
+}
+} // namespace
+
+namespace {
+constexpr char LOWER_XDIGITS[] = "0123456789abcdef";
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_hex_low(const uint8_t *src, size_t srclen, OutputIterator d_first,
+ OutputIterator d_last) {
+ auto nwrite = std::min(static_cast<size_t>(std::distance(d_first, d_last)),
+ srclen * 2) /
+ 2;
+ for (size_t i = 0; i < nwrite; ++i) {
+ *d_first++ = LOWER_XDIGITS[src[i] >> 4];
+ *d_first++ = LOWER_XDIGITS[src[i] & 0xf];
+ }
+ return std::make_pair(d_first, d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator, typename T>
+std::pair<OutputIterator, OutputIterator> copy(T n, OutputIterator d_first,
+ OutputIterator d_last) {
+ if (static_cast<size_t>(std::distance(d_first, d_last)) <
+ NGHTTP2_MAX_UINT64_DIGITS) {
+ return std::make_pair(d_last, d_last);
+ }
+ return std::make_pair(util::utos(d_first, n), d_last);
+}
+} // namespace
+
+namespace {
+// 1 means that character must be escaped as "\xNN", where NN is ascii
+// code of the character in hex notation.
+constexpr uint8_t ESCAPE_TBL[] = {
+ 1 /* NUL */, 1 /* SOH */, 1 /* STX */, 1 /* ETX */, 1 /* EOT */,
+ 1 /* ENQ */, 1 /* ACK */, 1 /* BEL */, 1 /* BS */, 1 /* HT */,
+ 1 /* LF */, 1 /* VT */, 1 /* FF */, 1 /* CR */, 1 /* SO */,
+ 1 /* SI */, 1 /* DLE */, 1 /* DC1 */, 1 /* DC2 */, 1 /* DC3 */,
+ 1 /* DC4 */, 1 /* NAK */, 1 /* SYN */, 1 /* ETB */, 1 /* CAN */,
+ 1 /* EM */, 1 /* SUB */, 1 /* ESC */, 1 /* FS */, 1 /* GS */,
+ 1 /* RS */, 1 /* US */, 0 /* SPC */, 0 /* ! */, 1 /* " */,
+ 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */,
+ 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 0 /* , */,
+ 0 /* - */, 0 /* . */, 0 /* / */, 0 /* 0 */, 0 /* 1 */,
+ 0 /* 2 */, 0 /* 3 */, 0 /* 4 */, 0 /* 5 */, 0 /* 6 */,
+ 0 /* 7 */, 0 /* 8 */, 0 /* 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 /* [ */, 1 /* \ */, 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 /* } */, 0 /* ~ */, 1 /* 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 */,
+};
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator>
+copy_escape(const char *src, size_t srclen, OutputIterator d_first,
+ OutputIterator d_last) {
+ auto safe_first = src;
+ for (auto p = src; p != src + srclen && d_first != d_last; ++p) {
+ unsigned char c = *p;
+ if (!ESCAPE_TBL[c]) {
+ continue;
+ }
+
+ auto n =
+ std::min(std::distance(d_first, d_last), std::distance(safe_first, p));
+ d_first = std::copy_n(safe_first, n, d_first);
+ if (std::distance(d_first, d_last) < 4) {
+ return std::make_pair(d_first, d_last);
+ }
+ *d_first++ = '\\';
+ *d_first++ = 'x';
+ *d_first++ = LOWER_XDIGITS[c >> 4];
+ *d_first++ = LOWER_XDIGITS[c & 0xf];
+ safe_first = p + 1;
+ }
+
+ auto n = std::min(std::distance(d_first, d_last),
+ std::distance(safe_first, src + srclen));
+ return std::make_pair(std::copy_n(safe_first, n, d_first), d_last);
+}
+} // namespace
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, OutputIterator> copy_escape(const StringRef &src,
+ OutputIterator d_first,
+ OutputIterator d_last) {
+ return copy_escape(src.c_str(), src.size(), d_first, d_last);
+}
+} // namespace
+
+namespace {
+// Construct absolute request URI from |Request|, mainly to log
+// request URI for proxy request (HTTP/2 proxy or client proxy). This
+// is mostly same routine found in
+// HttpDownstreamConnection::push_request_headers(), but vastly
+// simplified since we only care about absolute URI.
+StringRef construct_absolute_request_uri(BlockAllocator &balloc,
+ const Request &req) {
+ if (req.authority.empty()) {
+ return req.path;
+ }
+
+ auto len = req.authority.size() + req.path.size();
+ if (req.scheme.empty()) {
+ len += str_size("http://");
+ } else {
+ len += req.scheme.size() + str_size("://");
+ }
+
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ if (req.scheme.empty()) {
+ // We may have to log the request which lacks scheme (e.g.,
+ // http/1.1 with origin form).
+ p = util::copy_lit(p, "http://");
+ } else {
+ p = std::copy(std::begin(req.scheme), std::end(req.scheme), p);
+ p = util::copy_lit(p, "://");
+ }
+ p = std::copy(std::begin(req.authority), std::end(req.authority), p);
+ p = std::copy(std::begin(req.path), std::end(req.path), p);
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+} // namespace
+
+void upstream_accesslog(const std::vector<LogFragment> &lfv,
+ const LogSpec &lgsp) {
+ auto config = get_config();
+ auto lgconf = log_config();
+ auto &accessconf = get_config()->logging.access;
+
+ if (lgconf->accesslog_fd == -1 && !accessconf.syslog) {
+ return;
+ }
+
+ std::array<char, 4_k> buf;
+
+ auto downstream = lgsp.downstream;
+
+ const auto &req = downstream->request();
+ const auto &resp = downstream->response();
+ const auto &tstamp = req.tstamp;
+ auto &balloc = downstream->get_block_allocator();
+
+ auto downstream_addr = downstream->get_addr();
+ auto method = req.method == -1 ? StringRef::from_lit("<unknown>")
+ : http2::to_method_string(req.method);
+ auto path =
+ req.method == HTTP_CONNECT ? req.authority
+ : config->http2_proxy ? construct_absolute_request_uri(balloc, req)
+ : req.path.empty() ? req.method == HTTP_OPTIONS ? StringRef::from_lit("*")
+ : StringRef::from_lit("-")
+ : req.path;
+ auto path_without_query =
+ req.method == HTTP_CONNECT
+ ? path
+ : StringRef{std::begin(path),
+ std::find(std::begin(path), std::end(path), '?')};
+
+ auto p = std::begin(buf);
+ auto last = std::end(buf) - 2;
+
+ for (auto &lf : lfv) {
+ switch (lf.type) {
+ case LogFragmentType::LITERAL:
+ std::tie(p, last) = copy(lf.value, p, last);
+ break;
+ case LogFragmentType::REMOTE_ADDR:
+ std::tie(p, last) = copy(lgsp.remote_addr, p, last);
+ break;
+ case LogFragmentType::TIME_LOCAL:
+ std::tie(p, last) = copy(tstamp->time_local, p, last);
+ break;
+ case LogFragmentType::TIME_ISO8601:
+ std::tie(p, last) = copy(tstamp->time_iso8601, p, last);
+ break;
+ case LogFragmentType::REQUEST:
+ std::tie(p, last) = copy(method, p, last);
+ std::tie(p, last) = copy(' ', p, last);
+ std::tie(p, last) = copy_escape(path, p, last);
+ std::tie(p, last) = copy_l(" HTTP/", p, last);
+ std::tie(p, last) = copy(req.http_major, p, last);
+ if (req.http_major < 2) {
+ std::tie(p, last) = copy('.', p, last);
+ std::tie(p, last) = copy(req.http_minor, p, last);
+ }
+ break;
+ case LogFragmentType::METHOD:
+ std::tie(p, last) = copy(method, p, last);
+ break;
+ case LogFragmentType::PATH:
+ std::tie(p, last) = copy_escape(path, p, last);
+ break;
+ case LogFragmentType::PATH_WITHOUT_QUERY:
+ std::tie(p, last) = copy_escape(path_without_query, p, last);
+ break;
+ case LogFragmentType::PROTOCOL_VERSION:
+ std::tie(p, last) = copy_l("HTTP/", p, last);
+ std::tie(p, last) = copy(req.http_major, p, last);
+ if (req.http_major < 2) {
+ std::tie(p, last) = copy('.', p, last);
+ std::tie(p, last) = copy(req.http_minor, p, last);
+ }
+ break;
+ case LogFragmentType::STATUS:
+ std::tie(p, last) = copy(resp.http_status, p, last);
+ break;
+ case LogFragmentType::BODY_BYTES_SENT:
+ std::tie(p, last) = copy(downstream->response_sent_body_length, p, last);
+ break;
+ case LogFragmentType::HTTP: {
+ auto hd = req.fs.header(lf.value);
+ if (hd) {
+ std::tie(p, last) = copy_escape((*hd).value, p, last);
+ break;
+ }
+
+ std::tie(p, last) = copy('-', p, last);
+
+ break;
+ }
+ case LogFragmentType::AUTHORITY:
+ if (!req.authority.empty()) {
+ std::tie(p, last) = copy(req.authority, p, last);
+ break;
+ }
+
+ std::tie(p, last) = copy('-', p, last);
+
+ break;
+ case LogFragmentType::REMOTE_PORT:
+ std::tie(p, last) = copy(lgsp.remote_port, p, last);
+ break;
+ case LogFragmentType::SERVER_PORT:
+ std::tie(p, last) = copy(lgsp.server_port, p, last);
+ break;
+ case LogFragmentType::REQUEST_TIME: {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ lgsp.request_end_time - downstream->get_request_start_time())
+ .count();
+ std::tie(p, last) = copy(t / 1000, p, last);
+ std::tie(p, last) = copy('.', p, last);
+ auto frac = t % 1000;
+ if (frac < 100) {
+ auto n = frac < 10 ? 2 : 1;
+ std::tie(p, last) = copy("000", n, p, last);
+ }
+ std::tie(p, last) = copy(frac, p, last);
+ break;
+ }
+ case LogFragmentType::PID:
+ std::tie(p, last) = copy(lgsp.pid, p, last);
+ break;
+ case LogFragmentType::ALPN:
+ std::tie(p, last) = copy_escape(lgsp.alpn, p, last);
+ break;
+ case LogFragmentType::TLS_CIPHER:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(SSL_get_cipher_name(lgsp.ssl), p, last);
+ break;
+ case LogFragmentType::TLS_PROTOCOL:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) =
+ copy(nghttp2::tls::get_tls_protocol(lgsp.ssl), p, last);
+ break;
+ case LogFragmentType::TLS_SESSION_ID: {
+ auto session = SSL_get_session(lgsp.ssl);
+ if (!session) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ unsigned int session_id_length = 0;
+ auto session_id = SSL_SESSION_get_id(session, &session_id_length);
+ if (session_id_length == 0) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_hex_low(session_id, session_id_length, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_SESSION_REUSED:
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) =
+ copy(SSL_session_reused(lgsp.ssl) ? 'r' : '.', p, last);
+ break;
+ case LogFragmentType::TLS_SNI:
+ if (lgsp.sni.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_escape(lgsp.sni, p, last);
+ break;
+ case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1:
+ case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::array<uint8_t, 32> buf;
+ auto len = tls::get_x509_fingerprint(
+ buf.data(), buf.size(), x,
+ lf.type == LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256
+ ? EVP_sha256()
+ : EVP_sha1());
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (len <= 0) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy_hex_low(buf.data(), len, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_CLIENT_ISSUER_NAME:
+ case LogFragmentType::TLS_CLIENT_SUBJECT_NAME: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ auto name = lf.type == LogFragmentType::TLS_CLIENT_ISSUER_NAME
+ ? tls::get_x509_issuer_name(balloc, x)
+ : tls::get_x509_subject_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (name.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(name, p, last);
+ break;
+ }
+ case LogFragmentType::TLS_CLIENT_SERIAL: {
+ if (!lgsp.ssl) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(lgsp.ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(lgsp.ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ auto sn = tls::get_x509_serial(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (sn.empty()) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(sn, p, last);
+ break;
+ }
+ case LogFragmentType::BACKEND_HOST:
+ if (!downstream_addr) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(downstream_addr->host, p, last);
+ break;
+ case LogFragmentType::BACKEND_PORT:
+ if (!downstream_addr) {
+ std::tie(p, last) = copy('-', p, last);
+ break;
+ }
+ std::tie(p, last) = copy(downstream_addr->port, p, last);
+ break;
+ case LogFragmentType::NONE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ *p = '\0';
+
+ if (accessconf.syslog) {
+ syslog(LOG_INFO, "%s", buf.data());
+
+ return;
+ }
+
+ *p++ = '\n';
+
+ auto nwrite = std::distance(std::begin(buf), p);
+ while (write(lgconf->accesslog_fd, buf.data(), nwrite) == -1 &&
+ errno == EINTR)
+ ;
+}
+
+int reopen_log_files(const LoggingConfig &loggingconf) {
+ int res = 0;
+ int new_accesslog_fd = -1;
+ int new_errorlog_fd = -1;
+
+ auto lgconf = log_config();
+ auto &accessconf = loggingconf.access;
+ auto &errorconf = loggingconf.error;
+
+ if (!accessconf.syslog && !accessconf.file.empty()) {
+ new_accesslog_fd = open_log_file(accessconf.file.c_str());
+
+ if (new_accesslog_fd == -1) {
+ LOG(ERROR) << "Failed to open accesslog file " << accessconf.file;
+ res = -1;
+ }
+ }
+
+ if (!errorconf.syslog && !errorconf.file.empty()) {
+ new_errorlog_fd = open_log_file(errorconf.file.c_str());
+
+ if (new_errorlog_fd == -1) {
+ if (lgconf->errorlog_fd != -1) {
+ LOG(ERROR) << "Failed to open errorlog file " << errorconf.file;
+ } else {
+ std::cerr << "Failed to open errorlog file " << errorconf.file
+ << std::endl;
+ }
+
+ res = -1;
+ }
+ }
+
+ close_log_file(lgconf->accesslog_fd);
+ close_log_file(lgconf->errorlog_fd);
+
+ lgconf->accesslog_fd = new_accesslog_fd;
+ lgconf->errorlog_fd = new_errorlog_fd;
+ lgconf->errorlog_tty =
+ (new_errorlog_fd == -1) ? false : isatty(new_errorlog_fd);
+
+ return res;
+}
+
+void log_chld(pid_t pid, int rstatus, const char *msg) {
+ std::string signalstr;
+ if (WIFSIGNALED(rstatus)) {
+ signalstr += "; signal ";
+ auto sig = WTERMSIG(rstatus);
+ auto s = strsignal(sig);
+ if (s) {
+ signalstr += s;
+ signalstr += '(';
+ } else {
+ signalstr += "UNKNOWN(";
+ }
+ signalstr += util::utos(sig);
+ signalstr += ')';
+ }
+
+ LOG(NOTICE) << msg << ": [" << pid << "] exited "
+ << (WIFEXITED(rstatus) ? "normally" : "abnormally")
+ << " with status " << log::hex << rstatus << log::dec
+ << "; exit status "
+ << (WIFEXITED(rstatus) ? WEXITSTATUS(rstatus) : 0)
+ << (signalstr.empty() ? "" : signalstr.c_str());
+}
+
+void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf) {
+ auto lgconf = log_config();
+ auto &errorconf = loggingconf.error;
+
+ if (errorconf.syslog || lgconf->errorlog_fd == -1) {
+ return;
+ }
+
+ dup2(lgconf->errorlog_fd, STDERR_FILENO);
+}
+
+namespace {
+int STDERR_COPY = -1;
+int STDOUT_COPY = -1;
+} // namespace
+
+void store_original_fds() {
+ // consider dup'ing stdout too
+ STDERR_COPY = dup(STDERR_FILENO);
+ STDOUT_COPY = STDOUT_FILENO;
+ // no race here, since it is called early
+ util::make_socket_closeonexec(STDERR_COPY);
+}
+
+void restore_original_fds() { dup2(STDERR_COPY, STDERR_FILENO); }
+
+void close_log_file(int &fd) {
+ if (fd != STDERR_COPY && fd != STDOUT_COPY && fd != -1) {
+ close(fd);
+ }
+ fd = -1;
+}
+
+int open_log_file(const char *path) {
+
+ if (strcmp(path, "/dev/stdout") == 0 ||
+ strcmp(path, "/proc/self/fd/1") == 0) {
+ return STDOUT_COPY;
+ }
+
+ if (strcmp(path, "/dev/stderr") == 0 ||
+ strcmp(path, "/proc/self/fd/2") == 0) {
+ return STDERR_COPY;
+ }
+#ifdef 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) {
+ util::make_socket_closeonexec(fd);
+ }
+
+#endif // !O_CLOEXEC
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ return fd;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_log.h b/src/shrpx_log.h
new file mode 100644
index 0000000..bc30097
--- /dev/null
+++ b/src/shrpx_log.h
@@ -0,0 +1,318 @@
+/*
+ * 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 <memory>
+#include <vector>
+#include <chrono>
+
+#include "shrpx_config.h"
+#include "shrpx_log_config.h"
+#include "tls.h"
+#include "template.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+#define ENABLE_LOG 1
+
+#define LOG_ENABLED(SEVERITY) (ENABLE_LOG && shrpx::Log::log_enabled(SEVERITY))
+
+#ifdef __FILE_NAME__
+# define NGHTTP2_FILE_NAME __FILE_NAME__
+#else // !__FILE_NAME__
+# define NGHTTP2_FILE_NAME __FILE__
+#endif // !__FILE_NAME__
+
+#define LOG(SEVERITY) shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__)
+
+// Listener log
+#define LLOG(SEVERITY, LISTEN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[LISTEN:" << LISTEN << "] ")
+
+// Worker log
+#define WLOG(SEVERITY, WORKER) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[WORKER:" << WORKER << "] ")
+
+// ClientHandler log
+#define CLOG(SEVERITY, CLIENT_HANDLER) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[CLIENT_HANDLER:" << CLIENT_HANDLER << "] ")
+
+// Upstream log
+#define ULOG(SEVERITY, UPSTREAM) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[UPSTREAM:" << UPSTREAM << "] ")
+
+// Downstream log
+#define DLOG(SEVERITY, DOWNSTREAM) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DOWNSTREAM:" << DOWNSTREAM << "] ")
+
+// Downstream connection log
+#define DCLOG(SEVERITY, DCONN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DCONN:" << DCONN << "] ")
+
+// Downstream HTTP2 session log
+#define SSLOG(SEVERITY, HTTP2) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[DHTTP2:" << HTTP2 << "] ")
+
+// Memcached connection log
+#define MCLOG(SEVERITY, MCONN) \
+ (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \
+ << "[MCONN:" << MCONN << "] ")
+
+namespace shrpx {
+
+class Downstream;
+struct DownstreamAddr;
+
+enum SeverityLevel { INFO, NOTICE, WARN, ERROR, FATAL };
+
+using LogBuffer = std::array<uint8_t, 4_k>;
+
+class Log {
+public:
+ Log(int severity, const char *filename, int linenum);
+ ~Log();
+ Log &operator<<(const std::string &s);
+ Log &operator<<(const char *s);
+ Log &operator<<(const StringRef &s);
+ Log &operator<<(const ImmutableString &s);
+ Log &operator<<(short n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(int n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(long n) { return *this << static_cast<long long>(n); }
+ Log &operator<<(long long n);
+ Log &operator<<(unsigned short n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned int n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned long n) {
+ return *this << static_cast<unsigned long long>(n);
+ }
+ Log &operator<<(unsigned long long n);
+ Log &operator<<(float n) { return *this << static_cast<double>(n); }
+ Log &operator<<(double n);
+ Log &operator<<(long double n);
+ Log &operator<<(bool n);
+ Log &operator<<(const void *p);
+ template <typename T> Log &operator<<(const std::shared_ptr<T> &ptr) {
+ return *this << ptr.get();
+ }
+ Log &operator<<(void (*func)(Log &log)) {
+ func(*this);
+ return *this;
+ }
+ template <typename InputIt> void write_seq(InputIt first, InputIt last) {
+ if (full_) {
+ return;
+ }
+
+ auto d = std::distance(first, last);
+ auto n = std::min(wleft(), static_cast<size_t>(d));
+ last_ = std::copy(first, first + n, last_);
+ update_full();
+ }
+
+ template <typename T> void write_hex(T n) {
+ if (full_) {
+ return;
+ }
+
+ if (n == 0) {
+ if (wleft() < 4 /* for "0x00" */) {
+ full_ = true;
+ return;
+ }
+ *last_++ = '0';
+ *last_++ = 'x';
+ *last_++ = '0';
+ *last_++ = '0';
+ update_full();
+ return;
+ }
+
+ size_t nlen = 0;
+ for (auto t = n; t; t >>= 8, ++nlen)
+ ;
+
+ nlen *= 2;
+
+ if (wleft() < 2 /* for "0x" */ + nlen) {
+ full_ = true;
+ return;
+ }
+
+ *last_++ = '0';
+ *last_++ = 'x';
+
+ last_ += nlen;
+ update_full();
+
+ auto p = last_ - 1;
+ for (; n; n >>= 8) {
+ uint8_t b = n & 0xff;
+ *p-- = util::LOWER_XDIGITS[b & 0xf];
+ *p-- = util::LOWER_XDIGITS[b >> 4];
+ }
+ }
+ static void set_severity_level(int severity);
+ // Returns the severity level by |name|. Returns -1 if |name| is
+ // unknown.
+ static int get_severity_level_by_name(const StringRef &name);
+ static bool log_enabled(int severity) { return severity >= severity_thres_; }
+
+ enum {
+ fmt_dec = 0x00,
+ fmt_hex = 0x01,
+ };
+
+ void set_flags(int flags) { flags_ = flags; }
+
+private:
+ size_t rleft() { return last_ - begin_; }
+ size_t wleft() { return end_ - last_; }
+ void update_full() { full_ = last_ == end_; }
+
+ LogBuffer &buf_;
+ uint8_t *begin_;
+ uint8_t *end_;
+ uint8_t *last_;
+ const char *filename_;
+ uint32_t flags_;
+ int severity_;
+ int linenum_;
+ bool full_;
+ static int severity_thres_;
+};
+
+namespace log {
+void hex(Log &log);
+void dec(Log &log);
+} // namespace log
+
+#define TTY_HTTP_HD (log_config()->errorlog_tty ? "\033[1;34m" : "")
+#define TTY_RST (log_config()->errorlog_tty ? "\033[0m" : "")
+
+enum class LogFragmentType {
+ NONE,
+ LITERAL,
+ REMOTE_ADDR,
+ TIME_LOCAL,
+ TIME_ISO8601,
+ REQUEST,
+ STATUS,
+ BODY_BYTES_SENT,
+ HTTP,
+ AUTHORITY,
+ REMOTE_PORT,
+ SERVER_PORT,
+ REQUEST_TIME,
+ PID,
+ ALPN,
+ TLS_CIPHER,
+ SSL_CIPHER = TLS_CIPHER,
+ TLS_PROTOCOL,
+ SSL_PROTOCOL = TLS_PROTOCOL,
+ TLS_SESSION_ID,
+ SSL_SESSION_ID = TLS_SESSION_ID,
+ TLS_SESSION_REUSED,
+ SSL_SESSION_REUSED = TLS_SESSION_REUSED,
+ TLS_SNI,
+ TLS_CLIENT_FINGERPRINT_SHA1,
+ TLS_CLIENT_FINGERPRINT_SHA256,
+ TLS_CLIENT_ISSUER_NAME,
+ TLS_CLIENT_SERIAL,
+ TLS_CLIENT_SUBJECT_NAME,
+ BACKEND_HOST,
+ BACKEND_PORT,
+ METHOD,
+ PATH,
+ PATH_WITHOUT_QUERY,
+ PROTOCOL_VERSION,
+};
+
+struct LogFragment {
+ LogFragment(LogFragmentType type, StringRef value = StringRef::from_lit(""))
+ : type(type), value(std::move(value)) {}
+ LogFragmentType type;
+ StringRef value;
+};
+
+struct LogSpec {
+ Downstream *downstream;
+ StringRef remote_addr;
+ StringRef alpn;
+ StringRef sni;
+ SSL *ssl;
+ std::chrono::high_resolution_clock::time_point request_end_time;
+ StringRef remote_port;
+ uint16_t server_port;
+ pid_t pid;
+};
+
+void upstream_accesslog(const std::vector<LogFragment> &lf,
+ const LogSpec &lgsp);
+
+int reopen_log_files(const LoggingConfig &loggingconf);
+
+// Logs message when process whose pid is |pid| and exist status is
+// |rstatus| exited. The |msg| is prepended to the log message.
+void log_chld(pid_t pid, int rstatus, const char *msg);
+
+void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf);
+
+// Makes internal copy of stderr (and possibly stdout in the future),
+// which is then used as pointer to /dev/stderr or /proc/self/fd/2
+void store_original_fds();
+
+// Restores the original stderr that was stored with copy_original_fds
+// Used just before execv
+void restore_original_fds();
+
+// Closes |fd| which was returned by open_log_file (see below)
+// and sets it to -1. In the case that |fd| points to stdout or
+// stderr, or is -1, the descriptor is not closed (but still set to -1).
+void close_log_file(int &fd);
+
+// 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 open_log_file(const char *path);
+
+} // namespace shrpx
+
+#endif // SHRPX_LOG_H
diff --git a/src/shrpx_log_config.cc b/src/shrpx_log_config.cc
new file mode 100644
index 0000000..92eb055
--- /dev/null
+++ b/src/shrpx_log_config.cc
@@ -0,0 +1,127 @@
+/*
+ * 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_log_config.h"
+
+#include <unistd.h>
+
+#include <thread>
+#include <sstream>
+
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+Timestamp::Timestamp(const std::chrono::system_clock::time_point &tp) {
+ time_local = util::format_common_log(time_local_buf.data(), tp);
+ time_iso8601 = util::format_iso8601(time_iso8601_buf.data(), tp);
+ time_http = util::format_http_date(time_http_buf.data(), tp);
+}
+
+LogConfig::LogConfig()
+ : time_str_updated(std::chrono::system_clock::now()),
+ tstamp(std::make_shared<Timestamp>(time_str_updated)),
+ pid(getpid()),
+ accesslog_fd(-1),
+ errorlog_fd(-1),
+ errorlog_tty(false) {
+ auto tid = std::this_thread::get_id();
+ auto tid_hash =
+ util::hash32(StringRef{reinterpret_cast<uint8_t *>(&tid),
+ reinterpret_cast<uint8_t *>(&tid) + sizeof(tid)});
+ thread_id = util::format_hex(reinterpret_cast<uint8_t *>(&tid_hash),
+ sizeof(tid_hash));
+}
+
+#ifndef NOTHREADS
+# ifdef HAVE_THREAD_LOCAL
+namespace {
+thread_local std::unique_ptr<LogConfig> config = std::make_unique<LogConfig>();
+} // namespace
+
+LogConfig *log_config() { return config.get(); }
+void delete_log_config() {}
+# else // !HAVE_THREAD_LOCAL
+namespace {
+pthread_key_t lckey;
+pthread_once_t lckey_once = PTHREAD_ONCE_INIT;
+} // namespace
+
+namespace {
+void make_key() { pthread_key_create(&lckey, nullptr); }
+} // namespace
+
+LogConfig *log_config() {
+ pthread_once(&lckey_once, make_key);
+ LogConfig *config = (LogConfig *)pthread_getspecific(lckey);
+ if (!config) {
+ config = new LogConfig();
+ pthread_setspecific(lckey, config);
+ }
+ return config;
+}
+
+void delete_log_config() { delete log_config(); }
+# endif // !HAVE_THREAD_LOCAL
+#else // NOTHREADS
+namespace {
+std::unique_ptr<LogConfig> config = std::make_unique<LogConfig>();
+} // namespace
+
+LogConfig *log_config() { return config.get(); }
+
+void delete_log_config() {}
+#endif // NOTHREADS
+
+void LogConfig::update_tstamp_millis(
+ const std::chrono::system_clock::time_point &now) {
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(
+ now.time_since_epoch()) ==
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ time_str_updated.time_since_epoch())) {
+ return;
+ }
+
+ time_str_updated = now;
+
+ tstamp = std::make_shared<Timestamp>(now);
+}
+
+void LogConfig::update_tstamp(
+ const std::chrono::system_clock::time_point &now) {
+ if (std::chrono::duration_cast<std::chrono::seconds>(
+ now.time_since_epoch()) ==
+ std::chrono::duration_cast<std::chrono::seconds>(
+ time_str_updated.time_since_epoch())) {
+ return;
+ }
+
+ time_str_updated = now;
+
+ tstamp = std::make_shared<Timestamp>(now);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_log_config.h b/src/shrpx_log_config.h
new file mode 100644
index 0000000..76fa78b
--- /dev/null
+++ b/src/shrpx_log_config.h
@@ -0,0 +1,79 @@
+/*
+ * 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_LOG_CONFIG_H
+#define SHRPX_LOG_CONFIG_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+
+#include <chrono>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct Timestamp {
+ Timestamp(const std::chrono::system_clock::time_point &tp);
+
+ std::array<char, sizeof("03/Jul/2014:00:19:38 +0900")> time_local_buf;
+ std::array<char, sizeof("2014-11-15T12:58:24.741+09:00")> time_iso8601_buf;
+ std::array<char, sizeof("Mon, 10 Oct 2016 10:25:58 GMT")> time_http_buf;
+ StringRef time_local;
+ StringRef time_iso8601;
+ StringRef time_http;
+};
+
+struct LogConfig {
+ std::chrono::system_clock::time_point time_str_updated;
+ std::shared_ptr<Timestamp> tstamp;
+ std::string thread_id;
+ pid_t pid;
+ int accesslog_fd;
+ int errorlog_fd;
+ // true if errorlog_fd is referring to a terminal.
+ bool errorlog_tty;
+
+ LogConfig();
+ // Updates time stamp if difference between time_str_updated and now
+ // is 1 or more milliseconds.
+ void update_tstamp_millis(const std::chrono::system_clock::time_point &now);
+ // Updates time stamp if difference between time_str_updated and
+ // now, converted to time_t, is 1 or more seconds.
+ void update_tstamp(const std::chrono::system_clock::time_point &now);
+};
+
+// We need LogConfig per thread to avoid data race around opening file
+// descriptor for log files.
+LogConfig *log_config();
+
+// Deletes log_config
+void delete_log_config();
+
+} // namespace shrpx
+
+#endif // SHRPX_LOG_CONFIG_H
diff --git a/src/shrpx_memcached_connection.cc b/src/shrpx_memcached_connection.cc
new file mode 100644
index 0000000..f72cb11
--- /dev/null
+++ b/src/shrpx_memcached_connection.cc
@@ -0,0 +1,777 @@
+/*
+ * 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_memcached_connection.h"
+
+#include <limits.h>
+#include <sys/uio.h>
+
+#include <cerrno>
+
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_result.h"
+#include "shrpx_config.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (w == &conn->rt && !conn->expired_rt()) {
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, mconn) << "Time out";
+ }
+
+ mconn->disconnect();
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->on_read() != 0) {
+ mconn->reconnect_or_fail();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->on_write() != 0) {
+ mconn->reconnect_or_fail();
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn = static_cast<Connection *>(w->data);
+ auto mconn = static_cast<MemcachedConnection *>(conn->data);
+
+ if (mconn->connected() != 0) {
+ mconn->disconnect();
+ return;
+ }
+
+ writecb(loop, w, revents);
+}
+} // namespace
+
+constexpr auto write_timeout = 10_s;
+constexpr auto read_timeout = 10_s;
+
+MemcachedConnection::MemcachedConnection(const Address *addr,
+ struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ const StringRef &sni_name,
+ MemchunkPool *mcpool,
+ std::mt19937 &gen)
+ : conn_(loop, -1, nullptr, mcpool, write_timeout, read_timeout, {}, {},
+ connectcb, readcb, timeoutcb, this, 0, 0., Proto::MEMCACHED),
+ do_read_(&MemcachedConnection::noop),
+ do_write_(&MemcachedConnection::noop),
+ sni_name_(sni_name),
+ connect_blocker_(
+ gen, loop, [] {}, [] {}),
+ parse_state_{},
+ addr_(addr),
+ ssl_ctx_(ssl_ctx),
+ sendsum_(0),
+ try_count_(0),
+ connected_(false) {}
+
+MemcachedConnection::~MemcachedConnection() { conn_.disconnect(); }
+
+namespace {
+void clear_request(std::deque<std::unique_ptr<MemcachedRequest>> &q) {
+ for (auto &req : q) {
+ if (req->cb) {
+ req->cb(req.get(),
+ MemcachedResult(MemcachedStatusCode::EXT_NETWORK_ERROR));
+ }
+ }
+ q.clear();
+}
+} // namespace
+
+void MemcachedConnection::disconnect() {
+ clear_request(recvq_);
+ clear_request(sendq_);
+
+ sendbufv_.clear();
+ sendsum_ = 0;
+
+ parse_state_ = {};
+
+ connected_ = false;
+
+ conn_.disconnect();
+
+ assert(recvbuf_.rleft() == 0);
+ recvbuf_.reset();
+
+ do_read_ = do_write_ = &MemcachedConnection::noop;
+}
+
+int MemcachedConnection::initiate_connection() {
+ assert(conn_.fd == -1);
+
+ if (ssl_ctx_) {
+ auto ssl = tls::create_ssl(ssl_ctx_);
+ if (!ssl) {
+ return -1;
+ }
+ conn_.set_ssl(ssl);
+ conn_.tls.client_session_cache = &tls_session_cache_;
+ }
+
+ conn_.fd = util::create_nonblock_socket(addr_->su.storage.ss_family);
+
+ if (conn_.fd == -1) {
+ auto error = errno;
+ MCLOG(WARN, this) << "socket() failed; errno=" << error;
+
+ return -1;
+ }
+
+ int rv;
+ rv = connect(conn_.fd, &addr_->su.sa, addr_->len);
+ if (rv != 0 && errno != EINPROGRESS) {
+ auto error = errno;
+ MCLOG(WARN, this) << "connect() failed; errno=" << error;
+
+ close(conn_.fd);
+ conn_.fd = -1;
+
+ return -1;
+ }
+
+ if (ssl_ctx_) {
+ if (!util::numeric_host(sni_name_.c_str())) {
+ SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name_.c_str());
+ }
+
+ auto session = tls::reuse_tls_session(tls_session_cache_);
+ if (session) {
+ SSL_set_session(conn_.tls.ssl, session);
+ SSL_SESSION_free(session);
+ }
+
+ conn_.prepare_client_handshake();
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Connecting to memcached server";
+ }
+
+ ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+ ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+ ev_set_cb(&conn_.wev, connectcb);
+
+ conn_.wlimit.startw();
+ ev_timer_again(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::connected() {
+ auto sock_error = util::get_socket_error(conn_.fd);
+ if (sock_error != 0) {
+ MCLOG(WARN, this) << "memcached connect failed; addr="
+ << util::to_numeric_addr(addr_)
+ << ": errno=" << sock_error;
+
+ connect_blocker_.on_failure();
+
+ conn_.wlimit.stopw();
+
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "connected to memcached server";
+ }
+
+ conn_.rlimit.startw();
+
+ ev_set_cb(&conn_.wev, writecb);
+
+ if (conn_.tls.ssl) {
+ conn_.again_rt();
+
+ do_read_ = &MemcachedConnection::tls_handshake;
+ do_write_ = &MemcachedConnection::tls_handshake;
+
+ return 0;
+ }
+
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ connected_ = true;
+
+ connect_blocker_.on_success();
+
+ do_read_ = &MemcachedConnection::read_clear;
+ do_write_ = &MemcachedConnection::write_clear;
+
+ return 0;
+}
+
+int MemcachedConnection::on_write() { return do_write_(*this); }
+int MemcachedConnection::on_read() { return do_read_(*this); }
+
+int MemcachedConnection::tls_handshake() {
+ ERR_clear_error();
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ auto rv = conn_.tls_handshake();
+ if (rv == SHRPX_ERR_INPROGRESS) {
+ return 0;
+ }
+
+ if (rv < 0) {
+ connect_blocker_.on_failure();
+ return rv;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "SSL/TLS handshake completed";
+ }
+
+ auto &tlsconf = get_config()->tls;
+
+ if (!tlsconf.insecure &&
+ tls::check_cert(conn_.tls.ssl, addr_, sni_name_) != 0) {
+ connect_blocker_.on_failure();
+ return -1;
+ }
+
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ connected_ = true;
+
+ connect_blocker_.on_success();
+
+ do_read_ = &MemcachedConnection::read_tls;
+ do_write_ = &MemcachedConnection::write_tls;
+
+ return on_write();
+}
+
+int MemcachedConnection::write_tls() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+ std::array<uint8_t, 16_k> buf;
+
+ for (; !sendq_.empty();) {
+ auto iovcnt = fill_request_buffer(iov.data(), iov.size());
+ auto p = std::begin(buf);
+ for (size_t i = 0; i < iovcnt; ++i) {
+ auto &v = iov[i];
+ auto n = std::min(static_cast<size_t>(std::end(buf) - p), v.iov_len);
+ p = std::copy_n(static_cast<uint8_t *>(v.iov_base), n, p);
+ if (p == std::end(buf)) {
+ break;
+ }
+ }
+
+ auto nwrite = conn_.write_tls(buf.data(), p - std::begin(buf));
+ if (nwrite < 0) {
+ return -1;
+ }
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ drain_send_queue(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::read_tls() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ for (;;) {
+ auto nread = conn_.read_tls(recvbuf_.last, recvbuf_.wleft());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ recvbuf_.write(nread);
+
+ if (parse_packet() != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int MemcachedConnection::write_clear() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+ for (; !sendq_.empty();) {
+ auto iovcnt = fill_request_buffer(iov.data(), iov.size());
+ auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+ if (nwrite < 0) {
+ return -1;
+ }
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ drain_send_queue(nwrite);
+ }
+
+ conn_.wlimit.stopw();
+ ev_timer_stop(conn_.loop, &conn_.wt);
+
+ return 0;
+}
+
+int MemcachedConnection::read_clear() {
+ if (!connected_) {
+ return 0;
+ }
+
+ conn_.last_read = std::chrono::steady_clock::now();
+
+ for (;;) {
+ auto nread = conn_.read_clear(recvbuf_.last, recvbuf_.wleft());
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ if (nread < 0) {
+ return -1;
+ }
+
+ recvbuf_.write(nread);
+
+ if (parse_packet() != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int MemcachedConnection::parse_packet() {
+ auto in = recvbuf_.pos;
+
+ for (;;) {
+ auto busy = false;
+
+ switch (parse_state_.state) {
+ case MemcachedParseState::HEADER24: {
+ if (recvbuf_.last - in < 24) {
+ recvbuf_.drain_reset(in - recvbuf_.pos);
+ return 0;
+ }
+
+ if (recvq_.empty()) {
+ MCLOG(WARN, this)
+ << "Response received, but there is no in-flight request.";
+ return -1;
+ }
+
+ auto &req = recvq_.front();
+
+ if (*in != MEMCACHED_RES_MAGIC) {
+ MCLOG(WARN, this) << "Response has bad magic: "
+ << static_cast<uint32_t>(*in);
+ return -1;
+ }
+ ++in;
+
+ parse_state_.op = static_cast<MemcachedOp>(*in++);
+ parse_state_.keylen = util::get_uint16(in);
+ in += 2;
+ parse_state_.extralen = *in++;
+ // skip 1 byte reserved data type
+ ++in;
+ parse_state_.status_code =
+ static_cast<MemcachedStatusCode>(util::get_uint16(in));
+ in += 2;
+ parse_state_.totalbody = util::get_uint32(in);
+ in += 4;
+ // skip 4 bytes opaque
+ in += 4;
+ parse_state_.cas = util::get_uint64(in);
+ in += 8;
+
+ if (req->op != parse_state_.op) {
+ MCLOG(WARN, this)
+ << "opcode in response does not match to the request: want "
+ << static_cast<uint32_t>(req->op) << ", got "
+ << static_cast<uint32_t>(parse_state_.op);
+ return -1;
+ }
+
+ if (parse_state_.keylen != 0) {
+ MCLOG(WARN, this) << "zero length keylen expected: got "
+ << parse_state_.keylen;
+ return -1;
+ }
+
+ if (parse_state_.totalbody > 16_k) {
+ MCLOG(WARN, this) << "totalbody is too large: got "
+ << parse_state_.totalbody;
+ return -1;
+ }
+
+ if (parse_state_.op == MemcachedOp::GET &&
+ parse_state_.status_code == MemcachedStatusCode::NO_ERROR &&
+ parse_state_.extralen == 0) {
+ MCLOG(WARN, this) << "response for GET does not have extra";
+ return -1;
+ }
+
+ if (parse_state_.totalbody <
+ parse_state_.keylen + parse_state_.extralen) {
+ MCLOG(WARN, this) << "totalbody is too short: totalbody "
+ << parse_state_.totalbody << ", want min "
+ << parse_state_.keylen + parse_state_.extralen;
+ return -1;
+ }
+
+ if (parse_state_.extralen) {
+ parse_state_.state = MemcachedParseState::EXTRA;
+ parse_state_.read_left = parse_state_.extralen;
+ } else {
+ parse_state_.state = MemcachedParseState::VALUE;
+ parse_state_.read_left = parse_state_.totalbody - parse_state_.keylen -
+ parse_state_.extralen;
+ }
+ busy = true;
+ break;
+ }
+ case MemcachedParseState::EXTRA: {
+ // We don't use extra for now. Just read and forget.
+ auto n = std::min(static_cast<size_t>(recvbuf_.last - in),
+ parse_state_.read_left);
+
+ parse_state_.read_left -= n;
+ in += n;
+ if (parse_state_.read_left) {
+ recvbuf_.reset();
+ return 0;
+ }
+ parse_state_.state = MemcachedParseState::VALUE;
+ // since we require keylen == 0, totalbody - extralen ==
+ // valuelen
+ parse_state_.read_left =
+ parse_state_.totalbody - parse_state_.keylen - parse_state_.extralen;
+ busy = true;
+ break;
+ }
+ case MemcachedParseState::VALUE: {
+ auto n = std::min(static_cast<size_t>(recvbuf_.last - in),
+ parse_state_.read_left);
+
+ parse_state_.value.insert(std::end(parse_state_.value), in, in + n);
+
+ parse_state_.read_left -= n;
+ in += n;
+ if (parse_state_.read_left) {
+ recvbuf_.reset();
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ if (parse_state_.status_code != MemcachedStatusCode::NO_ERROR) {
+ MCLOG(INFO, this) << "response returned error status: "
+ << static_cast<uint16_t>(parse_state_.status_code);
+ }
+ }
+
+ // We require at least one complete response to clear try count.
+ try_count_ = 0;
+
+ auto req = std::move(recvq_.front());
+ recvq_.pop_front();
+
+ if (sendq_.empty() && recvq_.empty()) {
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ }
+
+ if (!req->canceled && req->cb) {
+ req->cb(req.get(), MemcachedResult(parse_state_.status_code,
+ std::move(parse_state_.value)));
+ }
+
+ parse_state_ = {};
+ break;
+ }
+ }
+
+ if (!busy && in == recvbuf_.last) {
+ break;
+ }
+ }
+
+ assert(in == recvbuf_.last);
+ recvbuf_.reset();
+
+ return 0;
+}
+
+#undef DEFAULT_WR_IOVCNT
+#define DEFAULT_WR_IOVCNT 128
+
+#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
+
+size_t MemcachedConnection::fill_request_buffer(struct iovec *iov,
+ size_t iovlen) {
+ if (sendsum_ == 0) {
+ for (auto &req : sendq_) {
+ if (req->canceled) {
+ continue;
+ }
+ if (serialized_size(req.get()) + sendsum_ > 1300) {
+ break;
+ }
+ sendbufv_.emplace_back();
+ sendbufv_.back().req = req.get();
+ make_request(&sendbufv_.back(), req.get());
+ sendsum_ += sendbufv_.back().left();
+ }
+
+ if (sendsum_ == 0) {
+ sendq_.clear();
+ return 0;
+ }
+ }
+
+ size_t iovcnt = 0;
+ for (auto &buf : sendbufv_) {
+ if (iovcnt + 2 > iovlen) {
+ break;
+ }
+
+ auto req = buf.req;
+ if (buf.headbuf.rleft()) {
+ iov[iovcnt++] = {buf.headbuf.pos, buf.headbuf.rleft()};
+ }
+ if (buf.send_value_left) {
+ iov[iovcnt++] = {req->value.data() + req->value.size() -
+ buf.send_value_left,
+ buf.send_value_left};
+ }
+ }
+
+ return iovcnt;
+}
+
+void MemcachedConnection::drain_send_queue(size_t nwrite) {
+ sendsum_ -= nwrite;
+
+ while (nwrite > 0) {
+ auto &buf = sendbufv_.front();
+ auto &req = sendq_.front();
+ if (req->canceled) {
+ sendq_.pop_front();
+ continue;
+ }
+ assert(buf.req == req.get());
+ auto n = std::min(static_cast<size_t>(nwrite), buf.headbuf.rleft());
+ buf.headbuf.drain(n);
+ nwrite -= n;
+ n = std::min(static_cast<size_t>(nwrite), buf.send_value_left);
+ buf.send_value_left -= n;
+ nwrite -= n;
+
+ if (buf.headbuf.rleft() || buf.send_value_left) {
+ break;
+ }
+ sendbufv_.pop_front();
+ recvq_.push_back(std::move(sendq_.front()));
+ sendq_.pop_front();
+ }
+
+ // start read timer only when we wait for responses.
+ if (recvq_.empty()) {
+ ev_timer_stop(conn_.loop, &conn_.rt);
+ } else if (!ev_is_active(&conn_.rt)) {
+ conn_.again_rt();
+ }
+}
+
+size_t MemcachedConnection::serialized_size(MemcachedRequest *req) {
+ switch (req->op) {
+ case MemcachedOp::GET:
+ return 24 + req->key.size();
+ case MemcachedOp::ADD:
+ default:
+ return 24 + 8 + req->key.size() + req->value.size();
+ }
+}
+
+void MemcachedConnection::make_request(MemcachedSendbuf *sendbuf,
+ MemcachedRequest *req) {
+ auto &headbuf = sendbuf->headbuf;
+
+ std::fill(std::begin(headbuf.buf), std::end(headbuf.buf), 0);
+
+ headbuf[0] = MEMCACHED_REQ_MAGIC;
+ headbuf[1] = static_cast<uint8_t>(req->op);
+ switch (req->op) {
+ case MemcachedOp::GET:
+ util::put_uint16be(&headbuf[2], req->key.size());
+ util::put_uint32be(&headbuf[8], req->key.size());
+ headbuf.write(24);
+ break;
+ case MemcachedOp::ADD:
+ util::put_uint16be(&headbuf[2], req->key.size());
+ headbuf[4] = 8;
+ util::put_uint32be(&headbuf[8], 8 + req->key.size() + req->value.size());
+ util::put_uint32be(&headbuf[28], req->expiry);
+ headbuf.write(32);
+ break;
+ }
+
+ headbuf.write(req->key.c_str(), req->key.size());
+
+ sendbuf->send_value_left = req->value.size();
+}
+
+int MemcachedConnection::add_request(std::unique_ptr<MemcachedRequest> req) {
+ if (connect_blocker_.blocked()) {
+ return -1;
+ }
+
+ sendq_.push_back(std::move(req));
+
+ if (connected_) {
+ signal_write();
+ return 0;
+ }
+
+ if (conn_.fd == -1 && initiate_connection() != 0) {
+ connect_blocker_.on_failure();
+ disconnect();
+ return -1;
+ }
+
+ return 0;
+}
+
+// TODO should we start write timer too?
+void MemcachedConnection::signal_write() { conn_.wlimit.startw(); }
+
+int MemcachedConnection::noop() { return 0; }
+
+void MemcachedConnection::reconnect_or_fail() {
+ if (!connected_ || (recvq_.empty() && sendq_.empty())) {
+ disconnect();
+ return;
+ }
+
+ constexpr size_t MAX_TRY_COUNT = 3;
+
+ if (++try_count_ >= MAX_TRY_COUNT) {
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Tried " << MAX_TRY_COUNT
+ << " times, and all failed. Aborting";
+ }
+ try_count_ = 0;
+ disconnect();
+ return;
+ }
+
+ std::vector<std::unique_ptr<MemcachedRequest>> q;
+ q.reserve(recvq_.size() + sendq_.size());
+
+ if (LOG_ENABLED(INFO)) {
+ MCLOG(INFO, this) << "Retry connection, enqueue "
+ << recvq_.size() + sendq_.size() << " request(s) again";
+ }
+
+ q.insert(std::end(q), std::make_move_iterator(std::begin(recvq_)),
+ std::make_move_iterator(std::end(recvq_)));
+ q.insert(std::end(q), std::make_move_iterator(std::begin(sendq_)),
+ std::make_move_iterator(std::end(sendq_)));
+
+ recvq_.clear();
+ sendq_.clear();
+
+ disconnect();
+
+ sendq_.insert(std::end(sendq_), std::make_move_iterator(std::begin(q)),
+ std::make_move_iterator(std::end(q)));
+
+ if (initiate_connection() != 0) {
+ connect_blocker_.on_failure();
+ disconnect();
+ return;
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_memcached_connection.h b/src/shrpx_memcached_connection.h
new file mode 100644
index 0000000..2e097e1
--- /dev/null
+++ b/src/shrpx_memcached_connection.h
@@ -0,0 +1,155 @@
+/*
+ * 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_MEMCACHED_CONNECTION_H
+#define SHRPX_MEMCACHED_CONNECTION_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <deque>
+
+#include <ev.h>
+
+#include "shrpx_connection.h"
+#include "shrpx_tls.h"
+#include "shrpx_connect_blocker.h"
+#include "buffer.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct MemcachedRequest;
+enum class MemcachedOp : uint8_t;
+enum class MemcachedStatusCode : uint16_t;
+
+enum class MemcachedParseState {
+ HEADER24,
+ EXTRA,
+ VALUE,
+};
+
+// Stores state when parsing response from memcached server
+struct MemcachedParseContext {
+ // Buffer for value, dynamically allocated.
+ std::vector<uint8_t> value;
+ // cas in response
+ uint64_t cas;
+ // keylen in response
+ size_t keylen;
+ // extralen in response
+ size_t extralen;
+ // totalbody in response. The length of value is totalbody -
+ // extralen - keylen.
+ size_t totalbody;
+ // Number of bytes left to read variable length field.
+ size_t read_left;
+ // Parser state; see enum above
+ MemcachedParseState state;
+ // status_code in response
+ MemcachedStatusCode status_code;
+ // op in response
+ MemcachedOp op;
+};
+
+struct MemcachedSendbuf {
+ // Buffer for header + extra + key
+ Buffer<512> headbuf;
+ // MemcachedRequest associated to this object
+ MemcachedRequest *req;
+ // Number of bytes left when sending value
+ size_t send_value_left;
+ // Returns the number of bytes this object transmits.
+ size_t left() const { return headbuf.rleft() + send_value_left; }
+};
+
+constexpr uint8_t MEMCACHED_REQ_MAGIC = 0x80;
+constexpr uint8_t MEMCACHED_RES_MAGIC = 0x81;
+
+// MemcachedConnection implements part of memcached binary protocol.
+// This is not full brown implementation. Just the part we need is
+// implemented. We only use GET and ADD.
+//
+// https://github.com/memcached/memcached/blob/master/doc/protocol-binary.xml
+// https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol
+class MemcachedConnection {
+public:
+ MemcachedConnection(const Address *addr, struct ev_loop *loop,
+ SSL_CTX *ssl_ctx, const StringRef &sni_name,
+ MemchunkPool *mcpool, std::mt19937 &gen);
+ ~MemcachedConnection();
+
+ void disconnect();
+
+ int add_request(std::unique_ptr<MemcachedRequest> req);
+ int initiate_connection();
+
+ int connected();
+ int on_write();
+ int on_read();
+
+ int write_clear();
+ int read_clear();
+
+ int tls_handshake();
+ int write_tls();
+ int read_tls();
+
+ size_t fill_request_buffer(struct iovec *iov, size_t iovlen);
+ void drain_send_queue(size_t nwrite);
+
+ void make_request(MemcachedSendbuf *sendbuf, MemcachedRequest *req);
+ int parse_packet();
+ size_t serialized_size(MemcachedRequest *req);
+
+ void signal_write();
+
+ int noop();
+
+ void reconnect_or_fail();
+
+private:
+ Connection conn_;
+ std::deque<std::unique_ptr<MemcachedRequest>> recvq_;
+ std::deque<std::unique_ptr<MemcachedRequest>> sendq_;
+ std::deque<MemcachedSendbuf> sendbufv_;
+ std::function<int(MemcachedConnection &)> do_read_, do_write_;
+ StringRef sni_name_;
+ tls::TLSSessionCache tls_session_cache_;
+ ConnectBlocker connect_blocker_;
+ MemcachedParseContext parse_state_;
+ const Address *addr_;
+ SSL_CTX *ssl_ctx_;
+ // Sum of the bytes to be transmitted in sendbufv_.
+ size_t sendsum_;
+ size_t try_count_;
+ bool connected_;
+ Buffer<8_k> recvbuf_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_CONNECTION_H
diff --git a/src/shrpx_memcached_dispatcher.cc b/src/shrpx_memcached_dispatcher.cc
new file mode 100644
index 0000000..024bd5a
--- /dev/null
+++ b/src/shrpx_memcached_dispatcher.cc
@@ -0,0 +1,53 @@
+/*
+ * 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_memcached_dispatcher.h"
+
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_connection.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+MemcachedDispatcher::MemcachedDispatcher(const Address *addr,
+ struct ev_loop *loop, SSL_CTX *ssl_ctx,
+ const StringRef &sni_name,
+ MemchunkPool *mcpool,
+ std::mt19937 &gen)
+ : loop_(loop),
+ mconn_(std::make_unique<MemcachedConnection>(addr, loop_, ssl_ctx,
+ sni_name, mcpool, gen)) {}
+
+MemcachedDispatcher::~MemcachedDispatcher() {}
+
+int MemcachedDispatcher::add_request(std::unique_ptr<MemcachedRequest> req) {
+ if (mconn_->add_request(std::move(req)) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_memcached_dispatcher.h b/src/shrpx_memcached_dispatcher.h
new file mode 100644
index 0000000..de1af80
--- /dev/null
+++ b/src/shrpx_memcached_dispatcher.h
@@ -0,0 +1,63 @@
+/*
+ * 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_MEMCACHED_DISPATCHER_H
+#define SHRPX_MEMCACHED_DISPATCHER_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <random>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "memchunk.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct MemcachedRequest;
+class MemcachedConnection;
+
+class MemcachedDispatcher {
+public:
+ MemcachedDispatcher(const Address *addr, struct ev_loop *loop,
+ SSL_CTX *ssl_ctx, const StringRef &sni_name,
+ MemchunkPool *mcpool, std::mt19937 &gen);
+ ~MemcachedDispatcher();
+
+ int add_request(std::unique_ptr<MemcachedRequest> req);
+
+private:
+ struct ev_loop *loop_;
+ std::unique_ptr<MemcachedConnection> mconn_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_DISPATCHER_H
diff --git a/src/shrpx_memcached_request.h b/src/shrpx_memcached_request.h
new file mode 100644
index 0000000..3696983
--- /dev/null
+++ b/src/shrpx_memcached_request.h
@@ -0,0 +1,59 @@
+/*
+ * 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_MEMCACHED_REQUEST_H
+#define SHRPX_MEMCACHED_REQUEST_H
+
+#include "shrpx.h"
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#include "shrpx_memcached_result.h"
+
+namespace shrpx {
+
+enum class MemcachedOp : uint8_t {
+ GET = 0x00,
+ ADD = 0x02,
+};
+
+struct MemcachedRequest;
+
+using MemcachedResultCallback =
+ std::function<void(MemcachedRequest *req, MemcachedResult res)>;
+
+struct MemcachedRequest {
+ std::string key;
+ std::vector<uint8_t> value;
+ MemcachedResultCallback cb;
+ uint32_t expiry;
+ MemcachedOp op;
+ bool canceled;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_REQUEST_H
diff --git a/src/shrpx_memcached_result.h b/src/shrpx_memcached_result.h
new file mode 100644
index 0000000..60a87af
--- /dev/null
+++ b/src/shrpx_memcached_result.h
@@ -0,0 +1,50 @@
+/*
+ * 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_MEMCACHED_RESULT_H
+#define SHRPX_MEMCACHED_RESULT_H
+
+#include "shrpx.h"
+
+#include <vector>
+
+namespace shrpx {
+
+enum class MemcachedStatusCode : uint16_t {
+ NO_ERROR,
+ EXT_NETWORK_ERROR = 0x1001,
+};
+
+struct MemcachedResult {
+ MemcachedResult(MemcachedStatusCode status_code) : status_code(status_code) {}
+ MemcachedResult(MemcachedStatusCode status_code, std::vector<uint8_t> value)
+ : value(std::move(value)), status_code(status_code) {}
+
+ std::vector<uint8_t> value;
+ MemcachedStatusCode status_code;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_MEMCACHED_RESULT_H
diff --git a/src/shrpx_mruby.cc b/src/shrpx_mruby.cc
new file mode 100644
index 0000000..b5c6ed3
--- /dev/null
+++ b/src/shrpx_mruby.cc
@@ -0,0 +1,238 @@
+/*
+ * 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_mruby.h"
+
+#include <mruby/compile.h>
+#include <mruby/string.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env)
+ : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {}
+
+MRubyContext::~MRubyContext() {
+ if (mrb_) {
+ mrb_close(mrb_);
+ }
+}
+
+int MRubyContext::run_app(Downstream *downstream, int phase) {
+ if (!mrb_) {
+ return 0;
+ }
+
+ MRubyAssocData data{downstream, phase};
+
+ mrb_->ud = &data;
+
+ int rv = 0;
+ auto ai = mrb_gc_arena_save(mrb_);
+ auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); });
+
+ const char *method;
+ switch (phase) {
+ case PHASE_REQUEST:
+ if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) {
+ return 0;
+ }
+ method = "on_req";
+ break;
+ case PHASE_RESPONSE:
+ if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) {
+ return 0;
+ }
+ method = "on_resp";
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ auto res = mrb_funcall(mrb_, app_, method, 1, env_);
+ (void)res;
+
+ if (mrb_->exc) {
+ // If response has been committed, ignore error
+ if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
+ rv = -1;
+ }
+
+ auto exc = mrb_obj_value(mrb_->exc);
+ auto inspect = mrb_inspect(mrb_, exc);
+
+ LOG(ERROR) << "Exception caught while executing mruby code: "
+ << mrb_str_to_cstr(mrb_, inspect);
+ }
+
+ mrb_->ud = nullptr;
+
+ return rv;
+}
+
+int MRubyContext::run_on_request_proc(Downstream *downstream) {
+ return run_app(downstream, PHASE_REQUEST);
+}
+
+int MRubyContext::run_on_response_proc(Downstream *downstream) {
+ return run_app(downstream, PHASE_RESPONSE);
+}
+
+void MRubyContext::delete_downstream(Downstream *downstream) {
+ if (!mrb_) {
+ return;
+ }
+ delete_downstream_from_module(mrb_, downstream);
+}
+
+namespace {
+mrb_value instantiate_app(mrb_state *mrb, RProc *proc) {
+ mrb->ud = nullptr;
+
+ auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0);
+
+ if (mrb->exc) {
+ auto exc = mrb_obj_value(mrb->exc);
+ auto inspect = mrb_inspect(mrb, exc);
+
+ LOG(ERROR) << "Exception caught while executing mruby code: "
+ << mrb_str_to_cstr(mrb, inspect);
+
+ return mrb_nil_value();
+ }
+
+ return res;
+}
+} // namespace
+
+// Based on
+// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
+// very hard to write these kind of code because mruby has almost no
+// documentation about compiling or generating code, at least at the
+// time of this writing.
+RProc *compile(mrb_state *mrb, const StringRef &filename) {
+ if (filename.empty()) {
+ return nullptr;
+ }
+
+ auto infile = fopen(filename.c_str(), "rb");
+ if (infile == nullptr) {
+ LOG(ERROR) << "Could not open mruby file " << filename;
+ return nullptr;
+ }
+ auto infile_d = defer(fclose, infile);
+
+ auto mrbc = mrbc_context_new(mrb);
+ if (mrbc == nullptr) {
+ LOG(ERROR) << "mrb_context_new failed";
+ return nullptr;
+ }
+ auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
+
+ auto parser = mrb_parse_file(mrb, infile, nullptr);
+ if (parser == nullptr) {
+ LOG(ERROR) << "mrb_parse_nstring failed";
+ return nullptr;
+ }
+ auto parser_d = defer(mrb_parser_free, parser);
+
+ if (parser->nerr != 0) {
+ LOG(ERROR) << "mruby parser detected parse error";
+ return nullptr;
+ }
+
+ auto proc = mrb_generate_code(mrb, parser);
+ if (proc == nullptr) {
+ LOG(ERROR) << "mrb_generate_code failed";
+ return nullptr;
+ }
+
+ return proc;
+}
+
+std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename) {
+ if (filename.empty()) {
+ return std::make_unique<MRubyContext>(nullptr, mrb_nil_value(),
+ mrb_nil_value());
+ }
+
+ auto mrb = mrb_open();
+ if (mrb == nullptr) {
+ LOG(ERROR) << "mrb_open failed";
+ return nullptr;
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ auto req_proc = compile(mrb, filename);
+
+ if (!req_proc) {
+ mrb_gc_arena_restore(mrb, ai);
+ LOG(ERROR) << "Could not compile mruby code " << filename;
+ mrb_close(mrb);
+ return nullptr;
+ }
+
+ auto env = init_module(mrb);
+
+ auto app = instantiate_app(mrb, req_proc);
+ if (mrb_nil_p(app)) {
+ mrb_gc_arena_restore(mrb, ai);
+ LOG(ERROR) << "Could not instantiate mruby app from " << filename;
+ mrb_close(mrb);
+ return nullptr;
+ }
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ // TODO These are not necessary, because we retain app and env?
+ mrb_gc_protect(mrb, env);
+ mrb_gc_protect(mrb, app);
+
+ return std::make_unique<MRubyContext>(mrb, std::move(app), std::move(env));
+}
+
+mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
+ auto p = reinterpret_cast<uintptr_t>(ptr);
+
+ return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
+}
+
+void check_phase(mrb_state *mrb, int phase, int phase_mask) {
+ if ((phase & phase_mask) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase");
+ }
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby.h b/src/shrpx_mruby.h
new file mode 100644
index 0000000..518063d
--- /dev/null
+++ b/src/shrpx_mruby.h
@@ -0,0 +1,89 @@
+/*
+ * 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_MRUBY_H
+#define SHRPX_MRUBY_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <mruby.h>
+#include <mruby/proc.h>
+
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+namespace mruby {
+
+class MRubyContext {
+public:
+ MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env);
+ ~MRubyContext();
+
+ int run_on_request_proc(Downstream *downstream);
+ int run_on_response_proc(Downstream *downstream);
+
+ int run_app(Downstream *downstream, int phase);
+
+ void delete_downstream(Downstream *downstream);
+
+private:
+ mrb_state *mrb_;
+ mrb_value app_;
+ mrb_value env_;
+};
+
+enum {
+ PHASE_NONE = 0,
+ PHASE_REQUEST = 1,
+ PHASE_RESPONSE = 1 << 1,
+};
+
+struct MRubyAssocData {
+ Downstream *downstream;
+ int phase;
+};
+
+RProc *compile(mrb_state *mrb, const StringRef &filename);
+
+std::unique_ptr<MRubyContext> create_mruby_context(const StringRef &filename);
+
+// Return interned |ptr|.
+mrb_sym intern_ptr(mrb_state *mrb, void *ptr);
+
+// Checks that |phase| is set in |phase_mask|. If not set, raise
+// exception.
+void check_phase(mrb_state *mrb, int phase, int phase_mask);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_H
diff --git a/src/shrpx_mruby_module.cc b/src/shrpx_mruby_module.cc
new file mode 100644
index 0000000..27b7769
--- /dev/null
+++ b/src/shrpx_mruby_module.cc
@@ -0,0 +1,113 @@
+/*
+ * 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_mruby_module.h"
+
+#include <array>
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module_env.h"
+#include "shrpx_mruby_module_request.h"
+#include "shrpx_mruby_module_response.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value create_env(mrb_state *mrb) {
+ auto module = mrb_module_get(mrb, "Nghttpx");
+
+ auto env_class = mrb_class_get_under(mrb, module, "Env");
+ auto request_class = mrb_class_get_under(mrb, module, "Request");
+ auto response_class = mrb_class_get_under(mrb, module, "Response");
+
+ auto env = mrb_obj_new(mrb, env_class, 0, nullptr);
+ auto req = mrb_obj_new(mrb, request_class, 0, nullptr);
+ auto resp = mrb_obj_new(mrb, response_class, 0, nullptr);
+
+ mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "req"), req);
+ mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "resp"), resp);
+
+ return env;
+}
+} // namespace
+
+void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream) {
+ auto module = mrb_module_get(mrb, "Nghttpx");
+ auto env = mrb_obj_iv_get(mrb, reinterpret_cast<RObject *>(module),
+ mrb_intern_lit(mrb, "env"));
+ if (mrb_nil_p(env)) {
+ return;
+ }
+
+ mrb_iv_remove(mrb, env, intern_ptr(mrb, downstream));
+}
+
+mrb_value init_module(mrb_state *mrb) {
+ auto module = mrb_define_module(mrb, "Nghttpx");
+
+ mrb_define_const(mrb, module, "REQUEST_PHASE",
+ mrb_fixnum_value(PHASE_REQUEST));
+ mrb_define_const(mrb, module, "RESPONSE_PHASE",
+ mrb_fixnum_value(PHASE_RESPONSE));
+
+ init_env_class(mrb, module);
+ init_request_class(mrb, module);
+ init_response_class(mrb, module);
+
+ return create_env(mrb);
+}
+
+mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers) {
+ auto hash = mrb_hash_new(mrb);
+
+ for (auto &hd : headers) {
+ if (hd.name.empty() || hd.name[0] == ':') {
+ continue;
+ }
+ auto ai = mrb_gc_arena_save(mrb);
+
+ auto key = mrb_str_new(mrb, hd.name.c_str(), hd.name.size());
+ auto ary = mrb_hash_get(mrb, hash, key);
+ if (mrb_nil_p(ary)) {
+ ary = mrb_ary_new(mrb);
+ mrb_hash_set(mrb, hash, key, ary);
+ }
+ mrb_ary_push(mrb, ary, mrb_str_new(mrb, hd.value.c_str(), hd.value.size()));
+
+ mrb_gc_arena_restore(mrb, ai);
+ }
+
+ return hash;
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module.h b/src/shrpx_mruby_module.h
new file mode 100644
index 0000000..a426bea
--- /dev/null
+++ b/src/shrpx_mruby_module.h
@@ -0,0 +1,52 @@
+/*
+ * 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_MRUBY_MODULE_H
+#define SHRPX_MRUBY_MODULE_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+#include "http2.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Downstream;
+
+namespace mruby {
+
+mrb_value init_module(mrb_state *mrb);
+
+void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream);
+
+mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_H
diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc
new file mode 100644
index 0000000..5ebd9c0
--- /dev/null
+++ b/src/shrpx_mruby_module_env.cc
@@ -0,0 +1,500 @@
+/*
+ * 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_mruby_module_env.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "shrpx_tls.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value env_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value env_get_req(mrb_state *mrb, mrb_value self) {
+ return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "req"));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_resp(mrb_state *mrb, mrb_value self) {
+ return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "resp"));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_ctx(mrb_state *mrb, mrb_value self) {
+ auto data = reinterpret_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+
+ auto dsym = intern_ptr(mrb, downstream);
+
+ auto ctx = mrb_iv_get(mrb, self, dsym);
+ if (mrb_nil_p(ctx)) {
+ ctx = mrb_hash_new(mrb);
+ mrb_iv_set(mrb, self, dsym, ctx);
+ }
+
+ return ctx;
+}
+} // namespace
+
+namespace {
+mrb_value env_get_phase(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+
+ return mrb_fixnum_value(data->phase);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_remote_addr(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ auto &ipaddr = handler->get_ipaddr();
+
+ return mrb_str_new(mrb, ipaddr.c_str(), ipaddr.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_server_port(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+
+ return mrb_fixnum_value(faddr->port);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_server_addr(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto faddr = handler->get_upstream_addr();
+
+ return mrb_str_new(mrb, faddr->host.c_str(), faddr->host.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_used(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+
+ return handler->get_ssl() ? mrb_true_value() : mrb_false_value();
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_sni(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto sni = handler->get_tls_sni();
+
+ return mrb_str_new(mrb, sni.c_str(), sni.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_md(mrb_state *mrb, const EVP_MD *md) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ // Currently the largest hash value is SHA-256, which is 32 bytes.
+ std::array<uint8_t, 32> buf;
+ auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x, md);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ if (slen == -1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint");
+ }
+
+ // TODO Use template version of format_hex
+ auto &balloc = downstream->get_block_allocator();
+ auto f = util::format_hex(balloc,
+ StringRef{std::begin(buf), std::begin(buf) + slen});
+ return mrb_str_new(mrb, f.c_str(), f.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_sha256(mrb_state *mrb,
+ mrb_value self) {
+ return env_get_tls_client_fingerprint_md(mrb, EVP_sha256());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_fingerprint_sha1(mrb_state *mrb, mrb_value self) {
+ return env_get_tls_client_fingerprint_md(mrb, EVP_sha1());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_subject_name(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto name = tls::get_x509_subject_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, name.c_str(), name.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_issuer_name(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto name = tls::get_x509_issuer_name(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, name.c_str(), name.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_serial(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+ auto sn = tls::get_x509_serial(balloc, x);
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+ return mrb_str_new(mrb, sn.c_str(), sn.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_not_before(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_fixnum_value(0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_fixnum_value(0);
+ }
+
+ time_t t;
+ if (tls::get_x509_not_before(t, x) != 0) {
+ t = 0;
+ }
+
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+
+ return mrb_fixnum_value(t);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_client_not_after(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_fixnum_value(0);
+ }
+
+#if OPENSSL_3_0_0_API
+ auto x = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto x = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!x) {
+ return mrb_fixnum_value(0);
+ }
+
+ time_t t;
+ if (tls::get_x509_not_after(t, x) != 0) {
+ t = 0;
+ }
+
+#if !OPENSSL_3_0_0_API
+ X509_free(x);
+#endif // !OPENSSL_3_0_0_API
+
+ return mrb_fixnum_value(t);
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_cipher(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ return mrb_str_new_cstr(mrb, SSL_get_cipher_name(ssl));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_protocol(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ return mrb_str_new_cstr(mrb, nghttp2::tls::get_tls_protocol(ssl));
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_session_id(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ auto session = SSL_get_session(ssl);
+ if (!session) {
+ return mrb_str_new_static(mrb, "", 0);
+ }
+
+ unsigned int session_id_length = 0;
+ auto session_id = SSL_SESSION_get_id(session, &session_id_length);
+
+ // TODO Use template version of util::format_hex.
+ auto &balloc = downstream->get_block_allocator();
+ auto id = util::format_hex(balloc, StringRef{session_id, session_id_length});
+ return mrb_str_new(mrb, id.c_str(), id.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_session_reused(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto ssl = handler->get_ssl();
+
+ if (!ssl) {
+ return mrb_false_value();
+ }
+
+ return SSL_session_reused(ssl) ? mrb_true_value() : mrb_false_value();
+}
+} // namespace
+
+namespace {
+mrb_value env_get_alpn(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto alpn = handler->get_alpn();
+ return mrb_str_new(mrb, alpn.c_str(), alpn.size());
+}
+} // namespace
+
+namespace {
+mrb_value env_get_tls_handshake_finished(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+ auto handler = upstream->get_client_handler();
+ auto conn = handler->get_connection();
+ return SSL_is_init_finished(conn->tls.ssl) ? mrb_true_value()
+ : mrb_false_value();
+}
+} // namespace
+
+void init_env_class(mrb_state *mrb, RClass *module) {
+ auto env_class =
+ mrb_define_class_under(mrb, module, "Env", mrb->object_class);
+
+ mrb_define_method(mrb, env_class, "initialize", env_init, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "req", env_get_req, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "resp", env_get_resp, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "ctx", env_get_ctx, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "phase", env_get_phase, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "remote_addr", env_get_remote_addr,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "server_addr", env_get_server_addr,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "server_port", env_get_server_port,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_used", env_get_tls_used,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_sni", env_get_tls_sni,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha256",
+ env_get_tls_client_fingerprint_sha256, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha1",
+ env_get_tls_client_fingerprint_sha1, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_issuer_name",
+ env_get_tls_client_issuer_name, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_subject_name",
+ env_get_tls_client_subject_name, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_serial",
+ env_get_tls_client_serial, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_not_before",
+ env_get_tls_client_not_before, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_client_not_after",
+ env_get_tls_client_not_after, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_cipher", env_get_tls_cipher,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_protocol", env_get_tls_protocol,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_session_id", env_get_tls_session_id,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_session_reused",
+ env_get_tls_session_reused, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "alpn", env_get_alpn, MRB_ARGS_NONE());
+ mrb_define_method(mrb, env_class, "tls_handshake_finished",
+ env_get_tls_handshake_finished, MRB_ARGS_NONE());
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_env.h b/src/shrpx_mruby_module_env.h
new file mode 100644
index 0000000..0884678
--- /dev/null
+++ b/src/shrpx_mruby_module_env.h
@@ -0,0 +1,42 @@
+/*
+ * 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_MRUBY_MODULE_ENV_H
+#define SHRPX_MRUBY_MODULE_ENV_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_env_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_ENV_H
diff --git a/src/shrpx_mruby_module_request.cc b/src/shrpx_mruby_module_request.cc
new file mode 100644
index 0000000..1ed3aa9
--- /dev/null
+++ b/src/shrpx_mruby_module_request.cc
@@ -0,0 +1,367 @@
+/*
+ * 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_mruby_module_request.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return mrb_fixnum_value(req.http_major);
+}
+} // namespace
+
+namespace {
+mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return mrb_fixnum_value(req.http_minor);
+}
+} // namespace
+
+namespace {
+mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ auto method = http2::to_method_string(req.method);
+
+ return mrb_str_new(mrb, method.c_str(), method.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *method;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &method, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string");
+ }
+ auto token =
+ http2::lookup_method_token(reinterpret_cast<const uint8_t *>(method), n);
+ if (token == -1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
+ }
+
+ req.method = token;
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.authority.c_str(), req.authority.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *authority;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &authority, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
+ }
+
+ req.authority =
+ make_string_ref(balloc, StringRef{authority, static_cast<size_t>(n)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *scheme;
+ mrb_int n;
+ mrb_get_args(mrb, "s", &scheme, &n);
+ if (n == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
+ }
+
+ req.scheme =
+ make_string_ref(balloc, StringRef{scheme, static_cast<size_t>(n)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+
+ return mrb_str_new(mrb, req.path.c_str(), req.path.size());
+}
+} // namespace
+
+namespace {
+mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ const char *path;
+ mrb_int pathlen;
+ mrb_get_args(mrb, "s", &path, &pathlen);
+
+ req.path =
+ make_string_ref(balloc, StringRef{path, static_cast<size_t>(pathlen)});
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &req = downstream->request();
+ return create_headers_hash(mrb, req.fs.headers());
+}
+} // namespace
+
+namespace {
+mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ mrb_value key, values;
+ mrb_get_args(mrb, "So", &key, &values);
+
+ if (RSTRING_LEN(key) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref =
+ make_string_ref(balloc, StringRef{RSTRING_PTR(key),
+ static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (repl) {
+ size_t p = 0;
+ auto &headers = req.fs.headers();
+ for (size_t i = 0; i < headers.size(); ++i) {
+ auto &kv = headers[i];
+ if (kv.name == keyref) {
+ continue;
+ }
+ if (i != p) {
+ headers[p] = std::move(kv);
+ }
+ ++p;
+ }
+ headers.resize(p);
+ }
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ req.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ req.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value request_set_header(mrb_state *mrb, mrb_value self) {
+ return request_mod_header(mrb, self, true);
+}
+} // namespace
+
+namespace {
+mrb_value request_add_header(mrb_state *mrb, mrb_value self) {
+ return request_mod_header(mrb, self, false);
+}
+} // namespace
+
+namespace {
+mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+
+ check_phase(mrb, data->phase, PHASE_REQUEST);
+
+ req.fs.clear_headers();
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value request_push(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto upstream = downstream->get_upstream();
+
+ const char *uri;
+ mrb_int len;
+ mrb_get_args(mrb, "s", &uri, &len);
+
+ upstream->initiate_push(downstream, StringRef{uri, static_cast<size_t>(len)});
+
+ return mrb_nil_value();
+}
+} // namespace
+
+void init_request_class(mrb_state *mrb, RClass *module) {
+ auto request_class =
+ mrb_define_class_under(mrb, module, "Request", mrb->object_class);
+
+ mrb_define_method(mrb, request_class, "initialize", request_init,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "http_version_major",
+ request_get_http_version_major, MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "http_version_minor",
+ request_get_http_version_minor, MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "method", request_get_method,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "method=", request_set_method,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "authority", request_get_authority,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "authority=", request_set_authority,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "scheme", request_get_scheme,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "scheme=", request_set_scheme,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "path", request_get_path,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "path=", request_set_path,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, request_class, "headers", request_get_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "add_header", request_add_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, request_class, "set_header", request_set_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1));
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_request.h b/src/shrpx_mruby_module_request.h
new file mode 100644
index 0000000..e74f335
--- /dev/null
+++ b/src/shrpx_mruby_module_request.h
@@ -0,0 +1,42 @@
+/*
+ * 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_MRUBY_MODULE_REQUEST_H
+#define SHRPX_MRUBY_MODULE_REQUEST_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_request_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_REQUEST_H
diff --git a/src/shrpx_mruby_module_response.cc b/src/shrpx_mruby_module_response.cc
new file mode 100644
index 0000000..1de1d5f
--- /dev/null
+++ b/src/shrpx_mruby_module_response.cc
@@ -0,0 +1,398 @@
+/*
+ * 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_mruby_module_response.h"
+
+#include <mruby/variable.h>
+#include <mruby/string.h>
+#include <mruby/hash.h>
+#include <mruby/array.h>
+
+#include "shrpx_downstream.h"
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_mruby.h"
+#include "shrpx_mruby_module.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace mruby {
+
+namespace {
+mrb_value response_init(mrb_state *mrb, mrb_value self) { return self; }
+} // namespace
+
+namespace {
+mrb_value response_get_http_version_major(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_major);
+}
+} // namespace
+
+namespace {
+mrb_value response_get_http_version_minor(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_minor);
+}
+} // namespace
+
+namespace {
+mrb_value response_get_status(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+ return mrb_fixnum_value(resp.http_status);
+}
+} // namespace
+
+namespace {
+mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+
+ mrb_int status;
+ mrb_get_args(mrb, "i", &status);
+ // We don't support 1xx status code for mruby scripting yet.
+ if (status < 200 || status > 999) {
+ mrb_raise(mrb, E_RUNTIME_ERROR,
+ "invalid status; it should be [200, 999], inclusive");
+ }
+
+ resp.http_status = status;
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value response_get_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ const auto &resp = downstream->response();
+
+ return create_headers_hash(mrb, resp.fs.headers());
+}
+} // namespace
+
+namespace {
+mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ mrb_value key, values;
+ mrb_get_args(mrb, "So", &key, &values);
+
+ if (RSTRING_LEN(key) == 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
+ }
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref =
+ make_string_ref(balloc, StringRef{RSTRING_PTR(key),
+ static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (repl) {
+ size_t p = 0;
+ auto &headers = resp.fs.headers();
+ for (size_t i = 0; i < headers.size(); ++i) {
+ auto &kv = headers[i];
+ if (kv.name == keyref) {
+ continue;
+ }
+ if (i != p) {
+ headers[p] = std::move(kv);
+ }
+ ++p;
+ }
+ headers.resize(p);
+ }
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value response_set_header(mrb_state *mrb, mrb_value self) {
+ return response_mod_header(mrb, self, true);
+}
+} // namespace
+
+namespace {
+mrb_value response_add_header(mrb_state *mrb, mrb_value self) {
+ return response_mod_header(mrb, self, false);
+}
+} // namespace
+
+namespace {
+mrb_value response_clear_headers(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+
+ resp.fs.clear_headers();
+
+ return mrb_nil_value();
+}
+} // namespace
+
+namespace {
+mrb_value response_return(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &req = downstream->request();
+ auto &resp = downstream->response();
+ int rv;
+
+ auto &balloc = downstream->get_block_allocator();
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
+ }
+
+ const char *val;
+ mrb_int vallen;
+ mrb_get_args(mrb, "|s", &val, &vallen);
+
+ const uint8_t *body = nullptr;
+ size_t bodylen = 0;
+
+ if (resp.http_status == 0) {
+ resp.http_status = 200;
+ }
+
+ if (downstream->expect_response_body() && vallen > 0) {
+ body = reinterpret_cast<const uint8_t *>(val);
+ bodylen = vallen;
+ }
+
+ auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH);
+
+ if (resp.http_status == 204 ||
+ (resp.http_status == 200 && req.method == HTTP_CONNECT)) {
+ if (cl) {
+ // Delete content-length here
+ http2::erase_header(cl);
+ }
+
+ resp.fs.content_length = -1;
+ } else {
+ auto content_length = util::make_string_ref_uint(balloc, vallen);
+
+ if (cl) {
+ cl->value = content_length;
+ } else {
+ resp.fs.add_header_token(StringRef::from_lit("content-length"),
+ content_length, false, http2::HD_CONTENT_LENGTH);
+ }
+
+ resp.fs.content_length = vallen;
+ }
+
+ auto date = resp.fs.header(http2::HD_DATE);
+ if (!date) {
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ resp.fs.add_header_token(StringRef::from_lit("date"),
+ make_string_ref(balloc, lgconf->tstamp->time_http),
+ false, http2::HD_DATE);
+ }
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->send_reply(downstream, body, bodylen);
+ if (rv != 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not send response");
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ handler->signal_write();
+
+ return self;
+}
+} // namespace
+
+namespace {
+mrb_value response_send_info(mrb_state *mrb, mrb_value self) {
+ auto data = static_cast<MRubyAssocData *>(mrb->ud);
+ auto downstream = data->downstream;
+ auto &resp = downstream->response();
+ int rv;
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
+ }
+
+ mrb_int http_status;
+ mrb_value hash;
+ mrb_get_args(mrb, "iH", &http_status, &hash);
+
+ if (http_status / 100 != 1) {
+ mrb_raise(mrb, E_RUNTIME_ERROR,
+ "status_code must be in range [100, 199], inclusive");
+ }
+
+ auto &balloc = downstream->get_block_allocator();
+
+ auto keys = mrb_hash_keys(mrb, hash);
+ auto keyslen = RARRAY_LEN(keys);
+
+ for (int i = 0; i < keyslen; ++i) {
+ auto key = mrb_ary_ref(mrb, keys, i);
+ if (!mrb_string_p(key)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "key must be string");
+ }
+
+ auto values = mrb_hash_get(mrb, hash, key);
+
+ auto ai = mrb_gc_arena_save(mrb);
+
+ key = mrb_funcall(mrb, key, "downcase", 0);
+
+ auto keyref = make_string_ref(
+ balloc,
+ StringRef{RSTRING_PTR(key), static_cast<size_t>(RSTRING_LEN(key))});
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ auto token = http2::lookup_token(keyref.byte(), keyref.size());
+
+ if (mrb_array_p(values)) {
+ auto n = RARRAY_LEN(values);
+ for (int i = 0; i < n; ++i) {
+ auto value = mrb_ary_ref(mrb, values, i);
+ if (!mrb_string_p(value)) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(value),
+ static_cast<size_t>(RSTRING_LEN(value))}),
+ false, token);
+ }
+ } else if (mrb_string_p(values)) {
+ resp.fs.add_header_token(
+ keyref,
+ make_string_ref(balloc,
+ StringRef{RSTRING_PTR(values),
+ static_cast<size_t>(RSTRING_LEN(values))}),
+ false, token);
+ } else {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
+ }
+ }
+
+ resp.http_status = http_status;
+
+ auto upstream = downstream->get_upstream();
+
+ rv = upstream->on_downstream_header_complete(downstream);
+ if (rv != 0) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "could not send non-final response");
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ handler->signal_write();
+
+ return self;
+}
+} // namespace
+
+void init_response_class(mrb_state *mrb, RClass *module) {
+ auto response_class =
+ mrb_define_class_under(mrb, module, "Response", mrb->object_class);
+
+ mrb_define_method(mrb, response_class, "initialize", response_init,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "http_version_major",
+ response_get_http_version_major, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "http_version_minor",
+ response_get_http_version_minor, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "status", response_get_status,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "status=", response_set_status,
+ MRB_ARGS_REQ(1));
+ mrb_define_method(mrb, response_class, "headers", response_get_headers,
+ MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "add_header", response_add_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, response_class, "set_header", response_set_header,
+ MRB_ARGS_REQ(2));
+ mrb_define_method(mrb, response_class, "clear_headers",
+ response_clear_headers, MRB_ARGS_NONE());
+ mrb_define_method(mrb, response_class, "return", response_return,
+ MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, response_class, "send_info", response_send_info,
+ MRB_ARGS_REQ(2));
+}
+
+} // namespace mruby
+
+} // namespace shrpx
diff --git a/src/shrpx_mruby_module_response.h b/src/shrpx_mruby_module_response.h
new file mode 100644
index 0000000..a35b42b
--- /dev/null
+++ b/src/shrpx_mruby_module_response.h
@@ -0,0 +1,42 @@
+/*
+ * 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_MRUBY_MODULE_RESPONSE_H
+#define SHRPX_MRUBY_MODULE_RESPONSE_H
+
+#include "shrpx.h"
+
+#include <mruby.h>
+
+namespace shrpx {
+
+namespace mruby {
+
+void init_response_class(mrb_state *mrb, RClass *module);
+
+} // namespace mruby
+
+} // namespace shrpx
+
+#endif // SHRPX_MRUBY_MODULE_RESPONSE_H
diff --git a/src/shrpx_null_downstream_connection.cc b/src/shrpx_null_downstream_connection.cc
new file mode 100644
index 0000000..cd81c8a
--- /dev/null
+++ b/src/shrpx_null_downstream_connection.cc
@@ -0,0 +1,88 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_null_downstream_connection.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+NullDownstreamConnection::NullDownstreamConnection(
+ const std::shared_ptr<DownstreamAddrGroup> &group)
+ : group_(group) {}
+
+NullDownstreamConnection::~NullDownstreamConnection() {}
+
+int NullDownstreamConnection::attach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+ }
+
+ downstream_ = downstream;
+
+ return 0;
+}
+
+void NullDownstreamConnection::detach_downstream(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+ }
+ downstream_ = nullptr;
+}
+
+int NullDownstreamConnection::push_request_headers() { return 0; }
+
+int NullDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+ size_t datalen) {
+ return 0;
+}
+
+int NullDownstreamConnection::end_upload_data() { return 0; }
+
+void NullDownstreamConnection::pause_read(IOCtrlReason reason) {}
+
+int NullDownstreamConnection::resume_read(IOCtrlReason reason,
+ size_t consumed) {
+ return 0;
+}
+
+void NullDownstreamConnection::force_resume_read() {}
+
+int NullDownstreamConnection::on_read() { return 0; }
+
+int NullDownstreamConnection::on_write() { return 0; }
+
+void NullDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+bool NullDownstreamConnection::poolable() const { return false; }
+
+const std::shared_ptr<DownstreamAddrGroup> &
+NullDownstreamConnection::get_downstream_addr_group() const {
+ return group_;
+}
+
+DownstreamAddr *NullDownstreamConnection::get_addr() const { return nullptr; }
+
+} // namespace shrpx
diff --git a/src/shrpx_null_downstream_connection.h b/src/shrpx_null_downstream_connection.h
new file mode 100644
index 0000000..7defcc3
--- /dev/null
+++ b/src/shrpx_null_downstream_connection.h
@@ -0,0 +1,68 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_NULL_DOWNSTREAM_CONNECTION_H
+#define SHRPX_NULL_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx_downstream_connection.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class NullDownstreamConnection : public DownstreamConnection {
+public:
+ NullDownstreamConnection(const std::shared_ptr<DownstreamAddrGroup> &group);
+ virtual ~NullDownstreamConnection();
+ 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);
+
+ // true if this object is poolable.
+ virtual bool poolable() const;
+
+ virtual const std::shared_ptr<DownstreamAddrGroup> &
+ get_downstream_addr_group() const;
+ virtual DownstreamAddr *get_addr() const;
+
+private:
+ std::shared_ptr<DownstreamAddrGroup> group_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_NULL_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_process.h b/src/shrpx_process.h
new file mode 100644
index 0000000..d35461b
--- /dev/null
+++ b/src/shrpx_process.h
@@ -0,0 +1,37 @@
+/*
+ * 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_PROCESS_H
+#define SHRPX_PROCESS_H
+
+#include "shrpx.h"
+
+namespace shrpx {
+
+constexpr uint8_t SHRPX_IPC_REOPEN_LOG = 1;
+constexpr uint8_t SHRPX_IPC_GRACEFUL_SHUTDOWN = 2;
+
+} // namespace shrpx
+
+#endif // SHRPX_PROCESS_H
diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc
new file mode 100644
index 0000000..2d4de59
--- /dev/null
+++ b/src/shrpx_quic.cc
@@ -0,0 +1,393 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_quic.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/udp.h>
+
+#include <array>
+#include <chrono>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <nghttp3/nghttp3.h>
+
+#include <openssl/rand.h>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "xsi_strerror.h"
+
+bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) {
+ return ngtcp2_cid_eq(&lhs, &rhs);
+}
+
+namespace shrpx {
+
+ngtcp2_tstamp quic_timestamp() {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+
+int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ iovec msg_iov = {const_cast<uint8_t *>(data), datalen};
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(remote_sa);
+ msg.msg_namelen = remote_salen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) +
+#ifdef UDP_SEGMENT
+ CMSG_SPACE(sizeof(uint16_t)) +
+#endif // UDP_SEGMENT
+ CMSG_SPACE(sizeof(in6_pktinfo))];
+
+ memset(msg_ctrl, 0, sizeof(msg_ctrl));
+
+ msg.msg_control = msg_ctrl;
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ size_t controllen = 0;
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+
+ switch (local_sa->sa_family) {
+ case AF_INET: {
+ controllen += CMSG_SPACE(sizeof(in_pktinfo));
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ in_pktinfo pktinfo{};
+ auto addrin =
+ reinterpret_cast<sockaddr_in *>(const_cast<sockaddr *>(local_sa));
+ pktinfo.ipi_spec_dst = addrin->sin_addr;
+ memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo));
+
+ break;
+ }
+ case AF_INET6: {
+ controllen += CMSG_SPACE(sizeof(in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ in6_pktinfo pktinfo{};
+ auto addrin =
+ reinterpret_cast<sockaddr_in6 *>(const_cast<sockaddr *>(local_sa));
+ pktinfo.ipi6_addr = addrin->sin6_addr;
+ memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo));
+
+ break;
+ }
+ default:
+ assert(0);
+ }
+
+#ifdef UDP_SEGMENT
+ if (gso_size && datalen > gso_size) {
+ controllen += CMSG_SPACE(sizeof(uint16_t));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ uint16_t n = gso_size;
+ memcpy(CMSG_DATA(cm), &n, sizeof(n));
+ }
+#endif // UDP_SEGMENT
+
+ controllen += CMSG_SPACE(sizeof(int));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ unsigned int tos = pi.ecn;
+ memcpy(CMSG_DATA(cm), &tos, sizeof(tos));
+
+ switch (local_sa->sa_family) {
+ case AF_INET:
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_TOS;
+
+ break;
+ case AF_INET6:
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_TCLASS;
+
+ break;
+ default:
+ assert(0);
+ }
+
+ msg.msg_controllen = controllen;
+
+ ssize_t nwrite;
+
+ do {
+ nwrite = sendmsg(faddr->fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ if (LOG_ENABLED(INFO)) {
+ auto error = errno;
+ LOG(INFO) << "sendmsg failed: errno=" << error;
+ }
+
+ return -errno;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "QUIC sent packet: local="
+ << util::to_numeric_addr(local_sa, local_salen)
+ << " remote=" << util::to_numeric_addr(remote_sa, remote_salen)
+ << " ecn=" << log::hex << pi.ecn << log::dec << " " << nwrite
+ << " bytes";
+ }
+
+ return 0;
+}
+
+int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *server_id, uint8_t km_id,
+ const uint8_t *key) {
+ assert(cidlen == SHRPX_QUIC_SCIDLEN);
+
+ if (RAND_bytes(cid.data, cidlen) != 1) {
+ return -1;
+ }
+
+ cid.datalen = cidlen;
+
+ cid.data[0] = (cid.data[0] & 0x3f) | km_id;
+
+ auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
+
+ std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, p);
+
+ return encrypt_quic_connection_id(p, p, key);
+}
+
+int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *cid_prefix, uint8_t km_id,
+ const uint8_t *key) {
+ assert(cidlen == SHRPX_QUIC_SCIDLEN);
+
+ if (RAND_bytes(cid.data, cidlen) != 1) {
+ return -1;
+ }
+
+ cid.datalen = cidlen;
+
+ cid.data[0] = (cid.data[0] & 0x3f) | km_id;
+
+ auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
+
+ std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, p);
+
+ return encrypt_quic_connection_id(p, p, key);
+}
+
+int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key) {
+ auto ctx = EVP_CIPHER_CTX_new();
+ auto d = defer(EVP_CIPHER_CTX_free, ctx);
+
+ if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
+ return -1;
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ int len;
+
+ if (!EVP_EncryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
+ !EVP_EncryptFinal_ex(ctx, dest + len, &len)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key) {
+ auto ctx = EVP_CIPHER_CTX_new();
+ auto d = defer(EVP_CIPHER_CTX_free, ctx);
+
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
+ return -1;
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ int len;
+
+ if (!EVP_DecryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
+ !EVP_DecryptFinal_ex(ctx, dest + len, &len)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_cid &cid) {
+ auto ctx = EVP_MD_CTX_new();
+ auto d = defer(EVP_MD_CTX_free, ctx);
+
+ std::array<uint8_t, 32> h;
+ unsigned int hlen = EVP_MD_size(EVP_sha256());
+
+ if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
+ !EVP_DigestUpdate(ctx, &remote_addr.su.sa, remote_addr.len) ||
+ !EVP_DigestUpdate(ctx, &local_addr.su.sa, local_addr.len) ||
+ !EVP_DigestUpdate(ctx, cid.data, cid.datalen) ||
+ !EVP_DigestFinal_ex(ctx, h.data(), &hlen)) {
+ return -1;
+ }
+
+ assert(hlen == h.size());
+
+ std::copy_n(std::begin(h), sizeof(dest.data), std::begin(dest.data));
+ dest.datalen = sizeof(dest.data);
+
+ return 0;
+}
+
+int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
+ const uint8_t *secret,
+ size_t secretlen) {
+ if (ngtcp2_crypto_generate_stateless_reset_token(token, secret, secretlen,
+ &cid) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid,
+ const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto stokenlen = ngtcp2_crypto_generate_retry_token(
+ token, secret, secretlen, version, sa, salen, &retry_scid, &odcid, t);
+ if (stokenlen < 0) {
+ return -1;
+ }
+
+ tokenlen = stokenlen;
+
+ return 0;
+}
+
+int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, const ngtcp2_cid &dcid,
+ const sockaddr *sa, socklen_t salen,
+ const uint8_t *secret, size_t secretlen) {
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_retry_token(&odcid, token, tokenlen, secret,
+ secretlen, version, sa, salen, &dcid,
+ 10 * NGTCP2_SECONDS, t) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+ size_t salen, const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto stokenlen = ngtcp2_crypto_generate_regular_token(
+ token, secret, secretlen, sa, salen, t);
+ if (stokenlen < 0) {
+ return -1;
+ }
+
+ tokenlen = stokenlen;
+
+ return 0;
+}
+
+int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
+ socklen_t salen, const uint8_t *secret, size_t secretlen) {
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_regular_token(token, tokenlen, secret, secretlen, sa,
+ salen, 3600 * NGTCP2_SECONDS,
+ t) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const uint8_t *salt,
+ size_t saltlen) {
+ constexpr uint8_t info[] = "connection id encryption key";
+ ngtcp2_crypto_md sha256;
+ ngtcp2_crypto_md_init(
+ &sha256, reinterpret_cast<void *>(const_cast<EVP_MD *>(EVP_sha256())));
+
+ if (ngtcp2_crypto_hkdf(key, keylen, &sha256, secret, secretlen, salt, saltlen,
+ info, str_size(info)) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+const QUICKeyingMaterial *
+select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id) {
+ for (auto &qkm : qkms.keying_materials) {
+ if (km_id == qkm.id) {
+ return &qkm;
+ }
+ }
+
+ return &qkms.keying_materials.front();
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h
new file mode 100644
index 0000000..b2f6087
--- /dev/null
+++ b/src/shrpx_quic.h
@@ -0,0 +1,138 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_QUIC_H
+#define SHRPX_QUIC_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+
+#include <functional>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace std {
+template <> struct hash<ngtcp2_cid> {
+ std::size_t operator()(const ngtcp2_cid &cid) const noexcept {
+ // FNV-1a 64bits variant
+ constexpr uint64_t basis = 0xCBF29CE484222325ULL;
+ const uint8_t *p = cid.data, *end = cid.data + cid.datalen;
+ uint64_t h = basis;
+
+ for (; p != end;) {
+ h ^= *p++;
+ h *= basis;
+ }
+
+ return static_cast<size_t>(h);
+ }
+};
+} // namespace std
+
+bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs);
+
+namespace shrpx {
+
+struct UpstreamAddr;
+struct QUICKeyingMaterials;
+struct QUICKeyingMaterial;
+
+constexpr size_t SHRPX_QUIC_SCIDLEN = 20;
+constexpr size_t SHRPX_QUIC_SERVER_IDLEN = 4;
+// SHRPX_QUIC_CID_PREFIXLEN includes SHRPX_QUIC_SERVER_IDLEN.
+constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8;
+constexpr size_t SHRPX_QUIC_CID_PREFIX_OFFSET = 1;
+constexpr size_t SHRPX_QUIC_DECRYPTED_DCIDLEN = 16;
+constexpr size_t SHRPX_QUIC_CID_ENCRYPTION_KEYLEN = 16;
+constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472;
+constexpr size_t SHRPX_QUIC_CONN_CLOSE_PKTLEN = 256;
+constexpr size_t SHRPX_QUIC_STATELESS_RESET_BURST = 100;
+constexpr size_t SHRPX_QUIC_SECRET_RESERVEDLEN = 4;
+constexpr size_t SHRPX_QUIC_SECRETLEN = 32;
+constexpr size_t SHRPX_QUIC_SALTLEN = 32;
+constexpr uint8_t SHRPX_QUIC_DCID_KM_ID_MASK = 0xc0;
+
+ngtcp2_tstamp quic_timestamp();
+
+int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
+ size_t remote_salen, const sockaddr *local_sa,
+ size_t local_salen, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+
+int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *server_id, uint8_t km_id,
+ const uint8_t *key);
+
+int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
+ const uint8_t *cid_prefix, uint8_t km_id,
+ const uint8_t *key);
+
+int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key);
+
+int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
+ const uint8_t *key);
+
+int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_cid &cid);
+
+int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
+ const uint8_t *secret,
+ size_t secretlen);
+
+int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid,
+ const uint8_t *secret, size_t secretlen);
+
+int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, const ngtcp2_cid &dcid,
+ const sockaddr *sa, socklen_t salen,
+ const uint8_t *secret, size_t secretlen);
+
+int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+ size_t salen, const uint8_t *secret, size_t secretlen);
+
+int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
+ socklen_t salen, const uint8_t *secret, size_t secretlen);
+
+int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const uint8_t *salt,
+ size_t saltlen);
+
+const QUICKeyingMaterial *
+select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id);
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_H
diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc
new file mode 100644
index 0000000..6287971
--- /dev/null
+++ b/src/shrpx_quic_connection_handler.cc
@@ -0,0 +1,761 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_quic_connection_handler.h"
+
+#include <openssl/rand.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "shrpx_worker.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_log.h"
+#include "shrpx_http3_upstream.h"
+#include "shrpx_connection_handler.h"
+#include "ssl_compat.h"
+
+namespace shrpx {
+
+namespace {
+void stateless_reset_bucket_regen_timercb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto quic_conn_handler = static_cast<QUICConnectionHandler *>(w->data);
+
+ quic_conn_handler->on_stateless_reset_bucket_regen();
+}
+} // namespace
+
+QUICConnectionHandler::QUICConnectionHandler(Worker *worker)
+ : worker_{worker},
+ stateless_reset_bucket_{SHRPX_QUIC_STATELESS_RESET_BURST} {
+ ev_timer_init(&stateless_reset_bucket_regen_timer_,
+ stateless_reset_bucket_regen_timercb, 0., 1.);
+ stateless_reset_bucket_regen_timer_.data = this;
+}
+
+QUICConnectionHandler::~QUICConnectionHandler() {
+ ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+}
+
+int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen) {
+ int rv;
+ ngtcp2_version_cid vc;
+
+ rv = ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN);
+ switch (rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_VERSION_NEGOTIATION:
+ send_version_negotiation(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr);
+
+ return 0;
+ default:
+ return 0;
+ }
+
+ auto config = get_config();
+
+ ngtcp2_cid dcid_key;
+ ngtcp2_cid_init(&dcid_key, vc.dcid, vc.dcidlen);
+
+ auto conn_handler = worker_->get_connection_handler();
+
+ ClientHandler *handler;
+
+ auto &quicconf = config->quic;
+
+ auto it = connections_.find(dcid_key);
+ if (it == std::end(connections_)) {
+ auto cwit = close_waits_.find(dcid_key);
+ if (cwit != std::end(close_waits_)) {
+ auto cw = (*cwit).second;
+
+ cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
+
+ return 0;
+ }
+
+ if (data[0] & 0x80) {
+ if (generate_quic_hashed_connection_id(dcid_key, remote_addr, local_addr,
+ dcid_key) != 0) {
+ return 0;
+ }
+
+ it = connections_.find(dcid_key);
+ if (it == std::end(connections_)) {
+ auto cwit = close_waits_.find(dcid_key);
+ if (cwit != std::end(close_waits_)) {
+ auto cw = (*cwit).second;
+
+ cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
+
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (it == std::end(connections_)) {
+ std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
+
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ const QUICKeyingMaterial *qkm = nullptr;
+
+ if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) {
+ qkm = select_quic_keying_material(
+ *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK);
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm->cid_encryption_key.data()) != 0) {
+ return 0;
+ }
+
+ if (qkm != &qkms->keying_materials.front() ||
+ !std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ auto quic_lwp =
+ conn_handler->match_quic_lingering_worker_process_cid_prefix(
+ decrypted_dcid.data(), decrypted_dcid.size());
+ if (quic_lwp) {
+ if (conn_handler->forward_quic_packet_to_lingering_worker_process(
+ quic_lwp, remote_addr, local_addr, pi, data, datalen) == 0) {
+ return 0;
+ }
+
+ return 0;
+ }
+ }
+ }
+
+ // new connection
+
+ auto &upstreamconf = config->conn.upstream;
+ if (worker_->get_worker_stat()->num_connections >=
+ upstreamconf.worker_connections) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Too many connections >="
+ << upstreamconf.worker_connections;
+ }
+
+ return 0;
+ }
+
+ ngtcp2_pkt_hd hd;
+ ngtcp2_cid odcid, *podcid = nullptr;
+ const uint8_t *token = nullptr;
+ size_t tokenlen = 0;
+ ngtcp2_token_type token_type = NGTCP2_TOKEN_TYPE_UNKNOWN;
+
+ switch (ngtcp2_accept(&hd, data, datalen)) {
+ case 0: {
+ // If we get Initial and it has the CID prefix of this worker,
+ // it is likely that client is intentionally use the prefix.
+ // Just drop it.
+ if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) {
+ if (qkm != &qkms->keying_materials.front()) {
+ qkm = &qkms->keying_materials.front();
+
+ if (decrypt_quic_connection_id(decrypted_dcid.data(),
+ vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
+ qkm->cid_encryption_key.data()) != 0) {
+ return 0;
+ }
+ }
+
+ if (std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ return 0;
+ }
+ }
+
+ if (worker_->get_graceful_shutdown()) {
+ send_connection_close(faddr, hd.version, hd.dcid, hd.scid, remote_addr,
+ local_addr, NGTCP2_CONNECTION_REFUSED,
+ datalen * 3);
+ return 0;
+ }
+
+ if (hd.tokenlen == 0) {
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ switch (hd.token[0]) {
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: {
+ if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) {
+ // Initial packets with Retry token must have DCID chosen by
+ // server.
+ return 0;
+ }
+
+ auto qkm = select_quic_keying_material(
+ *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK);
+
+ if (verify_retry_token(odcid, hd.token, hd.tokenlen, hd.version,
+ hd.dcid, &remote_addr.su.sa, remote_addr.len,
+ qkm->secret.data(), qkm->secret.size()) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate Retry token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ // 2nd Retry packet is not allowed, so send CONNECTION_CLOSE
+ // with INVALID_TOKEN.
+ send_connection_close(faddr, hd.version, hd.dcid, hd.scid,
+ remote_addr, local_addr, NGTCP2_INVALID_TOKEN,
+ datalen * 3);
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Successfully validated Retry token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ podcid = &odcid;
+ token = hd.token;
+ tokenlen = hd.tokenlen;
+ token_type = NGTCP2_TOKEN_TYPE_RETRY;
+
+ break;
+ }
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: {
+ // If a token is a regular token, it must be at least
+ // NGTCP2_MIN_INITIAL_DCIDLEN bytes long.
+ if (vc.dcidlen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+ return 0;
+ }
+
+ if (hd.tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN + 1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ auto qkm = select_quic_keying_material(
+ *qkms.get(), hd.token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]);
+
+ if (verify_token(hd.token, hd.tokenlen - 1, &remote_addr.su.sa,
+ remote_addr.len, qkm->secret.data(),
+ qkm->secret.size()) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Failed to validate token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Successfully validated token from remote="
+ << util::to_numeric_addr(&remote_addr);
+ }
+
+ token = hd.token;
+ tokenlen = hd.tokenlen;
+ token_type = NGTCP2_TOKEN_TYPE_NEW_TOKEN;
+
+ break;
+ }
+ default:
+ if (quicconf.upstream.require_token) {
+ send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid,
+ vc.scidlen, remote_addr, local_addr, datalen * 3);
+
+ return 0;
+ }
+
+ break;
+ }
+
+ break;
+ }
+ default:
+ if (!config->single_thread && !(data[0] & 0x80) &&
+ vc.dcidlen == SHRPX_QUIC_SCIDLEN &&
+ !std::equal(std::begin(decrypted_dcid),
+ std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
+ worker_->get_cid_prefix())) {
+ if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr,
+ pi, decrypted_dcid.data(), data,
+ datalen) == 0) {
+ return 0;
+ }
+ }
+
+ if (!(data[0] & 0x80)) {
+ // TODO Must be rate limited
+ send_stateless_reset(faddr, vc.dcid, vc.dcidlen, remote_addr,
+ local_addr);
+ }
+
+ return 0;
+ }
+
+ handler = handle_new_connection(faddr, remote_addr, local_addr, hd, podcid,
+ token, tokenlen, token_type);
+ if (handler == nullptr) {
+ return 0;
+ }
+ } else {
+ handler = (*it).second;
+ }
+
+ if (handler->read_quic(faddr, remote_addr, local_addr, pi, data, datalen) !=
+ 0) {
+ delete handler;
+ return 0;
+ }
+
+ handler->signal_write();
+
+ return 0;
+}
+
+ClientHandler *QUICConnectionHandler::handle_new_connection(
+ const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &hd, const ngtcp2_cid *odcid,
+ const uint8_t *token, size_t tokenlen, ngtcp2_token_type token_type) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> service;
+ int rv;
+
+ rv = getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(),
+ host.size(), service.data(), service.size(),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ auto ssl_ctx = worker_->get_quic_sv_ssl_ctx();
+
+ assert(ssl_ctx);
+
+ auto ssl = tls::create_ssl(ssl_ctx);
+ if (ssl == nullptr) {
+ return nullptr;
+ }
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ assert(SSL_is_quic(ssl));
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ SSL_set_accept_state(ssl);
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ if (quicconf.upstream.early_data) {
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ SSL_set_quic_early_data_enabled(ssl, 1);
+#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ SSL_set_early_data_enabled(ssl, 1);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ }
+
+ // Disable TLS session ticket if we don't have working ticket
+ // keys.
+ if (!worker_->get_ticket_keys()) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+
+ auto handler = std::make_unique<ClientHandler>(
+ worker_, faddr->fd, ssl, StringRef{host.data()},
+ StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr);
+
+ auto upstream = std::make_unique<Http3Upstream>(handler.get());
+ if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token, tokenlen,
+ token_type) != 0) {
+ return nullptr;
+ }
+
+ handler->setup_http3_upstream(std::move(upstream));
+
+ return handler.release();
+}
+
+namespace {
+uint32_t generate_reserved_version(const Address &addr, uint32_t version) {
+ uint32_t h = 0x811C9DC5u;
+ const uint8_t *p = reinterpret_cast<const uint8_t *>(&addr.su.sa);
+ const uint8_t *ep = p + addr.len;
+
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+
+ version = htonl(version);
+ p = (const uint8_t *)&version;
+ ep = p + sizeof(version);
+
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+
+ h &= 0xf0f0f0f0u;
+ h |= 0x0a0a0a0au;
+
+ return h;
+}
+} // namespace
+
+int QUICConnectionHandler::send_retry(
+ const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid,
+ size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr, size_t max_pktlen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(), host.size(),
+ port.data(), port.size(),
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+ return -1;
+ }
+
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ auto conn_handler = worker_->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ ngtcp2_cid retry_scid;
+
+ if (generate_quic_retry_connection_id(retry_scid, SHRPX_QUIC_SCIDLEN,
+ quicconf.server_id.data(), qkm.id,
+ qkm.cid_encryption_key.data()) != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;
+ size_t tokenlen;
+
+ ngtcp2_cid idcid, iscid;
+ ngtcp2_cid_init(&idcid, ini_dcid, ini_dcidlen);
+ ngtcp2_cid_init(&iscid, ini_scid, ini_scidlen);
+
+ if (generate_retry_token(token.data(), tokenlen, version, &remote_addr.su.sa,
+ remote_addr.len, retry_scid, idcid,
+ qkm.secret.data(), qkm.secret.size()) != 0) {
+ return -1;
+ }
+
+ std::vector<uint8_t> buf;
+ buf.resize(std::min(max_pktlen, static_cast<size_t>(256)));
+
+ auto nwrite =
+ ngtcp2_crypto_write_retry(buf.data(), buf.size(), version, &iscid,
+ &retry_scid, &idcid, token.data(), tokenlen);
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_crypto_write_retry: " << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ buf.resize(nwrite);
+
+ quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), buf.size(), 0);
+
+ if (generate_quic_hashed_connection_id(idcid, remote_addr, local_addr,
+ idcid) != 0) {
+ return -1;
+ }
+
+ auto d =
+ static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT * 3) / NGTCP2_SECONDS;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Enter close-wait period " << d << "s with " << buf.size()
+ << " bytes sentinel packet";
+ }
+
+ auto cw = std::make_unique<CloseWait>(worker_, std::vector<ngtcp2_cid>{idcid},
+ std::move(buf), d);
+
+ add_close_wait(cw.release());
+
+ return 0;
+}
+
+int QUICConnectionHandler::send_version_negotiation(
+ const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid,
+ size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr) {
+ std::array<uint32_t, 2> sv{
+ generate_reserved_version(remote_addr, version),
+ NGTCP2_PROTO_VER_V1,
+ };
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ uint8_t rand_byte;
+ util::random_bytes(&rand_byte, &rand_byte + 1, worker_->get_randgen());
+
+ auto nwrite = ngtcp2_pkt_write_version_negotiation(
+ buf.data(), buf.size(), rand_byte, ini_scid, ini_scidlen, ini_dcid,
+ ini_dcidlen, sv.data(), sv.size());
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_write_version_negotiation: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
+ const uint8_t *dcid,
+ size_t dcidlen,
+ const Address &remote_addr,
+ const Address &local_addr) {
+ if (stateless_reset_bucket_ == 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Stateless Reset bucket has been depleted";
+ }
+
+ return 0;
+ }
+
+ --stateless_reset_bucket_;
+
+ if (!ev_is_active(&stateless_reset_bucket_regen_timer_)) {
+ ev_timer_again(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+ }
+
+ int rv;
+ std::array<uint8_t, NGTCP2_STATELESS_RESET_TOKENLEN> token;
+ ngtcp2_cid cid;
+
+ ngtcp2_cid_init(&cid, dcid, dcidlen);
+
+ auto conn_handler = worker_->get_connection_handler();
+ auto &qkms = conn_handler->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ rv = generate_quic_stateless_reset_token(token.data(), cid, qkm.secret.data(),
+ qkm.secret.size());
+ if (rv != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_MIN_STATELESS_RESET_RANDLEN> rand_bytes;
+
+ if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ auto nwrite =
+ ngtcp2_pkt_write_stateless_reset(buf.data(), buf.size(), token.data(),
+ rand_bytes.data(), rand_bytes.size());
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_pkt_write_stateless_reset: "
+ << ngtcp2_strerror(nwrite);
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Send stateless_reset to remote="
+ << util::to_numeric_addr(&remote_addr)
+ << " dcid=" << util::format_hex(dcid, dcidlen);
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+int QUICConnectionHandler::send_connection_close(
+ const UpstreamAddr *faddr, uint32_t version, const ngtcp2_cid &ini_dcid,
+ const ngtcp2_cid &ini_scid, const Address &remote_addr,
+ const Address &local_addr, uint64_t error_code, size_t max_pktlen) {
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ max_pktlen = std::min(max_pktlen, buf.size());
+
+ auto nwrite = ngtcp2_crypto_write_connection_close(
+ buf.data(), max_pktlen, version, &ini_scid, &ini_dcid, error_code,
+ nullptr, 0);
+ if (nwrite < 0) {
+ LOG(ERROR) << "ngtcp2_crypto_write_connection_close failed";
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Send Initial CONNECTION_CLOSE with error_code=" << log::hex
+ << error_code << log::dec
+ << " to remote=" << util::to_numeric_addr(&remote_addr)
+ << " dcid=" << util::format_hex(ini_scid.data, ini_scid.datalen)
+ << " scid=" << util::format_hex(ini_dcid.data, ini_dcid.datalen);
+ }
+
+ return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ buf.data(), nwrite, 0);
+}
+
+void QUICConnectionHandler::add_connection_id(const ngtcp2_cid &cid,
+ ClientHandler *handler) {
+ connections_.emplace(cid, handler);
+}
+
+void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid &cid) {
+ connections_.erase(cid);
+}
+
+void QUICConnectionHandler::add_close_wait(CloseWait *cw) {
+ for (auto &cid : cw->scids) {
+ close_waits_.emplace(cid, cw);
+ }
+}
+
+void QUICConnectionHandler::remove_close_wait(const CloseWait *cw) {
+ for (auto &cid : cw->scids) {
+ close_waits_.erase(cid);
+ }
+}
+
+void QUICConnectionHandler::on_stateless_reset_bucket_regen() {
+ assert(stateless_reset_bucket_ < SHRPX_QUIC_STATELESS_RESET_BURST);
+
+ if (++stateless_reset_bucket_ == SHRPX_QUIC_STATELESS_RESET_BURST) {
+ ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_);
+ }
+}
+
+static void close_wait_timeoutcb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto cw = static_cast<CloseWait *>(w->data);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "close-wait period finished";
+ }
+
+ auto quic_conn_handler = cw->worker->get_quic_connection_handler();
+ quic_conn_handler->remove_close_wait(cw);
+
+ delete cw;
+}
+
+CloseWait::CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
+ std::vector<uint8_t> pkt, ev_tstamp period)
+ : worker{worker},
+ scids{std::move(scids)},
+ pkt{std::move(pkt)},
+ bytes_recv{0},
+ bytes_sent{0},
+ num_pkts_recv{0},
+ next_pkts_recv{1} {
+ ++worker->get_worker_stat()->num_close_waits;
+
+ ev_timer_init(&timer, close_wait_timeoutcb, period, 0.);
+ timer.data = this;
+
+ ev_timer_start(worker->get_loop(), &timer);
+}
+
+CloseWait::~CloseWait() {
+ auto loop = worker->get_loop();
+
+ ev_timer_stop(loop, &timer);
+
+ auto worker_stat = worker->get_worker_stat();
+ --worker_stat->num_close_waits;
+
+ if (worker->get_graceful_shutdown() && worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 0) {
+ ev_break(loop);
+ }
+}
+
+int CloseWait::handle_packet(const UpstreamAddr *faddr,
+ const Address &remote_addr,
+ const Address &local_addr,
+ const ngtcp2_pkt_info &pi, const uint8_t *data,
+ size_t datalen) {
+ if (pkt.empty()) {
+ return 0;
+ }
+
+ ++num_pkts_recv;
+ bytes_recv += datalen;
+
+ if (bytes_sent + pkt.size() > 3 * bytes_recv ||
+ next_pkts_recv > num_pkts_recv) {
+ return 0;
+ }
+
+ if (quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
+ &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
+ pkt.data(), pkt.size(), 0) != 0) {
+ return -1;
+ }
+
+ next_pkts_recv *= 2;
+ bytes_sent += pkt.size();
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h
new file mode 100644
index 0000000..29e73a4
--- /dev/null
+++ b/src/shrpx_quic_connection_handler.h
@@ -0,0 +1,142 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_QUIC_CONNECTION_HANDLER_H
+#define SHRPX_QUIC_CONNECTION_HANDLER_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <unordered_map>
+#include <string>
+#include <vector>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <ev.h>
+
+#include "shrpx_quic.h"
+#include "network.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct UpstreamAddr;
+class ClientHandler;
+class Worker;
+
+// CloseWait handles packets received in close-wait (draining or
+// closing period).
+struct CloseWait {
+ CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
+ std::vector<uint8_t> pkt, ev_tstamp period);
+ ~CloseWait();
+
+ int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+
+ Worker *worker;
+ // Source Connection IDs of the connection.
+ std::vector<ngtcp2_cid> scids;
+ // QUIC packet which is sent in response to the incoming packet. It
+ // might be empty.
+ std::vector<uint8_t> pkt;
+ // Close-wait (draining or closing period) timer.
+ ev_timer timer;
+ // The number of bytes received during close-wait period.
+ size_t bytes_recv;
+ // The number of bytes sent during close-wait period.
+ size_t bytes_sent;
+ // The number of packets received during close-wait period.
+ size_t num_pkts_recv;
+ // If the number of packets received reaches this number, send a
+ // QUIC packet.
+ size_t next_pkts_recv;
+};
+
+class QUICConnectionHandler {
+public:
+ QUICConnectionHandler(Worker *worker);
+ ~QUICConnectionHandler();
+ int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen);
+ // Send Retry packet. |ini_dcid| is the destination Connection ID
+ // which appeared in Client Initial packet and its length is
+ // |dcidlen|. |ini_scid| is the source Connection ID which appeared
+ // in Client Initial packet and its length is |scidlen|.
+ int send_retry(const UpstreamAddr *faddr, uint32_t version,
+ const uint8_t *ini_dcid, size_t ini_dcidlen,
+ const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr, const Address &local_addr,
+ size_t max_pktlen);
+ // Send Version Negotiation packet. |ini_dcid| is the destination
+ // Connection ID which appeared in Client Initial packet and its
+ // length is |dcidlen|. |ini_scid| is the source Connection ID
+ // which appeared in Client Initial packet and its length is
+ // |scidlen|.
+ int send_version_negotiation(const UpstreamAddr *faddr, uint32_t version,
+ const uint8_t *ini_dcid, size_t ini_dcidlen,
+ const uint8_t *ini_scid, size_t ini_scidlen,
+ const Address &remote_addr,
+ const Address &local_addr);
+ int send_stateless_reset(const UpstreamAddr *faddr, const uint8_t *dcid,
+ size_t dcidlen, const Address &remote_addr,
+ const Address &local_addr);
+ // Send Initial CONNECTION_CLOSE. |ini_dcid| is the destination
+ // Connection ID which appeared in Client Initial packet.
+ // |ini_scid| is the source Connection ID which appeared in Client
+ // Initial packet.
+ int send_connection_close(const UpstreamAddr *faddr, uint32_t version,
+ const ngtcp2_cid &ini_dcid,
+ const ngtcp2_cid &ini_scid,
+ const Address &remote_addr,
+ const Address &local_addr, uint64_t error_code,
+ size_t max_pktlen);
+ ClientHandler *
+ handle_new_connection(const UpstreamAddr *faddr, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_hd &hd,
+ const ngtcp2_cid *odcid, const uint8_t *token,
+ size_t tokenlen, ngtcp2_token_type token_type);
+ void add_connection_id(const ngtcp2_cid &cid, ClientHandler *handler);
+ void remove_connection_id(const ngtcp2_cid &cid);
+
+ void add_close_wait(CloseWait *cw);
+ void remove_close_wait(const CloseWait *cw);
+
+ void on_stateless_reset_bucket_regen();
+
+private:
+ Worker *worker_;
+ std::unordered_map<ngtcp2_cid, ClientHandler *> connections_;
+ std::unordered_map<ngtcp2_cid, CloseWait *> close_waits_;
+ ev_timer stateless_reset_bucket_regen_timer_;
+ size_t stateless_reset_bucket_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_CONNECTION_HANDLER_H
diff --git a/src/shrpx_quic_listener.cc b/src/shrpx_quic_listener.cc
new file mode 100644
index 0000000..9b9f120
--- /dev/null
+++ b/src/shrpx_quic_listener.cc
@@ -0,0 +1,132 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_quic_listener.h"
+#include "shrpx_worker.h"
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revent) {
+ auto l = static_cast<QUICListener *>(w->data);
+ l->on_read();
+}
+} // namespace
+
+QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker)
+ : faddr_{faddr}, worker_{worker} {
+ ev_io_init(&rev_, readcb, faddr_->fd, EV_READ);
+ rev_.data = this;
+ ev_io_start(worker_->get_loop(), &rev_);
+}
+
+QUICListener::~QUICListener() {
+ ev_io_stop(worker_->get_loop(), &rev_);
+ close(faddr_->fd);
+}
+
+void QUICListener::on_read() {
+ sockaddr_union su;
+ std::array<uint8_t, 64_k> buf;
+ size_t pktcnt = 0;
+ iovec msg_iov{buf.data(), buf.size()};
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo)) +
+ CMSG_SPACE(sizeof(uint16_t))];
+ msg.msg_control = msg_ctrl;
+
+ auto quic_conn_handler = worker_->get_quic_connection_handler();
+
+ for (; pktcnt < 10;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(faddr_->fd, &msg, 0);
+ if (nread == -1) {
+ return;
+ }
+
+ Address local_addr{};
+ if (util::msghdr_get_local_addr(local_addr, &msg, su.storage.ss_family) !=
+ 0) {
+ ++pktcnt;
+
+ continue;
+ }
+
+ util::set_port(local_addr, faddr_->port);
+
+ ngtcp2_pkt_info pi{
+ .ecn = util::msghdr_get_ecn(&msg, su.storage.ss_family),
+ };
+
+ auto gso_size = util::msghdr_get_udp_gro(&msg);
+ if (gso_size == 0) {
+ gso_size = static_cast<size_t>(nread);
+ }
+
+ auto data = buf.data();
+
+ for (;;) {
+ auto datalen = std::min(static_cast<size_t>(nread), gso_size);
+
+ ++pktcnt;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "QUIC received packet: local="
+ << util::to_numeric_addr(&local_addr) << " remote="
+ << util::to_numeric_addr(&su.sa, msg.msg_namelen)
+ << " ecn=" << log::hex << pi.ecn << log::dec << " " << datalen
+ << " bytes";
+ }
+
+ if (datalen == 0) {
+ break;
+ }
+
+ Address remote_addr;
+ remote_addr.su = su;
+ remote_addr.len = msg.msg_namelen;
+
+ quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, pi,
+ data, datalen);
+
+ nread -= datalen;
+ if (nread == 0) {
+ break;
+ }
+
+ data += datalen;
+ }
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_quic_listener.h b/src/shrpx_quic_listener.h
new file mode 100644
index 0000000..3d70921
--- /dev/null
+++ b/src/shrpx_quic_listener.h
@@ -0,0 +1,51 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2021 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_QUIC_LISTENER_H
+#define SHRPX_QUIC_LISTENER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+struct UpstreamAddr;
+class Worker;
+
+class QUICListener {
+public:
+ QUICListener(const UpstreamAddr *faddr, Worker *worker);
+ ~QUICListener();
+ void on_read();
+
+private:
+ const UpstreamAddr *faddr_;
+ Worker *worker_;
+ ev_io rev_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_QUIC_LISTENER_H
diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc
new file mode 100644
index 0000000..0d4f921
--- /dev/null
+++ b/src/shrpx_rate_limit.cc
@@ -0,0 +1,123 @@
+/*
+ * 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>
+
+#include "shrpx_connection.h"
+#include "shrpx_log.h"
+
+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,
+ Connection *conn)
+ : w_(w),
+ loop_(loop),
+ conn_(conn),
+ 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 (w_->fd >= 0 && avail_ > 0 && startw_req_) {
+ ev_io_start(loop_, w_);
+ handle_tls_pending_read();
+ }
+}
+
+void RateLimit::startw() {
+ if (w_->fd < 0) {
+ return;
+ }
+ startw_req_ = true;
+ if (rate_ == 0 || avail_ > 0) {
+ ev_io_start(loop_, w_);
+ handle_tls_pending_read();
+ return;
+ }
+}
+
+void RateLimit::stopw() {
+ startw_req_ = false;
+ ev_io_stop(loop_, w_);
+}
+
+void RateLimit::handle_tls_pending_read() {
+ if (!conn_ || !conn_->tls.ssl ||
+ (SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0 &&
+ (!conn_->tls.initial_handshake_done ||
+ conn_->tls.earlybuf.rleft() == 0))) {
+ return;
+ }
+
+ // Note that ev_feed_event works without starting watcher, but we
+ // only call this function if watcher is active.
+ ev_feed_event(loop_, w_, EV_READ);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h
new file mode 100644
index 0000000..7502a27
--- /dev/null
+++ b/src/shrpx_rate_limit.h
@@ -0,0 +1,68 @@
+/*
+ * 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>
+
+#include <openssl/ssl.h>
+
+namespace shrpx {
+
+struct Connection;
+
+class RateLimit {
+public:
+ // We need |conn| object to check that it has unread bytes for TLS
+ // connection.
+ RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
+ Connection *conn = nullptr);
+ ~RateLimit();
+ size_t avail() const;
+ void drain(size_t n);
+ void regen();
+ void startw();
+ void stopw();
+ // Feeds event if conn_->tls object has unread bytes. This is
+ // required since it is buffered in conn_->tls object, io event is
+ // not generated unless new incoming data is received.
+ void handle_tls_pending_read();
+
+private:
+ ev_timer t_;
+ ev_io *w_;
+ struct ev_loop *loop_;
+ Connection *conn_;
+ size_t rate_;
+ size_t burst_;
+ size_t avail_;
+ bool startw_req_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_RATE_LIMIT_H
diff --git a/src/shrpx_router.cc b/src/shrpx_router.cc
new file mode 100644
index 0000000..d3565db
--- /dev/null
+++ b/src/shrpx_router.cc
@@ -0,0 +1,420 @@
+/*
+ * 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_router.h"
+
+#include <algorithm>
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+RNode::RNode() : s(nullptr), len(0), index(-1), wildcard_index(-1) {}
+
+RNode::RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index)
+ : s(s), len(len), index(index), wildcard_index(wildcard_index) {}
+
+Router::Router() : balloc_(1024, 1024), root_{} {}
+
+Router::~Router() {}
+
+namespace {
+RNode *find_next_node(const RNode *node, char c) {
+ auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), c,
+ [](const std::unique_ptr<RNode> &lhs,
+ const char c) { return lhs->s[0] < c; });
+ if (itr == std::end(node->next) || (*itr)->s[0] != c) {
+ return nullptr;
+ }
+
+ return (*itr).get();
+}
+} // namespace
+
+namespace {
+void add_next_node(RNode *node, std::unique_ptr<RNode> new_node) {
+ auto itr = std::lower_bound(std::begin(node->next), std::end(node->next),
+ new_node->s[0],
+ [](const std::unique_ptr<RNode> &lhs,
+ const char c) { return lhs->s[0] < c; });
+ node->next.insert(itr, std::move(new_node));
+}
+} // namespace
+
+void Router::add_node(RNode *node, const char *pattern, size_t patlen,
+ ssize_t index, ssize_t wildcard_index) {
+ auto pat = make_string_ref(balloc_, StringRef{pattern, patlen});
+ auto new_node =
+ std::make_unique<RNode>(pat.c_str(), pat.size(), index, wildcard_index);
+ add_next_node(node, std::move(new_node));
+}
+
+size_t Router::add_route(const StringRef &pattern, size_t idx, bool wildcard) {
+ ssize_t index = -1, wildcard_index = -1;
+ if (wildcard) {
+ wildcard_index = idx;
+ } else {
+ index = idx;
+ }
+
+ auto node = &root_;
+ size_t i = 0;
+
+ for (;;) {
+ auto next_node = find_next_node(node, pattern[i]);
+ if (next_node == nullptr) {
+ add_node(node, pattern.c_str() + i, pattern.size() - i, index,
+ wildcard_index);
+ return idx;
+ }
+
+ node = next_node;
+
+ auto slen = pattern.size() - i;
+ auto s = pattern.c_str() + i;
+ auto n = std::min(node->len, slen);
+ size_t j;
+ for (j = 0; j < n && node->s[j] == s[j]; ++j)
+ ;
+ if (j == n) {
+ // The common prefix was matched
+ if (slen == node->len) {
+ // Complete match
+ if (index != -1) {
+ if (node->index != -1) {
+ // Return the existing index for duplicates.
+ return node->index;
+ }
+ node->index = index;
+ return idx;
+ }
+
+ assert(wildcard_index != -1);
+
+ if (node->wildcard_index != -1) {
+ return node->wildcard_index;
+ }
+ node->wildcard_index = wildcard_index;
+ return idx;
+ }
+
+ if (slen > node->len) {
+ // We still have pattern to add
+ i += j;
+
+ continue;
+ }
+ }
+
+ if (node->len > j) {
+ // node must be split into 2 nodes. new_node is now the child
+ // of node.
+ auto new_node = std::make_unique<RNode>(
+ &node->s[j], node->len - j, node->index, node->wildcard_index);
+ std::swap(node->next, new_node->next);
+
+ node->len = j;
+ node->index = -1;
+ node->wildcard_index = -1;
+
+ add_next_node(node, std::move(new_node));
+
+ if (slen == j) {
+ node->index = index;
+ node->wildcard_index = wildcard_index;
+ return idx;
+ }
+ }
+
+ i += j;
+
+ assert(pattern.size() > i);
+ add_node(node, pattern.c_str() + i, pattern.size() - i, index,
+ wildcard_index);
+
+ return idx;
+ }
+}
+
+namespace {
+const RNode *match_complete(size_t *offset, const RNode *node,
+ const char *first, const char *last) {
+ *offset = 0;
+
+ if (first == last) {
+ return node;
+ }
+
+ auto p = first;
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return nullptr;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return nullptr;
+ }
+ p += n;
+ if (p == last) {
+ *offset = n;
+ return node;
+ }
+ }
+}
+} // namespace
+
+namespace {
+const RNode *match_partial(bool *pattern_is_wildcard, const RNode *node,
+ size_t offset, const char *first, const char *last) {
+ *pattern_is_wildcard = false;
+
+ if (first == last) {
+ if (node->len == offset) {
+ return node;
+ }
+ return nullptr;
+ }
+
+ auto p = first;
+
+ const RNode *found_node = nullptr;
+
+ if (offset > 0) {
+ auto n = std::min(node->len - offset, static_cast<size_t>(last - first));
+ if (memcmp(node->s + offset, first, n) != 0) {
+ return nullptr;
+ }
+
+ p += n;
+
+ if (p == last) {
+ if (node->len == offset + n) {
+ if (node->index != -1) {
+ return node;
+ }
+
+ // The last '/' handling, see below.
+ node = find_next_node(node, '/');
+ if (node != nullptr && node->index != -1 && node->len == 1) {
+ return node;
+ }
+
+ return nullptr;
+ }
+
+ // The last '/' handling, see below.
+ if (node->index != -1 && offset + n + 1 == node->len &&
+ node->s[node->len - 1] == '/') {
+ return node;
+ }
+
+ return nullptr;
+ }
+
+ if (node->wildcard_index != -1) {
+ found_node = node;
+ *pattern_is_wildcard = true;
+ } else if (node->index != -1 && node->s[node->len - 1] == '/') {
+ found_node = node;
+ *pattern_is_wildcard = false;
+ }
+
+ assert(node->len == offset + n);
+ }
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return found_node;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return found_node;
+ }
+
+ p += n;
+
+ if (p == last) {
+ if (node->len == n) {
+ // Complete match with this node
+ if (node->index != -1) {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ // The last '/' handling, see below.
+ node = find_next_node(node, '/');
+ if (node != nullptr && node->index != -1 && node->len == 1) {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ return found_node;
+ }
+
+ // We allow match without trailing "/" at the end of pattern.
+ // So, if pattern ends with '/', and pattern and path matches
+ // without that slash, we consider they match to deal with
+ // request to the directory without trailing slash. That is if
+ // pattern is "/foo/" and path is "/foo", we consider they
+ // match.
+ if (node->index != -1 && n + 1 == node->len && node->s[n] == '/') {
+ *pattern_is_wildcard = false;
+ return node;
+ }
+
+ return found_node;
+ }
+
+ if (node->wildcard_index != -1) {
+ found_node = node;
+ *pattern_is_wildcard = true;
+ } else if (node->index != -1 && node->s[node->len - 1] == '/') {
+ // This is the case when pattern which ends with "/" is included
+ // in query.
+ found_node = node;
+ *pattern_is_wildcard = false;
+ }
+
+ assert(node->len == n);
+ }
+}
+} // namespace
+
+ssize_t Router::match(const StringRef &host, const StringRef &path) const {
+ const RNode *node;
+ size_t offset;
+
+ node = match_complete(&offset, &root_, std::begin(host), std::end(host));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ bool pattern_is_wildcard;
+ node = match_partial(&pattern_is_wildcard, node, offset, std::begin(path),
+ std::end(path));
+ if (node == nullptr || node == &root_) {
+ return -1;
+ }
+
+ return pattern_is_wildcard ? node->wildcard_index : node->index;
+}
+
+ssize_t Router::match(const StringRef &s) const {
+ const RNode *node;
+ size_t offset;
+
+ node = match_complete(&offset, &root_, std::begin(s), std::end(s));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ if (node->len != offset) {
+ return -1;
+ }
+
+ return node->index;
+}
+
+namespace {
+const RNode *match_prefix(size_t *nread, const RNode *node, const char *first,
+ const char *last) {
+ if (first == last) {
+ return nullptr;
+ }
+
+ auto p = first;
+
+ for (;;) {
+ auto next_node = find_next_node(node, *p);
+ if (next_node == nullptr) {
+ return nullptr;
+ }
+
+ node = next_node;
+
+ auto n = std::min(node->len, static_cast<size_t>(last - p));
+ if (memcmp(node->s, p, n) != 0) {
+ return nullptr;
+ }
+
+ p += n;
+
+ if (p != last) {
+ if (node->index != -1) {
+ *nread = p - first;
+ return node;
+ }
+ continue;
+ }
+
+ if (node->len == n) {
+ *nread = p - first;
+ return node;
+ }
+
+ return nullptr;
+ }
+}
+} // namespace
+
+ssize_t Router::match_prefix(size_t *nread, const RNode **last_node,
+ const StringRef &s) const {
+ if (*last_node == nullptr) {
+ *last_node = &root_;
+ }
+
+ auto node =
+ ::shrpx::match_prefix(nread, *last_node, std::begin(s), std::end(s));
+ if (node == nullptr) {
+ return -1;
+ }
+
+ *last_node = node;
+
+ return node->index;
+}
+
+namespace {
+void dump_node(const RNode *node, int depth) {
+ fprintf(stderr, "%*ss='%.*s', len=%zu, index=%zd\n", depth, "",
+ (int)node->len, node->s, node->len, node->index);
+ for (auto &nd : node->next) {
+ dump_node(nd.get(), depth + 4);
+ }
+}
+} // namespace
+
+void Router::dump() const { dump_node(&root_, 0); }
+
+} // namespace shrpx
diff --git a/src/shrpx_router.h b/src/shrpx_router.h
new file mode 100644
index 0000000..295db7e
--- /dev/null
+++ b/src/shrpx_router.h
@@ -0,0 +1,110 @@
+/*
+ * 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_ROUTER_H
+#define SHRPX_ROUTER_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <memory>
+
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+struct RNode {
+ RNode();
+ RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index);
+ RNode(RNode &&) = default;
+ RNode(const RNode &) = delete;
+ RNode &operator=(RNode &&) = default;
+ RNode &operator=(const RNode &) = delete;
+
+ // Next RNode, sorted by s[0].
+ std::vector<std::unique_ptr<RNode>> next;
+ // Stores pointer to the string this node represents. Not
+ // NULL-terminated.
+ const char *s;
+ // Length of |s|
+ size_t len;
+ // Index of pattern if match ends in this node. Note that we don't
+ // store duplicated pattern.
+ ssize_t index;
+ // Index of wildcard pattern if query includes this node as prefix
+ // and it still has suffix to match. Note that we don't store
+ // duplicated pattern.
+ ssize_t wildcard_index;
+};
+
+class Router {
+public:
+ Router();
+ ~Router();
+ Router(Router &&) = default;
+ Router(const Router &) = delete;
+ Router &operator=(Router &&) = default;
+ Router &operator=(const Router &) = delete;
+
+ // Adds route |pattern| with its |index|. If same pattern has
+ // already been added, the existing index is returned. If
+ // |wildcard| is true, |pattern| is considered as wildcard pattern,
+ // and all paths which have the |pattern| as prefix and are strictly
+ // longer than |pattern| match. The wildcard pattern only works
+ // with match(const StringRef&, const StringRef&).
+ size_t add_route(const StringRef &pattern, size_t index,
+ bool wildcard = false);
+ // Returns the matched index of pattern. -1 if there is no match.
+ ssize_t match(const StringRef &host, const StringRef &path) const;
+ // Returns the matched index of pattern |s|. -1 if there is no
+ // match.
+ ssize_t match(const StringRef &s) const;
+ // Returns the matched index of pattern if a pattern is a suffix of
+ // |s|, otherwise -1. If |*last_node| is not nullptr, it specifies
+ // the first node to start matching. If it is nullptr, match will
+ // start from scratch. When the match was found (the return value
+ // is not -1), |*nread| has the number of bytes matched in |s|, and
+ // |*last_node| has the last matched node. One can continue to
+ // match the longer pattern using the returned |*last_node| to the
+ // another invocation of this function until it returns -1.
+ ssize_t match_prefix(size_t *nread, const RNode **last_node,
+ const StringRef &s) const;
+
+ void add_node(RNode *node, const char *pattern, size_t patlen, ssize_t index,
+ ssize_t wildcard_index);
+
+ void dump() const;
+
+private:
+ BlockAllocator balloc_;
+ // The root node of Patricia tree. This is special node and its s
+ // field is nulptr, and len field is 0.
+ RNode root_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ROUTER_H
diff --git a/src/shrpx_router_test.cc b/src/shrpx_router_test.cc
new file mode 100644
index 0000000..21c2f51
--- /dev/null
+++ b/src/shrpx_router_test.cc
@@ -0,0 +1,184 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_router_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_router.h"
+
+namespace shrpx {
+
+struct Pattern {
+ StringRef pattern;
+ size_t idx;
+ bool wildcard;
+};
+
+void test_shrpx_router_match(void) {
+ auto patterns = std::vector<Pattern>{
+ {StringRef::from_lit("nghttp2.org/"), 0},
+ {StringRef::from_lit("nghttp2.org/alpha"), 1},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 2},
+ {StringRef::from_lit("nghttp2.org/alpha/bravo/"), 3},
+ {StringRef::from_lit("www.nghttp2.org/alpha/"), 4},
+ {StringRef::from_lit("/alpha"), 5},
+ {StringRef::from_lit("example.com/alpha/"), 6},
+ {StringRef::from_lit("nghttp2.org/alpha/bravo2/"), 7},
+ {StringRef::from_lit("www2.nghttp2.org/alpha/"), 8},
+ {StringRef::from_lit("www2.nghttp2.org/alpha2/"), 9},
+ };
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx);
+ }
+
+ ssize_t idx;
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/"));
+
+ CU_ASSERT(0 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(1 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/"));
+
+ CU_ASSERT(2 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie"));
+
+ CU_ASSERT(2 == idx);
+
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/"));
+
+ CU_ASSERT(3 == idx);
+
+ // matches pattern when last '/' is missing in path
+ idx = router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo"));
+
+ CU_ASSERT(3 == idx);
+
+ idx = router.match(StringRef::from_lit("www2.nghttp2.org"),
+ StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(8 == idx);
+
+ idx = router.match(StringRef{}, StringRef::from_lit("/alpha"));
+
+ CU_ASSERT(5 == idx);
+}
+
+void test_shrpx_router_match_wildcard(void) {
+ constexpr auto patterns = std::array<Pattern, 6>{{
+ {StringRef::from_lit("nghttp2.org/"), 0},
+ {StringRef::from_lit("nghttp2.org/"), 1, true},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 2},
+ {StringRef::from_lit("nghttp2.org/alpha/"), 3, true},
+ {StringRef::from_lit("nghttp2.org/bravo"), 4},
+ {StringRef::from_lit("nghttp2.org/bravo"), 5, true},
+ }};
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx, p.wildcard);
+ }
+
+ CU_ASSERT(0 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/")));
+
+ CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/a")));
+
+ CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/charlie")));
+
+ CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha")));
+
+ CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/")));
+
+ CU_ASSERT(3 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/b")));
+
+ CU_ASSERT(4 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravo")));
+
+ CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravocharlie")));
+
+ CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/bravo/")));
+}
+
+void test_shrpx_router_match_prefix(void) {
+ auto patterns = std::vector<Pattern>{
+ {StringRef::from_lit("gro.2ptthgn."), 0},
+ {StringRef::from_lit("gro.2ptthgn.www."), 1},
+ {StringRef::from_lit("gro.2ptthgn.gmi."), 2},
+ {StringRef::from_lit("gro.2ptthgn.gmi.ahpla."), 3},
+ };
+
+ Router router;
+
+ for (auto &p : patterns) {
+ router.add_route(p.pattern, p.idx);
+ }
+
+ ssize_t idx;
+ const RNode *node;
+ size_t nread;
+
+ node = nullptr;
+
+ idx = router.match_prefix(&nread, &node,
+ StringRef::from_lit("gro.2ptthgn.gmi.ahpla.ovarb"));
+
+ CU_ASSERT(0 == idx);
+ CU_ASSERT(12 == nread);
+
+ idx = router.match_prefix(&nread, &node,
+ StringRef::from_lit("gmi.ahpla.ovarb"));
+
+ CU_ASSERT(2 == idx);
+ CU_ASSERT(4 == nread);
+
+ idx = router.match_prefix(&nread, &node, StringRef::from_lit("ahpla.ovarb"));
+
+ CU_ASSERT(3 == idx);
+ CU_ASSERT(6 == nread);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_router_test.h b/src/shrpx_router_test.h
new file mode 100644
index 0000000..d39cb87
--- /dev/null
+++ b/src/shrpx_router_test.h
@@ -0,0 +1,40 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_ROUTER_TEST_H
+#define SHRPX_ROUTER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_router_match(void);
+void test_shrpx_router_match_wildcard(void);
+void test_shrpx_router_match_prefix(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_ROUTER_TEST_H
diff --git a/src/shrpx_signal.cc b/src/shrpx_signal.cc
new file mode 100644
index 0000000..63fcc07
--- /dev/null
+++ b/src/shrpx_signal.cc
@@ -0,0 +1,138 @@
+/*
+ * 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_signal.h"
+
+#include <cerrno>
+
+#include "shrpx_log.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+int shrpx_signal_block_all(sigset_t *oldset) {
+ sigset_t newset;
+
+ sigfillset(&newset);
+
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, &newset, oldset);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, &newset, oldset);
+#endif // NOTHREADS
+}
+
+int shrpx_signal_unblock_all() {
+ sigset_t newset;
+
+ sigemptyset(&newset);
+
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, &newset, nullptr);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, &newset, nullptr);
+#endif // NOTHREADS
+}
+
+int shrpx_signal_set(sigset_t *set) {
+#ifndef NOTHREADS
+ int rv;
+
+ rv = pthread_sigmask(SIG_SETMASK, set, nullptr);
+
+ if (rv != 0) {
+ errno = rv;
+ return -1;
+ }
+
+ return 0;
+#else // NOTHREADS
+ return sigprocmask(SIG_SETMASK, set, nullptr);
+#endif // NOTHREADS
+}
+
+namespace {
+template <typename Signals>
+int signal_set_handler(void (*handler)(int), Signals &&sigs) {
+ struct sigaction act {};
+ act.sa_handler = handler;
+ sigemptyset(&act.sa_mask);
+ int rv;
+ for (auto sig : sigs) {
+ rv = sigaction(sig, &act, nullptr);
+ if (rv != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+constexpr auto main_proc_ign_signals = std::array<int, 1>{SIGPIPE};
+} // namespace
+
+namespace {
+constexpr auto worker_proc_ign_signals =
+ std::array<int, 5>{REOPEN_LOG_SIGNAL, EXEC_BINARY_SIGNAL,
+ GRACEFUL_SHUTDOWN_SIGNAL, RELOAD_SIGNAL, SIGPIPE};
+} // namespace
+
+int shrpx_signal_set_main_proc_ign_handler() {
+ return signal_set_handler(SIG_IGN, main_proc_ign_signals);
+}
+
+int shrpx_signal_unset_main_proc_ign_handler() {
+ return signal_set_handler(SIG_DFL, main_proc_ign_signals);
+}
+
+int shrpx_signal_set_worker_proc_ign_handler() {
+ return signal_set_handler(SIG_IGN, worker_proc_ign_signals);
+}
+
+int shrpx_signal_unset_worker_proc_ign_handler() {
+ return signal_set_handler(SIG_DFL, worker_proc_ign_signals);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_signal.h b/src/shrpx_signal.h
new file mode 100644
index 0000000..152ca36
--- /dev/null
+++ b/src/shrpx_signal.h
@@ -0,0 +1,60 @@
+/*
+ * 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_SIGNAL_H
+#define SHRPX_SIGNAL_H
+
+#include "shrpx.h"
+
+#include <signal.h>
+
+namespace shrpx {
+
+constexpr int REOPEN_LOG_SIGNAL = SIGUSR1;
+constexpr int EXEC_BINARY_SIGNAL = SIGUSR2;
+constexpr int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
+constexpr int RELOAD_SIGNAL = SIGHUP;
+
+// Blocks all signals. The previous signal mask is stored into
+// |oldset| if it is not nullptr. This function returns 0 if it
+// succeeds, or -1. The errno will indicate the error.
+int shrpx_signal_block_all(sigset_t *oldset);
+
+// Unblocks all signals. This function returns 0 if it succeeds, or
+// -1. The errno will indicate the error.
+int shrpx_signal_unblock_all();
+
+// Sets signal mask |set|. This function returns 0 if it succeeds, or
+// -1. The errno will indicate the error.
+int shrpx_signal_set(sigset_t *set);
+
+int shrpx_signal_set_main_proc_ign_handler();
+int shrpx_signal_unset_main_proc_ign_handler();
+
+int shrpx_signal_set_worker_proc_ign_handler();
+int shrpx_signal_unset_worker_proc_ign_handler();
+
+} // namespace shrpx
+
+#endif // SHRPX_SIGNAL_H
diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc
new file mode 100644
index 0000000..aa0c9f2
--- /dev/null
+++ b/src/shrpx_tls.cc
@@ -0,0 +1,2465 @@
+/*
+ * 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_tls.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <netinet/tcp.h>
+#include <pthread.h>
+#include <sys/types.h>
+
+#include <vector>
+#include <string>
+#include <iomanip>
+
+#include <iostream>
+
+#include "ssl_compat.h"
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/dh.h>
+#ifndef OPENSSL_NO_OCSP
+# include <openssl/ocsp.h>
+#endif // OPENSSL_NO_OCSP
+#if OPENSSL_3_0_0_API
+# include <openssl/params.h>
+# include <openssl/core_names.h>
+# include <openssl/decoder.h>
+#endif // OPENSSL_3_0_0_API
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+# include <openssl/hmac.h>
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef ENABLE_HTTP3
+# include <ngtcp2/ngtcp2.h>
+# include <ngtcp2/ngtcp2_crypto.h>
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# include <ngtcp2/ngtcp2_crypto_quictls.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+# include <ngtcp2/ngtcp2_crypto_boringssl.h>
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+#endif // ENABLE_HTTP3
+
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_memcached_dispatcher.h"
+#include "shrpx_connection_handler.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_http3_upstream.h"
+#endif // ENABLE_HTTP3
+#include "util.h"
+#include "tls.h"
+#include "template.h"
+#include "timegm.h"
+
+using namespace nghttp2;
+using namespace std::chrono_literals;
+
+namespace shrpx {
+
+namespace tls {
+
+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);
+ if (err == X509_V_ERR_CERT_HAS_EXPIRED && depth == 0 &&
+ get_config()->tls.client_verify.tolerate_expired) {
+ LOG(INFO) << "The client certificate has expired, but is accepted by "
+ "configuration";
+ return 1;
+ }
+ LOG(ERROR) << "client certificate verify error:num=" << err << ":"
+ << X509_verify_cert_error_string(err) << ":depth=" << depth;
+ }
+ return preverify_ok;
+}
+} // namespace
+
+int set_alpn_prefs(std::vector<unsigned char> &out,
+ const std::vector<StringRef> &protos) {
+ size_t len = 0;
+
+ for (const auto &proto : protos) {
+ if (proto.size() > 255) {
+ LOG(FATAL) << "Too long ALPN identifier: " << proto.size();
+ return -1;
+ }
+
+ len += 1 + proto.size();
+ }
+
+ if (len > (1 << 16) - 1) {
+ LOG(FATAL) << "Too long ALPN identifier list: " << len;
+ return -1;
+ }
+
+ out.resize(len);
+ auto ptr = out.data();
+
+ for (const auto &proto : protos) {
+ *ptr++ = proto.size();
+ ptr = std::copy(std::begin(proto), std::end(proto), ptr);
+ }
+
+ return 0;
+}
+
+namespace {
+int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
+ auto config = static_cast<Config *>(user_data);
+ auto len = static_cast<int>(config->tls.private_key_passwd.size());
+ 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->tls.private_key_passwd.c_str(), len + 1);
+ return len;
+}
+} // namespace
+
+namespace {
+// *al is set to SSL_AD_UNRECOGNIZED_NAME by openssl, so we don't have
+// to set it explicitly.
+int servername_callback(SSL *ssl, int *al, void *arg) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+
+ auto rawhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (rawhost == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ auto len = strlen(rawhost);
+ // NI_MAXHOST includes terminal NULL.
+ if (len == 0 || len + 1 > NI_MAXHOST) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ auto end_buf = std::copy_n(rawhost, len, std::begin(buf));
+
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto hostname = StringRef{std::begin(buf), end_buf};
+
+#ifdef ENABLE_HTTP3
+ auto cert_tree = conn->proto == Proto::HTTP3
+ ? worker->get_quic_cert_lookup_tree()
+ : worker->get_cert_lookup_tree();
+#else // !ENABLE_HTTP3
+ auto cert_tree = worker->get_cert_lookup_tree();
+#endif // !ENABLE_HTTP3
+
+ auto idx = cert_tree->lookup(hostname);
+ if (idx == -1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ handler->set_tls_sni(hostname);
+
+ auto conn_handler = worker->get_connection_handler();
+
+#ifdef ENABLE_HTTP3
+ const auto &ssl_ctx_list = conn->proto == Proto::HTTP3
+ ? conn_handler->get_quic_indexed_ssl_ctx(idx)
+ : conn_handler->get_indexed_ssl_ctx(idx);
+#else // !ENABLE_HTTP3
+ const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx);
+#endif // !ENABLE_HTTP3
+
+ assert(!ssl_ctx_list.empty());
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ auto num_sigalgs =
+ SSL_get_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr);
+
+ for (idx = 0; idx < num_sigalgs; ++idx) {
+ int signhash;
+
+ SSL_get_sigalgs(ssl, idx, nullptr, nullptr, &signhash, nullptr, nullptr);
+ switch (signhash) {
+ case NID_ecdsa_with_SHA256:
+ case NID_ecdsa_with_SHA384:
+ case NID_ecdsa_with_SHA512:
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+
+ if (idx == num_sigalgs) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]);
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ auto num_shared_curves = SSL_get_shared_curve(ssl, -1);
+
+ for (auto i = 0; i < num_shared_curves; ++i) {
+ auto shared_curve = SSL_get_shared_curve(ssl, i);
+# if OPENSSL_3_0_0_API
+ // It looks like only short name is defined in OpenSSL. No idea
+ // which one to use because it is unknown that which one
+ // EVP_PKEY_get_utf8_string_param("group") returns.
+ auto shared_curve_name = OBJ_nid2sn(shared_curve);
+ if (shared_curve_name == nullptr) {
+ continue;
+ }
+# endif // OPENSSL_3_0_0_API
+
+ for (auto ssl_ctx : ssl_ctx_list) {
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto pubkey = X509_get0_pubkey(cert);
+
+ if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) {
+ continue;
+ }
+
+# if OPENSSL_3_0_0_API
+ std::array<char, 64> curve_name;
+ if (!EVP_PKEY_get_utf8_string_param(pubkey, "group", curve_name.data(),
+ curve_name.size(), nullptr)) {
+ continue;
+ }
+
+ if (strcmp(shared_curve_name, curve_name.data()) == 0) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx);
+ return SSL_TLSEXT_ERR_OK;
+ }
+# else // !OPENSSL_3_0_0_API
+ auto eckey = EVP_PKEY_get0_EC_KEY(pubkey);
+ if (eckey == nullptr) {
+ continue;
+ }
+
+ auto ecgroup = EC_KEY_get0_group(eckey);
+ auto cert_curve = EC_GROUP_get_curve_name(ecgroup);
+
+ if (shared_curve == cert_curve) {
+ SSL_set_SSL_CTX(ssl, ssl_ctx);
+ return SSL_TLSEXT_ERR_OK;
+ }
+# endif // !OPENSSL_3_0_0_API
+ }
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+ SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]);
+
+ return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+namespace {
+std::shared_ptr<std::vector<uint8_t>>
+get_ocsp_data(TLSContextData *tls_ctx_data) {
+# ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ return std::atomic_load_explicit(&tls_ctx_data->ocsp_data,
+ std::memory_order_acquire);
+# else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(tls_ctx_data->mu);
+ return tls_ctx_data->ocsp_data;
+# endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+} // namespace
+
+namespace {
+int ocsp_resp_cb(SSL *ssl, void *arg) {
+ auto ssl_ctx = SSL_get_SSL_CTX(ssl);
+ auto tls_ctx_data =
+ static_cast<TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ auto data = get_ocsp_data(tls_ctx_data);
+
+ if (!data) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ auto buf = static_cast<uint8_t *>(
+ CRYPTO_malloc(data->size(), NGHTTP2_FILE_NAME, __LINE__));
+
+ if (!buf) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ std::copy(std::begin(*data), std::end(*data), buf);
+
+ SSL_set_tlsext_status_ocsp_resp(ssl, buf, data->size());
+
+ return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+constexpr auto MEMCACHED_SESSION_CACHE_KEY_PREFIX =
+ StringRef::from_lit("nghttpx:tls-session-cache:");
+
+namespace {
+int tls_session_client_new_cb(SSL *ssl, SSL_SESSION *session) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ if (conn->tls.client_session_cache == nullptr) {
+ return 0;
+ }
+
+ try_cache_tls_session(conn->tls.client_session_cache, session,
+ std::chrono::steady_clock::now());
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto dispatcher = worker->get_session_cache_memcached_dispatcher();
+ auto &balloc = handler->get_block_allocator();
+
+#ifdef TLS1_3_VERSION
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return 0;
+ }
+#endif // TLS1_3_VERSION
+
+ const unsigned char *id;
+ unsigned int idlen;
+
+ id = SSL_SESSION_get_id(session, &idlen);
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: cache session, id=" << util::format_hex(id, idlen);
+ }
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->op = MemcachedOp::ADD;
+ req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str();
+ req->key +=
+ util::format_hex(balloc, StringRef{id, static_cast<size_t>(idlen)});
+
+ auto sessionlen = i2d_SSL_SESSION(session, nullptr);
+ req->value.resize(sessionlen);
+ auto buf = &req->value[0];
+ i2d_SSL_SESSION(session, &buf);
+ req->expiry = 12_h;
+ req->cb = [](MemcachedRequest *req, MemcachedResult res) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: session cache done. key=" << req->key
+ << ", status_code=" << static_cast<uint16_t>(res.status_code)
+ << ", value="
+ << std::string(std::begin(res.value), std::end(res.value));
+ }
+ if (res.status_code != MemcachedStatusCode::NO_ERROR) {
+ LOG(WARN) << "Memcached: failed to cache session key=" << req->key
+ << ", status_code=" << static_cast<uint16_t>(res.status_code)
+ << ", value="
+ << std::string(std::begin(res.value), std::end(res.value));
+ }
+ };
+ assert(!req->canceled);
+
+ dispatcher->add_request(std::move(req));
+
+ return 0;
+}
+} // namespace
+
+namespace {
+SSL_SESSION *tls_session_get_cb(SSL *ssl, const unsigned char *id, int idlen,
+ int *copy) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto dispatcher = worker->get_session_cache_memcached_dispatcher();
+ auto &balloc = handler->get_block_allocator();
+
+ if (idlen == 0) {
+ return nullptr;
+ }
+
+ if (conn->tls.cached_session) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: found cached session, id="
+ << util::format_hex(id, idlen);
+ }
+
+ // This is required, without this, memory leak occurs.
+ *copy = 0;
+
+ auto session = conn->tls.cached_session;
+ conn->tls.cached_session = nullptr;
+ return session;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: get cached session, id="
+ << util::format_hex(id, idlen);
+ }
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->op = MemcachedOp::GET;
+ req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str();
+ req->key +=
+ util::format_hex(balloc, StringRef{id, static_cast<size_t>(idlen)});
+ req->cb = [conn](MemcachedRequest *, MemcachedResult res) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: returned status code "
+ << static_cast<uint16_t>(res.status_code);
+ }
+
+ // We might stop reading, so start it again
+ conn->rlimit.startw();
+ ev_timer_again(conn->loop, &conn->rt);
+
+ conn->wlimit.startw();
+ ev_timer_again(conn->loop, &conn->wt);
+
+ conn->tls.cached_session_lookup_req = nullptr;
+ if (res.status_code != MemcachedStatusCode::NO_ERROR) {
+ conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE;
+ return;
+ }
+
+ const uint8_t *p = res.value.data();
+
+ auto session = d2i_SSL_SESSION(nullptr, &p, res.value.size());
+ if (!session) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "cannot materialize session";
+ }
+ conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE;
+ return;
+ }
+
+ conn->tls.cached_session = session;
+ conn->tls.handshake_state = TLSHandshakeState::GOT_SESSION_CACHE;
+ };
+
+ conn->tls.handshake_state = TLSHandshakeState::WAIT_FOR_SESSION_CACHE;
+ conn->tls.cached_session_lookup_req = req.get();
+
+ dispatcher->add_request(std::move(req));
+
+ return nullptr;
+}
+} // namespace
+
+namespace {
+int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
+ EVP_CIPHER_CTX *ctx,
+#if OPENSSL_3_0_0_API
+ EVP_MAC_CTX *hctx,
+#else // !OPENSSL_3_0_0_API
+ HMAC_CTX *hctx,
+#endif // !OPENSSL_3_0_0_API
+ int enc) {
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ auto worker = handler->get_worker();
+ auto ticket_keys = worker->get_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.data.name);
+ }
+
+ std::copy(std::begin(key.data.name), std::end(key.data.name), key_name);
+
+ EVP_EncryptInit_ex(ctx, get_config()->tls.ticket.cipher, nullptr,
+ key.data.enc_key.data(), iv);
+#if OPENSSL_3_0_0_API
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST,
+ const_cast<char *>(EVP_MD_get0_name(key.hmac)), 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed";
+ }
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
+ nullptr);
+#endif // !OPENSSL_3_0_0_API
+ return 1;
+ }
+
+ size_t i;
+ for (i = 0; i < keys.size(); ++i) {
+ auto &key = keys[i];
+ if (std::equal(std::begin(key.data.name), std::end(key.data.name),
+ key_name)) {
+ 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];
+#if OPENSSL_3_0_0_API
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST, const_cast<char *>(EVP_MD_get0_name(key.hmac)),
+ 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed";
+ }
+ return -1;
+ }
+#else // !OPENSSL_3_0_0_API
+ HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
+ nullptr);
+#endif // !OPENSSL_3_0_0_API
+ EVP_DecryptInit_ex(ctx, key.cipher, nullptr, key.data.enc_key.data(), iv);
+
+#ifdef TLS1_3_VERSION
+ // If ticket_key_cb is not set, OpenSSL always renew ticket for
+ // TLSv1.3.
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return 2;
+ }
+#endif // TLS1_3_VERSION
+
+ return i == 0 ? 1 : 2;
+}
+} // namespace
+
+namespace {
+void info_callback(const SSL *ssl, int where, int ret) {
+#ifdef TLS1_3_VERSION
+ // TLSv1.3 has no renegotiation.
+ if (SSL_version(ssl) == TLS1_3_VERSION) {
+ return;
+ }
+#endif // TLS1_3_VERSION
+
+ // 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) {
+ auto handler = static_cast<ClientHandler *>(conn->data);
+ if (LOG_ENABLED(INFO)) {
+ CLOG(INFO, handler) << "TLS renegotiation started";
+ }
+ handler->start_immediate_shutdown();
+ }
+ }
+}
+} // namespace
+
+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()->alpn_list contains ALPN protocol
+ // identifier sorted by preference order. So we just break when we
+ // found the first overlap.
+ for (const auto &target_proto_id : get_config()->tls.alpn_list) {
+ for (auto p = in, end = in + inlen; p < end;) {
+ auto proto_id = p + 1;
+ auto proto_len = *p;
+
+ if (proto_id + proto_len <= end &&
+ util::streq(target_proto_id, StringRef{proto_id, proto_len})) {
+
+ *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
+
+#ifdef ENABLE_HTTP3
+namespace {
+int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ constexpr StringRef alpnlist[] = {
+ StringRef::from_lit("h3"),
+ StringRef::from_lit("h3-29"),
+ };
+
+ for (auto &alpn : alpnlist) {
+ for (auto p = in, end = in + inlen; p < end;) {
+ auto proto_id = p + 1;
+ auto proto_len = *p;
+
+ if (alpn.size() == proto_len &&
+ memcmp(alpn.byte(), proto_id, alpn.size()) == 0) {
+ *out = proto_id;
+ *outlen = proto_len;
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ p += 1 + proto_len;
+ }
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+namespace {
+int sct_add_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char **out, size_t *outlen, X509 *x,
+ size_t chainidx, int *al, void *add_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ if (!conn->tls.sct_requested) {
+ return 0;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "sct_add_cb is called, chainidx=" << chainidx << ", x=" << x
+ << ", context=" << log::hex << context;
+ }
+
+ // We only have SCTs for leaf certificate.
+ if (chainidx != 0) {
+ return 0;
+ }
+
+ auto ssl_ctx = SSL_get_SSL_CTX(ssl);
+ auto tls_ctx_data =
+ static_cast<TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
+
+ *out = tls_ctx_data->sct_data.data();
+ *outlen = tls_ctx_data->sct_data.size();
+
+ return 1;
+}
+} // namespace
+
+namespace {
+void sct_free_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char *out, void *add_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+}
+} // namespace
+
+namespace {
+int sct_parse_cb(SSL *ssl, unsigned int ext_type, unsigned int context,
+ const unsigned char *in, size_t inlen, X509 *x,
+ size_t chainidx, int *al, void *parse_arg) {
+ assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp);
+ // client SHOULD send 0 length extension_data, but it is still
+ // SHOULD, and not MUST.
+
+ // For TLSv1.3 Certificate message, sct_add_cb is called even if
+ // client has not sent signed_certificate_timestamp extension in its
+ // ClientHello. Explicitly remember that client has included it
+ // here.
+ auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+ conn->tls.sct_requested = true;
+
+ return 1;
+}
+} // namespace
+
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk,
+ unsigned int max_psk_len) {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ auto it = tlsconf.psk_secrets.find(StringRef{identity});
+ if (it == std::end(tlsconf.psk_secrets)) {
+ return 0;
+ }
+
+ auto &secret = (*it).second;
+ if (secret.size() > max_psk_len) {
+ LOG(ERROR) << "The size of PSK secret is " << secret.size()
+ << ", but the acceptable maximum size is" << max_psk_len;
+ return 0;
+ }
+
+ std::copy(std::begin(secret), std::end(secret), psk);
+
+ return static_cast<unsigned int>(secret.size());
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+#ifndef OPENSSL_NO_PSK
+namespace {
+unsigned int psk_client_cb(SSL *ssl, const char *hint, char *identity_out,
+ unsigned int max_identity_len, unsigned char *psk,
+ unsigned int max_psk_len) {
+ auto config = get_config();
+ auto &tlsconf = config->tls;
+
+ auto &identity = tlsconf.client.psk.identity;
+ auto &secret = tlsconf.client.psk.secret;
+
+ if (identity.empty()) {
+ return 0;
+ }
+
+ if (identity.size() + 1 > max_identity_len) {
+ LOG(ERROR) << "The size of PSK identity is " << identity.size()
+ << ", but the acceptable maximum size is " << max_identity_len;
+ return 0;
+ }
+
+ if (secret.size() > max_psk_len) {
+ LOG(ERROR) << "The size of PSK secret is " << secret.size()
+ << ", but the acceptable maximum size is " << max_psk_len;
+ return 0;
+ }
+
+ *std::copy(std::begin(identity), std::end(identity), identity_out) = '\0';
+ std::copy(std::begin(secret), std::end(secret), psk);
+
+ return static_cast<unsigned int>(secret.size());
+}
+} // namespace
+#endif // !OPENSSL_NO_PSK
+
+struct TLSProtocol {
+ StringRef name;
+ long int mask;
+};
+
+constexpr TLSProtocol TLS_PROTOS[] = {
+ TLSProtocol{StringRef::from_lit("TLSv1.2"), SSL_OP_NO_TLSv1_2},
+ TLSProtocol{StringRef::from_lit("TLSv1.1"), SSL_OP_NO_TLSv1_1},
+ TLSProtocol{StringRef::from_lit("TLSv1.0"), SSL_OP_NO_TLSv1}};
+
+long int create_tls_proto_mask(const std::vector<StringRef> &tls_proto_list) {
+ long int res = 0;
+
+ for (auto &supported : TLS_PROTOS) {
+ auto ok = false;
+ for (auto &name : tls_proto_list) {
+ if (util::strieq(supported.name, name)) {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok) {
+ res |= supported.mask;
+ }
+ }
+ return res;
+}
+
+SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ 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
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ // The reason for disabling built-in anti-replay in
+ // OpenSSL is that it only works if client gets back
+ // to the same server. The freshness check
+ // described in
+ // https://tools.ietf.org/html/rfc8446#section-8.3
+ // is still performed.
+ | SSL_OP_NO_ANTI_REPLAY
+#endif // NGHTTP2_GENUINE_OPENSSL
+ ;
+
+ auto config = mod_config();
+ auto &tlsconf = config->tls;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (tlsconf.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) {
+ LOG(FATAL) << "Could not set TLS protocol version";
+ DIE();
+ }
+
+ 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);
+
+ if (!tlsconf.session_cache.memcached.host.empty()) {
+ SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb);
+ SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb);
+ }
+
+ SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+#ifndef OPENSSL_NO_EC
+ if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves
+ << " failed";
+ DIE();
+ }
+#endif // OPENSSL_NO_EC
+
+ if (!tlsconf.dh_param_file.empty()) {
+ // Read DH parameters from file
+ auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb");
+ if (bio == nullptr) {
+ LOG(FATAL) << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ LOG(FATAL) << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#else // !OPENSSL_3_0_0_API
+ 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);
+#endif // !OPENSSL_3_0_0_API
+ BIO_free(bio);
+ }
+
+ 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 (!tlsconf.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(),
+ nullptr) != 1) {
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.private_key_passwd.empty()) {
+ SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config);
+ }
+
+#ifndef HAVE_NEVERBLEED
+ 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();
+ }
+#else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file,
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data();
+ DIE();
+ }
+#endif // HAVE_NEVERBLEED
+
+ 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 (tlsconf.client_verify.enabled) {
+ if (!tlsconf.client_verify.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(
+ ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << 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(tlsconf.client_verify.cacert.c_str());
+ if (!list) {
+ LOG(FATAL) << "Could not load ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << 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);
+#if OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb);
+#else // !OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
+#endif // !OPENSSL_3_0_0_API
+#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_info_callback(ssl_ctx, info_callback);
+
+#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
+
+ auto tls_ctx_data = new TLSContextData();
+ tls_ctx_data->cert_file = cert_file;
+ tls_ctx_data->sct_data = sct_data;
+
+ SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
+ // returns 1, which means OpenSSL internally handles it. But
+ // OpenSSL handles signed_certificate_timestamp extension specially,
+ // and it lets custom handler to process the extension.
+ if (!sct_data.empty()) {
+ // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is
+ // required here. sct_parse_cb is called without
+ // SSL_EXT_CLIENT_HELLO being set. But the passed context value
+ // is SSL_EXT_CLIENT_HELLO.
+ if (SSL_CTX_add_custom_ext(
+ ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
+ SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO |
+ SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION,
+ sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) {
+ LOG(FATAL) << "SSL_CTX_add_custom_ext failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (!tls_ctx_data->sct_data.empty() &&
+ SSL_CTX_set_signed_cert_timestamp_list(
+ ssl_ctx, tls_ctx_data->sct_data.data(),
+ tls_ctx_data->sct_data.size()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+#ifdef NGHTTP2_GENUINE_OPENSSL
+ if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ if (SSL_CTX_set_recv_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_recv_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL
+
+#ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
+#endif // !LIBRESSL_NO_PSK
+
+ return ssl_ctx;
+}
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *create_quic_ssl_context(const char *private_key_file,
+ const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+) {
+ auto ssl_ctx = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ constexpr auto ssl_opts =
+ (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
+ SSL_OP_SINGLE_DH_USE |
+ SSL_OP_CIPHER_SERVER_PREFERENCE
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ // The reason for disabling built-in anti-replay in OpenSSL is
+ // that it only works if client gets back to the same server.
+ // The freshness check described in
+ // https://tools.ietf.org/html/rfc8446#section-8.3 is still
+ // performed.
+ | SSL_OP_NO_ANTI_REPLAY
+# endif // NGHTTP2_GENUINE_OPENSSL
+ ;
+
+ auto config = mod_config();
+ auto &tlsconf = config->tls;
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts);
+
+# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+ if (ngtcp2_crypto_quictls_configure_server_context(ssl_ctx) != 0) {
+ LOG(FATAL) << "ngtcp2_crypto_quictls_configure_server_context failed";
+ DIE();
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS
+# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+ if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx) != 0) {
+ LOG(FATAL) << "ngtcp2_crypto_boringssl_configure_server_context failed";
+ DIE();
+ }
+# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
+
+ 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_OFF);
+
+ SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+# if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+# ifndef OPENSSL_NO_EC
+ if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves
+ << " failed";
+ DIE();
+ }
+# endif // OPENSSL_NO_EC
+
+ if (!tlsconf.dh_param_file.empty()) {
+ // Read DH parameters from file
+ auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb");
+ if (bio == nullptr) {
+ LOG(FATAL) << "BIO_new_file() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# if OPENSSL_3_0_0_API
+ EVP_PKEY *dh = nullptr;
+ auto dctx = OSSL_DECODER_CTX_new_for_pkey(
+ &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+ nullptr, nullptr);
+
+ if (!OSSL_DECODER_from_bio(dctx, bio)) {
+ LOG(FATAL) << "OSSL_DECODER_from_bio() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
+ LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# else // !OPENSSL_3_0_0_API
+ 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);
+# endif // !OPENSSL_3_0_0_API
+ BIO_free(bio);
+ }
+
+ 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 (!tlsconf.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(),
+ nullptr) != 1) {
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.cacert << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.private_key_passwd.empty()) {
+ SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config);
+ }
+
+# ifndef HAVE_NEVERBLEED
+ 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();
+ }
+# else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file,
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data();
+ DIE();
+ }
+# endif // HAVE_NEVERBLEED
+
+ 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 (tlsconf.client_verify.enabled) {
+ if (!tlsconf.client_verify.cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(
+ ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << 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(tlsconf.client_verify.cacert.c_str());
+ if (!list) {
+ LOG(FATAL) << "Could not load ca certificates from "
+ << tlsconf.client_verify.cacert << ": "
+ << 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);
+# if OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb);
+# else // !OPENSSL_3_0_0_API
+ SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
+# endif // !OPENSSL_3_0_0_API
+# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL
+ SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+ // ALPN selection callback
+ SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr);
+
+ auto tls_ctx_data = new TLSContextData();
+ tls_ctx_data->cert_file = cert_file;
+ tls_ctx_data->sct_data = sct_data;
+
+ SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
+ // returns 1, which means OpenSSL internally handles it. But
+ // OpenSSL handles signed_certificate_timestamp extension specially,
+ // and it lets custom handler to process the extension.
+ if (!sct_data.empty()) {
+ // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is
+ // required here. sct_parse_cb is called without
+ // SSL_EXT_CLIENT_HELLO being set. But the passed context value
+ // is SSL_EXT_CLIENT_HELLO.
+ if (SSL_CTX_add_custom_ext(
+ ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
+ SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO |
+ SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION,
+ sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) {
+ LOG(FATAL) << "SSL_CTX_add_custom_ext failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+# elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL)
+ if (!tls_ctx_data->sct_data.empty() &&
+ SSL_CTX_set_signed_cert_timestamp_list(
+ ssl_ctx, tls_ctx_data->sct_data.data(),
+ tls_ctx_data->sct_data.size()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_OPENSSL_IS_BORINGSSL
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+ auto &quicconf = config->quic;
+
+ if (quicconf.upstream.early_data &&
+ SSL_CTX_set_max_early_data(ssl_ctx,
+ std::numeric_limits<uint32_t>::max()) != 1) {
+ LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+# endif // NGHTTP2_GENUINE_OPENSSL
+
+# ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
+# endif // !LIBRESSL_NO_PSK
+
+ return ssl_ctx;
+}
+#endif // ENABLE_HTTP3
+
+SSL_CTX *create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb,
+#endif // HAVE_NEVERBLEED
+ const StringRef &cacert, const StringRef &cert_file,
+ const StringRef &private_key_file) {
+ auto ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx) {
+ LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+
+ auto &tlsconf = get_config()->tls;
+
+#ifdef SSL_OP_ENABLE_KTLS
+ if (tlsconf.ktls) {
+ ssl_opts |= SSL_OP_ENABLE_KTLS;
+ }
+#endif // SSL_OP_ENABLE_KTLS
+
+ SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
+
+ SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL_STORE);
+ SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_client_new_cb);
+
+ if (nghttp2::tls::ssl_ctx_set_proto_versions(
+ ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) {
+ LOG(FATAL) << "Could not set TLS protocol version";
+ DIE();
+ }
+
+ if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) {
+ LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) ==
+ 0) {
+ LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers
+ << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ 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 (!cacert.empty()) {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, cacert.c_str(), nullptr) != 1) {
+
+ LOG(FATAL) << "Could not load trusted ca certificates from " << cacert
+ << ": " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!tlsconf.insecure) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
+ }
+
+ if (!cert_file.empty()) {
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file.c_str()) != 1) {
+
+ LOG(FATAL) << "Could not load client certificate from " << cert_file
+ << ": " << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+ }
+
+ if (!private_key_file.empty()) {
+#ifndef HAVE_NEVERBLEED
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file.c_str(),
+ SSL_FILETYPE_PEM) != 1) {
+ LOG(FATAL) << "Could not load client private key from "
+ << private_key_file << ": "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ DIE();
+ }
+#else // HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
+ if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file.c_str(),
+ errbuf.data()) != 1) {
+ LOG(FATAL) << "neverbleed_load_private_key_file: could not load client "
+ "private key from "
+ << private_key_file << ": " << errbuf.data();
+ DIE();
+ }
+#endif // HAVE_NEVERBLEED
+ }
+
+#ifndef OPENSSL_NO_PSK
+ SSL_CTX_set_psk_client_callback(ssl_ctx, psk_client_cb);
+#endif // !OPENSSL_NO_PSK
+
+ return ssl_ctx;
+}
+
+SSL *create_ssl(SSL_CTX *ssl_ctx) {
+ auto ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ LOG(ERROR) << "SSL_new() failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return nullptr;
+ }
+
+ return ssl;
+}
+
+ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
+ int addrlen, const UpstreamAddr *faddr) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> service;
+ int rv;
+
+ if (addr->sa_family == AF_UNIX) {
+ std::copy_n("localhost", sizeof("localhost"), std::begin(host));
+ service[0] = '\0';
+ } else {
+ rv = getnameinfo(addr, addrlen, host.data(), host.size(), service.data(),
+ service.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ rv = util::make_socket_nodelay(fd);
+ if (rv == -1) {
+ LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
+ }
+ }
+ SSL *ssl = nullptr;
+ if (faddr->tls) {
+ auto ssl_ctx = worker->get_sv_ssl_ctx();
+
+ assert(ssl_ctx);
+
+ ssl = create_ssl(ssl_ctx);
+ if (!ssl) {
+ return nullptr;
+ }
+ // Disable TLS session ticket if we don't have working ticket
+ // keys.
+ if (!worker->get_ticket_keys()) {
+ SSL_set_options(ssl, SSL_OP_NO_TICKET);
+ }
+ }
+
+ return new ClientHandler(worker, fd, ssl, StringRef{host.data()},
+ StringRef{service.data()}, addr->sa_family, faddr);
+}
+
+bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname) {
+ auto ptWildcard = std::find(std::begin(pattern), std::end(pattern), '*');
+ if (ptWildcard == std::end(pattern)) {
+ return util::strieq(pattern, hostname);
+ }
+
+ auto ptLeftLabelEnd = std::find(std::begin(pattern), std::end(pattern), '.');
+ auto 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 == std::end(pattern) ||
+ std::find(ptLeftLabelEnd + 1, std::end(pattern), '.') ==
+ std::end(pattern) ||
+ ptLeftLabelEnd < ptWildcard || util::istarts_with_l(pattern, "xn--")) {
+ wildcardEnabled = false;
+ }
+
+ if (!wildcardEnabled) {
+ return util::strieq(pattern, hostname);
+ }
+
+ auto hnLeftLabelEnd =
+ std::find(std::begin(hostname), std::end(hostname), '.');
+ if (hnLeftLabelEnd == std::end(hostname) ||
+ !util::strieq(StringRef{ptLeftLabelEnd, std::end(pattern)},
+ StringRef{hnLeftLabelEnd, std::end(hostname)})) {
+ return false;
+ }
+ // Perform wildcard match. Here '*' must match at least one
+ // character.
+ if (hnLeftLabelEnd - std::begin(hostname) <
+ ptLeftLabelEnd - std::begin(pattern)) {
+ return false;
+ }
+ return util::istarts_with(StringRef{std::begin(hostname), hnLeftLabelEnd},
+ StringRef{std::begin(pattern), ptWildcard}) &&
+ util::iends_with(StringRef{std::begin(hostname), hnLeftLabelEnd},
+ StringRef{ptWildcard + 1, ptLeftLabelEnd});
+}
+
+namespace {
+// if return value is not empty, StringRef.c_str() must be freed using
+// OPENSSL_free().
+StringRef get_common_name(X509 *cert) {
+ auto subjectname = X509_get_subject_name(cert);
+ if (!subjectname) {
+ LOG(WARN) << "Could not get X509 name object from the certificate.";
+ return StringRef{};
+ }
+ int lastpos = -1;
+ for (;;) {
+ lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
+ if (lastpos == -1) {
+ break;
+ }
+ auto entry = X509_NAME_get_entry(subjectname, lastpos);
+
+ unsigned char *p;
+ auto plen = ASN1_STRING_to_UTF8(&p, X509_NAME_ENTRY_get_data(entry));
+ if (plen < 0) {
+ continue;
+ }
+ if (std::find(p, p + plen, '\0') != p + plen) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+ if (plen == 0) {
+ LOG(WARN) << "X509 name is empty";
+ OPENSSL_free(p);
+ continue;
+ }
+
+ return StringRef{p, static_cast<size_t>(plen)};
+ }
+ return StringRef{};
+}
+} // namespace
+
+int verify_numeric_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr) {
+ const void *saddr;
+ size_t saddrlen;
+ switch (addr->su.storage.ss_family) {
+ case AF_INET:
+ saddr = &addr->su.in.sin_addr;
+ saddrlen = sizeof(addr->su.in.sin_addr);
+ break;
+ case AF_INET6:
+ saddr = &addr->su.in6.sin6_addr;
+ saddrlen = sizeof(addr->su.in6.sin6_addr);
+ break;
+ default:
+ return -1;
+ }
+
+ auto 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);
+ auto ip_found = false;
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_IPADD) {
+ continue;
+ }
+
+ auto ip_addr = altname->d.iPAddress->data;
+ if (!ip_addr) {
+ continue;
+ }
+ size_t ip_addrlen = altname->d.iPAddress->length;
+
+ ip_found = true;
+ if (saddrlen == ip_addrlen && memcmp(saddr, ip_addr, ip_addrlen) == 0) {
+ return 0;
+ }
+ }
+
+ if (ip_found) {
+ return -1;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return -1;
+ }
+
+ // cn is not NULL terminated
+ auto rv = util::streq(hostname, cn);
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ if (rv) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int verify_dns_hostname(X509 *cert, const StringRef &hostname) {
+ auto altnames = static_cast<GENERAL_NAMES *>(
+ X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+ if (altnames) {
+ auto dns_found = false;
+ auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
+ size_t n = sk_GENERAL_NAME_num(altnames);
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_DNS) {
+ continue;
+ }
+
+ auto name = ASN1_STRING_get0_data(altname->d.ia5);
+ if (!name) {
+ continue;
+ }
+
+ auto len = ASN1_STRING_length(altname->d.ia5);
+ if (len == 0) {
+ continue;
+ }
+ if (std::find(name, name + len, '\0') != name + len) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+
+ if (name[len - 1] == '.') {
+ --len;
+ if (len == 0) {
+ continue;
+ }
+ }
+
+ dns_found = true;
+
+ if (tls_hostname_match(StringRef{name, static_cast<size_t>(len)},
+ hostname)) {
+ return 0;
+ }
+ }
+
+ // RFC 6125, section 6.4.4. says that client MUST not seek a match
+ // for CN if a dns dNSName is found.
+ if (dns_found) {
+ return -1;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return -1;
+ }
+
+ if (cn[cn.size() - 1] == '.') {
+ if (cn.size() == 1) {
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return -1;
+ }
+ cn = StringRef{cn.c_str(), cn.size() - 1};
+ }
+
+ auto rv = tls_hostname_match(cn, hostname);
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return rv ? 0 : -1;
+}
+
+namespace {
+int verify_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr) {
+ if (util::numeric_host(hostname.c_str())) {
+ return verify_numeric_hostname(cert, hostname, addr);
+ }
+
+ return verify_dns_hostname(cert, hostname);
+}
+} // namespace
+
+int check_cert(SSL *ssl, const Address *addr, const StringRef &host) {
+#if OPENSSL_3_0_0_API
+ auto cert = SSL_get0_peer_certificate(ssl);
+#else // !OPENSSL_3_0_0_API
+ auto cert = SSL_get_peer_certificate(ssl);
+#endif // !OPENSSL_3_0_0_API
+ if (!cert) {
+ // By the protocol definition, TLS server always sends certificate
+ // if it has. If certificate cannot be retrieved, authentication
+ // without certificate is used, such as PSK.
+ return 0;
+ }
+#if !OPENSSL_3_0_0_API
+ auto cert_deleter = defer(X509_free, cert);
+#endif // !OPENSSL_3_0_0_API
+
+ if (verify_hostname(cert, host, addr) != 0) {
+ LOG(ERROR) << "Certificate verification failed: hostname does not match";
+ return -1;
+ }
+ return 0;
+}
+
+int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr) {
+ auto hostname =
+ addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni};
+ return check_cert(ssl, raddr, hostname);
+}
+
+CertLookupTree::CertLookupTree() {}
+
+ssize_t CertLookupTree::add_cert(const StringRef &hostname, size_t idx) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ // NI_MAXHOST includes terminal NULL byte
+ if (hostname.empty() || hostname.size() + 1 > buf.size()) {
+ return -1;
+ }
+
+ auto wildcard_it = std::find(std::begin(hostname), std::end(hostname), '*');
+ if (wildcard_it != std::end(hostname) &&
+ wildcard_it + 1 != std::end(hostname)) {
+ auto wildcard_prefix = StringRef{std::begin(hostname), wildcard_it};
+ auto wildcard_suffix = StringRef{wildcard_it + 1, std::end(hostname)};
+
+ auto rev_suffix = StringRef{std::begin(buf),
+ std::reverse_copy(std::begin(wildcard_suffix),
+ std::end(wildcard_suffix),
+ std::begin(buf))};
+
+ WildcardPattern *wpat;
+
+ if (wildcard_patterns_.size() !=
+ rev_wildcard_router_.add_route(rev_suffix, wildcard_patterns_.size())) {
+ auto wcidx = rev_wildcard_router_.match(rev_suffix);
+
+ assert(wcidx != -1);
+
+ wpat = &wildcard_patterns_[wcidx];
+ } else {
+ wildcard_patterns_.emplace_back();
+ wpat = &wildcard_patterns_.back();
+ }
+
+ auto rev_prefix = StringRef{std::begin(buf),
+ std::reverse_copy(std::begin(wildcard_prefix),
+ std::end(wildcard_prefix),
+ std::begin(buf))};
+
+ for (auto &p : wpat->rev_prefix) {
+ if (p.prefix == rev_prefix) {
+ return p.idx;
+ }
+ }
+
+ wpat->rev_prefix.emplace_back(rev_prefix, idx);
+
+ return idx;
+ }
+
+ return router_.add_route(hostname, idx);
+}
+
+ssize_t CertLookupTree::lookup(const StringRef &hostname) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ // NI_MAXHOST includes terminal NULL byte
+ if (hostname.empty() || hostname.size() + 1 > buf.size()) {
+ return -1;
+ }
+
+ // Always prefer exact match
+ auto idx = router_.match(hostname);
+ if (idx != -1) {
+ return idx;
+ }
+
+ if (wildcard_patterns_.empty()) {
+ return -1;
+ }
+
+ ssize_t best_idx = -1;
+ size_t best_prefixlen = 0;
+ const RNode *last_node = nullptr;
+
+ auto rev_host = StringRef{
+ std::begin(buf), std::reverse_copy(std::begin(hostname),
+ std::end(hostname), std::begin(buf))};
+
+ for (;;) {
+ size_t nread = 0;
+
+ auto wcidx =
+ rev_wildcard_router_.match_prefix(&nread, &last_node, rev_host);
+ if (wcidx == -1) {
+ return best_idx;
+ }
+
+ // '*' must match at least one byte
+ if (nread == rev_host.size()) {
+ return best_idx;
+ }
+
+ rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)};
+
+ auto rev_prefix = StringRef{std::begin(rev_host) + 1, std::end(rev_host)};
+
+ auto &wpat = wildcard_patterns_[wcidx];
+ for (auto &wprefix : wpat.rev_prefix) {
+ if (!util::ends_with(rev_prefix, wprefix.prefix)) {
+ continue;
+ }
+
+ auto prefixlen =
+ wprefix.prefix.size() +
+ (reinterpret_cast<const uint8_t *>(&rev_host[0]) - &buf[0]);
+
+ // Breaking a tie with longer suffix
+ if (prefixlen < best_prefixlen) {
+ continue;
+ }
+
+ best_idx = wprefix.idx;
+ best_prefixlen = prefixlen;
+ }
+ }
+}
+
+void CertLookupTree::dump() const {
+ std::cerr << "exact:" << std::endl;
+ router_.dump();
+ std::cerr << "wildcard suffix (reversed):" << std::endl;
+ rev_wildcard_router_.dump();
+}
+
+int cert_lookup_tree_add_ssl_ctx(
+ CertLookupTree *lt, std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ SSL_CTX *ssl_ctx) {
+ std::array<uint8_t, NI_MAXHOST> buf;
+
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto 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);
+ auto dns_found = false;
+ for (size_t i = 0; i < n; ++i) {
+ auto altname = sk_GENERAL_NAME_value(altnames, i);
+ if (altname->type != GEN_DNS) {
+ continue;
+ }
+
+ auto name = ASN1_STRING_get0_data(altname->d.ia5);
+ if (!name) {
+ continue;
+ }
+
+ auto len = ASN1_STRING_length(altname->d.ia5);
+ if (len == 0) {
+ continue;
+ }
+ if (std::find(name, name + len, '\0') != name + len) {
+ // Embedded NULL is not permitted.
+ continue;
+ }
+
+ if (name[len - 1] == '.') {
+ --len;
+ if (len == 0) {
+ continue;
+ }
+ }
+
+ dns_found = true;
+
+ if (static_cast<size_t>(len) + 1 > buf.size()) {
+ continue;
+ }
+
+ auto end_buf = std::copy_n(name, len, std::begin(buf));
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto idx = lt->add_cert(StringRef{std::begin(buf), end_buf},
+ indexed_ssl_ctx.size());
+ if (idx == -1) {
+ continue;
+ }
+
+ if (static_cast<size_t>(idx) < indexed_ssl_ctx.size()) {
+ indexed_ssl_ctx[idx].push_back(ssl_ctx);
+ } else {
+ assert(static_cast<size_t>(idx) == indexed_ssl_ctx.size());
+ indexed_ssl_ctx.emplace_back(std::vector<SSL_CTX *>{ssl_ctx});
+ }
+ }
+
+ // Don't bother CN if we have dNSName.
+ if (dns_found) {
+ return 0;
+ }
+ }
+
+ auto cn = get_common_name(cert);
+ if (cn.empty()) {
+ return 0;
+ }
+
+ if (cn[cn.size() - 1] == '.') {
+ if (cn.size() == 1) {
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ return 0;
+ }
+
+ cn = StringRef{cn.c_str(), cn.size() - 1};
+ }
+
+ auto end_buf = std::copy(std::begin(cn), std::end(cn), std::begin(buf));
+
+ OPENSSL_free(const_cast<char *>(cn.c_str()));
+
+ util::inp_strlower(std::begin(buf), end_buf);
+
+ auto idx =
+ lt->add_cert(StringRef{std::begin(buf), end_buf}, indexed_ssl_ctx.size());
+ if (idx == -1) {
+ return 0;
+ }
+
+ if (static_cast<size_t>(idx) < indexed_ssl_ctx.size()) {
+ indexed_ssl_ctx[idx].push_back(ssl_ctx);
+ } else {
+ assert(static_cast<size_t>(idx) == indexed_ssl_ctx.size());
+ indexed_ssl_ctx.emplace_back(std::vector<SSL_CTX *>{ssl_ctx});
+ }
+
+ return 0;
+}
+
+bool in_proto_list(const std::vector<StringRef> &protos,
+ const StringRef &needle) {
+ for (auto &proto : protos) {
+ if (util::streq(proto, needle)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool upstream_tls_enabled(const ConnectionConfig &connconf) {
+#ifdef ENABLE_HTTP3
+ if (connconf.quic_listener.addrs.size()) {
+ return true;
+ }
+#endif // ENABLE_HTTP3
+
+ const auto &faddrs = connconf.listener.addrs;
+ return std::any_of(std::begin(faddrs), std::end(faddrs),
+ [](const UpstreamAddr &faddr) { return faddr.tls; });
+}
+
+X509 *load_certificate(const char *filename) {
+ auto bio = BIO_new(BIO_s_file());
+ if (!bio) {
+ fprintf(stderr, "BIO_new() failed\n");
+ return nullptr;
+ }
+ auto bio_deleter = defer(BIO_vfree, bio);
+ if (!BIO_read_filename(bio, filename)) {
+ fprintf(stderr, "Could not read certificate file '%s'\n", filename);
+ return nullptr;
+ }
+ auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
+ if (!cert) {
+ fprintf(stderr, "Could not read X509 structure from file '%s'\n", filename);
+ return nullptr;
+ }
+
+ return cert;
+}
+
+SSL_CTX *
+setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto config = get_config();
+
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+
+ auto &tlsconf = config->tls;
+
+ auto ssl_ctx = create_ssl_context(tlsconf.private_key_file.c_str(),
+ tlsconf.cert_file.c_str(), tlsconf.sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb
+#endif // HAVE_NEVERBLEED
+ );
+
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ assert(cert_tree);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) {
+ LOG(FATAL) << "Failed to add default certificate.";
+ DIE();
+ }
+
+ for (auto &c : tlsconf.subcerts) {
+ auto ssl_ctx = create_ssl_context(c.private_key_file.c_str(),
+ c.cert_file.c_str(), c.sct_data
+#ifdef HAVE_NEVERBLEED
+ ,
+ nb
+#endif // HAVE_NEVERBLEED
+ );
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) ==
+ -1) {
+ LOG(FATAL) << "Failed to add sub certificate.";
+ DIE();
+ }
+ }
+
+ return ssl_ctx;
+}
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *setup_quic_server_ssl_context(
+ std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+) {
+ auto config = get_config();
+
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+
+ auto &tlsconf = config->tls;
+
+ auto ssl_ctx =
+ create_quic_ssl_context(tlsconf.private_key_file.c_str(),
+ tlsconf.cert_file.c_str(), tlsconf.sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb
+# endif // HAVE_NEVERBLEED
+ );
+
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ assert(cert_tree);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) {
+ LOG(FATAL) << "Failed to add default certificate.";
+ DIE();
+ }
+
+ for (auto &c : tlsconf.subcerts) {
+ auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(),
+ c.cert_file.c_str(), c.sct_data
+# ifdef HAVE_NEVERBLEED
+ ,
+ nb
+# endif // HAVE_NEVERBLEED
+ );
+ all_ssl_ctx.push_back(ssl_ctx);
+
+ if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) ==
+ -1) {
+ LOG(FATAL) << "Failed to add sub certificate.";
+ DIE();
+ }
+ }
+
+ return ssl_ctx;
+}
+#endif // ENABLE_HTTP3
+
+SSL_CTX *setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ auto &tlsconf = get_config()->tls;
+
+ return create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ nb,
+#endif // HAVE_NEVERBLEED
+ tlsconf.cacert, tlsconf.client.cert_file,
+ tlsconf.client.private_key_file);
+}
+
+void setup_downstream_http2_alpn(SSL *ssl) {
+ // ALPN advertisement
+ auto alpn = util::get_default_alpn();
+ SSL_set_alpn_protos(ssl, alpn.data(), alpn.size());
+}
+
+void setup_downstream_http1_alpn(SSL *ssl) {
+ // ALPN advertisement
+ SSL_set_alpn_protos(ssl, NGHTTP2_H1_1_ALPN.byte(), NGHTTP2_H1_1_ALPN.size());
+}
+
+std::unique_ptr<CertLookupTree> create_cert_lookup_tree() {
+ auto config = get_config();
+ if (!upstream_tls_enabled(config->conn)) {
+ return nullptr;
+ }
+ return std::make_unique<CertLookupTree>();
+}
+
+namespace {
+std::vector<uint8_t> serialize_ssl_session(SSL_SESSION *session) {
+ auto len = i2d_SSL_SESSION(session, nullptr);
+ auto buf = std::vector<uint8_t>(len);
+ auto p = buf.data();
+ i2d_SSL_SESSION(session, &p);
+
+ return buf;
+}
+} // namespace
+
+void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session,
+ const std::chrono::steady_clock::time_point &t) {
+ if (cache->last_updated + 1min > t) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Client session cache entry is still fresh.";
+ }
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Update client cache entry "
+ << "timestamp = " << t.time_since_epoch().count();
+ }
+
+ cache->session_data = serialize_ssl_session(session);
+ cache->last_updated = t;
+}
+
+SSL_SESSION *reuse_tls_session(const TLSSessionCache &cache) {
+ if (cache.session_data.empty()) {
+ return nullptr;
+ }
+
+ auto p = cache.session_data.data();
+ return d2i_SSL_SESSION(nullptr, &p, cache.session_data.size());
+}
+
+int proto_version_from_string(const StringRef &v) {
+#ifdef TLS1_3_VERSION
+ if (util::strieq_l("TLSv1.3", v)) {
+ return TLS1_3_VERSION;
+ }
+#endif // TLS1_3_VERSION
+ if (util::strieq_l("TLSv1.2", v)) {
+ return TLS1_2_VERSION;
+ }
+ if (util::strieq_l("TLSv1.1", v)) {
+ return TLS1_1_VERSION;
+ }
+ if (util::strieq_l("TLSv1.0", v)) {
+ return TLS1_VERSION;
+ }
+ return -1;
+}
+
+int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp,
+ size_t ocsp_resplen) {
+#ifndef OPENSSL_NO_OCSP
+ int rv;
+
+ STACK_OF(X509) * chain_certs;
+ SSL_CTX_get0_chain_certs(ssl_ctx, &chain_certs);
+
+ auto resp = d2i_OCSP_RESPONSE(nullptr, &ocsp_resp, ocsp_resplen);
+ if (resp == nullptr) {
+ LOG(ERROR) << "d2i_OCSP_RESPONSE failed";
+ return -1;
+ }
+ auto resp_deleter = defer(OCSP_RESPONSE_free, resp);
+
+ if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ LOG(ERROR) << "OCSP response status is not successful";
+ return -1;
+ }
+
+ ERR_clear_error();
+
+ auto bs = OCSP_response_get1_basic(resp);
+ if (bs == nullptr) {
+ LOG(ERROR) << "OCSP_response_get1_basic failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+ auto bs_deleter = defer(OCSP_BASICRESP_free, bs);
+
+ auto store = SSL_CTX_get_cert_store(ssl_ctx);
+
+ ERR_clear_error();
+
+ rv = OCSP_basic_verify(bs, chain_certs, store, 0);
+
+ if (rv != 1) {
+ LOG(ERROR) << "OCSP_basic_verify failed: "
+ << ERR_error_string(ERR_get_error(), nullptr);
+ return -1;
+ }
+
+ auto sresp = OCSP_resp_get0(bs, 0);
+ if (sresp == nullptr) {
+ LOG(ERROR) << "OCSP response verification failed: no single response";
+ return -1;
+ }
+
+ auto certid = OCSP_SINGLERESP_get0_id(sresp);
+ assert(certid != nullptr);
+
+ ASN1_INTEGER *serial;
+ rv = OCSP_id_get0_info(nullptr, nullptr, nullptr, &serial,
+ const_cast<OCSP_CERTID *>(certid));
+ if (rv != 1) {
+ LOG(ERROR) << "OCSP_id_get0_info failed";
+ return -1;
+ }
+
+ if (serial == nullptr) {
+ LOG(ERROR) << "OCSP response does not contain serial number";
+ return -1;
+ }
+
+ auto cert = SSL_CTX_get0_certificate(ssl_ctx);
+ auto cert_serial = X509_get_serialNumber(cert);
+
+ if (ASN1_INTEGER_cmp(cert_serial, serial)) {
+ LOG(ERROR) << "OCSP verification serial numbers do not match";
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "OCSP verification succeeded";
+ }
+#endif // !OPENSSL_NO_OCSP
+
+ return 0;
+}
+
+ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x,
+ const EVP_MD *md) {
+ unsigned int len = dstlen;
+ if (X509_digest(x, md, dst, &len) != 1) {
+ return -1;
+ }
+ return len;
+}
+
+namespace {
+StringRef get_x509_name(BlockAllocator &balloc, X509_NAME *nm) {
+ auto b = BIO_new(BIO_s_mem());
+ if (!b) {
+ return StringRef{};
+ }
+
+ auto b_deleter = defer(BIO_free, b);
+
+ // Not documented, but it seems that X509_NAME_print_ex returns the
+ // number of bytes written into b.
+ auto slen = X509_NAME_print_ex(b, nm, 0, XN_FLAG_RFC2253);
+ if (slen <= 0) {
+ return StringRef{};
+ }
+
+ auto iov = make_byte_ref(balloc, slen + 1);
+ BIO_read(b, iov.base, slen);
+ iov.base[slen] = '\0';
+ return StringRef{iov.base, static_cast<size_t>(slen)};
+}
+} // namespace
+
+StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x) {
+ return get_x509_name(balloc, X509_get_subject_name(x));
+}
+
+StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x) {
+ return get_x509_name(balloc, X509_get_issuer_name(x));
+}
+
+StringRef get_x509_serial(BlockAllocator &balloc, X509 *x) {
+ auto sn = X509_get_serialNumber(x);
+ auto bn = BN_new();
+ auto bn_d = defer(BN_free, bn);
+ if (!ASN1_INTEGER_to_BN(sn, bn) || BN_num_bytes(bn) > 20) {
+ return StringRef{};
+ }
+
+ std::array<uint8_t, 20> b;
+ auto n = BN_bn2bin(bn, b.data());
+ assert(n <= 20);
+
+ return util::format_hex(balloc, StringRef{b.data(), static_cast<size_t>(n)});
+}
+
+namespace {
+// Performs conversion from |at| to time_t. The result is stored in
+// |t|. This function returns 0 if it succeeds, or -1.
+int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
+ int rv;
+
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ struct tm tm;
+ rv = ASN1_TIME_to_tm(at, &tm);
+ if (rv != 1) {
+ return -1;
+ }
+
+ t = nghttp2_timegm(&tm);
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ auto b = BIO_new(BIO_s_mem());
+ if (!b) {
+ return -1;
+ }
+
+ auto bio_deleter = defer(BIO_free, b);
+
+ rv = ASN1_TIME_print(b, at);
+ if (rv != 1) {
+ return -1;
+ }
+
+# ifdef NGHTTP2_OPENSSL_IS_BORINGSSL
+ char *s;
+# else
+ unsigned char *s;
+# endif
+ auto slen = BIO_get_mem_data(b, &s);
+ auto tt = util::parse_openssl_asn1_time_print(
+ StringRef{s, static_cast<size_t>(slen)});
+ if (tt == 0) {
+ return -1;
+ }
+
+ t = tt;
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+
+ return 0;
+}
+} // namespace
+
+int get_x509_not_before(time_t &t, X509 *x) {
+ auto at = X509_get0_notBefore(x);
+ if (!at) {
+ return -1;
+ }
+
+ return time_t_from_asn1_time(t, at);
+}
+
+int get_x509_not_after(time_t &t, X509 *x) {
+ auto at = X509_get0_notAfter(x);
+ if (!at) {
+ return -1;
+ }
+
+ return time_t_from_asn1_time(t, at);
+}
+
+} // namespace tls
+
+} // namespace shrpx
diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h
new file mode 100644
index 0000000..f740507
--- /dev/null
+++ b/src/shrpx_tls.h
@@ -0,0 +1,321 @@
+/*
+ * 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_TLS_H
+#define SHRPX_TLS_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <mutex>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+#ifdef HAVE_NEVERBLEED
+# include <neverbleed.h>
+#endif // HAVE_NEVERBLEED
+
+#include "network.h"
+#include "shrpx_config.h"
+#include "shrpx_router.h"
+
+namespace shrpx {
+
+class ClientHandler;
+class Worker;
+class DownstreamConnectionPool;
+struct DownstreamAddr;
+struct UpstreamAddr;
+
+namespace tls {
+
+struct TLSSessionCache {
+ // ASN1 representation of SSL_SESSION object. See
+ // i2d_SSL_SESSION(3SSL).
+ std::vector<uint8_t> session_data;
+ // The last time stamp when this cache entry is created or updated.
+ std::chrono::steady_clock::time_point last_updated;
+};
+
+// This struct stores the additional information per SSL_CTX. This is
+// attached to SSL_CTX using SSL_CTX_set_app_data().
+struct TLSContextData {
+ // SCT data formatted so that this can be directly sent as
+ // extension_data of signed_certificate_timestamp.
+ std::vector<uint8_t> sct_data;
+#ifndef HAVE_ATOMIC_STD_SHARED_PTR
+ // Protects ocsp_data;
+ std::mutex mu;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+ // OCSP response
+ std::shared_ptr<std::vector<uint8_t>> ocsp_data;
+
+ // Path to certificate file
+ const char *cert_file;
+};
+
+// Create server side SSL_CTX
+SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
+ const std::vector<uint8_t> &sct_data
+
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+// Create client side SSL_CTX. This does not configure ALPN settings.
+SSL_CTX *create_ssl_client_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb,
+#endif // HAVE_NEVERBLEED
+ const StringRef &cacert, const StringRef &cert_file,
+ const StringRef &private_key_file);
+
+ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
+ int addrlen, const UpstreamAddr *faddr);
+
+// Check peer's certificate against given |address| and |host|.
+int check_cert(SSL *ssl, const Address *addr, const StringRef &host);
+// Check peer's certificate against given host name described in
+// |addr| and numeric address in |raddr|. Note that |raddr| might not
+// point to &addr->addr.
+int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr);
+
+// Verify |cert| using numeric IP address. |hostname| and |addr|
+// should contain the same numeric IP address. This function returns
+// 0 if it succeeds, or -1.
+int verify_numeric_hostname(X509 *cert, const StringRef &hostname,
+ const Address *addr);
+
+// Verify |cert| using DNS name hostname. This function returns 0 if
+// it succeeds, or -1.
+int verify_dns_hostname(X509 *cert, const StringRef &hostname);
+
+struct WildcardRevPrefix {
+ WildcardRevPrefix(const StringRef &prefix, size_t idx)
+ : prefix(std::begin(prefix), std::end(prefix)), idx(idx) {}
+
+ // "Prefix" of wildcard pattern. It is reversed from original form.
+ // For example, if the original wildcard is "test*.nghttp2.org",
+ // prefix would be "tset".
+ ImmutableString prefix;
+ // The index of SSL_CTX. See ConnectionHandler::get_ssl_ctx().
+ size_t idx;
+};
+
+struct WildcardPattern {
+ // Wildcard host sharing only suffix is probably rare, so we just do
+ // linear search.
+ std::vector<WildcardRevPrefix> rev_prefix;
+};
+
+class CertLookupTree {
+public:
+ CertLookupTree();
+
+ // Adds hostname pattern |hostname| to the lookup tree, associating
+ // value |index|. When the queried host matches this pattern,
+ // |index| is returned. We support wildcard pattern. The left most
+ // '*' is considered as wildcard character, and it must match at
+ // least one character. If the same pattern has been already added,
+ // this function does not alter the tree, and returns the existing
+ // matching index.
+ //
+ // The caller should lower-case |hostname| since this function does
+ // do that, and lookup function performs case-sensitive match.
+ //
+ // TODO Treat wildcard pattern described as RFC 6125.
+ //
+ // This function returns the index. It returns -1 if it fails
+ // (e.g., hostname is too long). If the returned index equals to
+ // |index|, then hostname is added to the tree with the value
+ // |index|. If it is not -1, and does not equal to |index|, same
+ // hostname has already been added to the tree.
+ ssize_t add_cert(const StringRef &hostname, size_t index);
+
+ // Looks up index using the given |hostname|. The exact match takes
+ // precedence over wildcard match. For wildcard match, longest
+ // match (sum of matched suffix and prefix length in bytes) is
+ // preferred, breaking a tie with longer suffix.
+ //
+ // The caller should lower-case |hostname| since this function
+ // performs case-sensitive match.
+ ssize_t lookup(const StringRef &hostname);
+
+ // Dumps the contents of this lookup tree to stderr.
+ void dump() const;
+
+private:
+ // Exact match
+ Router router_;
+ // Wildcard reversed suffix match. The returned index is into
+ // wildcard_patterns_.
+ Router rev_wildcard_router_;
+ // Stores wildcard suffix patterns.
+ std::vector<WildcardPattern> wildcard_patterns_;
+};
+
+// Adds hostnames in certificate in |ssl_ctx| to lookup tree |lt|.
+// The subjectAltNames and commonName are considered as eligible
+// hostname. If there is at least one dNSName in subjectAltNames,
+// commonName is not considered. |ssl_ctx| is also added to
+// |indexed_ssl_ctx|. This function returns 0 if it succeeds, or -1.
+int cert_lookup_tree_add_ssl_ctx(
+ CertLookupTree *lt, std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ SSL_CTX *ssl_ctx);
+
+// Returns true if |proto| is included in the
+// protocol list |protos|.
+bool in_proto_list(const std::vector<StringRef> &protos,
+ const StringRef &proto);
+
+// 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<StringRef> &tls_proto_list);
+
+int set_alpn_prefs(std::vector<unsigned char> &out,
+ const std::vector<StringRef> &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 available
+// (get_config()->subcerts), caller should provide CertLookupTree
+// object as |cert_tree| parameter, otherwise SNI does not work. All
+// the created SSL_CTX is stored into |all_ssl_ctx|. They are also
+// added to |indexed_ssl_ctx|. |cert_tree| uses its index to
+// associate hostname to the SSL_CTX.
+SSL_CTX *
+setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+#ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *setup_quic_server_ssl_context(
+ std::vector<SSL_CTX *> &all_ssl_ctx,
+ std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
+ CertLookupTree *cert_tree
+# ifdef HAVE_NEVERBLEED
+ ,
+ neverbleed_t *nb
+# endif // HAVE_NEVERBLEED
+);
+#endif // ENABLE_HTTP3
+
+// Setups client side SSL_CTX.
+SSL_CTX *setup_downstream_client_ssl_context(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+);
+
+// Sets ALPN settings in |SSL| suitable for HTTP/2 use.
+void setup_downstream_http2_alpn(SSL *ssl);
+// Sets ALPN settings in |SSL| suitable for HTTP/1.1 use.
+void setup_downstream_http1_alpn(SSL *ssl);
+
+// Creates CertLookupTree. If frontend is configured not to use TLS,
+// this function returns nullptr.
+std::unique_ptr<CertLookupTree> create_cert_lookup_tree();
+
+SSL *create_ssl(SSL_CTX *ssl_ctx);
+
+// Returns true if SSL/TLS is enabled on upstream
+bool upstream_tls_enabled(const ConnectionConfig &connconf);
+
+// Performs TLS hostname match. |pattern| can contain wildcard
+// character '*', which matches prefix of target hostname. There are
+// several restrictions to make wildcard work. The matching algorithm
+// is based on RFC 6125.
+bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname);
+
+// Caches |session|. |session| is serialized into ASN1
+// representation, and stored. |t| is used as a time stamp.
+// Depending on the existing cache's time stamp, |session| might not
+// be cached.
+void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session,
+ const std::chrono::steady_clock::time_point &t);
+
+// Returns cached session associated |addr|. If no cache entry is
+// found associated to |addr|, nullptr will be returned.
+SSL_SESSION *reuse_tls_session(const TLSSessionCache &addr);
+
+// Loads certificate form file |filename|. The caller should delete
+// the returned object using X509_free().
+X509 *load_certificate(const char *filename);
+
+// Returns TLS version from |v|. The returned value is defined in
+// OpenSSL header file. This function returns -1 if |v| is not valid
+// TLS version string.
+int proto_version_from_string(const StringRef &v);
+
+// Verifies OCSP response |ocsp_resp| of length |ocsp_resplen|. This
+// function returns 0 if it succeeds, or -1.
+int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp,
+ size_t ocsp_resplen);
+
+// Stores fingerprint of |x| in |dst| of length |dstlen|. |md|
+// specifies hash function to use, and |dstlen| must be large enough
+// to include hash value (e.g., 32 bytes for SHA-256). This function
+// returns the number of bytes written in |dst|, or -1.
+ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x,
+ const EVP_MD *md);
+
+// Returns subject name of |x|. If this function fails to get subject
+// name, it returns an empty string.
+StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x);
+
+// Returns issuer name of |x|. If this function fails to get issuer
+// name, it returns an empty string.
+StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x);
+
+// Returns serial number of |x|. If this function fails to get serial
+// number, it returns an empty string. number
+StringRef get_x509_serial(BlockAllocator &balloc, X509 *x);
+
+// Fills NotBefore of |x| in |t|. This function returns 0 if it
+// succeeds, or -1.
+int get_x509_not_before(time_t &t, X509 *x);
+
+// Fills NotAfter of |x| in |t|. This function returns 0 if it
+// succeeds, or -1.
+int get_x509_not_after(time_t &t, X509 *x);
+
+} // namespace tls
+
+} // namespace shrpx
+
+#endif // SHRPX_TLS_H
diff --git a/src/shrpx_tls_test.cc b/src/shrpx_tls_test.cc
new file mode 100644
index 0000000..02fb168
--- /dev/null
+++ b/src/shrpx_tls_test.cc
@@ -0,0 +1,339 @@
+/*
+ * 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_tls_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_shrpx_tls_create_lookup_tree(void) {
+ auto tree = std::make_unique<tls::CertLookupTree>();
+
+ constexpr StringRef hostnames[] = {
+ StringRef::from_lit("example.com"), // 0
+ StringRef::from_lit("www.example.org"), // 1
+ StringRef::from_lit("*www.example.org"), // 2
+ StringRef::from_lit("xy*.host.domain"), // 3
+ StringRef::from_lit("*yy.host.domain"), // 4
+ StringRef::from_lit("nghttp2.sourceforge.net"), // 5
+ StringRef::from_lit("sourceforge.net"), // 6
+ StringRef::from_lit("sourceforge.net"), // 7, duplicate
+ StringRef::from_lit("*.foo.bar"), // 8, oo.bar is suffix of *.foo.bar
+ StringRef::from_lit("oo.bar") // 9
+ };
+ auto num = array_size(hostnames);
+
+ for (size_t idx = 0; idx < num; ++idx) {
+ tree->add_cert(hostnames[idx], idx);
+ }
+
+ tree->dump();
+
+ CU_ASSERT(0 == tree->lookup(hostnames[0]));
+ CU_ASSERT(1 == tree->lookup(hostnames[1]));
+ CU_ASSERT(2 == tree->lookup(StringRef::from_lit("2www.example.org")));
+ CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("www2.example.org")));
+ CU_ASSERT(3 == tree->lookup(StringRef::from_lit("xy1.host.domain")));
+ // Does not match *yy.host.domain, because * must match at least 1
+ // character.
+ CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("yy.host.domain")));
+ CU_ASSERT(4 == tree->lookup(StringRef::from_lit("xyy.host.domain")));
+ CU_ASSERT(-1 == tree->lookup(StringRef{}));
+ CU_ASSERT(5 == tree->lookup(hostnames[5]));
+ CU_ASSERT(6 == tree->lookup(hostnames[6]));
+ static constexpr char h6[] = "pdylay.sourceforge.net";
+ for (int i = 0; i < 7; ++i) {
+ CU_ASSERT(-1 == tree->lookup(StringRef{h6 + i, str_size(h6) - i}));
+ }
+ CU_ASSERT(8 == tree->lookup(StringRef::from_lit("x.foo.bar")));
+ CU_ASSERT(9 == tree->lookup(hostnames[9]));
+
+ constexpr StringRef names[] = {
+ StringRef::from_lit("rab"), // 1
+ StringRef::from_lit("zab"), // 2
+ StringRef::from_lit("zzub"), // 3
+ StringRef::from_lit("ab") // 4
+ };
+ num = array_size(names);
+
+ tree = std::make_unique<tls::CertLookupTree>();
+ for (size_t idx = 0; idx < num; ++idx) {
+ tree->add_cert(names[idx], idx);
+ }
+ for (size_t i = 0; i < num; ++i) {
+ CU_ASSERT((ssize_t)i == tree->lookup(names[i]));
+ }
+}
+
+// We use cfssl to generate key pairs.
+//
+// CA self-signed key pairs generation:
+//
+// $ cfssl genkey -initca ca.nghttp2.org.csr.json |
+// cfssljson -bare ca.nghttp2.org
+//
+// Create CSR:
+//
+// $ cfssl genkey test.nghttp2.org.csr.json | cfssljson -bare test.nghttp2.org
+// $ cfssl genkey test.example.com.csr.json | cfssljson -bare test.example.com
+//
+// Sign CSR:
+//
+// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem
+// -config=ca-config.json -profile=server test.nghttp2.org.csr |
+// cfssljson -bare test.nghttp2.org
+//
+// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem
+// -config=ca-config.json -profile=server test.example.com.csr |
+// cfssljson -bare test.example.com
+//
+void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) {
+ int rv;
+
+ static constexpr char nghttp2_certfile[] =
+ NGHTTP2_SRC_DIR "/test.nghttp2.org.pem";
+ auto nghttp2_ssl_ctx = SSL_CTX_new(TLS_server_method());
+ auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx);
+ auto nghttp2_tls_ctx_data = std::make_unique<tls::TLSContextData>();
+ nghttp2_tls_ctx_data->cert_file = nghttp2_certfile;
+ SSL_CTX_set_app_data(nghttp2_ssl_ctx, nghttp2_tls_ctx_data.get());
+ rv = SSL_CTX_use_certificate_chain_file(nghttp2_ssl_ctx, nghttp2_certfile);
+
+ CU_ASSERT(1 == rv);
+
+ static constexpr char examples_certfile[] =
+ NGHTTP2_SRC_DIR "/test.example.com.pem";
+ auto examples_ssl_ctx = SSL_CTX_new(TLS_server_method());
+ auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx);
+ auto examples_tls_ctx_data = std::make_unique<tls::TLSContextData>();
+ examples_tls_ctx_data->cert_file = examples_certfile;
+ SSL_CTX_set_app_data(examples_ssl_ctx, examples_tls_ctx_data.get());
+ rv = SSL_CTX_use_certificate_chain_file(examples_ssl_ctx, examples_certfile);
+
+ CU_ASSERT(1 == rv);
+
+ tls::CertLookupTree tree;
+ std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx;
+
+ rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx,
+ nghttp2_ssl_ctx);
+
+ CU_ASSERT(0 == rv);
+
+ rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx,
+ examples_ssl_ctx);
+
+ CU_ASSERT(0 == rv);
+
+ CU_ASSERT(-1 == tree.lookup(StringRef::from_lit("not-used.nghttp2.org")));
+ CU_ASSERT(0 == tree.lookup(StringRef::from_lit("test.nghttp2.org")));
+ CU_ASSERT(1 == tree.lookup(StringRef::from_lit("w.test.nghttp2.org")));
+ CU_ASSERT(2 == tree.lookup(StringRef::from_lit("www.test.nghttp2.org")));
+ CU_ASSERT(3 == tree.lookup(StringRef::from_lit("test.example.com")));
+}
+
+template <size_t N, size_t M>
+bool tls_hostname_match_wrapper(const char (&pattern)[N],
+ const char (&hostname)[M]) {
+ return tls::tls_hostname_match(StringRef{pattern, N}, StringRef{hostname, M});
+}
+
+void test_shrpx_tls_tls_hostname_match(void) {
+ CU_ASSERT(tls_hostname_match_wrapper("example.com", "example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("example.com", "EXAMPLE.com"));
+
+ // check wildcard
+ CU_ASSERT(tls_hostname_match_wrapper("*.example.com", "www.example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("*w.example.com", "www.example.com"));
+ CU_ASSERT(tls_hostname_match_wrapper("www*.example.com", "www1.example.com"));
+ CU_ASSERT(
+ tls_hostname_match_wrapper("www*.example.com", "WWW12.EXAMPLE.com"));
+ // at least 2 dots are required after '*'
+ CU_ASSERT(!tls_hostname_match_wrapper("*.com", "example.com"));
+ CU_ASSERT(!tls_hostname_match_wrapper("*", "example.com"));
+ // '*' must be in left most label
+ CU_ASSERT(
+ !tls_hostname_match_wrapper("blog.*.example.com", "blog.my.example.com"));
+ // prefix is wrong
+ CU_ASSERT(
+ !tls_hostname_match_wrapper("client*.example.com", "server.example.com"));
+ // '*' must match at least one character
+ CU_ASSERT(!tls_hostname_match_wrapper("www*.example.com", "www.example.com"));
+
+ CU_ASSERT(!tls_hostname_match_wrapper("example.com", "nghttp2.org"));
+ CU_ASSERT(!tls_hostname_match_wrapper("www.example.com", "example.com"));
+ CU_ASSERT(!tls_hostname_match_wrapper("example.com", "www.example.com"));
+}
+
+static X509 *load_cert(const char *path) {
+ auto f = fopen(path, "r");
+ auto cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
+
+ fclose(f);
+
+ return cert;
+}
+
+static Address parse_addr(const char *ipaddr) {
+ addrinfo hints{};
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+ addrinfo *res = nullptr;
+
+ auto rv = getaddrinfo(ipaddr, "443", &hints, &res);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(nullptr != res);
+
+ Address addr;
+ addr.len = res->ai_addrlen;
+ memcpy(&addr.su, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return addr;
+}
+
+void test_shrpx_tls_verify_numeric_hostname(void) {
+ {
+ // Successful IPv4 address match in SAN
+ static constexpr char ipaddr[] = "127.0.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful IPv6 address match in SAN
+ static constexpr char ipaddr[] = "::1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Unsuccessful IPv4 address match in SAN
+ static constexpr char ipaddr[] = "192.168.0.127";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // CommonName is not used if SAN is available
+ static constexpr char ipaddr[] = "192.168.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/ipaddr.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful IPv4 address match in CommonName
+ static constexpr char ipaddr[] = "127.0.0.1";
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan_ip.crt");
+ auto addr = parse_addr(ipaddr);
+ auto rv =
+ tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr);
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+}
+
+void test_shrpx_tls_verify_dns_hostname(void) {
+ {
+ // Successful exact DNS name match in SAN
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(
+ cert, StringRef::from_lit("nghttp2.example.com"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful wildcard DNS name match in SAN
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(
+ cert, StringRef::from_lit("www.nghttp2.example.com"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // CommonName is not used if SAN is available.
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt");
+ auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost"));
+
+ CU_ASSERT(-1 == rv);
+
+ X509_free(cert);
+ }
+
+ {
+ // Successful DNS name match in CommonName
+ auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan.crt");
+ auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost"));
+
+ CU_ASSERT(0 == rv);
+
+ X509_free(cert);
+ }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_tls_test.h b/src/shrpx_tls_test.h
new file mode 100644
index 0000000..7edc742
--- /dev/null
+++ b/src/shrpx_tls_test.h
@@ -0,0 +1,42 @@
+/*
+ * 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_TLS_TEST_H
+#define SHRPX_TLS_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_tls_create_lookup_tree(void);
+void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void);
+void test_shrpx_tls_tls_hostname_match(void);
+void test_shrpx_tls_verify_numeric_hostname(void);
+void test_shrpx_tls_verify_dns_hostname(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_TLS_TEST_H
diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h
new file mode 100644
index 0000000..3da62c9
--- /dev/null
+++ b/src/shrpx_upstream.h
@@ -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 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;
+ // Called when the current request is aborted without forwarding it
+ // to backend, and it should be redirected to https URI.
+ virtual int
+ on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 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 for |downstream| 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(Downstream *downstream, bool no_retry) = 0;
+
+ virtual void pause_read(IOCtrlReason reason) = 0;
+ virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) = 0;
+ virtual int send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) = 0;
+
+ // Starts server push. The |downstream| is an associated stream for
+ // the pushed resource. This function returns 0 if it succeeds,
+ // otherwise -1.
+ virtual int initiate_push(Downstream *downstream, const StringRef &uri) = 0;
+
+ // Fills response data in |iov| whose capacity is |iovcnt|. Returns
+ // the number of iovs filled.
+ virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0;
+ virtual void response_drain(size_t n) = 0;
+ virtual bool response_empty() const = 0;
+
+ // Called when PUSH_PROMISE was started in downstream. The
+ // associated downstream is given as |downstream|. The promised
+ // stream ID is given as |promised_stream_id|. If upstream supports
+ // server push for the corresponding upstream connection, it should
+ // return Downstream object for pushed stream. Otherwise, returns
+ // nullptr.
+ virtual Downstream *
+ on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) = 0;
+ // Called when PUSH_PROMISE frame was completely received in
+ // downstream. The associated downstream is given as |downstream|.
+ // This function returns 0 if it succeeds, or -1.
+ virtual int
+ on_downstream_push_promise_complete(Downstream *downstream,
+ Downstream *promised_downstream) = 0;
+ // Returns true if server push is enabled in upstream connection.
+ virtual bool push_enabled() const = 0;
+ // Cancels promised downstream. This function is called when
+ // PUSH_PROMISE for |promised_downstream| is not submitted to
+ // upstream session.
+ virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_UPSTREAM_H
diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc
new file mode 100644
index 0000000..4c069db
--- /dev/null
+++ b/src/shrpx_worker.cc
@@ -0,0 +1,1347 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <netinet/udp.h>
+
+#include <cstdio>
+#include <memory>
+
+#include <openssl/rand.h>
+
+#ifdef HAVE_LIBBPF
+# include <bpf/bpf.h>
+# include <bpf/libbpf.h>
+#endif // HAVE_LIBBPF
+
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log_config.h"
+#include "shrpx_memcached_dispatcher.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic_listener.h"
+#endif // ENABLE_HTTP3
+#include "shrpx_connection_handler.h"
+#include "util.h"
+#include "template.h"
+#include "xsi_strerror.h"
+
+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
+
+namespace {
+void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ if (worker->get_worker_stat()->num_connections != 0) {
+ return;
+ }
+ auto mcpool = worker->get_mcpool();
+ if (mcpool->freelistsize == mcpool->poolsize) {
+ worker->get_mcpool()->clear();
+ }
+}
+} // namespace
+
+namespace {
+void proc_wev_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto worker = static_cast<Worker *>(w->data);
+ worker->process_events();
+}
+} // namespace
+
+DownstreamAddrGroup::DownstreamAddrGroup() : retired{false} {}
+
+DownstreamAddrGroup::~DownstreamAddrGroup() {}
+
+// DownstreamKey is used to index SharedDownstreamAddr in order to
+// find the same configuration.
+using DownstreamKey = std::tuple<
+ std::vector<
+ std::tuple<StringRef, StringRef, StringRef, size_t, size_t, Proto,
+ uint32_t, uint32_t, uint32_t, bool, bool, bool, bool>>,
+ bool, SessionAffinity, StringRef, StringRef, SessionAffinityCookieSecure,
+ SessionAffinityCookieStickiness, int64_t, int64_t, StringRef, bool>;
+
+namespace {
+DownstreamKey
+create_downstream_key(const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
+ const StringRef &mruby_file) {
+ DownstreamKey dkey;
+
+ auto &addrs = std::get<0>(dkey);
+ addrs.resize(shared_addr->addrs.size());
+ auto p = std::begin(addrs);
+ for (auto &a : shared_addr->addrs) {
+ std::get<0>(*p) = a.host;
+ std::get<1>(*p) = a.sni;
+ std::get<2>(*p) = a.group;
+ std::get<3>(*p) = a.fall;
+ std::get<4>(*p) = a.rise;
+ std::get<5>(*p) = a.proto;
+ std::get<6>(*p) = a.port;
+ std::get<7>(*p) = a.weight;
+ std::get<8>(*p) = a.group_weight;
+ std::get<9>(*p) = a.host_unix;
+ std::get<10>(*p) = a.tls;
+ std::get<11>(*p) = a.dns;
+ std::get<12>(*p) = a.upgrade_scheme;
+ ++p;
+ }
+ std::sort(std::begin(addrs), std::end(addrs));
+
+ std::get<1>(dkey) = shared_addr->redirect_if_not_tls;
+
+ auto &affinity = shared_addr->affinity;
+ std::get<2>(dkey) = affinity.type;
+ std::get<3>(dkey) = affinity.cookie.name;
+ std::get<4>(dkey) = affinity.cookie.path;
+ std::get<5>(dkey) = affinity.cookie.secure;
+ std::get<6>(dkey) = affinity.cookie.stickiness;
+ auto &timeout = shared_addr->timeout;
+ std::get<7>(dkey) = timeout.read;
+ std::get<8>(dkey) = timeout.write;
+ std::get<9>(dkey) = mruby_file;
+ std::get<10>(dkey) = shared_addr->dnf;
+
+ return dkey;
+}
+} // namespace
+
+Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+ SSL_CTX *tls_session_cache_memcached_ssl_ctx,
+ tls::CertLookupTree *cert_tree,
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree,
+ const uint8_t *cid_prefix, size_t cid_prefixlen,
+# ifdef HAVE_LIBBPF
+ size_t index,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ const std::shared_ptr<TicketKeys> &ticket_keys,
+ ConnectionHandler *conn_handler,
+ std::shared_ptr<DownstreamConfig> downstreamconf)
+ :
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ index_{index},
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+ randgen_(util::make_mt19937()),
+ worker_stat_{},
+ dns_tracker_(loop, get_config()->conn.downstream->family),
+#ifdef ENABLE_HTTP3
+ quic_upstream_addrs_{get_config()->conn.quic_listener.addrs},
+#endif // ENABLE_HTTP3
+ loop_(loop),
+ sv_ssl_ctx_(sv_ssl_ctx),
+ cl_ssl_ctx_(cl_ssl_ctx),
+ cert_tree_(cert_tree),
+ conn_handler_(conn_handler),
+#ifdef ENABLE_HTTP3
+ quic_sv_ssl_ctx_{quic_sv_ssl_ctx},
+ quic_cert_tree_{quic_cert_tree},
+ quic_conn_handler_{this},
+#endif // ENABLE_HTTP3
+ ticket_keys_(ticket_keys),
+ connect_blocker_(
+ std::make_unique<ConnectBlocker>(randgen_, loop_, nullptr, nullptr)),
+ graceful_shutdown_(false) {
+#ifdef ENABLE_HTTP3
+ std::copy_n(cid_prefix, cid_prefixlen, std::begin(cid_prefix_));
+#endif // ENABLE_HTTP3
+
+ ev_async_init(&w_, eventcb);
+ w_.data = this;
+ ev_async_start(loop_, &w_);
+
+ ev_timer_init(&mcpool_clear_timer_, mcpool_clear_cb, 0., 0.);
+ mcpool_clear_timer_.data = this;
+
+ ev_timer_init(&proc_wev_timer_, proc_wev_cb, 0., 0.);
+ proc_wev_timer_.data = this;
+
+ auto &session_cacheconf = get_config()->tls.session_cache;
+
+ if (!session_cacheconf.memcached.host.empty()) {
+ session_cache_memcached_dispatcher_ = std::make_unique<MemcachedDispatcher>(
+ &session_cacheconf.memcached.addr, loop,
+ tls_session_cache_memcached_ssl_ctx,
+ StringRef{session_cacheconf.memcached.host}, &mcpool_, randgen_);
+ }
+
+ replace_downstream_config(std::move(downstreamconf));
+}
+
+namespace {
+void ensure_enqueue_addr(
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater> &wgpq,
+ WeightGroup *wg, DownstreamAddr *addr) {
+ uint32_t cycle;
+ if (!wg->pq.empty()) {
+ auto &top = wg->pq.top();
+ cycle = top.cycle;
+ } else {
+ cycle = 0;
+ }
+
+ addr->cycle = cycle;
+ addr->pending_penalty = 0;
+ wg->pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle});
+ addr->queued = true;
+
+ if (!wg->queued) {
+ if (!wgpq.empty()) {
+ auto &top = wgpq.top();
+ cycle = top.cycle;
+ } else {
+ cycle = 0;
+ }
+
+ wg->cycle = cycle;
+ wg->pending_penalty = 0;
+ wgpq.push(WeightGroupEntry{wg, wg->seq, wg->cycle});
+ wg->queued = true;
+ }
+}
+} // namespace
+
+void Worker::replace_downstream_config(
+ std::shared_ptr<DownstreamConfig> downstreamconf) {
+ for (auto &g : downstream_addr_groups_) {
+ g->retired = true;
+
+ auto &shared_addr = g->shared_addr;
+ for (auto &addr : shared_addr->addrs) {
+ addr.dconn_pool->remove_all();
+ }
+ }
+
+ downstreamconf_ = downstreamconf;
+
+ // Making a copy is much faster with multiple thread on
+ // backendconfig API call.
+ auto groups = downstreamconf->addr_groups;
+
+ downstream_addr_groups_ =
+ std::vector<std::shared_ptr<DownstreamAddrGroup>>(groups.size());
+
+ std::map<DownstreamKey, size_t> addr_groups_indexer;
+#ifdef HAVE_MRUBY
+ // TODO It is a bit less efficient because
+ // mruby::create_mruby_context returns std::unique_ptr and we cannot
+ // use std::make_shared.
+ std::map<StringRef, std::shared_ptr<mruby::MRubyContext>> shared_mruby_ctxs;
+#endif // HAVE_MRUBY
+
+ for (size_t i = 0; i < groups.size(); ++i) {
+ auto &src = groups[i];
+ auto &dst = downstream_addr_groups_[i];
+
+ dst = std::make_shared<DownstreamAddrGroup>();
+ dst->pattern =
+ ImmutableString{std::begin(src.pattern), std::end(src.pattern)};
+
+ auto shared_addr = std::make_shared<SharedDownstreamAddr>();
+
+ shared_addr->addrs.resize(src.addrs.size());
+ shared_addr->affinity.type = src.affinity.type;
+ if (src.affinity.type == SessionAffinity::COOKIE) {
+ shared_addr->affinity.cookie.name =
+ make_string_ref(shared_addr->balloc, src.affinity.cookie.name);
+ if (!src.affinity.cookie.path.empty()) {
+ shared_addr->affinity.cookie.path =
+ make_string_ref(shared_addr->balloc, src.affinity.cookie.path);
+ }
+ shared_addr->affinity.cookie.secure = src.affinity.cookie.secure;
+ shared_addr->affinity.cookie.stickiness = src.affinity.cookie.stickiness;
+ }
+ shared_addr->affinity_hash = src.affinity_hash;
+ shared_addr->affinity_hash_map = src.affinity_hash_map;
+ shared_addr->redirect_if_not_tls = src.redirect_if_not_tls;
+ shared_addr->dnf = src.dnf;
+ shared_addr->timeout.read = src.timeout.read;
+ shared_addr->timeout.write = src.timeout.write;
+
+ for (size_t j = 0; j < src.addrs.size(); ++j) {
+ auto &src_addr = src.addrs[j];
+ auto &dst_addr = shared_addr->addrs[j];
+
+ dst_addr.addr = src_addr.addr;
+ dst_addr.host = make_string_ref(shared_addr->balloc, src_addr.host);
+ dst_addr.hostport =
+ make_string_ref(shared_addr->balloc, src_addr.hostport);
+ dst_addr.port = src_addr.port;
+ dst_addr.host_unix = src_addr.host_unix;
+ dst_addr.weight = src_addr.weight;
+ dst_addr.group = make_string_ref(shared_addr->balloc, src_addr.group);
+ dst_addr.group_weight = src_addr.group_weight;
+ dst_addr.affinity_hash = src_addr.affinity_hash;
+ dst_addr.proto = src_addr.proto;
+ dst_addr.tls = src_addr.tls;
+ dst_addr.sni = make_string_ref(shared_addr->balloc, src_addr.sni);
+ dst_addr.fall = src_addr.fall;
+ dst_addr.rise = src_addr.rise;
+ dst_addr.dns = src_addr.dns;
+ dst_addr.upgrade_scheme = src_addr.upgrade_scheme;
+ }
+
+#ifdef HAVE_MRUBY
+ auto mruby_ctx_it = shared_mruby_ctxs.find(src.mruby_file);
+ if (mruby_ctx_it == std::end(shared_mruby_ctxs)) {
+ shared_addr->mruby_ctx = mruby::create_mruby_context(src.mruby_file);
+ assert(shared_addr->mruby_ctx);
+ shared_mruby_ctxs.emplace(src.mruby_file, shared_addr->mruby_ctx);
+ } else {
+ shared_addr->mruby_ctx = (*mruby_ctx_it).second;
+ }
+#endif // HAVE_MRUBY
+
+ // share the connection if patterns have the same set of backend
+ // addresses.
+
+ auto dkey = create_downstream_key(shared_addr, src.mruby_file);
+ auto it = addr_groups_indexer.find(dkey);
+
+ if (it == std::end(addr_groups_indexer)) {
+ auto shared_addr_ptr = shared_addr.get();
+
+ for (auto &addr : shared_addr->addrs) {
+ addr.connect_blocker = std::make_unique<ConnectBlocker>(
+ randgen_, loop_, nullptr, [shared_addr_ptr, &addr]() {
+ if (!addr.queued) {
+ if (!addr.wg) {
+ return;
+ }
+ ensure_enqueue_addr(shared_addr_ptr->pq, addr.wg, &addr);
+ }
+ });
+
+ addr.live_check = std::make_unique<LiveCheck>(loop_, cl_ssl_ctx_, this,
+ &addr, randgen_);
+ }
+
+ size_t seq = 0;
+ for (auto &addr : shared_addr->addrs) {
+ addr.dconn_pool = std::make_unique<DownstreamConnectionPool>();
+ addr.seq = seq++;
+ }
+
+ util::shuffle(std::begin(shared_addr->addrs),
+ std::end(shared_addr->addrs), randgen_,
+ [](auto i, auto j) { std::swap((*i).seq, (*j).seq); });
+
+ if (shared_addr->affinity.type == SessionAffinity::NONE) {
+ std::map<StringRef, WeightGroup *> wgs;
+ size_t num_wgs = 0;
+ for (auto &addr : shared_addr->addrs) {
+ if (wgs.find(addr.group) == std::end(wgs)) {
+ ++num_wgs;
+ wgs.emplace(addr.group, nullptr);
+ }
+ }
+
+ shared_addr->wgs = std::vector<WeightGroup>(num_wgs);
+
+ for (auto &addr : shared_addr->addrs) {
+ auto &wg = wgs[addr.group];
+ if (wg == nullptr) {
+ wg = &shared_addr->wgs[--num_wgs];
+ wg->seq = num_wgs;
+ }
+
+ wg->weight = addr.group_weight;
+ wg->pq.push(DownstreamAddrEntry{&addr, addr.seq, addr.cycle});
+ addr.queued = true;
+ addr.wg = wg;
+ }
+
+ assert(num_wgs == 0);
+
+ for (auto &kv : wgs) {
+ shared_addr->pq.push(
+ WeightGroupEntry{kv.second, kv.second->seq, kv.second->cycle});
+ kv.second->queued = true;
+ }
+ }
+
+ dst->shared_addr = std::move(shared_addr);
+
+ addr_groups_indexer.emplace(std::move(dkey), i);
+ } else {
+ auto &g = *(std::begin(downstream_addr_groups_) + (*it).second);
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << dst->pattern << " shares the same backend group with "
+ << g->pattern;
+ }
+ dst->shared_addr = g->shared_addr;
+ }
+ }
+}
+
+Worker::~Worker() {
+ ev_async_stop(loop_, &w_);
+ ev_timer_stop(loop_, &mcpool_clear_timer_);
+ ev_timer_stop(loop_, &proc_wev_timer_);
+}
+
+void Worker::schedule_clear_mcpool() {
+ // libev manual says: "If the watcher is already active nothing will
+ // happen." Since we don't change any timeout here, we don't have
+ // to worry about querying ev_is_active.
+ ev_timer_start(loop_, &mcpool_clear_timer_);
+}
+
+void Worker::wait() {
+#ifndef NOTHREADS
+ fut_.get();
+#endif // !NOTHREADS
+}
+
+void Worker::run_async() {
+#ifndef NOTHREADS
+ fut_ = std::async(std::launch::async, [this] {
+ (void)reopen_log_files(get_config()->logging);
+ ev_run(loop_);
+ delete_log_config();
+ });
+#endif // !NOTHREADS
+}
+
+void Worker::send(WorkerEvent event) {
+ {
+ std::lock_guard<std::mutex> g(m_);
+
+ q_.emplace_back(std::move(event));
+ }
+
+ ev_async_send(loop_, &w_);
+}
+
+void Worker::process_events() {
+ WorkerEvent wev;
+ {
+ std::lock_guard<std::mutex> g(m_);
+
+ // Process event one at a time. This is important for
+ // WorkerEventType::NEW_CONNECTION event since accepting large
+ // number of new connections at once may delay time to 1st byte
+ // for existing connections.
+
+ if (q_.empty()) {
+ ev_timer_stop(loop_, &proc_wev_timer_);
+ return;
+ }
+
+ wev = std::move(q_.front());
+ q_.pop_front();
+ }
+
+ ev_timer_start(loop_, &proc_wev_timer_);
+
+ auto config = get_config();
+
+ auto worker_connections = config->conn.upstream.worker_connections;
+
+ switch (wev.type) {
+ case WorkerEventType::NEW_CONNECTION: {
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
+ << ", addrlen=" << wev.client_addrlen;
+ }
+
+ if (worker_stat_.num_connections >= worker_connections) {
+
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "Too many connections >= " << worker_connections;
+ }
+
+ close(wev.client_fd);
+
+ break;
+ }
+
+ auto client_handler =
+ tls::accept_connection(this, wev.client_fd, &wev.client_addr.sa,
+ wev.client_addrlen, wev.faddr);
+ if (!client_handler) {
+ if (LOG_ENABLED(INFO)) {
+ WLOG(ERROR, this) << "ClientHandler creation failed";
+ }
+ close(wev.client_fd);
+ break;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created";
+ }
+
+ break;
+ }
+ case WorkerEventType::REOPEN_LOG:
+ WLOG(NOTICE, this) << "Reopening log files: worker process (thread " << this
+ << ")";
+
+ reopen_log_files(config->logging);
+
+ break;
+ case WorkerEventType::GRACEFUL_SHUTDOWN:
+ WLOG(NOTICE, this) << "Graceful shutdown commencing";
+
+ graceful_shutdown_ = true;
+
+ if (worker_stat_.num_connections == 0 &&
+ worker_stat_.num_close_waits == 0) {
+ ev_break(loop_);
+
+ return;
+ }
+
+ break;
+ case WorkerEventType::REPLACE_DOWNSTREAM:
+ WLOG(NOTICE, this) << "Replace downstream";
+
+ replace_downstream_config(wev.downstreamconf);
+
+ break;
+#ifdef ENABLE_HTTP3
+ case WorkerEventType::QUIC_PKT_FORWARD: {
+ const UpstreamAddr *faddr;
+
+ if (wev.quic_pkt->upstream_addr_index == static_cast<size_t>(-1)) {
+ faddr = find_quic_upstream_addr(wev.quic_pkt->local_addr);
+ if (faddr == nullptr) {
+ LOG(ERROR) << "No suitable upstream address found";
+
+ break;
+ }
+ } else if (quic_upstream_addrs_.size() <=
+ wev.quic_pkt->upstream_addr_index) {
+ LOG(ERROR) << "upstream_addr_index is too large";
+
+ break;
+ } else {
+ faddr = &quic_upstream_addrs_[wev.quic_pkt->upstream_addr_index];
+ }
+
+ quic_conn_handler_.handle_packet(
+ faddr, wev.quic_pkt->remote_addr, wev.quic_pkt->local_addr,
+ wev.quic_pkt->pi, wev.quic_pkt->data.data(), wev.quic_pkt->data.size());
+
+ break;
+ }
+#endif // ENABLE_HTTP3
+ default:
+ if (LOG_ENABLED(INFO)) {
+ WLOG(INFO, this) << "unknown event type " << static_cast<int>(wev.type);
+ }
+ }
+}
+
+tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; }
+
+#ifdef ENABLE_HTTP3
+tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const {
+ return quic_cert_tree_;
+}
+#endif // ENABLE_HTTP3
+
+std::shared_ptr<TicketKeys> Worker::get_ticket_keys() {
+#ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire);
+#else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(ticket_keys_m_);
+ return ticket_keys_;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+
+void Worker::set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys) {
+#ifdef HAVE_ATOMIC_STD_SHARED_PTR
+ // This is single writer
+ std::atomic_store_explicit(&ticket_keys_, std::move(ticket_keys),
+ std::memory_order_release);
+#else // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::lock_guard<std::mutex> g(ticket_keys_m_);
+ ticket_keys_ = std::move(ticket_keys);
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+}
+
+WorkerStat *Worker::get_worker_stat() { return &worker_stat_; }
+
+struct ev_loop *Worker::get_loop() const { return loop_; }
+
+SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; }
+
+SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; }
+
+#ifdef ENABLE_HTTP3
+SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; }
+#endif // ENABLE_HTTP3
+
+void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; }
+
+bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; }
+
+MemchunkPool *Worker::get_mcpool() { return &mcpool_; }
+
+MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
+ return session_cache_memcached_dispatcher_.get();
+}
+
+std::mt19937 &Worker::get_randgen() { return randgen_; }
+
+#ifdef HAVE_MRUBY
+int Worker::create_mruby_context() {
+ mruby_ctx_ = mruby::create_mruby_context(StringRef{get_config()->mruby_file});
+ if (!mruby_ctx_) {
+ return -1;
+ }
+
+ return 0;
+}
+
+mruby::MRubyContext *Worker::get_mruby_context() const {
+ return mruby_ctx_.get();
+}
+#endif // HAVE_MRUBY
+
+std::vector<std::shared_ptr<DownstreamAddrGroup>> &
+Worker::get_downstream_addr_groups() {
+ return downstream_addr_groups_;
+}
+
+ConnectBlocker *Worker::get_connect_blocker() const {
+ return connect_blocker_.get();
+}
+
+const DownstreamConfig *Worker::get_downstream_config() const {
+ return downstreamconf_.get();
+}
+
+ConnectionHandler *Worker::get_connection_handler() const {
+ return conn_handler_;
+}
+
+#ifdef ENABLE_HTTP3
+QUICConnectionHandler *Worker::get_quic_connection_handler() {
+ return &quic_conn_handler_;
+}
+#endif // ENABLE_HTTP3
+
+DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
+
+#ifdef ENABLE_HTTP3
+# ifdef HAVE_LIBBPF
+bool Worker::should_attach_bpf() const {
+ auto config = get_config();
+ auto &quicconf = config->quic;
+ auto &apiconf = config->api;
+
+ if (quicconf.bpf.disabled) {
+ return false;
+ }
+
+ if (!config->single_thread && apiconf.enabled) {
+ return index_ == 1;
+ }
+
+ return index_ == 0;
+}
+
+bool Worker::should_update_bpf_map() const {
+ auto config = get_config();
+ auto &quicconf = config->quic;
+
+ return !quicconf.bpf.disabled;
+}
+
+uint32_t Worker::compute_sk_index() const {
+ auto config = get_config();
+ auto &apiconf = config->api;
+
+ if (!config->single_thread && apiconf.enabled) {
+ return index_ - 1;
+ }
+
+ return index_;
+}
+# endif // HAVE_LIBBPF
+
+int Worker::setup_quic_server_socket() {
+ size_t n = 0;
+
+ for (auto &addr : quic_upstream_addrs_) {
+ assert(!addr.host_unix);
+ if (create_quic_server_socket(addr) != 0) {
+ return -1;
+ }
+
+ // Make sure that each endpoint has a unique address.
+ for (size_t i = 0; i < n; ++i) {
+ const auto &a = quic_upstream_addrs_[i];
+
+ if (addr.hostport == a.hostport) {
+ LOG(FATAL)
+ << "QUIC frontend endpoint must be unique: a duplicate found for "
+ << addr.hostport;
+
+ return -1;
+ }
+ }
+
+ ++n;
+
+ quic_listeners_.emplace_back(std::make_unique<QUICListener>(&addr, this));
+ }
+
+ return 0;
+}
+
+int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ int fd = -1;
+ int rv;
+
+ auto service = util::utos(faddr.port);
+ addrinfo hints{};
+ hints.ai_family = faddr.family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+# ifdef AI_ADDRCONFIG
+ hints.ai_flags |= AI_ADDRCONFIG;
+# endif // AI_ADDRCONFIG
+
+ auto node =
+ faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str();
+
+ addrinfo *res, *rp;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+# ifdef AI_ADDRCONFIG
+ if (rv != 0) {
+ // Retry without AI_ADDRCONFIG
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ rv = getaddrinfo(node, service.c_str(), &hints, &res);
+ }
+# endif // AI_ADDRCONFIG
+ if (rv != 0) {
+ LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
+ << " address for " << faddr.host << ", port " << faddr.port
+ << ": " << gai_strerror(rv);
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ std::array<char, NI_MAXHOST> host;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
+ nullptr, 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
+ continue;
+ }
+
+# ifdef SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC,
+ rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+# else // !SOCK_NONBLOCK
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ auto error = errno;
+ LOG(WARN) << "socket() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ continue;
+ }
+ util::make_socket_nonblocking(fd);
+ util::make_socket_closeonexec(fd);
+# endif // !SOCK_NONBLOCK
+
+ int val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set SO_REUSEPORT option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (faddr.family == AF_INET6) {
+# ifdef IPV6_V6ONLY
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // IPV6_V6ONLY
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN)
+ << "Failed to set IPV6_RECVPKTINFO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IPV6_RECVTCLASS option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
+ int mtu_disc = IPV6_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_disc,
+ static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
+ auto error = errno;
+ LOG(WARN)
+ << "Failed to set IPV6_MTU_DISCOVER option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ } else {
+ if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_PKTINFO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_RECVTOS option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ int mtu_disc = IP_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu_disc,
+ static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set IP_MTU_DISCOVER option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+ }
+
+# ifdef UDP_GRO
+ if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) == -1) {
+ auto error = errno;
+ LOG(WARN) << "Failed to set UDP_GRO option to listener socket: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+# endif // UDP_GRO
+
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ auto error = errno;
+ LOG(WARN) << "bind() syscall failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ continue;
+ }
+
+# ifdef HAVE_LIBBPF
+ auto config = get_config();
+
+ auto &quic_bpf_refs = conn_handler_->get_quic_bpf_refs();
+
+ if (should_attach_bpf()) {
+ auto &bpfconf = config->quic.bpf;
+
+ auto obj = bpf_object__open_file(bpfconf.prog_file.c_str(), nullptr);
+ if (!obj) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to open bpf object file: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_object__load(obj);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to load bpf object file: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto prog = bpf_object__find_program_by_name(obj, "select_reuseport");
+ if (!prog) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to find sk_reuseport program: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto &ref = quic_bpf_refs[faddr.index];
+
+ ref.obj = obj;
+
+ ref.reuseport_array =
+ bpf_object__find_map_by_name(obj, "reuseport_array");
+ if (!ref.reuseport_array) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get reuseport_array: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ ref.cid_prefix_map = bpf_object__find_map_by_name(obj, "cid_prefix_map");
+ if (!ref.cid_prefix_map) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get cid_prefix_map: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto sk_info = bpf_object__find_map_by_name(obj, "sk_info");
+ if (!sk_info) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to get sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ constexpr uint32_t zero = 0;
+ uint64_t num_socks = config->num_worker;
+
+ rv = bpf_map__update_elem(sk_info, &zero, sizeof(zero), &num_socks,
+ sizeof(num_socks), BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ constexpr uint32_t key_high_idx = 1;
+ constexpr uint32_t key_low_idx = 2;
+
+ auto &qkms = conn_handler_->get_quic_keying_materials();
+ auto &qkm = qkms->keying_materials.front();
+
+ rv = bpf_map__update_elem(sk_info, &key_high_idx, sizeof(key_high_idx),
+ qkm.cid_encryption_key.data(),
+ qkm.cid_encryption_key.size() / 2, BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update key_high_idx sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_map__update_elem(sk_info, &key_low_idx, sizeof(key_low_idx),
+ qkm.cid_encryption_key.data() +
+ qkm.cid_encryption_key.size() / 2,
+ qkm.cid_encryption_key.size() / 2, BPF_ANY);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update key_low_idx sk_info: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ auto prog_fd = bpf_program__fd(prog);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd,
+ static_cast<socklen_t>(sizeof(prog_fd))) == -1) {
+ LOG(FATAL) << "Failed to attach bpf program: "
+ << xsi_strerror(errno, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+ }
+
+ if (should_update_bpf_map()) {
+ const auto &ref = quic_bpf_refs[faddr.index];
+ auto sk_index = compute_sk_index();
+
+ rv = bpf_map__update_elem(ref.reuseport_array, &sk_index,
+ sizeof(sk_index), &fd, sizeof(fd), BPF_NOEXIST);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update reuseport_array: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+
+ rv = bpf_map__update_elem(ref.cid_prefix_map, cid_prefix_.data(),
+ cid_prefix_.size(), &sk_index, sizeof(sk_index),
+ BPF_NOEXIST);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Failed to update cid_prefix_map: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ close(fd);
+ return -1;
+ }
+ }
+# endif // HAVE_LIBBPF
+
+ break;
+ }
+
+ if (!rp) {
+ LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
+ << " socket failed";
+
+ return -1;
+ }
+
+ faddr.fd = fd;
+ faddr.hostport = util::make_http_hostport(mod_config()->balloc,
+ StringRef{host.data()}, faddr.port);
+
+ LOG(NOTICE) << "Listening on " << faddr.hostport << ", quic";
+
+ return 0;
+}
+
+const uint8_t *Worker::get_cid_prefix() const { return cid_prefix_.data(); }
+
+const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
+ std::array<char, NI_MAXHOST> host;
+
+ auto rv = getnameinfo(&local_addr.su.sa, local_addr.len, host.data(),
+ host.size(), nullptr, 0, NI_NUMERICHOST);
+ if (rv != 0) {
+ LOG(ERROR) << "getnameinfo: " << gai_strerror(rv);
+
+ return nullptr;
+ }
+
+ uint16_t port;
+
+ switch (local_addr.su.sa.sa_family) {
+ case AF_INET:
+ port = htons(local_addr.su.in.sin_port);
+
+ break;
+ case AF_INET6:
+ port = htons(local_addr.su.in6.sin6_port);
+
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ std::array<char, util::max_hostport> hostport_buf;
+
+ auto hostport = util::make_http_hostport(std::begin(hostport_buf),
+ StringRef{host.data()}, port);
+ const UpstreamAddr *fallback_faddr = nullptr;
+
+ for (auto &faddr : quic_upstream_addrs_) {
+ if (faddr.hostport == hostport) {
+ return &faddr;
+ }
+
+ if (faddr.port != port || faddr.family != local_addr.su.sa.sa_family) {
+ continue;
+ }
+
+ if (faddr.port == 443 || faddr.port == 80) {
+ switch (faddr.family) {
+ case AF_INET:
+ if (util::streq(faddr.hostport, StringRef::from_lit("0.0.0.0"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ case AF_INET6:
+ if (util::streq(faddr.hostport, StringRef::from_lit("[::]"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ default:
+ assert(0);
+ }
+ } else {
+ switch (faddr.family) {
+ case AF_INET:
+ if (util::starts_with(faddr.hostport,
+ StringRef::from_lit("0.0.0.0:"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ case AF_INET6:
+ if (util::starts_with(faddr.hostport, StringRef::from_lit("[::]:"))) {
+ fallback_faddr = &faddr;
+ }
+
+ break;
+ default:
+ assert(0);
+ }
+ }
+ }
+
+ return fallback_faddr;
+}
+#endif // ENABLE_HTTP3
+
+namespace {
+size_t match_downstream_addr_group_host(
+ const RouterConfig &routerconf, const StringRef &host,
+ const StringRef &path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc) {
+
+ const auto &router = routerconf.router;
+ const auto &rev_wildcard_router = routerconf.rev_wildcard_router;
+ const auto &wildcard_patterns = routerconf.wildcard_patterns;
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Perform mapping selection, using host=" << host
+ << ", path=" << path;
+ }
+
+ auto group = router.match(host, path);
+ if (group != -1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found pattern with query " << host << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+ return group;
+ }
+
+ if (!wildcard_patterns.empty() && !host.empty()) {
+ auto rev_host_src = make_byte_ref(balloc, host.size() - 1);
+ auto ep =
+ std::copy(std::begin(host) + 1, std::end(host), rev_host_src.base);
+ std::reverse(rev_host_src.base, ep);
+ auto rev_host = StringRef{rev_host_src.base, ep};
+
+ ssize_t best_group = -1;
+ const RNode *last_node = nullptr;
+
+ for (;;) {
+ size_t nread = 0;
+ auto wcidx =
+ rev_wildcard_router.match_prefix(&nread, &last_node, rev_host);
+ if (wcidx == -1) {
+ break;
+ }
+
+ rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)};
+
+ auto &wc = wildcard_patterns[wcidx];
+ auto group = wc.router.match(StringRef{}, path);
+ if (group != -1) {
+ // We sorted wildcard_patterns in a way that first match is the
+ // longest host pattern.
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found wildcard pattern with query " << host << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+
+ best_group = group;
+ }
+ }
+
+ if (best_group != -1) {
+ return best_group;
+ }
+ }
+
+ group = router.match(StringRef::from_lit(""), path);
+ if (group != -1) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Found pattern with query " << path
+ << ", matched pattern=" << groups[group]->pattern;
+ }
+ return group;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "None match. Use catch-all pattern";
+ }
+ return catch_all;
+}
+} // namespace
+
+size_t match_downstream_addr_group(
+ const RouterConfig &routerconf, const StringRef &hostport,
+ const StringRef &raw_path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc) {
+ if (std::find(std::begin(hostport), std::end(hostport), '/') !=
+ std::end(hostport)) {
+ // We use '/' specially, and if '/' is included in host, it breaks
+ // our code. Select catch-all case.
+ return catch_all;
+ }
+
+ auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
+ auto query = std::find(std::begin(raw_path), fragment, '?');
+ auto path = StringRef{std::begin(raw_path), query};
+
+ if (path.empty() || path[0] != '/') {
+ path = StringRef::from_lit("/");
+ }
+
+ if (hostport.empty()) {
+ return match_downstream_addr_group_host(routerconf, hostport, path, groups,
+ catch_all, balloc);
+ }
+
+ StringRef host;
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return catch_all;
+ }
+ if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
+ return catch_all;
+ }
+ host = StringRef{std::begin(hostport), p + 1};
+ } else {
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return catch_all;
+ }
+ host = StringRef{std::begin(hostport), p};
+ }
+
+ if (std::find_if(std::begin(host), std::end(host), [](char c) {
+ return 'A' <= c || c <= 'Z';
+ }) != std::end(host)) {
+ auto low_host = make_byte_ref(balloc, host.size() + 1);
+ auto ep = std::copy(std::begin(host), std::end(host), low_host.base);
+ *ep = '\0';
+ util::inp_strlower(low_host.base, ep);
+ host = StringRef{low_host.base, ep};
+ }
+ return match_downstream_addr_group_host(routerconf, host, path, groups,
+ catch_all, balloc);
+}
+
+void downstream_failure(DownstreamAddr *addr, const Address *raddr) {
+ const auto &connect_blocker = addr->connect_blocker;
+
+ if (connect_blocker->in_offline()) {
+ return;
+ }
+
+ connect_blocker->on_failure();
+
+ if (addr->fall == 0) {
+ return;
+ }
+
+ auto fail_count = connect_blocker->get_fail_count();
+
+ if (fail_count >= addr->fall) {
+ if (raddr) {
+ LOG(WARN) << "Could not connect to " << util::to_numeric_addr(raddr)
+ << " " << fail_count
+ << " times in a row; considered as offline";
+ } else {
+ LOG(WARN) << "Could not connect to " << addr->host << ":" << addr->port
+ << " " << fail_count
+ << " times in a row; considered as offline";
+ }
+
+ connect_blocker->offline();
+
+ if (addr->rise) {
+ addr->live_check->schedule();
+ }
+ }
+}
+
+#ifdef ENABLE_HTTP3
+int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id) {
+ auto p = std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, cid_prefix);
+
+ if (RAND_bytes(p, SHRPX_QUIC_CID_PREFIXLEN - SHRPX_QUIC_SERVER_IDLEN) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h
new file mode 100644
index 0000000..3cc7b57
--- /dev/null
+++ b/src/shrpx_worker.h
@@ -0,0 +1,480 @@
+/*
+ * 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 <vector>
+#include <random>
+#include <unordered_map>
+#include <deque>
+#include <thread>
+#include <queue>
+#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"
+#include "memchunk.h"
+#include "shrpx_tls.h"
+#include "shrpx_live_check.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_dns_tracker.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic_connection_handler.h"
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+#include "allocator.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+class MemcachedDispatcher;
+struct UpstreamAddr;
+class ConnectionHandler;
+#ifdef ENABLE_HTTP3
+class QUICListener;
+#endif // ENABLE_HTTP3
+
+#ifdef HAVE_MRUBY
+namespace mruby {
+
+class MRubyContext;
+
+} // namespace mruby
+#endif // HAVE_MRUBY
+
+namespace tls {
+class CertLookupTree;
+} // namespace tls
+
+struct WeightGroup;
+
+struct DownstreamAddr {
+ Address addr;
+ // backend address. If |host_unix| is true, this is UNIX domain
+ // socket path.
+ StringRef host;
+ StringRef hostport;
+ // backend port. 0 if |host_unix| is true.
+ uint16_t port;
+ // true if |host| contains UNIX domain socket path.
+ bool host_unix;
+
+ // sni field to send remote server if TLS is enabled.
+ StringRef sni;
+
+ std::unique_ptr<ConnectBlocker> connect_blocker;
+ std::unique_ptr<LiveCheck> live_check;
+ // Connection pool for this particular address if session affinity
+ // is enabled
+ std::unique_ptr<DownstreamConnectionPool> dconn_pool;
+ size_t fall;
+ size_t rise;
+ // Client side TLS session cache
+ tls::TLSSessionCache tls_session_cache;
+ // List of Http2Session which is not fully utilized (i.e., the
+ // server advertised maximum concurrency is not reached). We will
+ // coalesce as much stream as possible in one Http2Session to fully
+ // utilize TCP connection.
+ DList<Http2Session> http2_extra_freelist;
+ WeightGroup *wg;
+ // total number of streams created in HTTP/2 connections for this
+ // address.
+ size_t num_dconn;
+ // the sequence number of this address to randomize the order access
+ // threads.
+ size_t seq;
+ // Application protocol used in this backend
+ Proto proto;
+ // cycle is used to prioritize this address. Lower value takes
+ // higher priority.
+ uint32_t cycle;
+ // penalty which is applied to the next cycle calculation.
+ uint32_t pending_penalty;
+ // Weight of this address inside a weight group. Its range is [1,
+ // 256], inclusive.
+ uint32_t weight;
+ // name of group which this address belongs to.
+ StringRef group;
+ // Weight of the weight group which this address belongs to. Its
+ // range is [1, 256], inclusive.
+ uint32_t group_weight;
+ // affinity hash for this address. It is assigned when strict
+ // stickiness is enabled.
+ uint32_t affinity_hash;
+ // true if TLS is used in this backend
+ bool tls;
+ // true if dynamic DNS is enabled
+ bool dns;
+ // true if :scheme pseudo header field should be upgraded to secure
+ // variant (e.g., "https") when forwarding request to a backend
+ // connected by TLS connection.
+ bool upgrade_scheme;
+ // true if this address is queued.
+ bool queued;
+};
+
+constexpr uint32_t MAX_DOWNSTREAM_ADDR_WEIGHT = 256;
+
+struct DownstreamAddrEntry {
+ DownstreamAddr *addr;
+ size_t seq;
+ uint32_t cycle;
+};
+
+struct DownstreamAddrEntryGreater {
+ bool operator()(const DownstreamAddrEntry &lhs,
+ const DownstreamAddrEntry &rhs) const {
+ auto d = lhs.cycle - rhs.cycle;
+ if (d == 0) {
+ return rhs.seq < lhs.seq;
+ }
+ return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1;
+ }
+};
+
+struct WeightGroup {
+ std::priority_queue<DownstreamAddrEntry, std::vector<DownstreamAddrEntry>,
+ DownstreamAddrEntryGreater>
+ pq;
+ size_t seq;
+ uint32_t weight;
+ uint32_t cycle;
+ uint32_t pending_penalty;
+ // true if this object is queued.
+ bool queued;
+};
+
+struct WeightGroupEntry {
+ WeightGroup *wg;
+ size_t seq;
+ uint32_t cycle;
+};
+
+struct WeightGroupEntryGreater {
+ bool operator()(const WeightGroupEntry &lhs,
+ const WeightGroupEntry &rhs) const {
+ auto d = lhs.cycle - rhs.cycle;
+ if (d == 0) {
+ return rhs.seq < lhs.seq;
+ }
+ return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1;
+ }
+};
+
+struct SharedDownstreamAddr {
+ SharedDownstreamAddr()
+ : balloc(1024, 1024),
+ affinity{SessionAffinity::NONE},
+ redirect_if_not_tls{false},
+ dnf{false},
+ timeout{} {}
+
+ SharedDownstreamAddr(const SharedDownstreamAddr &) = delete;
+ SharedDownstreamAddr(SharedDownstreamAddr &&) = delete;
+ SharedDownstreamAddr &operator=(const SharedDownstreamAddr &) = delete;
+ SharedDownstreamAddr &operator=(SharedDownstreamAddr &&) = delete;
+
+ BlockAllocator balloc;
+ std::vector<DownstreamAddr> addrs;
+ std::vector<WeightGroup> wgs;
+ std::priority_queue<WeightGroupEntry, std::vector<WeightGroupEntry>,
+ WeightGroupEntryGreater>
+ pq;
+ // Bunch of session affinity hash. Only used if affinity ==
+ // SessionAffinity::IP.
+ std::vector<AffinityHash> affinity_hash;
+ // Maps affinity hash of each DownstreamAddr to its index in addrs.
+ // It is only assigned when strict stickiness is enabled.
+ std::unordered_map<uint32_t, size_t> affinity_hash_map;
+#ifdef HAVE_MRUBY
+ std::shared_ptr<mruby::MRubyContext> mruby_ctx;
+#endif // HAVE_MRUBY
+ // Configuration for session affinity
+ AffinityConfig affinity;
+ // Session affinity
+ // true if this group requires that client connection must be TLS,
+ // and the request must be redirected to https URI.
+ bool redirect_if_not_tls;
+ // true if a request should not be forwarded to a backend.
+ bool dnf;
+ // Timeouts for backend connection.
+ struct {
+ ev_tstamp read;
+ ev_tstamp write;
+ } timeout;
+};
+
+struct DownstreamAddrGroup {
+ DownstreamAddrGroup();
+ ~DownstreamAddrGroup();
+
+ DownstreamAddrGroup(const DownstreamAddrGroup &) = delete;
+ DownstreamAddrGroup(DownstreamAddrGroup &&) = delete;
+ DownstreamAddrGroup &operator=(const DownstreamAddrGroup &) = delete;
+ DownstreamAddrGroup &operator=(DownstreamAddrGroup &&) = delete;
+
+ ImmutableString pattern;
+ std::shared_ptr<SharedDownstreamAddr> shared_addr;
+ // true if this group is no longer used for new request. If this is
+ // true, the connection made using one of address in shared_addr
+ // must not be pooled.
+ bool retired;
+};
+
+struct WorkerStat {
+ size_t num_connections;
+ size_t num_close_waits;
+};
+
+#ifdef ENABLE_HTTP3
+struct QUICPacket {
+ QUICPacket(size_t upstream_addr_index, const Address &remote_addr,
+ const Address &local_addr, const ngtcp2_pkt_info &pi,
+ const uint8_t *data, size_t datalen)
+ : upstream_addr_index{upstream_addr_index},
+ remote_addr{remote_addr},
+ local_addr{local_addr},
+ pi{pi},
+ data{data, data + datalen} {}
+ QUICPacket() : upstream_addr_index{}, remote_addr{}, local_addr{}, pi{} {}
+ size_t upstream_addr_index;
+ Address remote_addr;
+ Address local_addr;
+ ngtcp2_pkt_info pi;
+ std::vector<uint8_t> data;
+};
+#endif // ENABLE_HTTP3
+
+enum class WorkerEventType {
+ NEW_CONNECTION = 0x01,
+ REOPEN_LOG = 0x02,
+ GRACEFUL_SHUTDOWN = 0x03,
+ REPLACE_DOWNSTREAM = 0x04,
+#ifdef ENABLE_HTTP3
+ QUIC_PKT_FORWARD = 0x05,
+#endif // ENABLE_HTTP3
+};
+
+struct WorkerEvent {
+ WorkerEventType type;
+ struct {
+ sockaddr_union client_addr;
+ size_t client_addrlen;
+ int client_fd;
+ const UpstreamAddr *faddr;
+ };
+ std::shared_ptr<TicketKeys> ticket_keys;
+ std::shared_ptr<DownstreamConfig> downstreamconf;
+#ifdef ENABLE_HTTP3
+ std::unique_ptr<QUICPacket> quic_pkt;
+#endif // ENABLE_HTTP3
+};
+
+class Worker {
+public:
+ Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+ SSL_CTX *tls_session_cache_memcached_ssl_ctx,
+ tls::CertLookupTree *cert_tree,
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree,
+ const uint8_t *cid_prefix, size_t cid_prefixlen,
+# ifdef HAVE_LIBBPF
+ size_t index,
+# endif // HAVE_LIBBPF
+#endif // ENABLE_HTTP3
+ const std::shared_ptr<TicketKeys> &ticket_keys,
+ ConnectionHandler *conn_handler,
+ std::shared_ptr<DownstreamConfig> downstreamconf);
+ ~Worker();
+ void run_async();
+ void wait();
+ void process_events();
+ void send(WorkerEvent event);
+
+ tls::CertLookupTree *get_cert_lookup_tree() const;
+#ifdef ENABLE_HTTP3
+ tls::CertLookupTree *get_quic_cert_lookup_tree() const;
+#endif // ENABLE_HTTP3
+
+ // These 2 functions make a lock m_ to get/set ticket keys
+ // atomically.
+ std::shared_ptr<TicketKeys> get_ticket_keys();
+ void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
+
+ WorkerStat *get_worker_stat();
+ struct ev_loop *get_loop() const;
+ SSL_CTX *get_sv_ssl_ctx() const;
+ SSL_CTX *get_cl_ssl_ctx() const;
+#ifdef ENABLE_HTTP3
+ SSL_CTX *get_quic_sv_ssl_ctx() const;
+#endif // ENABLE_HTTP3
+
+ void set_graceful_shutdown(bool f);
+ bool get_graceful_shutdown() const;
+
+ MemchunkPool *get_mcpool();
+ void schedule_clear_mcpool();
+
+ MemcachedDispatcher *get_session_cache_memcached_dispatcher();
+
+ std::mt19937 &get_randgen();
+
+#ifdef HAVE_MRUBY
+ int create_mruby_context();
+
+ mruby::MRubyContext *get_mruby_context() const;
+#endif // HAVE_MRUBY
+
+ std::vector<std::shared_ptr<DownstreamAddrGroup>> &
+ get_downstream_addr_groups();
+
+ ConnectBlocker *get_connect_blocker() const;
+
+ const DownstreamConfig *get_downstream_config() const;
+
+ void
+ replace_downstream_config(std::shared_ptr<DownstreamConfig> downstreamconf);
+
+ ConnectionHandler *get_connection_handler() const;
+
+#ifdef ENABLE_HTTP3
+ QUICConnectionHandler *get_quic_connection_handler();
+
+ int setup_quic_server_socket();
+
+ const uint8_t *get_cid_prefix() const;
+
+# ifdef HAVE_LIBBPF
+ bool should_attach_bpf() const;
+
+ bool should_update_bpf_map() const;
+
+ uint32_t compute_sk_index() const;
+# endif // HAVE_LIBBPF
+
+ int create_quic_server_socket(UpstreamAddr &addr);
+
+ // Returns a pointer to UpstreamAddr which matches |local_addr|.
+ const UpstreamAddr *find_quic_upstream_addr(const Address &local_addr);
+#endif // ENABLE_HTTP3
+
+ DNSTracker *get_dns_tracker();
+
+private:
+#ifndef NOTHREADS
+ std::future<void> fut_;
+#endif // NOTHREADS
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ // Unique index of this worker.
+ size_t index_;
+#endif // ENABLE_HTTP3 && HAVE_LIBBPF
+ std::mutex m_;
+ std::deque<WorkerEvent> q_;
+ std::mt19937 randgen_;
+ ev_async w_;
+ ev_timer mcpool_clear_timer_;
+ ev_timer proc_wev_timer_;
+ MemchunkPool mcpool_;
+ WorkerStat worker_stat_;
+ DNSTracker dns_tracker_;
+
+#ifdef ENABLE_HTTP3
+ std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN> cid_prefix_;
+ std::vector<UpstreamAddr> quic_upstream_addrs_;
+ std::vector<std::unique_ptr<QUICListener>> quic_listeners_;
+#endif // ENABLE_HTTP3
+
+ std::shared_ptr<DownstreamConfig> downstreamconf_;
+ std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
+#ifdef HAVE_MRUBY
+ std::unique_ptr<mruby::MRubyContext> mruby_ctx_;
+#endif // HAVE_MRUBY
+ struct ev_loop *loop_;
+
+ // Following fields are shared across threads if
+ // get_config()->tls_ctx_per_worker == true.
+ SSL_CTX *sv_ssl_ctx_;
+ SSL_CTX *cl_ssl_ctx_;
+ tls::CertLookupTree *cert_tree_;
+ ConnectionHandler *conn_handler_;
+#ifdef ENABLE_HTTP3
+ SSL_CTX *quic_sv_ssl_ctx_;
+ tls::CertLookupTree *quic_cert_tree_;
+
+ QUICConnectionHandler quic_conn_handler_;
+#endif // ENABLE_HTTP3
+
+#ifndef HAVE_ATOMIC_STD_SHARED_PTR
+ std::mutex ticket_keys_m_;
+#endif // !HAVE_ATOMIC_STD_SHARED_PTR
+ std::shared_ptr<TicketKeys> ticket_keys_;
+ std::vector<std::shared_ptr<DownstreamAddrGroup>> downstream_addr_groups_;
+ // Worker level blocker for downstream connection. For example,
+ // this is used when file descriptor is exhausted.
+ std::unique_ptr<ConnectBlocker> connect_blocker_;
+
+ bool graceful_shutdown_;
+};
+
+// Selects group based on request's |hostport| and |path|. |hostport|
+// is the value taken from :authority or host header field, and may
+// contain port. The |path| may contain query part. We require the
+// catch-all pattern in place, so this function always selects one
+// group. The catch-all group index is given in |catch_all|. All
+// patterns are given in |groups|.
+size_t match_downstream_addr_group(
+ const RouterConfig &routerconfig, const StringRef &hostport,
+ const StringRef &path,
+ const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
+ size_t catch_all, BlockAllocator &balloc);
+
+// Calls this function if connecting to backend failed. |raddr| is
+// the actual address used to connect to backend, and it could be
+// nullptr. This function may schedule live check.
+void downstream_failure(DownstreamAddr *addr, const Address *raddr);
+
+#ifdef ENABLE_HTTP3
+// Creates unpredictable SHRPX_QUIC_CID_PREFIXLEN bytes sequence which
+// is used as a prefix of QUIC Connection ID. This function returns
+// -1 on failure. |server_id| must be 2 bytes long.
+int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id);
+#endif // ENABLE_HTTP3
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_H
diff --git a/src/shrpx_worker_process.cc b/src/shrpx_worker_process.cc
new file mode 100644
index 0000000..05eac02
--- /dev/null
+++ b/src/shrpx_worker_process.cc
@@ -0,0 +1,701 @@
+/*
+ * 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_worker_process.h"
+
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <grp.h>
+
+#include <cinttypes>
+#include <cstdlib>
+
+#include <openssl/rand.h>
+
+#include <ev.h>
+
+#include <ares.h>
+
+#include "shrpx_config.h"
+#include "shrpx_connection_handler.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_accept_handler.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_memcached_dispatcher.h"
+#include "shrpx_memcached_request.h"
+#include "shrpx_process.h"
+#include "shrpx_tls.h"
+#include "shrpx_log.h"
+#include "util.h"
+#include "app_helper.h"
+#include "template.h"
+#include "xsi_strerror.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void drop_privileges(
+#ifdef HAVE_NEVERBLEED
+ neverbleed_t *nb
+#endif // HAVE_NEVERBLEED
+) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto config = get_config();
+
+ if (getuid() == 0 && config->uid != 0) {
+#ifdef HAVE_NEVERBLEED
+ if (nb) {
+ neverbleed_setuidgid(nb, config->user.c_str(), 1);
+ }
+#endif // HAVE_NEVERBLEED
+
+ if (initgroups(config->user.c_str(), config->gid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change supplementary groups: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setgid(config->gid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change gid: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setuid(config->uid) != 0) {
+ auto error = errno;
+ LOG(FATAL) << "Could not change uid: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+ exit(EXIT_FAILURE);
+ }
+ if (setuid(0) != -1) {
+ LOG(FATAL) << "Still have root privileges?";
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+} // namespace
+
+namespace {
+void graceful_shutdown(ConnectionHandler *conn_handler) {
+ if (conn_handler->get_graceful_shutdown()) {
+ return;
+ }
+
+ LOG(NOTICE) << "Graceful shutdown signal received";
+
+ conn_handler->set_graceful_shutdown(true);
+
+ // TODO What happens for the connections not established in the
+ // kernel?
+ conn_handler->accept_pending_connection();
+ conn_handler->delete_acceptor();
+
+ conn_handler->graceful_shutdown_worker();
+
+ auto single_worker = conn_handler->get_single_worker();
+ if (single_worker) {
+ auto worker_stat = single_worker->get_worker_stat();
+ if (worker_stat->num_connections == 0 &&
+ worker_stat->num_close_waits == 0) {
+ ev_break(conn_handler->get_loop());
+ }
+
+ return;
+ }
+}
+} // namespace
+
+namespace {
+void reopen_log(ConnectionHandler *conn_handler) {
+ LOG(NOTICE) << "Reopening log files: worker process (thread main)";
+
+ auto config = get_config();
+ auto &loggingconf = config->logging;
+
+ (void)reopen_log_files(loggingconf);
+ redirect_stderr_to_errorlog(loggingconf);
+
+ conn_handler->worker_reopen_log_files();
+}
+} // namespace
+
+namespace {
+void ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+ std::array<uint8_t, 1024> buf;
+ ssize_t nread;
+ while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+ ;
+ if (nread == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Failed to read data from ipc channel: errno=" << error;
+ return;
+ }
+
+ if (nread == 0) {
+ // IPC socket closed. Perform immediate shutdown.
+ LOG(FATAL) << "IPC socket is closed. Perform immediate shutdown.";
+ nghttp2_Exit(EXIT_FAILURE);
+ }
+
+ for (ssize_t i = 0; i < nread; ++i) {
+ switch (buf[i]) {
+ case SHRPX_IPC_GRACEFUL_SHUTDOWN:
+ graceful_shutdown(conn_handler);
+ break;
+ case SHRPX_IPC_REOPEN_LOG:
+ reopen_log(conn_handler);
+ break;
+ }
+ }
+}
+} // namespace
+
+#ifdef ENABLE_HTTP3
+namespace {
+void quic_ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+
+ if (conn_handler->quic_ipc_read() != 0) {
+ LOG(ERROR) << "Failed to read data from QUIC IPC channel";
+
+ return;
+ }
+}
+} // namespace
+#endif // ENABLE_HTTP3
+
+namespace {
+int generate_ticket_key(TicketKey &ticket_key) {
+ ticket_key.cipher = get_config()->tls.ticket.cipher;
+ ticket_key.hmac = EVP_sha256();
+ ticket_key.hmac_keylen = EVP_MD_size(ticket_key.hmac);
+
+ assert(static_cast<size_t>(EVP_CIPHER_key_length(ticket_key.cipher)) <=
+ ticket_key.data.enc_key.size());
+ assert(ticket_key.hmac_keylen <= ticket_key.data.hmac_key.size());
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "enc_keylen=" << EVP_CIPHER_key_length(ticket_key.cipher)
+ << ", hmac_keylen=" << ticket_key.hmac_keylen;
+ }
+
+ if (RAND_bytes(reinterpret_cast<unsigned char *>(&ticket_key.data),
+ sizeof(ticket_key.data)) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+} // 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 = conn_handler->get_ticket_keys();
+
+ auto ticket_keys = std::make_shared<TicketKeys>();
+ LOG(NOTICE) << "Renew new ticket keys";
+
+ // If old_ticket_keys is not empty, it should contain at least 2
+ // keys: one for encryption, and last one for the next encryption
+ // key but decryption only. The keys in between are old keys and
+ // decryption only. The next key is provided to ensure to mitigate
+ // possible problem when one worker encrypt new key, but one worker,
+ // which did not take the that key yet, and cannot decrypt it.
+ //
+ // We keep keys for get_config()->tls_session_timeout seconds. The
+ // default is 12 hours. Thus the maximum ticket vector size is 12.
+ if (old_ticket_keys) {
+ auto &old_keys = old_ticket_keys->keys;
+ auto &new_keys = ticket_keys->keys;
+
+ assert(!old_keys.empty());
+
+ auto max_tickets =
+ static_cast<size_t>(std::chrono::duration_cast<std::chrono::hours>(
+ get_config()->tls.session_timeout)
+ .count());
+
+ new_keys.resize(std::min(max_tickets, old_keys.size() + 1));
+ std::copy_n(std::begin(old_keys), new_keys.size() - 1,
+ std::begin(new_keys) + 1);
+ } else {
+ ticket_keys->keys.resize(1);
+ }
+
+ auto &new_key = ticket_keys->keys[0];
+
+ if (generate_ticket_key(new_key) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "failed to generate ticket key";
+ }
+ conn_handler->set_ticket_keys(nullptr);
+ conn_handler->set_ticket_keys_to_worker(nullptr);
+ return;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ticket keys generation done";
+ assert(ticket_keys->keys.size() >= 1);
+ LOG(INFO) << 0 << " enc+dec: "
+ << util::format_hex(ticket_keys->keys[0].data.name);
+ for (size_t i = 1; i < ticket_keys->keys.size(); ++i) {
+ auto &key = ticket_keys->keys[i];
+ LOG(INFO) << i << " dec: " << util::format_hex(key.data.name);
+ }
+ }
+
+ conn_handler->set_ticket_keys(ticket_keys);
+ conn_handler->set_ticket_keys_to_worker(ticket_keys);
+}
+} // namespace
+
+namespace {
+void memcached_get_ticket_key_cb(struct ev_loop *loop, ev_timer *w,
+ int revents) {
+ auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+ auto dispatcher = conn_handler->get_tls_ticket_key_memcached_dispatcher();
+
+ auto req = std::make_unique<MemcachedRequest>();
+ req->key = "nghttpx:tls-ticket-key";
+ req->op = MemcachedOp::GET;
+ req->cb = [conn_handler, w](MemcachedRequest *req, MemcachedResult res) {
+ switch (res.status_code) {
+ case MemcachedStatusCode::NO_ERROR:
+ break;
+ case MemcachedStatusCode::EXT_NETWORK_ERROR:
+ conn_handler->on_tls_ticket_key_network_error(w);
+ return;
+ default:
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ // |version (4bytes)|len (2bytes)|key (variable length)|...
+ // (len, key) pairs are repeated as necessary.
+
+ auto &value = res.value;
+ if (value.size() < 4) {
+ LOG(WARN) << "Memcached: tls ticket key value is too small: got "
+ << value.size();
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto p = value.data();
+ auto version = util::get_uint32(p);
+ // Currently supported version is 1.
+ if (version != 1) {
+ LOG(WARN) << "Memcached: tls ticket key version: want 1, got " << version;
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+
+ auto end = p + value.size();
+ p += 4;
+
+ auto &ticketconf = get_config()->tls.ticket;
+
+ size_t expectedlen;
+ size_t enc_keylen;
+ size_t hmac_keylen;
+ if (ticketconf.cipher == EVP_aes_128_cbc()) {
+ expectedlen = 48;
+ enc_keylen = 16;
+ hmac_keylen = 16;
+ } else if (ticketconf.cipher == EVP_aes_256_cbc()) {
+ expectedlen = 80;
+ enc_keylen = 32;
+ hmac_keylen = 32;
+ } else {
+ return;
+ }
+
+ auto ticket_keys = std::make_shared<TicketKeys>();
+
+ for (; p != end;) {
+ if (end - p < 2) {
+ LOG(WARN) << "Memcached: tls ticket key data is too small";
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto len = util::get_uint16(p);
+ p += 2;
+ if (len != expectedlen) {
+ LOG(WARN) << "Memcached: wrong tls ticket key size: want "
+ << expectedlen << ", got " << len;
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ if (p + len > end) {
+ LOG(WARN) << "Memcached: too short tls ticket key payload: want " << len
+ << ", got " << (end - p);
+ conn_handler->on_tls_ticket_key_not_found(w);
+ return;
+ }
+ auto key = TicketKey();
+ key.cipher = ticketconf.cipher;
+ key.hmac = EVP_sha256();
+ key.hmac_keylen = hmac_keylen;
+
+ std::copy_n(p, key.data.name.size(), std::begin(key.data.name));
+ p += key.data.name.size();
+
+ std::copy_n(p, enc_keylen, std::begin(key.data.enc_key));
+ p += enc_keylen;
+
+ std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key));
+ p += hmac_keylen;
+
+ ticket_keys->keys.push_back(std::move(key));
+ }
+
+ conn_handler->on_tls_ticket_key_get_success(ticket_keys, w);
+ };
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Memcached: tls ticket key get request sent";
+ }
+
+ dispatcher->add_request(std::move(req));
+}
+
+} // namespace
+
+#ifdef HAVE_NEVERBLEED
+namespace {
+void nb_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ log_chld(w->rpid, w->rstatus, "neverbleed process");
+
+ ev_child_stop(loop, w);
+
+ LOG(FATAL) << "neverbleed process exited; aborting now";
+
+ nghttp2_Exit(EXIT_FAILURE);
+}
+} // namespace
+#endif // HAVE_NEVERBLEED
+
+namespace {
+int send_ready_event(int ready_ipc_fd) {
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ auto pid = getpid();
+ ssize_t nwrite;
+
+ while ((nwrite = write(ready_ipc_fd, &pid, sizeof(pid))) == -1 &&
+ errno == EINTR)
+ ;
+
+ if (nwrite < 0) {
+ auto error = errno;
+
+ LOG(ERROR) << "Writing PID to ready IPC channel failed: "
+ << xsi_strerror(error, errbuf.data(), errbuf.size());
+
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+int worker_process_event_loop(WorkerProcessConfig *wpconf) {
+ int rv;
+ std::array<char, STRERROR_BUFSIZE> errbuf;
+ (void)errbuf;
+
+ auto config = get_config();
+
+ if (reopen_log_files(config->logging) != 0) {
+ LOG(FATAL) << "Failed to open log file";
+ return -1;
+ }
+
+ rv = ares_library_init(ARES_LIB_INIT_ALL);
+ if (rv != 0) {
+ LOG(FATAL) << "ares_library_init failed: " << ares_strerror(rv);
+ return -1;
+ }
+
+ auto loop = EV_DEFAULT;
+
+ auto gen = util::make_mt19937();
+
+#ifdef HAVE_NEVERBLEED
+ std::array<char, NEVERBLEED_ERRBUF_SIZE> nb_errbuf;
+ auto nb = std::make_unique<neverbleed_t>();
+ if (neverbleed_init(nb.get(), nb_errbuf.data()) != 0) {
+ LOG(FATAL) << "neverbleed_init failed: " << nb_errbuf.data();
+ return -1;
+ }
+
+ LOG(NOTICE) << "neverbleed process [" << nb->daemon_pid << "] spawned";
+
+ ev_child nb_childev;
+
+ ev_child_init(&nb_childev, nb_child_cb, nb->daemon_pid, 0);
+ nb_childev.data = nullptr;
+ ev_child_start(loop, &nb_childev);
+#endif // HAVE_NEVERBLEED
+
+ auto conn_handler = std::make_unique<ConnectionHandler>(loop, gen);
+
+#ifdef HAVE_NEVERBLEED
+ conn_handler->set_neverbleed(nb.get());
+#endif // HAVE_NEVERBLEED
+
+#ifdef ENABLE_HTTP3
+ conn_handler->set_quic_ipc_fd(wpconf->quic_ipc_fd);
+ conn_handler->set_quic_lingering_worker_processes(
+ wpconf->quic_lingering_worker_processes);
+#endif // ENABLE_HTTP3
+
+ for (auto &addr : config->conn.listener.addrs) {
+ conn_handler->add_acceptor(
+ std::make_unique<AcceptHandler>(&addr, conn_handler.get()));
+ }
+
+ MemchunkPool mcpool;
+
+ ev_timer renew_ticket_key_timer;
+ if (tls::upstream_tls_enabled(config->conn)) {
+ auto &ticketconf = config->tls.ticket;
+ auto &memcachedconf = ticketconf.memcached;
+
+ if (!memcachedconf.host.empty()) {
+ SSL_CTX *ssl_ctx = nullptr;
+
+ if (memcachedconf.tls) {
+ ssl_ctx = conn_handler->create_tls_ticket_key_memcached_ssl_ctx();
+ }
+
+ conn_handler->set_tls_ticket_key_memcached_dispatcher(
+ std::make_unique<MemcachedDispatcher>(
+ &ticketconf.memcached.addr, loop, ssl_ctx,
+ StringRef{memcachedconf.host}, &mcpool, gen));
+
+ ev_timer_init(&renew_ticket_key_timer, memcached_get_ticket_key_cb, 0.,
+ 0.);
+ renew_ticket_key_timer.data = conn_handler.get();
+ // Get first ticket keys.
+ memcached_get_ticket_key_cb(loop, &renew_ticket_key_timer, 0);
+ } else {
+ bool auto_tls_ticket_key = true;
+ if (!ticketconf.files.empty()) {
+ if (!ticketconf.cipher_given) {
+ LOG(WARN)
+ << "It is strongly recommended to specify "
+ "--tls-ticket-key-cipher=aes-128-cbc (or "
+ "tls-ticket-key-cipher=aes-128-cbc in configuration file) "
+ "when --tls-ticket-key-file is used for the smooth "
+ "transition when the default value of --tls-ticket-key-cipher "
+ "becomes aes-256-cbc";
+ }
+ auto ticket_keys = read_tls_ticket_key_file(
+ ticketconf.files, ticketconf.cipher, EVP_sha256());
+ if (!ticket_keys) {
+ LOG(WARN) << "Use internal session ticket key generator";
+ } else {
+ conn_handler->set_ticket_keys(std::move(ticket_keys));
+ auto_tls_ticket_key = false;
+ }
+ }
+ if (auto_tls_ticket_key) {
+ // Generate new ticket key every 1hr.
+ ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 1_h);
+ 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);
+ }
+ }
+ }
+
+#ifdef ENABLE_HTTP3
+ auto &quicconf = config->quic;
+
+ std::shared_ptr<QUICKeyingMaterials> qkms;
+
+ if (!quicconf.upstream.secret_file.empty()) {
+ qkms = read_quic_secret_file(quicconf.upstream.secret_file);
+ if (!qkms) {
+ LOG(WARN) << "Use QUIC keying materials generated internally";
+ }
+ }
+
+ if (!qkms) {
+ qkms = std::make_shared<QUICKeyingMaterials>();
+ qkms->keying_materials.resize(1);
+
+ auto &qkm = qkms->keying_materials.front();
+
+ if (RAND_bytes(qkm.reserved.data(), qkm.reserved.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC secret reserved data";
+ return -1;
+ }
+
+ if (RAND_bytes(qkm.secret.data(), qkm.secret.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC secret";
+ return -1;
+ }
+
+ if (RAND_bytes(qkm.salt.data(), qkm.salt.size()) != 1) {
+ LOG(ERROR) << "Failed to generate QUIC salt";
+ return -1;
+ }
+ }
+
+ for (auto &qkm : qkms->keying_materials) {
+ if (generate_quic_connection_id_encryption_key(
+ qkm.cid_encryption_key.data(), qkm.cid_encryption_key.size(),
+ qkm.secret.data(), qkm.secret.size(), qkm.salt.data(),
+ qkm.salt.size()) != 0) {
+ LOG(ERROR) << "Failed to generate QUIC Connection ID encryption key";
+ return -1;
+ }
+ }
+
+ conn_handler->set_quic_keying_materials(std::move(qkms));
+
+ conn_handler->set_cid_prefixes(wpconf->cid_prefixes);
+ conn_handler->set_quic_lingering_worker_processes(
+ wpconf->quic_lingering_worker_processes);
+#endif // ENABLE_HTTP3
+
+ if (config->single_thread) {
+ rv = conn_handler->create_single_worker();
+ if (rv != 0) {
+ return -1;
+ }
+ } else {
+#ifndef NOTHREADS
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+
+ rv = pthread_sigmask(SIG_BLOCK, &set, nullptr);
+ if (rv != 0) {
+ LOG(ERROR) << "Blocking SIGCHLD failed: "
+ << xsi_strerror(rv, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#endif // !NOTHREADS
+
+ rv = conn_handler->create_worker_thread(config->num_worker);
+ if (rv != 0) {
+ return -1;
+ }
+
+#ifndef NOTHREADS
+ rv = pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
+ if (rv != 0) {
+ LOG(ERROR) << "Unblocking SIGCHLD failed: "
+ << xsi_strerror(rv, errbuf.data(), errbuf.size());
+ return -1;
+ }
+#endif // !NOTHREADS
+ }
+
+#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+ conn_handler->unload_bpf_objects();
+#endif // defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
+
+ drop_privileges(
+#ifdef HAVE_NEVERBLEED
+ nb.get()
+#endif // HAVE_NEVERBLEED
+ );
+
+ ev_io ipcev;
+ ev_io_init(&ipcev, ipc_readcb, wpconf->ipc_fd, EV_READ);
+ ipcev.data = conn_handler.get();
+ ev_io_start(loop, &ipcev);
+
+#ifdef ENABLE_HTTP3
+ ev_io quic_ipcev;
+ ev_io_init(&quic_ipcev, quic_ipc_readcb, wpconf->quic_ipc_fd, EV_READ);
+ quic_ipcev.data = conn_handler.get();
+ ev_io_start(loop, &quic_ipcev);
+#endif // ENABLE_HTTP3
+
+ if (tls::upstream_tls_enabled(config->conn) && !config->tls.ocsp.disabled) {
+ if (config->tls.ocsp.startup) {
+ conn_handler->set_enable_acceptor_on_ocsp_completion(true);
+ conn_handler->disable_acceptor();
+ }
+
+ conn_handler->proceed_next_cert_ocsp();
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Entering event loop";
+ }
+
+ if (send_ready_event(wpconf->ready_ipc_fd) != 0) {
+ return -1;
+ }
+
+ ev_run(loop, 0);
+
+ conn_handler->cancel_ocsp_update();
+
+ // Destroy SSL_CTX held in conn_handler before killing neverbleed
+ // daemon. Otherwise priv_rsa_finish yields "write error" and
+ // worker process aborts.
+ conn_handler.reset();
+
+#ifdef HAVE_NEVERBLEED
+ assert(nb->daemon_pid > 0);
+
+ rv = kill(nb->daemon_pid, SIGTERM);
+ if (rv != 0) {
+ auto error = errno;
+ LOG(ERROR) << "Could not send signal to neverbleed daemon: errno=" << error;
+ }
+
+ while ((rv = waitpid(nb->daemon_pid, nullptr, 0)) == -1 && errno == EINTR)
+ ;
+ if (rv == -1) {
+ auto error = errno;
+ LOG(ERROR) << "Error occurred while we were waiting for the completion "
+ "of neverbleed process: errno="
+ << error;
+ }
+#endif // HAVE_NEVERBLEED
+
+ ares_library_cleanup();
+
+ return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker_process.h b/src/shrpx_worker_process.h
new file mode 100644
index 0000000..f432503
--- /dev/null
+++ b/src/shrpx_worker_process.h
@@ -0,0 +1,67 @@
+/*
+ * 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_WORKER_PROCESS_H
+#define SHRPX_WORKER_PROCESS_H
+
+#include "shrpx.h"
+
+#include <vector>
+#include <array>
+
+#include "shrpx_connection_handler.h"
+#ifdef ENABLE_HTTP3
+# include "shrpx_quic.h"
+#endif // ENABLE_HTTP3
+
+namespace shrpx {
+
+class ConnectionHandler;
+
+struct WorkerProcessConfig {
+ // IPC socket to read event from main process
+ int ipc_fd;
+ // IPC socket to tell that a worker process is ready for service.
+ int ready_ipc_fd;
+ // IPv4 or UNIX domain socket, or -1 if not used
+ int server_fd;
+ // IPv6 socket, or -1 if not used
+ int server_fd6;
+#ifdef ENABLE_HTTP3
+ // CID prefixes for the new worker process.
+ std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
+ // IPC socket to read forwarded QUIC UDP datagram from the current
+ // worker process.
+ int quic_ipc_fd;
+ // Lingering worker processes which were created before this worker
+ // process to forward QUIC UDP datagram during reload.
+ std::vector<QUICLingeringWorkerProcess> quic_lingering_worker_processes;
+#endif // ENABLE_HTTP3
+};
+
+int worker_process_event_loop(WorkerProcessConfig *wpconf);
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_PROCESS_H
diff --git a/src/shrpx_worker_test.cc b/src/shrpx_worker_test.cc
new file mode 100644
index 0000000..7c5c329
--- /dev/null
+++ b/src/shrpx_worker_test.cc
@@ -0,0 +1,247 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_test.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_worker.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_log.h"
+
+namespace shrpx {
+
+void test_shrpx_worker_match_downstream_addr_group(void) {
+ auto groups = std::vector<std::shared_ptr<DownstreamAddrGroup>>();
+ for (auto &s : {"nghttp2.org/", "nghttp2.org/alpha/bravo/",
+ "nghttp2.org/alpha/charlie", "nghttp2.org/delta%3A",
+ "www.nghttp2.org/", "[::1]/", "nghttp2.org/alpha/bravo/delta",
+ // Check that match is done in the single node
+ "example.com/alpha/bravo", "192.168.0.1/alpha/", "/golf/"}) {
+ auto g = std::make_shared<DownstreamAddrGroup>();
+ g->pattern = ImmutableString(s);
+ groups.push_back(std::move(g));
+ }
+
+ BlockAllocator balloc(1024, 1024);
+ RouterConfig routerconf;
+
+ auto &router = routerconf.router;
+ auto &wcrouter = routerconf.rev_wildcard_router;
+ auto &wp = routerconf.wildcard_patterns;
+
+ for (size_t i = 0; i < groups.size(); ++i) {
+ auto &g = groups[i];
+ router.add_route(StringRef{g->pattern}, i);
+ }
+
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // port is removed
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org:8080"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // host is case-insensitive
+ CU_ASSERT(4 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("WWW.nghttp2.org"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/"), groups, 255,
+ balloc));
+
+ // /alpha/bravo also matches /alpha/bravo/
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ // path part is case-sensitive
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/Alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/charlie"), groups, 255,
+ balloc));
+
+ CU_ASSERT(2 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie"), groups, 255,
+ balloc));
+
+ // pattern which does not end with '/' must match its entirely. So
+ // this matches to group 0, not group 2.
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/charlie/"), groups, 255,
+ balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.org"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(""),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(""),
+ StringRef::from_lit("alpha"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("foo/bar"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // If path is StringRef::from_lit("*", only match with host + "/").
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("*"), groups, 255, balloc));
+
+ CU_ASSERT(5 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(5 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]:8080"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("[::1]8000"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // Check the case where adding route extends tree
+ CU_ASSERT(6 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/delta"), groups, 255,
+ balloc));
+
+ CU_ASSERT(1 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/alpha/bravo/delta/"), groups, 255,
+ balloc));
+
+ // Check the case where query is done in a single node
+ CU_ASSERT(7 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha/bravo/"), groups, 255,
+ balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("example.com"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ // Check the case where quey is done in a single node
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha"), groups, 255, balloc));
+
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha/"), groups, 255, balloc));
+
+ CU_ASSERT(8 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alpha/bravo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/alph"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("192.168.0.1"),
+ StringRef::from_lit("/"), groups, 255, balloc));
+
+ // Test for wildcard hosts
+ auto g1 = std::make_shared<DownstreamAddrGroup>();
+ g1->pattern = ImmutableString::from_lit("git.nghttp2.org");
+ groups.push_back(std::move(g1));
+
+ auto g2 = std::make_shared<DownstreamAddrGroup>();
+ g2->pattern = ImmutableString::from_lit(".nghttp2.org");
+ groups.push_back(std::move(g2));
+
+ auto g3 = std::make_shared<DownstreamAddrGroup>();
+ g3->pattern = ImmutableString::from_lit(".local");
+ groups.push_back(std::move(g3));
+
+ wp.emplace_back(StringRef::from_lit("git.nghttp2.org"));
+ wcrouter.add_route(StringRef::from_lit("gro.2ptthgn.tig"), 0);
+ wp.back().router.add_route(StringRef::from_lit("/echo/"), 10);
+
+ wp.emplace_back(StringRef::from_lit(".nghttp2.org"));
+ wcrouter.add_route(StringRef::from_lit("gro.2ptthgn."), 1);
+ wp.back().router.add_route(StringRef::from_lit("/echo/"), 11);
+ wp.back().router.add_route(StringRef::from_lit("/echo/foxtrot"), 12);
+
+ wp.emplace_back(StringRef::from_lit(".local"));
+ wcrouter.add_route(StringRef::from_lit("lacol."), 2);
+ wp.back().router.add_route(StringRef::from_lit("/"), 13);
+
+ CU_ASSERT(11 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("git.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(10 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("0git.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(11 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("it.nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(255 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit(".nghttp2.org"),
+ StringRef::from_lit("/echo/foxtrot"), groups, 255,
+ balloc));
+
+ CU_ASSERT(9 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("alpha.nghttp2.org"),
+ StringRef::from_lit("/golf"), groups, 255, balloc));
+
+ CU_ASSERT(0 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("nghttp2.org"),
+ StringRef::from_lit("/echo"), groups, 255, balloc));
+
+ CU_ASSERT(13 == match_downstream_addr_group(
+ routerconf, StringRef::from_lit("test.local"),
+ StringRef{}, groups, 255, balloc));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker_test.h b/src/shrpx_worker_test.h
new file mode 100644
index 0000000..8ffa2f1
--- /dev/null
+++ b/src/shrpx_worker_test.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_TEST_H
+#define SHRPX_WORKER_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace shrpx {
+
+void test_shrpx_worker_match_downstream_addr_group(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_TEST_H
diff --git a/src/ssl_compat.h b/src/ssl_compat.h
new file mode 100644
index 0000000..f691104
--- /dev/null
+++ b/src/ssl_compat.h
@@ -0,0 +1,48 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 OPENSSL_COMPAT_H
+
+# include <openssl/opensslv.h>
+
+# ifdef LIBRESSL_VERSION_NUMBER
+# define NGHTTP2_OPENSSL_IS_LIBRESSL
+# endif // !LIBRESSL_VERSION_NUMBER
+
+# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
+# define NGHTTP2_OPENSSL_IS_BORINGSSL
+# endif // OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC
+
+# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && \
+ !defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+# define NGHTTP2_GENUINE_OPENSSL
+# endif // !NGHTTP2_OPENSSL_IS_BORINGSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+
+# ifdef NGHTTP2_GENUINE_OPENSSL
+# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L)
+# else // !NGHTTP2_GENUINE_OPENSSL
+# define OPENSSL_3_0_0_API 0
+# endif // !NGHTTP2_GENUINE_OPENSSL
+
+#endif // OPENSSL_COMPAT_H
diff --git a/src/template.h b/src/template.h
new file mode 100644
index 0000000..530a1d1
--- /dev/null
+++ b/src/template.h
@@ -0,0 +1,548 @@
+/*
+ * 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 <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <array>
+#include <functional>
+#include <typeinfo>
+#include <algorithm>
+#include <ostream>
+#include <utility>
+
+namespace nghttp2 {
+
+// std::forward is constexpr since C++14
+template <typename... T>
+constexpr std::array<
+ typename std::decay<typename std::common_type<T...>::type>::type,
+ sizeof...(T)>
+make_array(T &&...t) {
+ return std::array<
+ typename std::decay<typename std::common_type<T...>::type>::type,
+ sizeof...(T)>{{std::forward<T>(t)...}};
+}
+
+template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
+ return N;
+}
+
+template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) {
+ return N - 1;
+}
+
+// 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) noexcept : 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)...);
+}
+
+template <typename T, typename F> bool test_flags(T t, F flags) {
+ return (t & flags) == flags;
+}
+
+// doubly linked list of element T*. T must have field T *dlprev and
+// T *dlnext, which point to previous element and next element in the
+// list respectively.
+template <typename T> struct DList {
+ DList() : head(nullptr), tail(nullptr), len(0) {}
+
+ DList(const DList &) = delete;
+ DList &operator=(const DList &) = delete;
+
+ DList(DList &&other) noexcept
+ : head{std::exchange(other.head, nullptr)},
+ tail{std::exchange(other.tail, nullptr)},
+ len{std::exchange(other.len, 0)} {}
+
+ DList &operator=(DList &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ head = std::exchange(other.head, nullptr);
+ tail = std::exchange(other.tail, nullptr);
+ len = std::exchange(other.len, 0);
+
+ return *this;
+ }
+
+ void append(T *t) {
+ ++len;
+ if (tail) {
+ tail->dlnext = t;
+ t->dlprev = tail;
+ tail = t;
+ return;
+ }
+ head = tail = t;
+ }
+
+ void remove(T *t) {
+ --len;
+ auto p = t->dlprev;
+ auto n = t->dlnext;
+ if (p) {
+ p->dlnext = n;
+ }
+ if (head == t) {
+ head = n;
+ }
+ if (n) {
+ n->dlprev = p;
+ }
+ if (tail == t) {
+ tail = p;
+ }
+ t->dlprev = t->dlnext = nullptr;
+ }
+
+ bool empty() const { return head == nullptr; }
+
+ size_t size() const { return len; }
+
+ T *head, *tail;
+ size_t len;
+};
+
+template <typename T> void dlist_delete_all(DList<T> &dl) {
+ for (auto e = dl.head; e;) {
+ auto next = e->dlnext;
+ delete e;
+ e = next;
+ }
+}
+
+// User-defined literals for K, M, and G (powers of 1024)
+
+constexpr unsigned long long operator"" _k(unsigned long long k) {
+ return k * 1024;
+}
+
+constexpr unsigned long long operator"" _m(unsigned long long m) {
+ return m * 1024 * 1024;
+}
+
+constexpr unsigned long long operator"" _g(unsigned long long g) {
+ return g * 1024 * 1024 * 1024;
+}
+
+// User-defined literals for time, converted into double in seconds
+
+// hours
+constexpr double operator"" _h(unsigned long long h) { return h * 60 * 60; }
+
+// minutes
+constexpr double operator"" _min(unsigned long long min) { return min * 60; }
+
+// seconds
+constexpr double operator"" _s(unsigned long long s) { return s; }
+
+// milliseconds
+constexpr double operator"" _ms(unsigned long long ms) { return ms / 1000.; }
+
+// Returns a copy of NULL-terminated string [first, last).
+template <typename InputIt>
+std::unique_ptr<char[]> strcopy(InputIt first, InputIt last) {
+ auto res = std::make_unique<char[]>(last - first + 1);
+ *std::copy(first, last, res.get()) = '\0';
+ return res;
+}
+
+// Returns a copy of NULL-terminated string |val|.
+inline std::unique_ptr<char[]> strcopy(const char *val) {
+ return strcopy(val, val + strlen(val));
+}
+
+inline std::unique_ptr<char[]> strcopy(const char *val, size_t n) {
+ return strcopy(val, val + n);
+}
+
+// Returns a copy of val.c_str().
+inline std::unique_ptr<char[]> strcopy(const std::string &val) {
+ return strcopy(std::begin(val), std::end(val));
+}
+
+inline std::unique_ptr<char[]> strcopy(const std::unique_ptr<char[]> &val) {
+ if (!val) {
+ return nullptr;
+ }
+ return strcopy(val.get());
+}
+
+inline std::unique_ptr<char[]> strcopy(const std::unique_ptr<char[]> &val,
+ size_t n) {
+ if (!val) {
+ return nullptr;
+ }
+ return strcopy(val.get(), val.get() + n);
+}
+
+// ImmutableString represents string that is immutable unlike
+// std::string. It has c_str() and size() functions to mimic
+// std::string. It manages buffer by itself. Just like std::string,
+// c_str() returns NULL-terminated string, but NULL character may
+// appear before the final terminal NULL.
+class ImmutableString {
+public:
+ using traits_type = std::char_traits<char>;
+ using value_type = traits_type::char_type;
+ using allocator_type = std::allocator<char>;
+ using size_type = std::allocator_traits<allocator_type>::size_type;
+ using difference_type =
+ std::allocator_traits<allocator_type>::difference_type;
+ using const_reference = const value_type &;
+ using const_pointer = const value_type *;
+ using const_iterator = const_pointer;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ ImmutableString() : len(0), base("") {}
+ ImmutableString(const char *s, size_t slen)
+ : len(slen), base(copystr(s, s + len)) {}
+ explicit ImmutableString(const char *s)
+ : len(strlen(s)), base(copystr(s, s + len)) {}
+ explicit ImmutableString(const std::string &s)
+ : len(s.size()), base(copystr(std::begin(s), std::end(s))) {}
+ template <typename InputIt>
+ ImmutableString(InputIt first, InputIt last)
+ : len(std::distance(first, last)), base(copystr(first, last)) {}
+ ImmutableString(const ImmutableString &other)
+ : len(other.len), base(copystr(std::begin(other), std::end(other))) {}
+ ImmutableString(ImmutableString &&other) noexcept
+ : len{std::exchange(other.len, 0)}, base{std::exchange(other.base, "")} {}
+ ~ImmutableString() {
+ if (len) {
+ delete[] base;
+ }
+ }
+
+ ImmutableString &operator=(const ImmutableString &other) {
+ if (this == &other) {
+ return *this;
+ }
+ if (len) {
+ delete[] base;
+ }
+ len = other.len;
+ base = copystr(std::begin(other), std::end(other));
+ return *this;
+ }
+ ImmutableString &operator=(ImmutableString &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ if (len) {
+ delete[] base;
+ }
+ len = std::exchange(other.len, 0);
+ base = std::exchange(other.base, "");
+ return *this;
+ }
+
+ template <size_t N> static ImmutableString from_lit(const char (&s)[N]) {
+ return ImmutableString(s, N - 1);
+ }
+
+ const_iterator begin() const { return base; };
+ const_iterator cbegin() const { return base; };
+
+ const_iterator end() const { return base + len; };
+ const_iterator cend() const { return base + len; };
+
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+
+ const_reverse_iterator rend() const { return const_reverse_iterator{base}; }
+ const_reverse_iterator crend() const { return const_reverse_iterator{base}; }
+
+ const char *c_str() const { return base; }
+ size_type size() const { return len; }
+ bool empty() const { return len == 0; }
+ const_reference operator[](size_type pos) const { return *(base + pos); }
+
+private:
+ template <typename InputIt> const char *copystr(InputIt first, InputIt last) {
+ if (first == last) {
+ return "";
+ }
+ auto res = new char[std::distance(first, last) + 1];
+ *std::copy(first, last, res) = '\0';
+ return res;
+ }
+
+ size_type len;
+ const char *base;
+};
+
+inline bool operator==(const ImmutableString &lhs, const ImmutableString &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const ImmutableString &lhs, const std::string &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const std::string &lhs, const ImmutableString &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const ImmutableString &lhs, const char *rhs) {
+ return lhs.size() == strlen(rhs) &&
+ std::equal(std::begin(lhs), std::end(lhs), rhs);
+}
+
+inline bool operator==(const char *lhs, const ImmutableString &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator!=(const ImmutableString &lhs, const ImmutableString &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const ImmutableString &lhs, const std::string &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const std::string &lhs, const ImmutableString &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator!=(const ImmutableString &lhs, const char *rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const char *lhs, const ImmutableString &rhs) {
+ return !(rhs == lhs);
+}
+
+inline std::ostream &operator<<(std::ostream &o, const ImmutableString &s) {
+ return o.write(s.c_str(), s.size());
+}
+
+inline std::string &operator+=(std::string &lhs, const ImmutableString &rhs) {
+ lhs.append(rhs.c_str(), rhs.size());
+ return lhs;
+}
+
+// StringRef is a reference to a string owned by something else. So
+// it behaves like simple string, but it does not own pointer. When
+// it is default constructed, it has empty string. You can freely
+// copy or move around this struct, but never free its pointer. str()
+// function can be used to export the content as std::string.
+class StringRef {
+public:
+ using traits_type = std::char_traits<char>;
+ using value_type = traits_type::char_type;
+ using allocator_type = std::allocator<char>;
+ using size_type = std::allocator_traits<allocator_type>::size_type;
+ using difference_type =
+ std::allocator_traits<allocator_type>::difference_type;
+ using const_reference = const value_type &;
+ using const_pointer = const value_type *;
+ using const_iterator = const_pointer;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ constexpr StringRef() : base(""), len(0) {}
+ explicit StringRef(const std::string &s) : base(s.c_str()), len(s.size()) {}
+ explicit StringRef(const ImmutableString &s)
+ : base(s.c_str()), len(s.size()) {}
+ explicit StringRef(const char *s) : base(s), len(strlen(s)) {}
+ constexpr StringRef(const char *s, size_t n) : base(s), len(n) {}
+ template <typename CharT>
+ constexpr StringRef(const CharT *s, size_t n)
+ : base(reinterpret_cast<const char *>(s)), len(n) {}
+ template <typename InputIt>
+ StringRef(InputIt first, InputIt last)
+ : base(reinterpret_cast<const char *>(&*first)),
+ len(std::distance(first, last)) {}
+ template <typename InputIt>
+ StringRef(InputIt *first, InputIt *last)
+ : base(reinterpret_cast<const char *>(first)),
+ len(std::distance(first, last)) {}
+ template <typename CharT, size_t N>
+ constexpr static StringRef from_lit(const CharT (&s)[N]) {
+ return StringRef{s, N - 1};
+ }
+ static StringRef from_maybe_nullptr(const char *s) {
+ if (s == nullptr) {
+ return StringRef();
+ }
+
+ return StringRef(s);
+ }
+
+ constexpr const_iterator begin() const { return base; };
+ constexpr const_iterator cbegin() const { return base; };
+
+ constexpr const_iterator end() const { return base + len; };
+ constexpr const_iterator cend() const { return base + len; };
+
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{base + len};
+ }
+
+ const_reverse_iterator rend() const { return const_reverse_iterator{base}; }
+ const_reverse_iterator crend() const { return const_reverse_iterator{base}; }
+
+ constexpr const char *c_str() const { return base; }
+ constexpr size_type size() const { return len; }
+ constexpr bool empty() const { return len == 0; }
+ constexpr const_reference operator[](size_type pos) const {
+ return *(base + pos);
+ }
+
+ std::string str() const { return std::string(base, len); }
+ const uint8_t *byte() const {
+ return reinterpret_cast<const uint8_t *>(base);
+ }
+
+private:
+ const char *base;
+ size_type len;
+};
+
+inline bool operator==(const StringRef &lhs, const StringRef &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const StringRef &lhs, const std::string &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const std::string &lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const StringRef &lhs, const char *rhs) {
+ return lhs.size() == strlen(rhs) &&
+ std::equal(std::begin(lhs), std::end(lhs), rhs);
+}
+
+inline bool operator==(const StringRef &lhs, const ImmutableString &rhs) {
+ return lhs.size() == rhs.size() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
+}
+
+inline bool operator==(const ImmutableString &lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator==(const char *lhs, const StringRef &rhs) {
+ return rhs == lhs;
+}
+
+inline bool operator!=(const StringRef &lhs, const StringRef &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const StringRef &lhs, const std::string &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const std::string &lhs, const StringRef &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator!=(const StringRef &lhs, const char *rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const char *lhs, const StringRef &rhs) {
+ return !(rhs == lhs);
+}
+
+inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
+ return std::lexicographical_compare(std::begin(lhs), std::end(lhs),
+ std::begin(rhs), std::end(rhs));
+}
+
+inline std::ostream &operator<<(std::ostream &o, const StringRef &s) {
+ return o.write(s.c_str(), s.size());
+}
+
+inline std::string &operator+=(std::string &lhs, const StringRef &rhs) {
+ lhs.append(rhs.c_str(), rhs.size());
+ return lhs;
+}
+
+inline int run_app(std::function<int(int, char **)> app, int argc,
+ char **argv) {
+ try {
+ return app(argc, argv);
+ } catch (const std::bad_alloc &) {
+ fputs("Out of memory\n", stderr);
+ } catch (const std::exception &x) {
+ fprintf(stderr, "Caught %s:\n%s\n", typeid(x).name(), x.what());
+ } catch (...) {
+ fputs("Unknown exception caught\n", stderr);
+ }
+ return EXIT_FAILURE;
+}
+
+} // namespace nghttp2
+
+namespace std {
+template <> struct hash<nghttp2::StringRef> {
+ std::size_t operator()(const nghttp2::StringRef &s) const noexcept {
+ // 32 bit FNV-1a:
+ // https://tools.ietf.org/html/draft-eastlake-fnv-16#section-6.1.1
+ uint32_t h = 2166136261u;
+ for (auto c : s) {
+ h ^= static_cast<uint8_t>(c);
+ h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
+ }
+ return h;
+ }
+};
+} // namespace std
+
+#endif // TEMPLATE_H
diff --git a/src/template_test.cc b/src/template_test.cc
new file mode 100644
index 0000000..4a77315
--- /dev/null
+++ b/src/template_test.cc
@@ -0,0 +1,204 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 "template_test.h"
+
+#include <cstring>
+#include <iostream>
+#include <sstream>
+
+#include <CUnit/CUnit.h>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+void test_template_immutable_string(void) {
+ ImmutableString null;
+
+ CU_ASSERT("" == null);
+ CU_ASSERT(0 == null.size());
+ CU_ASSERT(null.empty());
+
+ ImmutableString from_cstr("alpha");
+
+ CU_ASSERT(0 == strcmp("alpha", from_cstr.c_str()));
+ CU_ASSERT(5 == from_cstr.size());
+ CU_ASSERT(!from_cstr.empty());
+ CU_ASSERT("alpha" == from_cstr);
+ CU_ASSERT(from_cstr == "alpha");
+ CU_ASSERT(std::string("alpha") == from_cstr);
+ CU_ASSERT(from_cstr == std::string("alpha"));
+
+ // copy constructor
+ ImmutableString src("charlie");
+ ImmutableString copy = src;
+
+ CU_ASSERT("charlie" == copy);
+ CU_ASSERT(7 == copy.size());
+
+ // copy assignment
+ ImmutableString copy2;
+ copy2 = src;
+
+ CU_ASSERT("charlie" == copy2);
+ CU_ASSERT(7 == copy2.size());
+
+ // move constructor
+ ImmutableString move = std::move(copy);
+
+ CU_ASSERT("charlie" == move);
+ CU_ASSERT(7 == move.size());
+ CU_ASSERT("" == copy);
+ CU_ASSERT(0 == copy.size());
+
+ // move assignment
+ move = std::move(from_cstr);
+
+ CU_ASSERT("alpha" == move);
+ CU_ASSERT(5 == move.size());
+ CU_ASSERT("" == from_cstr);
+ CU_ASSERT(0 == from_cstr.size());
+
+ // from string literal
+ auto from_lit = StringRef::from_lit("bravo");
+
+ CU_ASSERT("bravo" == from_lit);
+ CU_ASSERT(5 == from_lit.size());
+
+ // equality
+ ImmutableString eq("delta");
+
+ CU_ASSERT("delta1" != eq);
+ CU_ASSERT("delt" != eq);
+ CU_ASSERT(eq != "delta1");
+ CU_ASSERT(eq != "delt");
+
+ // operator[]
+ ImmutableString br_op("foxtrot");
+
+ CU_ASSERT('f' == br_op[0]);
+ CU_ASSERT('o' == br_op[1]);
+ CU_ASSERT('t' == br_op[6]);
+ CU_ASSERT('\0' == br_op[7]);
+
+ // operator==(const ImmutableString &, const ImmutableString &)
+ {
+ ImmutableString a("foo");
+ ImmutableString b("foo");
+ ImmutableString c("fo");
+
+ CU_ASSERT(a == b);
+ CU_ASSERT(a != c);
+ CU_ASSERT(c != b);
+ }
+
+ // operator<<
+ {
+ ImmutableString a("foo");
+ std::stringstream ss;
+ ss << a;
+
+ CU_ASSERT("foo" == ss.str());
+ }
+
+ // operator +=(std::string &, const ImmutableString &)
+ {
+ std::string a = "alpha";
+ a += ImmutableString("bravo");
+
+ CU_ASSERT("alphabravo" == a);
+ }
+}
+
+void test_template_string_ref(void) {
+ StringRef empty;
+
+ CU_ASSERT("" == empty);
+ CU_ASSERT(0 == empty.size());
+
+ // from std::string
+ std::string alpha = "alpha";
+
+ StringRef ref(alpha);
+
+ CU_ASSERT("alpha" == ref);
+ CU_ASSERT(ref == "alpha");
+ CU_ASSERT(alpha == ref);
+ CU_ASSERT(ref == alpha);
+ CU_ASSERT(5 == ref.size());
+
+ // from string literal
+ auto from_lit = StringRef::from_lit("alpha");
+
+ CU_ASSERT("alpha" == from_lit);
+ CU_ASSERT(5 == from_lit.size());
+
+ // from ImmutableString
+ auto im = ImmutableString::from_lit("bravo");
+
+ StringRef imref(im);
+
+ CU_ASSERT("bravo" == imref);
+ CU_ASSERT(5 == imref.size());
+
+ // from C-string
+ StringRef cstrref("charlie");
+
+ CU_ASSERT("charlie" == cstrref);
+ CU_ASSERT(7 == cstrref.size());
+
+ // from C-string and its length
+ StringRef cstrnref("delta", 5);
+
+ CU_ASSERT("delta" == cstrnref);
+ CU_ASSERT(5 == cstrnref.size());
+
+ // operator[]
+ StringRef br_op("foxtrot");
+
+ CU_ASSERT('f' == br_op[0]);
+ CU_ASSERT('o' == br_op[1]);
+ CU_ASSERT('t' == br_op[6]);
+ CU_ASSERT('\0' == br_op[7]);
+
+ // operator<<
+ {
+ StringRef a("foo");
+ std::stringstream ss;
+ ss << a;
+
+ CU_ASSERT("foo" == ss.str());
+ }
+
+ // operator +=(std::string &, const StringRef &)
+ {
+ std::string a = "alpha";
+ a += StringRef("bravo");
+
+ CU_ASSERT("alphabravo" == a);
+ }
+}
+
+} // namespace nghttp2
diff --git a/src/template_test.h b/src/template_test.h
new file mode 100644
index 0000000..2c1448f
--- /dev/null
+++ b/src/template_test.h
@@ -0,0 +1,39 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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_TEST_H
+#define TEMPLATE_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+namespace nghttp2 {
+
+void test_template_immutable_string(void);
+void test_template_string_ref(void);
+
+} // namespace nghttp2
+
+#endif // TEMPLATE_TEST_H
diff --git a/src/test.example.com-key.pem b/src/test.example.com-key.pem
new file mode 100644
index 0000000..6d5515c
--- /dev/null
+++ b/src/test.example.com-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2FroLDwqVUYRlxp
+U/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpowUxvTDv/GNIHH
+XK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmIsYsO0Y4iBtwB
+M/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvcVzvSgZIzMLqS
+zvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7G/ZbX/4kUU5b
+PabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABAoIBAQCjUL69iFOs7muK
+CZGFe/iU1uxQM6XuGPN7mso3z15W07UxdlS3o6ZUSoLTONWcBxP/N4knulHJjZSY
+0LivbDYz3htic3t0kdGmxOxPVnLKRXN6ncbQTaeAE8tlBMAcWd/UH2Tlylz+Ac//
+6cV3gNJMShwUmhb3l4v3Rml0nZ6PO1pFc/Chk5L9REAV8G6rNtc9bzgmgoFucRO/
+8ce/uJrENt1Pu3vBvmz42DTGfG48v5RZ0OY4qEPawZJ7p+QYiTf6h3Eilss/AllW
+PPfQ0thdyB+yrZ3p6qb+ZUYphpGxgg6YlQxLfDKAikuo+EXwjPBPfeHhTO4kAj+h
+opDCroZhAoGBANyVFbagCWqwguE6nVPmnCaiNQUIh8b7L2CnkkLfdbPQr/KvyIjg
+Ua125bTJhe9Uk+ZBWsobQkjA0Baidiylx51pWYaxPVn5araVmkh2dqMluk2QE82X
+AWemBgKhAqCLLLMVXbrRYlxpKUm1Fc/lJ8Ig2R/MJSntTMpQhJtIejUbAoGBAMnt
+XMvlFABCoFbI9GMcteI/KkvNGQUy3OKEln/QCssnE4/XIu7LCxy6P+1lycbFy/mQ
+0bnp525sPEIIkMpi6LeAbSzYN2O3BRjNrjPcbx6Khz9DweNhRIo5qTFRszZ+pHbV
+N+9Oc9JVenwPw6EuW7uZRFKFhCHtsBFdUrWLJoSVAoGAQ3ytdwGBwA2fDW/UgL32
+mm9YT2DrwbpKJYU/X4xkw44ett6HOTGAa9ULtINPogi7c2AdeeZbIk0znSk5hLF3
+4DZCOM5zWdrQhmpBGNh9ta6uUFq7ZFRGDsMh5Z4DYsER/PyVf7neIS3ffviTYtbW
+kjNgmrTnzesXanK2D5heI28CgYEAhl+qjRTYhoPP53C7EOmeL/0QzHij2c3LKAJL
+lKqBREewwNvNp1L/BhL7T6OY7unZny48IpgBJn5oaxkAIW5IpzSTcnBAC99TSPo2
+ntRmLdDJx9PzRrkHv2Q3r1ZLCEymbV3eZyWx9ZpkdAKZkL0k1mZcDP5Eu79Ml4Ge
+9Kiw7TECgYEAh+nTKwrCUFGbe4RIVCj/QG7FVPbq5PdxJ3gILZ3/1XkhPcNRFKJS
+u5qPfA02tYEALz9KXATK1uRB/GlBM7Eap/g2GFiHpVxrw6wPpybLywJmNyNTwqiq
+eJxQ0FRzW9Kwwn1ThPY38LdFe/wvXZFOcNvGD8hHCLQRdlBR4zuTsBk=
+-----END RSA PRIVATE KEY-----
diff --git a/src/test.example.com.csr b/src/test.example.com.csr
new file mode 100644
index 0000000..8163935
--- /dev/null
+++ b/src/test.example.com.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAxMQdGVz
+dC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK39
+lAbBIf8cHf+FmX1RX+bDMlnJ7P9YnRdha6Cw8KlVGEZcaVPym6yBvF/L98OHFT/8
+Tl3dcvUPlvTiytmEnAHh3hPe5h2V23KaMFMb0w7/xjSBx1yv8r2DPEdhBJ2Ue9al
+3aF7AmpL/2htnsJJLwNdPHhG5NLu0cTZiLGLDtGOIgbcATPzEpAGD31na3R4WPc9
+JeDPBgMlDrmXNLeo9M8N385I6plYBhCb3Fc70oGSMzC6ks7wvPb4l7Y9acs0IzC4
+Jrg27NJeO7kR6WV4DBlchd1LkIqT874Tuxv2W1/+JFFOWz2m3bbz2447u3ROT3xq
+yjk4TOh0vd2O0L2E1LcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBMAUqwty7R
+/YWRrC8NuvrbSsW0r7Z7FXWxny5w5ImONCgVffc2wVydtBVQ0rfd3pDZLyu0P4sx
+4bJ/KBz67t2MsOKbCMDS7SJuFwHu9AUzaYNh455HeBOVwb6LemJDNnCtMG9DgcRv
+2BpwKqekUVTGDuUQmLibjE8qwDHw/p9k4gjQBxlfJe2sIZGs6oA/JGFJUU6ZIn8Y
+M6aazrbjWexbWCnjhiXkNa8kfKiSHzU+2ct+GY5QxI221+63bXRiAi2/LK0gaY+p
++3vYu75F7+8oPZOfsGmYEyPz7c1jPqcwPgVDk+sdvl1MO1TGFRaFNIlRP1DhpHkj
+fuJ/id6oUHhj
+-----END CERTIFICATE REQUEST-----
diff --git a/src/test.example.com.csr.json b/src/test.example.com.csr.json
new file mode 100644
index 0000000..5cd3e1e
--- /dev/null
+++ b/src/test.example.com.csr.json
@@ -0,0 +1,14 @@
+{
+ "CN": "test.example.com",
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/test.example.com.pem b/src/test.example.com.pem
new file mode 100644
index 0000000..1c7e71e
--- /dev/null
+++ b/src/test.example.com.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDwTCCAqmgAwIBAgIUDhKNhGRUq1TSHD6aG2k4TRR8iA0wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzNzAwWhcNMjYwNjIzMDkzNzAwWjBgMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2Fr
+oLDwqVUYRlxpU/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpow
+UxvTDv/GNIHHXK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmI
+sYsO0Y4iBtwBM/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvc
+VzvSgZIzMLqSzvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7
+G/ZbX/4kUU5bPabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABo3UwczAO
+BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
+ADAdBgNVHQ4EFgQUm8jn1FICope9qUce6ORQ0CtbmhYwHwYDVR0jBBgwFoAU0DnF
+VHVlxrFEkv1ULqnO4ua924YwDQYJKoZIhvcNAQELBQADggEBAD7RPz/5rAnS1MNP
+JfAj1TXZSBwlYgtmJL65yaFB6a1SNSTo15deAm/1Vl10LbmYdV4sVnGKeZjhKNk+
+bvVzetUSUS7Rh1fHtxlivJFkG1VrvPu9b416l2aKftBiaNyAWXbyjqXwLYli6Ehk
+uu6jZd0040Ggh7bY+KMSnDFDrp7Rar7OvGu9Iovs+sPdkc/iEbvwEiXdMjf3gwkT
+Wqx6br1VDLzhD83HAsFA9tt5fv6KTf91UgJnCmOi81Uo6fSEJG84g32T25gwwmCK
+q4U049aGF/f4u3QuWDsfYqNePycurAg3m5PC0wCoqvpY2u/q+PGbjWMi2PfZsF8U
+imgl/L0=
+-----END CERTIFICATE-----
diff --git a/src/test.nghttp2.org-key.pem b/src/test.nghttp2.org-key.pem
new file mode 100644
index 0000000..2532895
--- /dev/null
+++ b/src/test.nghttp2.org-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA7p6KKa3ctS+Sr/nf2uTKNtTshuDVzTsBTbaGydj8q0YDmT3n
+CnOPWXvvG1N+jJv5pcAXN2ZnV9UpGh3N5g/CaRcFTgQQ8o+NlCXYBdPIXAJ+Kkbx
+limDw3n9xIXfeL6+V2QuPNrqh6n23xwDg5boKaNkpf7X5OrjT1Ph57SEfX1op3GX
+bwkAP2+3WlxxYYs0htRq2gH97q9J4MlhHPDapi+uKGs+2b1y6Uxgf4nD5jEWdPmy
+VqeKs+fT4ja2n+3gujpdOo2lg504p50gL4zP8zhAlcqlQCmeJGL1xFzCtm2wHQo7
+6XHSWca4pJ7rxf2oIdtE7ikvgFlTVXnG1T3TEQIDAQABAoIBAQDlX/UD96MPcDmb
+e6EZ85AGgUsUpJAhBjVMlMagxTqtEVJoPj8XptoHdMD2DZ66XzztfedTU9bHcZpf
+BoNkQYXqKzzoL7Ry1leML4ymnVweRi8tSKD2bdXBVEUCYoXctc6WhzCDQxTrcBBl
+i7I9DhUB4ZTglEbIQJpdKQ8hAj/Rt55KWSxc+8X7ItSdtMrq+uz+pqg4PkysVAFS
+3aDybOqiI/2hzOvwQU4HaB48uUQwpOU6EGidt0C5nAdWOQMbS8kkCJz6UODiUfdM
+mLIyA4ygkQ45QthzrddKauUMhUd/y1SAFJOambR4ZiyA+bItomlbNq018sFx3FDr
+Uvg7nz2ZAoGBAPC6aO4W0U7vsWyL/lgC7ybFbtPJ4emj4wK/W87qx3LW1/dRgl7Q
+h6oblZTFK/oV6xA7J2/Foocz/s1ntnyIdxrtrZUYAIiBXlrWhDg9+MnnmErfXx7H
+CkRyWH6i9JbTRUeiWRNBGQ9yMkwQPc2Ckytxrh7w9M+RVCpyzUh8lX+XAoGBAP3B
+4V8cF3bVEUOk0tHshR5m2kcJ22qmUv8WUG+IdRUDb4tRpzVFC8HcKedEjk3jxkXR
+UInRSD+hLhx0HIhjZKWqJffSZI/G3U8AqoKu9+eh/xHZCah/8KW1YWNsn4rROcyZ
+5XFRiMn7psWTjLEZ17zQS4rk9g65SKc9u1wtTxeXAoGAIY3qOF2n2Tfh5D5zOnNW
+QHI+q3i1a6qzZtujgWkKWgCGY+vRn0Oz1Us5A16kbZyGgmGscpD6wZvGxXzSW/Nt
+nqxIiMKquFxH+aNzFJ/WwNXuTWlrSc/2p2nE2gn+y9MxEfYYMm3df2CskBuncbDk
+sKaM3bU6eoBIWg5cfOEYuYsCgYACB2bR59uYK6PzsoGtBAMcdx4Pq1iBxcqsF3WV
+LrYg8OIXbxOzLVYmuqfrHXU10jhnnoDSWUYGnDdOKu9/d6v6Vx3umVQMgj6Kvyqd
+2OBKjdUIQ3/8ROmbqZOZw+iSp5GavTBEc65wTv7KXZ+mWtqKu++esK32+CxIignR
+dttHCQKBgQDZUt94wj9s5p7H5LH6hxyqNr6P9JYEuYPao5l/3mOJ8N330wKuN2G+
+GUg7p/AhtQHwdoyErlsQavKZa791oCvfJiOURYa8gYU03sYsyI1tV45UexCwl40f
+oS+VQYgU16UdYo9B2petecEPNpM+mgpne3qzVUwJ5NUNURgmWpyiQw==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test.nghttp2.org.csr b/src/test.nghttp2.org.csr
new file mode 100644
index 0000000..dc4bb10
--- /dev/null
+++ b/src/test.nghttp2.org.csr
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIDATCCAekCAQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAxMUbm90
+LXVzZWQubmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDunooprdy1L5Kv+d/a5Mo21OyG4NXNOwFNtobJ2PyrRgOZPecKc49Ze+8bU36M
+m/mlwBc3ZmdX1SkaHc3mD8JpFwVOBBDyj42UJdgF08hcAn4qRvGWKYPDef3Ehd94
+vr5XZC482uqHqfbfHAODlugpo2Sl/tfk6uNPU+HntIR9fWincZdvCQA/b7daXHFh
+izSG1GraAf3ur0ngyWEc8NqmL64oaz7ZvXLpTGB/icPmMRZ0+bJWp4qz59PiNraf
+7eC6Ol06jaWDnTinnSAvjM/zOECVyqVAKZ4kYvXEXMK2bbAdCjvpcdJZxriknuvF
+/agh20TuKS+AWVNVecbVPdMRAgMBAAGgWDBWBgkqhkiG9w0BCQ4xSTBHMEUGA1Ud
+EQQ+MDyCEFRFU1QuTkdIVFRQMi5PUkeCEioudGVzdC5uZ2h0dHAyLm9yZ4IUdyp3
+LnRlc3QubmdodHRwMi5vcmcwDQYJKoZIhvcNAQELBQADggEBAIAEwnoM5moRwO5U
+eaeVCuzpxw1qQsB769GyQu+ey1aa+2BYflirv/FW+8x/uzQpCWGEgHqd5w+MXyXA
+PsyucHgKh5Ia6MUW6xxlHkkOtVtmZiH7lXWv90RNtdfHHGWnBzw8iGsk5WfEaNho
+NlPiuYLiFqA7W6jR/c4kOg3zziDlwTXaH6SWLCuDzLTb7E7nGcrWkN6moYj+QlSx
+viA4GsqDBoFgXT7cSfUzS8ZwIjrqbx7C1xkzPEt5jAiCD/UBX9ot0G+lEgCv3UQj
+Q1KkY+TO3bzMkt/kQSX2Q6plKj8D77tlDfFCjd77VC2lL3Qmzaz+M6T7uF+wyl9W
+AQJvoUg=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/test.nghttp2.org.csr.json b/src/test.nghttp2.org.csr.json
new file mode 100644
index 0000000..5ee3069
--- /dev/null
+++ b/src/test.nghttp2.org.csr.json
@@ -0,0 +1,19 @@
+{
+ "CN": "not-used.nghttp2.org",
+ "hosts": [
+ "TEST.NGHTTP2.ORG",
+ "*.test.nghttp2.org",
+ "w*w.test.nghttp2.org"
+ ],
+ "key": {
+ "algo": "rsa",
+ "size": 2048
+ },
+ "names": [
+ {
+ "C": "AU",
+ "ST": "Some-State",
+ "O": "Internet Widgits Pty Ltd"
+ }
+ ]
+}
diff --git a/src/test.nghttp2.org.pem b/src/test.nghttp2.org.pem
new file mode 100644
index 0000000..0c386fc
--- /dev/null
+++ b/src/test.nghttp2.org.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEDjCCAvagAwIBAgIUQBCY8Nre85JT1c7P+HbXUF9yzg8wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
+cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBkMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMR0wGwYDVQQDExRub3QtdXNlZC5uZ2h0dHAyLm9yZzCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6eiimt3LUvkq/539rkyjbU7Ibg1c07
+AU22hsnY/KtGA5k95wpzj1l77xtTfoyb+aXAFzdmZ1fVKRodzeYPwmkXBU4EEPKP
+jZQl2AXTyFwCfipG8ZYpg8N5/cSF33i+vldkLjza6oep9t8cA4OW6CmjZKX+1+Tq
+409T4ee0hH19aKdxl28JAD9vt1pccWGLNIbUatoB/e6vSeDJYRzw2qYvrihrPtm9
+culMYH+Jw+YxFnT5slanirPn0+I2tp/t4Lo6XTqNpYOdOKedIC+Mz/M4QJXKpUAp
+niRi9cRcwrZtsB0KO+lx0lnGuKSe68X9qCHbRO4pL4BZU1V5xtU90xECAwEAAaOB
+vTCBujAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T
+AQH/BAIwADAdBgNVHQ4EFgQUGlxgxowH6jrQiyyFpCbwPkCXXIYwHwYDVR0jBBgw
+FoAU0DnFVHVlxrFEkv1ULqnO4ua924YwRQYDVR0RBD4wPIIQVEVTVC5OR0hUVFAy
+Lk9SR4ISKi50ZXN0Lm5naHR0cDIub3JnghR3KncudGVzdC5uZ2h0dHAyLm9yZzAN
+BgkqhkiG9w0BAQsFAAOCAQEANCqM6ocfqOpgDEHYOOQTGFHJIptQhS3kRYAdTIo2
+G8XvGCoy+CDYe1GAUWbxE090+a1I1rsYMHcWKJnjKaCBZid7KMhyayIvrmgEsOCh
+L8iLf3bxkCoyIAmCpxJwa3LMxm2QQLtRx8AoMXWf+N8are4HY6MLNn6aP4zaTrTZ
+H+WkjKIh7WjSHtW/ro666PCXJDCCdRXljOf8v/fff3bYiLg8o70RBp7OFM0HaPtK
+wCfcLLxBeoVIncWswB6GtVUFhLeGjepDzWpuDHOdw6DtpghwSXvWFu9bRtl+x02m
+LAGfJ0kJrpYGfr9UB51NFX3aM/D3p2zxrjKwR2b59vJEcA==
+-----END CERTIFICATE-----
diff --git a/src/testdata/Makefile.am b/src/testdata/Makefile.am
new file mode 100644
index 0000000..b1cb575
--- /dev/null
+++ b/src/testdata/Makefile.am
@@ -0,0 +1,27 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2023 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 = \
+ ipaddr.crt \
+ nosan.crt \
+ nosan_ip.crt \
+ verify_hostname.crt
diff --git a/src/testdata/ipaddr.crt b/src/testdata/ipaddr.crt
new file mode 100644
index 0000000..cdacdf0
--- /dev/null
+++ b/src/testdata/ipaddr.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBfDCCASKgAwIBAgIBATAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwsxOTIuMTY4
+LjAuMTAeFw0yMzAzMTUxMjQ5MDBaFw0zMzAxMjExMjQ5MDBaMBYxFDASBgNVBAMT
+CzE5Mi4xNjguMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDh3hNne6xGM
+fOrf7ln5EFnlLpk98qadBx3MKjG5gAfMYHzf/S7v19G608sH1LtabubV+Tvjllon
+K56G2Gk0+6NhMF8wDgYDVR0PAQH/BAQDAgeAME0GA1UdEQRGMESCE25naHR0cDIu
+ZXhhbXBsZS5jb22CFSoubmdodHRwMi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA
+AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiEA3jZzO49MYccR5mYS08qVUCdh
+HsEAC8GhRXFwL6zvf2ACIFAJrca2zTU4QRjV6V+LGRHc2ZocE2e7wFTLobblmDfB
+-----END CERTIFICATE-----
diff --git a/src/testdata/nosan.crt b/src/testdata/nosan.crt
new file mode 100644
index 0000000..ebbb5c0
--- /dev/null
+++ b/src/testdata/nosan.crt
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBKDCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCWxvY2FsaG9z
+dDAeFw0yMzAzMTUxMjQzMzhaFw0zMzAxMjExMjQzMzhaMBQxEjAQBgNVBAMTCWxv
+Y2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIEpWYgtXtcx0uJ2oFPK
+RiII93iw5ITMrhMfBXQ0SzCfkUdvCJ0gNW+3isTBu4Jt0URpgP37eGwiJf2wPApq
+KpajEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNIADBFAiEA4IYil4G4
+cMxaVkcAnMGgiSdn7/qIgdhFB0Vx5AOd+EUCIGubRPhsXAJXvG//cK25mmxi3Wax
+r7AgRKuDtWxn2bCO
+-----END CERTIFICATE-----
diff --git a/src/testdata/nosan_ip.crt b/src/testdata/nosan_ip.crt
new file mode 100644
index 0000000..717a1bd
--- /dev/null
+++ b/src/testdata/nosan_ip.crt
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBJzCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCTEyNy4wLjAu
+MTAeFw0yMzAzMTUxMjQ1MTVaFw0zMzAxMjExMjQ1MTVaMBQxEjAQBgNVBAMTCTEy
+Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOXGPfSzXoeD7jszmAQO
+qAhak5HQMTmj32Q/xqO9WmCnXRQ+T06701o6q1hjotrC/HdMk9kabsKHc9V7Bk4O
+zkGjEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNHADBEAiAI3fKrkNTN
+IEo9qI8bd/pZ6on4d9vLcnHtqYhcuWZGTwIgW2zYMwASLUw4H1k/prBtTEEJOahJ
+bvFs3oMbJEprQ+g=
+-----END CERTIFICATE-----
diff --git a/src/testdata/verify_hostname.crt b/src/testdata/verify_hostname.crt
new file mode 100644
index 0000000..a327616
--- /dev/null
+++ b/src/testdata/verify_hostname.crt
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBeTCCAR6gAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlsb2NhbGhv
+c3QwHhcNMjMwMzE1MTIzNzU1WhcNMzMwMTIxMTIzNzU1WjAUMRIwEAYDVQQDEwls
+b2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMHcWmb55fi0KHNDwM
+cYzVTAOfzJf44AqrqC+Pq2zW/ig8tPZbXf3eA/Vvp07Di+yWmuo3fGatUcY4nsx+
+Jd62o2EwXzAOBgNVHQ8BAf8EBAMCB4AwTQYDVR0RBEYwRIITbmdodHRwMi5leGFt
+cGxlLmNvbYIVKi5uZ2h0dHAyLmV4YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAA
+AAAAAAABMAoGCCqGSM49BAMCA0kAMEYCIQDQJFRJ3Ah4cGy7bwpkzVYeTgG+NhDa
+55F4dPtJp9dS8wIhALQ9qf379lke1jVHg2t84iZLo3bL23RgICMezEYvqO3K
+-----END CERTIFICATE-----
diff --git a/src/timegm.c b/src/timegm.c
new file mode 100644
index 0000000..3a671b1
--- /dev/null
+++ b/src/timegm.c
@@ -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 "timegm.h"
+
+#include <inttypes.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;
+}
+
+/* Based on the algorithm of Python 2.7 calendar.timegm. */
+time_t nghttp2_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 + tm->tm_yday;
+ 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;
+}
+
+/* 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};
+
+time_t nghttp2_timegm_without_yday(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;
+}
diff --git a/src/timegm.h b/src/timegm.h
new file mode 100644
index 0000000..152917c
--- /dev/null
+++ b/src/timegm.h
@@ -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.
+ */
+#ifndef TIMEGM_H
+#define TIMEGM_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+time_t nghttp2_timegm(struct tm *tm);
+
+/* Just like nghttp2_timegm, but without using tm->tm_yday. This is
+ useful if we use tm from strptime, since some platforms do not
+ calculate tm_yday with that call. */
+time_t nghttp2_timegm_without_yday(struct tm *tm);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TIMEGM_H */
diff --git a/src/tls.cc b/src/tls.cc
new file mode 100644
index 0000000..e88dc56
--- /dev/null
+++ b/src/tls.cc
@@ -0,0 +1,125 @@
+/*
+ * 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 "tls.h"
+
+#include <cassert>
+#include <vector>
+#include <mutex>
+#include <iostream>
+
+#include <openssl/crypto.h>
+#include <openssl/conf.h>
+
+#include "ssl_compat.h"
+
+namespace nghttp2 {
+
+namespace tls {
+
+const char *get_tls_protocol(SSL *ssl) {
+ switch (SSL_version(ssl)) {
+ case SSL2_VERSION:
+ return "SSLv2";
+ case SSL3_VERSION:
+ return "SSLv3";
+#ifdef TLS1_3_VERSION
+ case TLS1_3_VERSION:
+ return "TLSv1.3";
+#endif // TLS1_3_VERSION
+ case TLS1_2_VERSION:
+ return "TLSv1.2";
+ case TLS1_1_VERSION:
+ return "TLSv1.1";
+ case TLS1_VERSION:
+ return "TLSv1";
+ default:
+ return "unknown";
+ }
+}
+
+TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) {
+ if (!ssl) {
+ return nullptr;
+ }
+
+ auto session = SSL_get_session(ssl);
+ if (!session) {
+ return nullptr;
+ }
+
+ tls_info->cipher = SSL_get_cipher_name(ssl);
+ tls_info->protocol = get_tls_protocol(ssl);
+ tls_info->session_reused = SSL_session_reused(ssl);
+
+ unsigned int session_id_length;
+ tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length);
+ tls_info->session_id_length = session_id_length;
+
+ return tls_info;
+}
+
+/* Conditional logic w/ lookup tables to check if id is one of the
+ the block listed cipher suites for HTTP/2 described in RFC 7540.
+ https://github.com/jay/http2_blacklisted_ciphers
+*/
+#define IS_CIPHER_BANNED_METHOD2(id) \
+ ((0x0000 <= id && id <= 0x00FF && \
+ "\xFF\xFF\xFF\xCF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x80\x3F\x00\x00" \
+ "\xF0\xFF\xFF\x3F\xF3\xF3\xFF\xFF\x3F\x00\x00\x00\x00\x00\x00\x80" \
+ [(id & 0xFF) / 8] & \
+ (1 << (id % 8))) || \
+ (0xC000 <= id && id <= 0xC0FF && \
+ "\xFE\xFF\xFF\xFF\xFF\x67\xFE\xFF\xFF\xFF\x33\xCF\xFC\xCF\xFF\xCF" \
+ "\x3C\xF3\xFC\x3F\x33\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
+ [(id & 0xFF) / 8] & \
+ (1 << (id % 8))))
+
+bool check_http2_cipher_block_list(SSL *ssl) {
+ int id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl)) & 0xFFFFFF;
+
+ return IS_CIPHER_BANNED_METHOD2(id);
+}
+
+bool check_http2_tls_version(SSL *ssl) {
+ auto tls_ver = SSL_version(ssl);
+
+ return tls_ver >= TLS1_2_VERSION;
+}
+
+bool check_http2_requirement(SSL *ssl) {
+ return check_http2_tls_version(ssl) && !check_http2_cipher_block_list(ssl);
+}
+
+int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) {
+ if (SSL_CTX_set_min_proto_version(ssl_ctx, min) != 1 ||
+ SSL_CTX_set_max_proto_version(ssl_ctx, max) != 1) {
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace tls
+
+} // namespace nghttp2
diff --git a/src/tls.h b/src/tls.h
new file mode 100644
index 0000000..59e2ccd
--- /dev/null
+++ b/src/tls.h
@@ -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 TLS_H
+#define TLS_H
+
+#include "nghttp2_config.h"
+
+#include <cinttypes>
+
+#include <openssl/ssl.h>
+
+#include "ssl_compat.h"
+
+namespace nghttp2 {
+
+namespace tls {
+
+// Recommended general purpose "Intermediate compatibility" cipher
+// suites for TLSv1.2 by mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+constexpr char DEFAULT_CIPHER_LIST[] =
+ "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-"
+ "AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-"
+ "POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-"
+ "AES256-GCM-SHA384";
+
+// Recommended general purpose "Modern compatibility" cipher suites
+// for TLSv1.3 by mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+constexpr char DEFAULT_TLS13_CIPHER_LIST[] =
+#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL)
+ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"
+#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ ""
+#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL
+ ;
+
+constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION;
+#ifdef TLS1_3_VERSION
+constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_3_VERSION;
+#else // !TLS1_3_VERSION
+constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_2_VERSION;
+#endif // !TLS1_3_VERSION
+
+const char *get_tls_protocol(SSL *ssl);
+
+struct TLSSessionInfo {
+ const char *cipher;
+ const char *protocol;
+ const uint8_t *session_id;
+ bool session_reused;
+ size_t session_id_length;
+};
+
+TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl);
+
+// Returns true iff the negotiated protocol is TLSv1.2.
+bool check_http2_tls_version(SSL *ssl);
+
+// Returns true iff the negotiated cipher suite is in HTTP/2 cipher
+// block list.
+bool check_http2_cipher_block_list(SSL *ssl);
+
+// Returns true if SSL/TLS requirement for HTTP/2 is fulfilled.
+// To fulfill the requirement, the following 2 terms must be hold:
+//
+// 1. The negotiated protocol must be TLSv1.2.
+// 2. The negotiated cipher cuite is not listed in the block list
+// described in RFC 7540.
+bool check_http2_requirement(SSL *ssl);
+
+// Sets TLS min and max versions to |ssl_ctx|. This function returns
+// 0 if it succeeds, or -1.
+int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max);
+
+} // namespace tls
+
+} // namespace nghttp2
+
+#endif // TLS_H
diff --git a/src/util.cc b/src/util.cc
new file mode 100644
index 0000000..0996c0a
--- /dev/null
+++ b/src/util.cc
@@ -0,0 +1,1796 @@
+/*
+ * 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 <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+#include <sys/stat.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif // HAVE_FCNTL_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif // HAVE_NETINET_IP_H
+#include <netinet/udp.h>
+#ifdef _WIN32
+# include <ws2tcpip.h>
+#else // !_WIN32
+# include <netinet/tcp.h>
+#endif // !_WIN32
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+#include <cmath>
+#include <cerrno>
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+
+#include <openssl/evp.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "ssl_compat.h"
+#include "timegm.h"
+
+namespace nghttp2 {
+
+namespace util {
+
+#ifndef _WIN32
+namespace {
+int nghttp2_inet_pton(int af, const char *src, void *dst) {
+ return inet_pton(af, src, dst);
+}
+} // namespace
+#else // _WIN32
+namespace {
+// inet_pton-wrapper for Windows
+int nghttp2_inet_pton(int af, const char *src, void *dst) {
+# if _WIN32_WINNT >= 0x0600
+ return InetPtonA(af, src, dst);
+# else
+ // the function takes a 'char*', so we need to make a copy
+ char addr[INET6_ADDRSTRLEN + 1];
+ strncpy(addr, src, sizeof(addr));
+ addr[sizeof(addr) - 1] = 0;
+
+ int size = sizeof(struct in6_addr);
+
+ if (WSAStringToAddress(addr, af, nullptr, (LPSOCKADDR)dst, &size) == 0)
+ return 1;
+ return 0;
+# endif
+}
+} // namespace
+#endif // _WIN32
+
+const char UPPER_XDIGITS[] = "0123456789ABCDEF";
+
+bool in_rfc3986_unreserved_chars(const char c) {
+ switch (c) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ return true;
+ }
+
+ return is_alpha(c) || is_digit(c);
+}
+
+bool in_rfc3986_sub_delims(const char c) {
+ switch (c) {
+ case '!':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case ',':
+ case ';':
+ case '=':
+ return true;
+ }
+
+ return false;
+}
+
+std::string percent_encode(const unsigned char *target, size_t len) {
+ std::string dest;
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char c = target[i];
+
+ if (in_rfc3986_unreserved_chars(c)) {
+ dest += c;
+ } else {
+ dest += '%';
+ dest += UPPER_XDIGITS[c >> 4];
+ dest += UPPER_XDIGITS[(c & 0x0f)];
+ }
+ }
+ return dest;
+}
+
+std::string percent_encode(const std::string &target) {
+ return percent_encode(reinterpret_cast<const unsigned char *>(target.c_str()),
+ target.size());
+}
+
+bool in_token(char c) {
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return true;
+ }
+
+ return is_alpha(c) || is_digit(c);
+}
+
+bool in_attr_char(char c) {
+ switch (c) {
+ case '*':
+ case '\'':
+ case '%':
+ return false;
+ }
+
+ return util::in_token(c);
+}
+
+StringRef percent_encode_token(BlockAllocator &balloc,
+ const StringRef &target) {
+ auto iov = make_byte_ref(balloc, target.size() * 3 + 1);
+ auto p = percent_encode_token(iov.base, target);
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+size_t percent_encode_tokenlen(const StringRef &target) {
+ size_t n = 0;
+
+ for (auto first = std::begin(target); first != std::end(target); ++first) {
+ uint8_t c = *first;
+
+ if (c != '%' && in_token(c)) {
+ ++n;
+ continue;
+ }
+
+ // percent-encoded character '%ff'
+ n += 3;
+ }
+
+ return n;
+}
+
+uint32_t hex_to_uint(char c) {
+ if (c <= '9') {
+ return c - '0';
+ }
+ if (c <= 'Z') {
+ return c - 'A' + 10;
+ }
+ if (c <= 'z') {
+ return c - 'a' + 10;
+ }
+ return 256;
+}
+
+StringRef quote_string(BlockAllocator &balloc, const StringRef &target) {
+ auto cnt = std::count(std::begin(target), std::end(target), '"');
+
+ if (cnt == 0) {
+ return make_string_ref(balloc, target);
+ }
+
+ auto iov = make_byte_ref(balloc, target.size() + cnt + 1);
+ auto p = quote_string(iov.base, target);
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+size_t quote_stringlen(const StringRef &target) {
+ size_t n = 0;
+
+ for (auto c : target) {
+ if (c == '"') {
+ n += 2;
+ } else {
+ ++n;
+ }
+ }
+
+ return n;
+}
+
+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 {
+constexpr const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+constexpr const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"};
+} // namespace
+
+std::string http_date(time_t t) {
+ /* Sat, 27 Sep 2014 06:31:15 GMT */
+ std::string res(29, 0);
+ http_date(&res[0], t);
+ return res;
+}
+
+char *http_date(char *res, time_t t) {
+ struct tm tms;
+
+ if (gmtime_r(&t, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = 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 p;
+}
+
+std::string common_log_date(time_t t) {
+ // 03/Jul/2014:00:19:38 +0900
+ std::string res(26, 0);
+ common_log_date(&res[0], t);
+ return res;
+}
+
+char *common_log_date(char *res, time_t t) {
+ struct tm tms;
+
+ if (localtime_r(&t, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = 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++ = ' ';
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - t;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+ if (gmtoff >= 0) {
+ *p++ = '+';
+ } else {
+ *p++ = '-';
+ gmtoff = -gmtoff;
+ }
+
+ p = cpydig(p, gmtoff / 3600, 2);
+ p = cpydig(p, (gmtoff % 3600) / 60, 2);
+
+ return p;
+}
+
+std::string iso8601_date(int64_t ms) {
+ // 2014-11-15T12:58:24.741Z
+ // 2014-11-15T12:58:24.741+09:00
+ std::string res(29, 0);
+ auto p = iso8601_date(&res[0], ms);
+ res.resize(p - &res[0]);
+ return res;
+}
+
+char *iso8601_date(char *res, int64_t ms) {
+ time_t sec = ms / 1000;
+
+ tm tms;
+ if (localtime_r(&sec, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = 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);
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - sec;
+#endif // !HAVE_STRUCT_TM_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);
+ }
+
+ return p;
+}
+
+char *iso8601_basic_date(char *res, int64_t ms) {
+ time_t sec = ms / 1000;
+
+ tm tms;
+ if (localtime_r(&sec, &tms) == nullptr) {
+ return res;
+ }
+
+ auto p = res;
+
+ p = cpydig(p, tms.tm_year + 1900, 4);
+ p = cpydig(p, tms.tm_mon + 1, 2);
+ p = cpydig(p, tms.tm_mday, 2);
+ *p++ = 'T';
+ p = cpydig(p, tms.tm_hour, 2);
+ p = cpydig(p, tms.tm_min, 2);
+ p = cpydig(p, tms.tm_sec, 2);
+ *p++ = '.';
+ p = cpydig(p, ms % 1000, 3);
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = tms.tm_gmtoff;
+#else // !HAVE_STRUCT_TM_TM_GMTOFF
+ auto gmtoff = nghttp2_timegm(&tms) - sec;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+ if (gmtoff == 0) {
+ *p++ = 'Z';
+ } else {
+ if (gmtoff > 0) {
+ *p++ = '+';
+ } else {
+ *p++ = '-';
+ gmtoff = -gmtoff;
+ }
+ p = cpydig(p, gmtoff / 3600, 2);
+ p = cpydig(p, (gmtoff % 3600) / 60, 2);
+ }
+
+ return p;
+}
+
+time_t parse_http_date(const StringRef &s) {
+ tm tm{};
+#ifdef _WIN32
+ // there is no strptime - use std::get_time
+ std::stringstream sstr(s.str());
+ sstr >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT");
+ if (sstr.fail()) {
+ return 0;
+ }
+#else // !_WIN32
+ char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
+ if (r == 0) {
+ return 0;
+ }
+#endif // !_WIN32
+ return nghttp2_timegm_without_yday(&tm);
+}
+
+time_t parse_openssl_asn1_time_print(const StringRef &s) {
+ tm tm{};
+ auto r = strptime(s.c_str(), "%b %d %H:%M:%S %Y GMT", &tm);
+ if (r == nullptr) {
+ return 0;
+ }
+ return nghttp2_timegm_without_yday(&tm);
+}
+
+char upcase(char c) {
+ if ('a' <= c && c <= 'z') {
+ return c - 'a' + 'A';
+ } else {
+ return c;
+ }
+}
+
+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;
+}
+
+StringRef format_hex(BlockAllocator &balloc, const StringRef &s) {
+ auto iov = make_byte_ref(balloc, s.size() * 2 + 1);
+ auto p = iov.base;
+
+ for (auto cc : s) {
+ uint8_t c = cc;
+ *p++ = LOWER_XDIGITS[c >> 4];
+ *p++ = LOWER_XDIGITS[c & 0xf];
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+void to_token68(std::string &base64str) {
+ std::transform(std::begin(base64str), std::end(base64str),
+ std::begin(base64str), [](char c) {
+ switch (c) {
+ case '+':
+ return '-';
+ case '/':
+ return '_';
+ default:
+ return c;
+ }
+ });
+ base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='),
+ std::end(base64str));
+}
+
+StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str) {
+ // At most 3 padding '='
+ auto len = token68str.size() + 3;
+ auto iov = make_byte_ref(balloc, len + 1);
+ auto p = iov.base;
+
+ p = std::transform(std::begin(token68str), std::end(token68str), p,
+ [](char c) {
+ switch (c) {
+ case '-':
+ return '+';
+ case '_':
+ return '/';
+ default:
+ return c;
+ }
+ });
+
+ auto rem = token68str.size() & 0x3;
+ if (rem) {
+ p = std::fill_n(p, 4 - rem, '=');
+ }
+
+ *p = '\0';
+
+ return StringRef{iov.base, p};
+}
+
+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, const 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 (istarts_with(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 candidates.
+ 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 &&
+ iends_with(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) {
+ return fieldeq(uri, u, field, StringRef{t});
+}
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const StringRef &t) {
+ if (!has_uri_field(u, field)) {
+ return t.empty();
+ }
+ auto &f = u.field_data[field];
+ return StringRef{uri + f.off, f.len} == t;
+}
+
+StringRef get_uri_field(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field) {
+ if (!util::has_uri_field(u, field)) {
+ return StringRef{};
+ }
+
+ return StringRef{uri + u.field_data[field].off, u.field_data[field].len};
+}
+
+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) {
+ return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6);
+}
+
+bool numeric_host(const char *hostname, int family) {
+ int rv;
+ std::array<uint8_t, sizeof(struct in6_addr)> dst;
+
+ rv = nghttp2_inet_pton(family, hostname, dst.data());
+
+ return rv == 1;
+}
+
+std::string numeric_name(const struct sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ auto rv = getnameinfo(sa, salen, host.data(), host.size(), nullptr, 0,
+ NI_NUMERICHOST);
+ if (rv != 0) {
+ return "unknown";
+ }
+ return host.data();
+}
+
+std::string to_numeric_addr(const Address *addr) {
+ return to_numeric_addr(&addr->su.sa, addr->len);
+}
+
+std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) {
+ auto family = sa->sa_family;
+#ifndef _WIN32
+ if (family == AF_UNIX) {
+ return reinterpret_cast<const sockaddr_un *>(sa)->sun_path;
+ }
+#endif // !_WIN32
+
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> serv;
+ auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(),
+ serv.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ return "unknown";
+ }
+
+ auto hostlen = strlen(host.data());
+ auto servlen = strlen(serv.data());
+
+ std::string s;
+ char *p;
+ if (family == AF_INET6) {
+ s.resize(hostlen + servlen + 2 + 1);
+ p = &s[0];
+ *p++ = '[';
+ p = std::copy_n(host.data(), hostlen, p);
+ *p++ = ']';
+ } else {
+ s.resize(hostlen + servlen + 1);
+ p = &s[0];
+ p = std::copy_n(host.data(), hostlen, p);
+ }
+ *p++ = ':';
+ std::copy_n(serv.data(), servlen, p);
+
+ return s;
+}
+
+void set_port(Address &addr, uint16_t port) {
+ switch (addr.su.storage.ss_family) {
+ case AF_INET:
+ addr.su.in.sin_port = htons(port);
+ break;
+ case AF_INET6:
+ addr.su.in6.sin6_port = htons(port);
+ break;
+ }
+}
+
+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));
+ if (path == nullptr) {
+ return nullptr;
+ }
+ memcpy(path, argv0, len + 1);
+ } else {
+ auto cwdlen = strlen(cwd);
+ path = static_cast<char *>(malloc(len + 1 + cwdlen + 1));
+ if (path == nullptr) {
+ return nullptr;
+ }
+ 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::ends_with_l(path, "/..") && !util::ends_with_l(path, "/.");
+}
+
+int64_t to_time64(const timeval &tv) {
+ return tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+bool check_h2_is_selected(const StringRef &proto) {
+ return streq(NGHTTP2_H2, proto) || streq(NGHTTP2_H2_16, proto) ||
+ streq(NGHTTP2_H2_14, proto);
+}
+
+namespace {
+bool select_proto(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ const StringRef &key) {
+ for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) {
+ if (std::equal(std::begin(key), std::end(key), p)) {
+ *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_proto(out, outlen, in, inlen, NGHTTP2_H2_ALPN) ||
+ select_proto(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN) ||
+ select_proto(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN);
+}
+
+bool select_protocol(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ std::vector<std::string> proto_list) {
+ for (const auto &proto : proto_list) {
+ if (select_proto(out, outlen, in, inlen, StringRef{proto})) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::vector<unsigned char> get_default_alpn() {
+ auto res = std::vector<unsigned char>(NGHTTP2_H2_ALPN.size() +
+ NGHTTP2_H2_16_ALPN.size() +
+ NGHTTP2_H2_14_ALPN.size());
+ auto p = std::begin(res);
+
+ p = std::copy_n(std::begin(NGHTTP2_H2_ALPN), NGHTTP2_H2_ALPN.size(), p);
+ p = std::copy_n(std::begin(NGHTTP2_H2_16_ALPN), NGHTTP2_H2_16_ALPN.size(), p);
+ p = std::copy_n(std::begin(NGHTTP2_H2_14_ALPN), NGHTTP2_H2_14_ALPN.size(), p);
+
+ return res;
+}
+
+std::vector<StringRef> split_str(const StringRef &s, char delim) {
+ size_t len = 1;
+ auto last = std::end(s);
+ StringRef::const_iterator d;
+ for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last;
+ ++len, first = d + 1)
+ ;
+
+ auto list = std::vector<StringRef>(len);
+
+ len = 0;
+ for (auto first = std::begin(s);; ++len) {
+ auto stop = std::find(first, last, delim);
+ list[len] = StringRef{first, stop};
+ if (stop == last) {
+ break;
+ }
+ first = stop + 1;
+ }
+ return list;
+}
+
+std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n) {
+ if (n == 0) {
+ return split_str(s, delim);
+ }
+
+ if (n == 1) {
+ return {s};
+ }
+
+ size_t len = 1;
+ auto last = std::end(s);
+ StringRef::const_iterator d;
+ for (auto first = std::begin(s);
+ len < n && (d = std::find(first, last, delim)) != last;
+ ++len, first = d + 1)
+ ;
+
+ auto list = std::vector<StringRef>(len);
+
+ len = 0;
+ for (auto first = std::begin(s);; ++len) {
+ if (len == n - 1) {
+ list[len] = StringRef{first, last};
+ break;
+ }
+
+ auto stop = std::find(first, last, delim);
+ list[len] = StringRef{first, stop};
+ if (stop == last) {
+ break;
+ }
+ first = stop + 1;
+ }
+ return list;
+}
+
+std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) {
+ auto sublist = split_str(s, delim);
+ auto res = std::vector<std::string>();
+ res.reserve(sublist.size());
+ for (const auto &s : sublist) {
+ res.emplace_back(std::begin(s), std::end(s));
+ }
+ return res;
+}
+
+int make_socket_closeonexec(int fd) {
+#ifdef _WIN32
+ (void)fd;
+ return 0;
+#else // !_WIN32
+ 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;
+#endif // !_WIN32
+}
+
+int make_socket_nonblocking(int fd) {
+ int rv;
+
+#ifdef _WIN32
+ u_long mode = 1;
+
+ rv = ioctlsocket(fd, FIONBIO, &mode);
+#else // !_WIN32
+ int flags;
+ while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+ ;
+ while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+ ;
+#endif // !_WIN32
+
+ 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
+
+ if (family == AF_INET || family == AF_INET6) {
+ make_socket_nodelay(fd);
+ }
+
+ return fd;
+}
+
+int create_nonblock_udp_socket(int family) {
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(family, SOCK_DGRAM, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ make_socket_nonblocking(fd);
+ make_socket_closeonexec(fd);
+#endif // !SOCK_NONBLOCK
+
+ return fd;
+}
+
+int bind_any_addr_udp(int fd, int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+ int rv;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ rv = getaddrinfo(nullptr, "0", &hints, &res);
+ if (rv != 0) {
+ return -1;
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+ }
+
+ freeaddrinfo(res);
+
+ if (!rp) {
+ return -1;
+ }
+
+ return 0;
+}
+
+bool check_socket_connected(int fd) {
+ int error;
+ socklen_t len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) {
+ return false;
+ }
+
+ return error == 0;
+}
+
+int get_socket_error(int fd) {
+ int error;
+ socklen_t len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) {
+ return -1;
+ }
+
+ return error;
+}
+
+bool ipv6_numeric_addr(const char *host) {
+ uint8_t dst[16];
+ return nghttp2_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) {
+ return parse_uint_with_unit(reinterpret_cast<const uint8_t *>(s), strlen(s));
+}
+
+int64_t parse_uint_with_unit(const StringRef &s) {
+ return parse_uint_with_unit(s.byte(), s.size());
+}
+
+int64_t parse_uint_with_unit(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) {
+ 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 StringRef &s) {
+ return parse_uint(s.byte(), 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) {
+ return parse_duration_with_unit(reinterpret_cast<const uint8_t *>(s),
+ strlen(s));
+}
+
+double parse_duration_with_unit(const StringRef &s) {
+ return parse_duration_with_unit(s.byte(), s.size());
+}
+
+double parse_duration_with_unit(const uint8_t *s, size_t len) {
+ constexpr auto max = std::numeric_limits<int64_t>::max();
+ int64_t n;
+ size_t i;
+
+ 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':
+ // seconds
+ if (i + 1 != len) {
+ goto fail;
+ }
+ return static_cast<double>(n);
+ case 'M':
+ case 'm':
+ if (i + 1 == len) {
+ // minutes
+ if (n > max / 60) {
+ goto fail;
+ }
+ return static_cast<double>(n) * 60;
+ }
+
+ if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
+ goto fail;
+ }
+ // milliseconds
+ return static_cast<double>(n) / 1000.;
+ case 'H':
+ case 'h':
+ // hours
+ if (i + 1 != len) {
+ goto fail;
+ }
+ if (n > max / 3600) {
+ goto fail;
+ }
+ return static_cast<double>(n) * 3600;
+ }
+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";
+ }
+ auto v = static_cast<int64_t>(t);
+ if (v % 60) {
+ return utos(v) + "s";
+ }
+ v /= 60;
+ if (v % 60) {
+ return utos(v) + "m";
+ }
+ v /= 60;
+ return utos(v) + "h";
+}
+
+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 format_duration(double t) {
+ const char *unit = "us";
+ if (t >= 1.) {
+ unit = "s";
+ } else if (t >= 0.001) {
+ t *= 1000.;
+ unit = "ms";
+ } else {
+ t *= 1000000.;
+ return utos(static_cast<int64_t>(t)) + unit;
+ }
+ return dtos(t) + unit;
+}
+
+std::string dtos(double n) {
+ auto m = llround(100. * n);
+ auto f = utos(m % 100);
+ return utos(m / 100) + "." + (f.size() == 1 ? "0" : "") + f;
+}
+
+StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port) {
+ auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
+ return make_http_hostport(iov.base, host, port);
+}
+
+StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port) {
+ auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
+ return make_hostport(iov.base, host, port);
+}
+
+namespace {
+void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) {
+ auto stop = std::min(first + 8, last);
+ for (auto k = first; k != stop; ++k) {
+ fprintf(out, "%02x ", *k);
+ }
+ // each byte needs 3 spaces (2 hex value and space)
+ for (; stop != first + 8; ++stop) {
+ fputs(" ", out);
+ }
+ // we have extra space after 8 bytes
+ fputc(' ', out);
+}
+} // namespace
+
+void hexdump(FILE *out, const uint8_t *src, size_t len) {
+ if (len == 0) {
+ return;
+ }
+ size_t buflen = 0;
+ auto repeated = false;
+ std::array<uint8_t, 16> buf{};
+ auto end = src + len;
+ auto i = src;
+ for (;;) {
+ auto nextlen =
+ std::min(static_cast<size_t>(16), static_cast<size_t>(end - i));
+ if (nextlen == buflen &&
+ std::equal(std::begin(buf), std::begin(buf) + buflen, i)) {
+ // as long as adjacent 16 bytes block are the same, we just
+ // print single '*'.
+ if (!repeated) {
+ repeated = true;
+ fputs("*\n", out);
+ }
+ i += nextlen;
+ continue;
+ }
+ repeated = false;
+ fprintf(out, "%08lx", static_cast<unsigned long>(i - src));
+ if (i == end) {
+ fputc('\n', out);
+ break;
+ }
+ fputs(" ", out);
+ hexdump8(out, i, end);
+ hexdump8(out, i + 8, std::max(i + 8, end));
+ fputc('|', out);
+ auto stop = std::min(i + 16, end);
+ buflen = stop - i;
+ auto p = buf.data();
+ for (; i != stop; ++i) {
+ *p++ = *i;
+ if (0x20 <= *i && *i <= 0x7e) {
+ fputc(*i, out);
+ } else {
+ fputc('.', out);
+ }
+ }
+ fputs("|\n", out);
+ }
+}
+
+void put_uint16be(uint8_t *buf, uint16_t n) {
+ uint16_t x = htons(n);
+ memcpy(buf, &x, sizeof(uint16_t));
+}
+
+void put_uint32be(uint8_t *buf, uint32_t n) {
+ uint32_t x = htonl(n);
+ memcpy(buf, &x, sizeof(uint32_t));
+}
+
+uint16_t get_uint16(const uint8_t *data) {
+ uint16_t n;
+ memcpy(&n, data, sizeof(uint16_t));
+ return ntohs(n);
+}
+
+uint32_t get_uint32(const uint8_t *data) {
+ uint32_t n;
+ memcpy(&n, data, sizeof(uint32_t));
+ return ntohl(n);
+}
+
+uint64_t get_uint64(const uint8_t *data) {
+ uint64_t n = 0;
+ n += static_cast<uint64_t>(data[0]) << 56;
+ n += static_cast<uint64_t>(data[1]) << 48;
+ n += static_cast<uint64_t>(data[2]) << 40;
+ n += static_cast<uint64_t>(data[3]) << 32;
+ n += static_cast<uint64_t>(data[4]) << 24;
+ n += data[5] << 16;
+ n += data[6] << 8;
+ n += data[7];
+ return n;
+}
+
+int read_mime_types(std::map<std::string, std::string> &res,
+ const char *filename) {
+ std::ifstream infile(filename);
+ if (!infile) {
+ return -1;
+ }
+
+ auto delim_pred = [](char c) { return c == ' ' || c == '\t'; };
+
+ std::string line;
+ while (std::getline(infile, line)) {
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto type_end = std::find_if(std::begin(line), std::end(line), delim_pred);
+ if (type_end == std::begin(line)) {
+ continue;
+ }
+
+ auto ext_end = type_end;
+ for (;;) {
+ auto ext_start = std::find_if_not(ext_end, std::end(line), delim_pred);
+ if (ext_start == std::end(line)) {
+ break;
+ }
+ ext_end = std::find_if(ext_start, std::end(line), delim_pred);
+#ifdef HAVE_STD_MAP_EMPLACE
+ res.emplace(std::string(ext_start, ext_end),
+ std::string(std::begin(line), type_end));
+#else // !HAVE_STD_MAP_EMPLACE
+ res.insert(std::make_pair(std::string(ext_start, ext_end),
+ std::string(std::begin(line), type_end)));
+#endif // !HAVE_STD_MAP_EMPLACE
+ }
+ }
+
+ return 0;
+}
+
+StringRef percent_decode(BlockAllocator &balloc, const StringRef &src) {
+ auto iov = make_byte_ref(balloc, src.size() * 3 + 1);
+ auto p = iov.base;
+ for (auto first = std::begin(src); first != std::end(src); ++first) {
+ if (*first != '%') {
+ *p++ = *first;
+ continue;
+ }
+
+ if (first + 1 != std::end(src) && first + 2 != std::end(src) &&
+ is_hex_digit(*(first + 1)) && is_hex_digit(*(first + 2))) {
+ *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
+ first += 2;
+ continue;
+ }
+
+ *p++ = *first;
+ }
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+// Returns x**y
+double int_pow(double x, size_t y) {
+ auto res = 1.;
+ for (; y; --y) {
+ res *= x;
+ }
+ return res;
+}
+
+uint32_t hash32(const StringRef &s) {
+ /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */
+ uint32_t h = 2166136261u;
+ size_t i;
+
+ for (i = 0; i < s.size(); ++i) {
+ h ^= s[i];
+ h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
+ }
+
+ return h;
+}
+
+namespace {
+int message_digest(uint8_t *res, const EVP_MD *meth, const StringRef &s) {
+ int rv;
+
+ auto ctx = EVP_MD_CTX_new();
+ if (ctx == nullptr) {
+ return -1;
+ }
+
+ auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
+
+ rv = EVP_DigestInit_ex(ctx, meth, nullptr);
+ if (rv != 1) {
+ return -1;
+ }
+
+ rv = EVP_DigestUpdate(ctx, s.c_str(), s.size());
+ if (rv != 1) {
+ return -1;
+ }
+
+ unsigned int mdlen = EVP_MD_size(meth);
+
+ rv = EVP_DigestFinal_ex(ctx, res, &mdlen);
+ if (rv != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+int sha256(uint8_t *res, const StringRef &s) {
+ return message_digest(res, EVP_sha256(), s);
+}
+
+int sha1(uint8_t *res, const StringRef &s) {
+ return message_digest(res, EVP_sha1(), s);
+}
+
+bool is_hex_string(const StringRef &s) {
+ if (s.size() % 2) {
+ return false;
+ }
+
+ for (auto c : s) {
+ if (!is_hex_digit(c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
+ auto iov = make_byte_ref(balloc, s.size() + 1);
+ auto p = decode_hex(iov.base, s);
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+StringRef extract_host(const StringRef &hostport) {
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return StringRef{};
+ }
+ if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
+ return StringRef{};
+ }
+ return StringRef{std::begin(hostport), p + 1};
+ }
+
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return StringRef{};
+ }
+ return StringRef{std::begin(hostport), p};
+}
+
+std::pair<StringRef, StringRef> split_hostport(const StringRef &hostport) {
+ if (hostport.empty()) {
+ return {};
+ }
+ if (hostport[0] == '[') {
+ // assume this is IPv6 numeric address
+ auto p = std::find(std::begin(hostport), std::end(hostport), ']');
+ if (p == std::end(hostport)) {
+ return {};
+ }
+ if (p + 1 == std::end(hostport)) {
+ return {StringRef{std::begin(hostport) + 1, p}, {}};
+ }
+ if (*(p + 1) != ':' || p + 2 == std::end(hostport)) {
+ return {};
+ }
+ return {StringRef{std::begin(hostport) + 1, p},
+ StringRef{p + 2, std::end(hostport)}};
+ }
+
+ auto p = std::find(std::begin(hostport), std::end(hostport), ':');
+ if (p == std::begin(hostport)) {
+ return {};
+ }
+ if (p == std::end(hostport)) {
+ return {StringRef{std::begin(hostport), p}, {}};
+ }
+ if (p + 1 == std::end(hostport)) {
+ return {};
+ }
+
+ return {StringRef{std::begin(hostport), p},
+ StringRef{p + 1, std::end(hostport)}};
+}
+
+std::mt19937 make_mt19937() {
+ std::random_device rd;
+ return std::mt19937(rd());
+}
+
+int daemonize(int nochdir, int noclose) {
+#ifdef __APPLE__
+ pid_t pid;
+ pid = fork();
+ if (pid == -1) {
+ return -1;
+ } else if (pid > 0) {
+ _exit(EXIT_SUCCESS);
+ }
+ if (setsid() == -1) {
+ return -1;
+ }
+ pid = fork();
+ if (pid == -1) {
+ return -1;
+ } else if (pid > 0) {
+ _exit(EXIT_SUCCESS);
+ }
+ if (nochdir == 0) {
+ if (chdir("/") == -1) {
+ return -1;
+ }
+ }
+ if (noclose == 0) {
+ if (freopen("/dev/null", "r", stdin) == nullptr) {
+ return -1;
+ }
+ if (freopen("/dev/null", "w", stdout) == nullptr) {
+ return -1;
+ }
+ if (freopen("/dev/null", "w", stderr) == nullptr) {
+ return -1;
+ }
+ }
+ return 0;
+#else // !__APPLE__
+ return daemon(nochdir, noclose);
+#endif // !__APPLE__
+}
+
+StringRef rstrip(BlockAllocator &balloc, const StringRef &s) {
+ auto it = std::rbegin(s);
+ for (; it != std::rend(s) && (*it == ' ' || *it == '\t'); ++it)
+ ;
+
+ auto len = it - std::rbegin(s);
+ if (len == 0) {
+ return s;
+ }
+
+ return make_string_ref(balloc, StringRef{s.c_str(), s.size() - len});
+}
+
+#ifdef ENABLE_HTTP3
+int msghdr_get_local_addr(Address &dest, msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+ in_pktinfo pktinfo;
+ memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
+ dest.len = sizeof(dest.su.in);
+ auto &sa = dest.su.in;
+ sa.sin_family = AF_INET;
+ sa.sin_addr = pktinfo.ipi_addr;
+
+ return 0;
+ }
+ }
+
+ return -1;
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
+ in6_pktinfo pktinfo;
+ memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
+ dest.len = sizeof(dest.su.in6);
+ auto &sa = dest.su.in6;
+ sa.sin6_family = AF_INET6;
+ sa.sin6_addr = pktinfo.ipi6_addr;
+ return 0;
+ }
+ }
+
+ return -1;
+ }
+
+ return -1;
+}
+
+uint8_t msghdr_get_ecn(msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP &&
+# ifdef __APPLE__
+ cmsg->cmsg_type == IP_RECVTOS
+# else // !__APPLE__
+ cmsg->cmsg_type == IP_TOS
+# endif // !__APPLE__
+ && cmsg->cmsg_len) {
+ return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK;
+ }
+ }
+
+ return 0;
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS &&
+ cmsg->cmsg_len) {
+ unsigned int tos;
+
+ memcpy(&tos, CMSG_DATA(cmsg), sizeof(tos));
+
+ return tos & IPTOS_ECN_MASK;
+ }
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+size_t msghdr_get_udp_gro(msghdr *msg) {
+ uint16_t gso_size = 0;
+
+# ifdef UDP_GRO
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
+ memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
+
+ break;
+ }
+ }
+# endif // UDP_GRO
+
+ return gso_size;
+}
+#endif // ENABLE_HTTP3
+
+} // namespace util
+
+} // namespace nghttp2
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..d818bf2
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,971 @@
+/*
+ * 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"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif // HAVE_NETDB_H
+
+#include <cmath>
+#include <cstring>
+#include <cassert>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <sstream>
+#include <memory>
+#include <chrono>
+#include <map>
+#include <random>
+
+#ifdef HAVE_LIBEV
+# include <ev.h>
+#endif // HAVE_LIBEV
+
+#include "url-parser/url_parser.h"
+
+#include "template.h"
+#include "network.h"
+#include "allocator.h"
+
+namespace nghttp2 {
+
+constexpr auto NGHTTP2_H2_ALPN = StringRef::from_lit("\x2h2");
+constexpr auto NGHTTP2_H2 = StringRef::from_lit("h2");
+
+// The additional HTTP/2 protocol ALPN protocol identifier we also
+// supports for our applications to make smooth migration into final
+// h2 ALPN ID.
+constexpr auto NGHTTP2_H2_16_ALPN = StringRef::from_lit("\x5h2-16");
+constexpr auto NGHTTP2_H2_16 = StringRef::from_lit("h2-16");
+
+constexpr auto NGHTTP2_H2_14_ALPN = StringRef::from_lit("\x5h2-14");
+constexpr auto NGHTTP2_H2_14 = StringRef::from_lit("h2-14");
+
+constexpr auto NGHTTP2_H1_1_ALPN = StringRef::from_lit("\x8http/1.1");
+constexpr auto NGHTTP2_H1_1 = StringRef::from_lit("http/1.1");
+
+constexpr size_t NGHTTP2_MAX_UINT64_DIGITS = str_size("18446744073709551615");
+
+namespace util {
+
+extern const char UPPER_XDIGITS[];
+
+inline bool is_alpha(const char c) {
+ return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+}
+
+inline bool is_digit(const char c) { return '0' <= c && c <= '9'; }
+
+inline bool is_hex_digit(const char c) {
+ return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
+}
+
+// Returns true if |s| is hex string.
+bool is_hex_string(const StringRef &s);
+
+bool in_rfc3986_unreserved_chars(const char c);
+
+bool in_rfc3986_sub_delims(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);
+
+// Returns integer corresponding to hex notation |c|. If
+// is_hex_digit(c) is false, it returns 256.
+uint32_t hex_to_uint(char c);
+
+std::string percent_encode(const unsigned char *target, size_t len);
+
+std::string percent_encode(const std::string &target);
+
+template <typename InputIt>
+std::string percent_decode(InputIt first, InputIt last) {
+ std::string result;
+ result.resize(last - first);
+ auto p = std::begin(result);
+ for (; first != last; ++first) {
+ if (*first != '%') {
+ *p++ = *first;
+ continue;
+ }
+
+ if (first + 1 != last && first + 2 != last && is_hex_digit(*(first + 1)) &&
+ is_hex_digit(*(first + 2))) {
+ *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
+ first += 2;
+ continue;
+ }
+
+ *p++ = *first;
+ }
+ result.resize(p - std::begin(result));
+ return result;
+}
+
+StringRef percent_decode(BlockAllocator &balloc, const StringRef &src);
+
+// Percent encode |target| if character is not in token or '%'.
+StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target);
+
+template <typename OutputIt>
+OutputIt percent_encode_token(OutputIt it, const StringRef &target) {
+ for (auto first = std::begin(target); first != std::end(target); ++first) {
+ uint8_t c = *first;
+
+ if (c != '%' && in_token(c)) {
+ *it++ = c;
+ continue;
+ }
+
+ *it++ = '%';
+ *it++ = UPPER_XDIGITS[c >> 4];
+ *it++ = UPPER_XDIGITS[(c & 0x0f)];
+ }
+
+ return it;
+}
+
+// Returns the number of bytes written by percent_encode_token with
+// the same |target| parameter. The return value does not include a
+// terminal NUL byte.
+size_t percent_encode_tokenlen(const StringRef &target);
+
+// Returns quotedString version of |target|. Currently, this function
+// just replace '"' with '\"'.
+StringRef quote_string(BlockAllocator &balloc, const StringRef &target);
+
+template <typename OutputIt>
+OutputIt quote_string(OutputIt it, const StringRef &target) {
+ for (auto c : target) {
+ if (c == '"') {
+ *it++ = '\\';
+ *it++ = '"';
+ } else {
+ *it++ = c;
+ }
+ }
+
+ return it;
+}
+
+// Returns the number of bytes written by quote_string with the same
+// |target| parameter. The return value does not include a terminal
+// NUL byte.
+size_t quote_stringlen(const StringRef &target);
+
+std::string format_hex(const unsigned char *s, size_t len);
+
+template <size_t N> std::string format_hex(const unsigned char (&s)[N]) {
+ return format_hex(s, N);
+}
+
+template <size_t N> std::string format_hex(const std::array<uint8_t, N> &s) {
+ return format_hex(s.data(), s.size());
+}
+
+StringRef format_hex(BlockAllocator &balloc, const StringRef &s);
+
+static constexpr char LOWER_XDIGITS[] = "0123456789abcdef";
+
+template <typename OutputIt>
+OutputIt format_hex(OutputIt it, const StringRef &s) {
+ for (auto cc : s) {
+ uint8_t c = cc;
+ *it++ = LOWER_XDIGITS[c >> 4];
+ *it++ = LOWER_XDIGITS[c & 0xf];
+ }
+
+ return it;
+}
+
+// decode_hex decodes hex string |s|, returns the decoded byte string.
+// This function assumes |s| is hex string, that is is_hex_string(s)
+// == true.
+StringRef decode_hex(BlockAllocator &balloc, const StringRef &s);
+
+template <typename OutputIt>
+OutputIt decode_hex(OutputIt d_first, const StringRef &s) {
+ for (auto it = std::begin(s); it != std::end(s); it += 2) {
+ *d_first++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1));
+ }
+
+ return d_first;
+}
+
+// Returns given time |t| from epoch in HTTP Date format (e.g., Mon,
+// 10 Oct 2016 10:25:58 GMT).
+std::string http_date(time_t t);
+// Writes given time |t| from epoch in HTTP Date format into the
+// buffer pointed by |res|. The buffer must be at least 29 bytes
+// long. This function returns the one beyond the last position.
+char *http_date(char *res, 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);
+// Writes given time |t| from epoch in Common Log format into the
+// buffer pointed by |res|. The buffer must be at least 26 bytes
+// long. This function returns the one beyond the last position.
+char *common_log_date(char *res, time_t t);
+
+// Returns given millisecond |ms| from epoch in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00)
+std::string iso8601_date(int64_t ms);
+// Writes given time |t| from epoch in ISO 8601 format into the buffer
+// pointed by |res|. The buffer must be at least 29 bytes long. This
+// function returns the one beyond the last position.
+char *iso8601_date(char *res, int64_t ms);
+
+// Writes given time |t| from epoch in ISO 8601 basic format into the
+// buffer pointed by |res|. The buffer must be at least 24 bytes
+// long. This function returns the one beyond the last position.
+char *iso8601_basic_date(char *res, int64_t ms);
+
+time_t parse_http_date(const StringRef &s);
+
+// Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3
+// 00:55:52 2015 GMT), which is specifically used by OpenSSL
+// ASN1_TIME_print().
+time_t parse_openssl_asn1_time_print(const StringRef &s);
+
+char upcase(char c);
+
+inline char lowcase(char c) {
+ constexpr 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)];
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool starts_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, first1);
+}
+
+template <typename S, typename T> bool starts_with(const S &a, const T &b) {
+ return starts_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+struct CaseCmp {
+ bool operator()(char lhs, char rhs) const {
+ return lowcase(lhs) == lowcase(rhs);
+ }
+};
+
+template <typename InputIterator1, typename InputIterator2>
+bool istarts_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, first1, CaseCmp());
+}
+
+template <typename S, typename T> bool istarts_with(const S &a, const T &b) {
+ return istarts_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool istarts_with_l(const T &a, const CharT (&b)[N]) {
+ return istarts_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool ends_with(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 T, typename S> bool ends_with(const T &a, const S &b) {
+ return ends_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool ends_with_l(const T &a, const CharT (&b)[N]) {
+ return ends_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool iends_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp());
+}
+
+template <typename T, typename S> bool iends_with(const T &a, const S &b) {
+ return iends_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename T, typename CharT, size_t N>
+bool iends_with_l(const T &a, const CharT (&b)[N]) {
+ return iends_with(a.begin(), a.end(), b, b + N - 1);
+}
+
+template <typename InputIt1, typename InputIt2>
+bool strieq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {
+ if (std::distance(first1, last1) != std::distance(first2, last2)) {
+ return false;
+ }
+
+ return std::equal(first1, last1, first2, CaseCmp());
+}
+
+template <typename T, typename S> bool strieq(const T &a, const S &b) {
+ return strieq(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename CharT, typename InputIt, size_t N>
+bool strieq_l(const CharT (&a)[N], InputIt b, size_t blen) {
+ return strieq(a, a + (N - 1), b, b + blen);
+}
+
+template <typename CharT, size_t N, typename T>
+bool strieq_l(const CharT (&a)[N], const T &b) {
+ return strieq(a, a + (N - 1), b.begin(), b.end());
+}
+
+template <typename InputIt1, typename InputIt2>
+bool streq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {
+ if (std::distance(first1, last1) != std::distance(first2, last2)) {
+ return false;
+ }
+ return std::equal(first1, last1, first2);
+}
+
+template <typename T, typename S> bool streq(const T &a, const S &b) {
+ return streq(a.begin(), a.end(), b.begin(), b.end());
+}
+
+template <typename CharT, typename InputIt, size_t N>
+bool streq_l(const CharT (&a)[N], InputIt b, size_t blen) {
+ return streq(a, a + (N - 1), b, b + blen);
+}
+
+template <typename CharT, size_t N, typename T>
+bool streq_l(const CharT (&a)[N], const T &b) {
+ return streq(a, a + (N - 1), b.begin(), b.end());
+}
+
+// Returns true if |a| contains |b|. If both |a| and |b| are empty,
+// this function returns false.
+template <typename S, typename T> bool strifind(const S &a, const T &b) {
+ return std::search(a.begin(), a.end(), b.begin(), b.end(), CaseCmp()) !=
+ a.end();
+}
+
+template <typename InputIt> void inp_strlower(InputIt first, InputIt last) {
+ std::transform(first, last, first, lowcase);
+}
+
+// Lowercase |s| in place.
+inline void inp_strlower(std::string &s) {
+ inp_strlower(std::begin(s), std::end(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;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ res.resize(nlen);
+ for (; n; n /= 10) {
+ res[--nlen] = (n % 10) + '0';
+ }
+ return res;
+}
+
+template <typename T, typename OutputIt> OutputIt utos(OutputIt dst, T n) {
+ if (n == 0) {
+ *dst++ = '0';
+ return dst;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ auto p = dst + nlen;
+ auto res = p;
+ for (; n; n /= 10) {
+ *--p = (n % 10) + '0';
+ }
+ return res;
+}
+
+template <typename T>
+StringRef make_string_ref_uint(BlockAllocator &balloc, T n) {
+ auto iov = make_byte_ref(balloc, NGHTTP2_MAX_UINT64_DIGITS + 1);
+ auto p = iov.base;
+ p = util::utos(p, n);
+ *p = '\0';
+ return StringRef{iov.base, p};
+}
+
+template <typename T> std::string utos_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_unit(), but 2 digits fraction part is followed.
+template <typename T> std::string utos_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;
+}
+
+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);
+
+StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str);
+
+void show_candidates(const char *unkopt, const 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);
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+ http_parser_url_fields field, const StringRef &t);
+
+StringRef 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);
+
+bool numeric_host(const char *hostname, int family);
+
+// Returns numeric address string of |addr|. If getnameinfo() is
+// failed, "unknown" is returned.
+std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
+
+// Returns string representation of numeric address and port of
+// |addr|. If address family is AF_UNIX, this return path to UNIX
+// domain socket. Otherwise, the format is like <HOST>:<PORT>. For
+// IPv6 address, address is enclosed by square brackets ([]).
+std::string to_numeric_addr(const Address *addr);
+
+std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen);
+
+// Sets |port| to |addr|.
+void set_port(Address &addr, uint16_t port);
+
+// 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]. Otherwise 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| is supported HTTP/2 protocol
+// identifier.
+bool check_h2_is_selected(const StringRef &proto);
+
+// 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);
+
+// Selects protocol ALPN ID if one of identifiers contained in |protolist| is
+// present in |in| of length inlen. Returns true if identifier is
+// selected.
+bool select_protocol(const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ std::vector<std::string> proto_list);
+
+// Returns default ALPN protocol list, which only contains supported
+// HTTP/2 protocol identifier.
+std::vector<unsigned char> get_default_alpn();
+
+// Parses delimited strings in |s| and returns the array of substring,
+// delimited by |delim|. The any white spaces around substring are
+// treated as a part of substring.
+std::vector<std::string> parse_config_str_list(const StringRef &s,
+ char delim = ',');
+
+// Parses delimited strings in |s| and returns Substrings in |s|
+// delimited by |delim|. The any white spaces around substring are
+// treated as a part of substring.
+std::vector<StringRef> split_str(const StringRef &s, char delim);
+
+// Behaves like split_str, but this variant splits at most |n| - 1
+// times and returns at most |n| sub-strings. If |n| is zero, it
+// falls back to split_str.
+std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n);
+
+// Writes given time |tp| in Common Log format (e.g.,
+// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer
+// must be at least 27 bytes, including terminal NULL byte. Expected
+// type of |tp| is std::chrono::time_point. This function returns
+// StringRef wrapping the buffer pointed by |out|, and this string is
+// terminated by NULL.
+template <typename T> StringRef format_common_log(char *out, const T &tp) {
+ auto t =
+ std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
+ auto p = common_log_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Returns given time |tp| in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00).
+// Expected type of |tp| is std::chrono::time_point
+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());
+}
+
+// Writes given time |tp| in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00) in
+// buffer pointed by |out|. The buffer must be at least 30 bytes,
+// including terminal NULL byte. Expected type of |tp| is
+// std::chrono::time_point. This function returns StringRef wrapping
+// the buffer pointed by |out|, and this string is terminated by NULL.
+template <typename T> StringRef format_iso8601(char *out, const T &tp) {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ tp.time_since_epoch());
+ auto p = iso8601_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Writes given time |tp| in ISO 8601 basic format (e.g.,
+// 20141115T125824.741Z or 20141115T125824.741+0900) in buffer pointed
+// by |out|. The buffer must be at least 25 bytes, including terminal
+// NULL byte. Expected type of |tp| is std::chrono::time_point. This
+// function returns StringRef wrapping the buffer pointed by |out|,
+// and this string is terminated by NULL.
+template <typename T> StringRef format_iso8601_basic(char *out, const T &tp) {
+ auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+ tp.time_since_epoch());
+ auto p = iso8601_basic_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// Writes given time |tp| in HTTP Date format (e.g., Mon, 10 Oct 2016
+// 10:25:58 GMT) in buffer pointed by |out|. The buffer must be at
+// least 30 bytes, including terminal NULL byte. Expected type of
+// |tp| is std::chrono::time_point. This function returns StringRef
+// wrapping the buffer pointed by |out|, and this string is terminated
+// by NULL.
+template <typename T> StringRef format_http_date(char *out, const T &tp) {
+ auto t =
+ std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
+ auto p = http_date(out, t.count());
+ *p = '\0';
+ return StringRef{out, p};
+}
+
+// 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();
+}
+
+#ifdef HAVE_LIBEV
+template <typename Duration = std::chrono::steady_clock::duration>
+Duration duration_from(ev_tstamp d) {
+ return std::chrono::duration_cast<Duration>(std::chrono::duration<double>(d));
+}
+
+template <typename Duration> ev_tstamp ev_tstamp_from(const Duration &d) {
+ return std::chrono::duration<double>(d).count();
+}
+#endif // HAVE_LIBEV
+
+int make_socket_closeonexec(int fd);
+int make_socket_nonblocking(int fd);
+int make_socket_nodelay(int fd);
+
+int create_nonblock_socket(int family);
+int create_nonblock_udp_socket(int family);
+
+int bind_any_addr_udp(int fd, int family);
+
+bool check_socket_connected(int fd);
+
+// Returns the error code (errno) by inspecting SO_ERROR of given
+// |fd|. This function returns the error code if it succeeds, or -1.
+// Returning 0 means no error.
+int get_socket_error(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);
+// The following overload does not require |s| is NULL terminated.
+int64_t parse_uint_with_unit(const uint8_t *s, size_t len);
+int64_t parse_uint_with_unit(const StringRef &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);
+// The following overload does not require |s| is NULL terminated.
+int64_t parse_uint(const uint8_t *s, size_t len);
+int64_t parse_uint(const std::string &s);
+int64_t parse_uint(const StringRef &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. Similarly, it also supports 'm' and 'h' for
+// minutes and hours respectively. 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);
+// The following overload does not require |s| is NULL terminated.
+double parse_duration_with_unit(const uint8_t *s, size_t len);
+double parse_duration_with_unit(const StringRef &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);
+
+// Just like above, but this takes |t| as seconds.
+std::string format_duration(double t);
+
+// The maximum buffer size including terminal NULL to store the result
+// of make_hostport.
+constexpr size_t max_hostport = NI_MAXHOST + /* [] for IPv6 */ 2 + /* : */ 1 +
+ /* port */ 5 + /* terminal NULL */ 1;
+
+// Just like make_http_hostport(), but doesn't treat 80 and 443
+// specially.
+StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port);
+
+template <typename OutputIt>
+StringRef make_hostport(OutputIt first, const StringRef &host, uint16_t port) {
+ auto ipv6 = ipv6_numeric_addr(host.c_str());
+ auto serv = utos(port);
+ auto p = first;
+
+ if (ipv6) {
+ *p++ = '[';
+ }
+
+ p = std::copy(std::begin(host), std::end(host), p);
+
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ *p++ = ':';
+
+ p = std::copy(std::begin(serv), std::end(serv), p);
+
+ *p = '\0';
+
+ return StringRef{first, p};
+}
+
+// Creates "host:port" string using given |host| and |port|. If
+// |host| is numeric IPv6 address (e.g., ::1), it is enclosed by "["
+// and "]". If |port| is 80 or 443, port part is omitted.
+StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host,
+ uint16_t port);
+
+template <typename OutputIt>
+StringRef make_http_hostport(OutputIt first, const StringRef &host,
+ uint16_t port) {
+ if (port != 80 && port != 443) {
+ return make_hostport(first, host, port);
+ }
+
+ auto ipv6 = ipv6_numeric_addr(host.c_str());
+ auto p = first;
+
+ if (ipv6) {
+ *p++ = '[';
+ }
+
+ p = std::copy(std::begin(host), std::end(host), p);
+
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ *p = '\0';
+
+ return StringRef{first, p};
+}
+
+// Dumps |src| of length |len| in the format similar to `hexdump -C`.
+void hexdump(FILE *out, const uint8_t *src, size_t len);
+
+// Copies 2 byte unsigned integer |n| in host byte order to |buf| in
+// network byte order.
+void 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 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 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 get_uint32(const uint8_t *data);
+
+// Retrieves 8 byte unsigned integer stored in |data| in network byte
+// order and returns it in host byte order.
+uint64_t get_uint64(const uint8_t *data);
+
+// Reads mime types file (see /etc/mime.types), and stores extension
+// -> MIME type map in |res|. This function returns 0 if it succeeds,
+// or -1.
+int read_mime_types(std::map<std::string, std::string> &res,
+ const char *filename);
+
+// Fills random alpha and digit byte to the range [|first|, |last|).
+// Returns the one beyond the |last|.
+template <typename OutputIt, typename Generator>
+OutputIt random_alpha_digit(OutputIt first, OutputIt last, Generator &gen) {
+ // If we use uint8_t instead char, gcc 6.2.0 complains by shouting
+ // char-array initialized from wide string.
+ static constexpr char s[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ std::uniform_int_distribution<> dis(0, 26 * 2 + 10 - 1);
+ for (; first != last; ++first) {
+ *first = s[dis(gen)];
+ }
+ return first;
+}
+
+// Fills random bytes to the range [|first|, |last|).
+template <typename OutputIt, typename Generator>
+void random_bytes(OutputIt first, OutputIt last, Generator &gen) {
+ std::uniform_int_distribution<uint8_t> dis;
+ std::generate(first, last, [&dis, &gen]() { return dis(gen); });
+}
+
+// Shuffles the range [|first|, |last|] by calling swap function |fun|
+// for each pair. |fun| takes 2 RandomIt iterators. If |fun| is
+// noop, no modification is made.
+template <typename RandomIt, typename Generator, typename SwapFun>
+void shuffle(RandomIt first, RandomIt last, Generator &&gen, SwapFun fun) {
+ auto len = std::distance(first, last);
+ if (len < 2) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < static_cast<unsigned int>(len - 1); ++i) {
+ auto dis = std::uniform_int_distribution<unsigned int>(i, len - 1);
+ auto j = dis(gen);
+ if (i == j) {
+ continue;
+ }
+ fun(first + i, first + j);
+ }
+}
+
+template <typename OutputIterator, typename CharT, size_t N>
+OutputIterator copy_lit(OutputIterator it, CharT (&s)[N]) {
+ return std::copy_n(s, N - 1, it);
+}
+
+// Returns x**y
+double int_pow(double x, size_t y);
+
+uint32_t hash32(const StringRef &s);
+
+// Computes SHA-256 of |s|, and stores it in |buf|. This function
+// returns 0 if it succeeds, or -1.
+int sha256(uint8_t *buf, const StringRef &s);
+
+// Computes SHA-1 of |s|, and stores it in |buf|. This function
+// returns 0 if it succeeds, or -1.
+int sha1(uint8_t *buf, const StringRef &s);
+
+// Returns host from |hostport|. If host cannot be found in
+// |hostport|, returns empty string. The returned string might not be
+// NULL-terminated.
+StringRef extract_host(const StringRef &hostport);
+
+// split_hostport splits host and port in |hostport|. Unlike
+// extract_host, square brackets enclosing host name is stripped. If
+// port is not available, it returns empty string in the second
+// string. The returned string might not be NULL-terminated. On any
+// error, it returns a pair which has empty strings.
+std::pair<StringRef, StringRef> split_hostport(const StringRef &hostport);
+
+// Returns new std::mt19937 object.
+std::mt19937 make_mt19937();
+
+// daemonize calls daemon(3). If __APPLE__ is defined, it implements
+// daemon() using fork().
+int daemonize(int nochdir, int noclose);
+
+// Returns |s| from which trailing white spaces (SPC or HTAB) are
+// removed. If any white spaces are removed, new string is allocated
+// by |balloc| and returned. Otherwise, the copy of |s| is returned
+// without allocation.
+StringRef rstrip(BlockAllocator &balloc, const StringRef &s);
+
+#ifdef ENABLE_HTTP3
+int msghdr_get_local_addr(Address &dest, msghdr *msg, int family);
+
+uint8_t msghdr_get_ecn(msghdr *msg, int family);
+
+// msghdr_get_udp_gro returns UDP_GRO value from |msg|. If UDP_GRO is
+// not found, or UDP_GRO is not supported, this function returns 0.
+size_t msghdr_get_udp_gro(msghdr *msg);
+#endif // ENABLE_HTTP3
+
+} // namespace util
+
+} // namespace nghttp2
+
+#endif // UTIL_H
diff --git a/src/util_test.cc b/src/util_test.cc
new file mode 100644
index 0000000..0ac0f1d
--- /dev/null
+++ b/src/util_test.cc
@@ -0,0 +1,707 @@
+/*
+ * 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 <random>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_util_streq(void) {
+ CU_ASSERT(
+ util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha")));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alpha"),
+ StringRef::from_lit("alphabravo")));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alphabravo"),
+ StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA")));
+ CU_ASSERT(!util::streq(StringRef{}, StringRef::from_lit("a")));
+ CU_ASSERT(util::streq(StringRef{}, StringRef{}));
+ CU_ASSERT(!util::streq(StringRef::from_lit("alpha"), StringRef{}));
+
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alph"), StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alph")));
+ CU_ASSERT(
+ !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA")));
+
+ CU_ASSERT(util::streq_l("alpha", "alpha", 5));
+ CU_ASSERT(util::streq_l("alpha", "alphabravo", 5));
+ CU_ASSERT(!util::streq_l("alpha", "alphabravo", 6));
+ CU_ASSERT(!util::streq_l("alphabravo", "alpha", 5));
+ CU_ASSERT(!util::streq_l("alpha", "alphA", 5));
+ CU_ASSERT(!util::streq_l("", "a", 1));
+ CU_ASSERT(util::streq_l("", "", 0));
+ CU_ASSERT(!util::streq_l("alpha", "", 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 ")));
+
+ CU_ASSERT(
+ util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha")));
+ CU_ASSERT(
+ util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("AlPhA")));
+ CU_ASSERT(util::strieq(StringRef{}, StringRef{}));
+ CU_ASSERT(!util::strieq(StringRef::from_lit("alpha"),
+ StringRef::from_lit("AlPhA ")));
+ CU_ASSERT(
+ !util::strieq(StringRef::from_lit(""), StringRef::from_lit("AlPhA ")));
+
+ CU_ASSERT(util::strieq_l("alpha", "alpha", 5));
+ CU_ASSERT(util::strieq_l("alpha", "AlPhA", 5));
+ CU_ASSERT(util::strieq_l("", static_cast<const char *>(nullptr), 0));
+ CU_ASSERT(!util::strieq_l("alpha", "AlPhA ", 6));
+ CU_ASSERT(!util::strieq_l("", "AlPhA ", 6));
+
+ CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("alpha")));
+ CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("AlPhA")));
+ CU_ASSERT(util::strieq_l("", StringRef{}));
+ CU_ASSERT(!util::strieq_l("alpha", StringRef::from_lit("AlPhA ")));
+ CU_ASSERT(!util::strieq_l("", StringRef::from_lit("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) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("AAA++B/=" ==
+ util::to_base64(balloc, StringRef::from_lit("AAA--B_")));
+ CU_ASSERT("AAA++B/B" ==
+ util::to_base64(balloc, StringRef::from_lit("AAA--B_B")));
+}
+
+void test_util_to_token68(void) {
+ std::string x = "AAA++B/=";
+ util::to_token68(x);
+ CU_ASSERT("AAA--B_" == x);
+
+ x = "AAA++B/B";
+ util::to_token68(x);
+ CU_ASSERT("AAA--B_B" == x);
+}
+
+void test_util_percent_encode_token(void) {
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("h2" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("h2")));
+ CU_ASSERT("h3~" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("h3~")));
+ CU_ASSERT("100%25" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("100%")));
+ CU_ASSERT("http%202" ==
+ util::percent_encode_token(balloc, StringRef::from_lit("http 2")));
+}
+
+void test_util_percent_decode(void) {
+ {
+ std::string s = "%66%6F%6f%62%61%72";
+ CU_ASSERT("foobar" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ {
+ std::string s = "%66%6";
+ CU_ASSERT("f%6" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ {
+ std::string s = "%66%";
+ CU_ASSERT("f%" == util::percent_decode(std::begin(s), std::end(s)));
+ }
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("foobar" == util::percent_decode(
+ balloc, StringRef::from_lit("%66%6F%6f%62%61%72")));
+
+ CU_ASSERT("f%6" ==
+ util::percent_decode(balloc, StringRef::from_lit("%66%6")));
+
+ CU_ASSERT("f%" == util::percent_decode(balloc, StringRef::from_lit("%66%")));
+}
+
+void test_util_quote_string(void) {
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("alpha" ==
+ util::quote_string(balloc, StringRef::from_lit("alpha")));
+ CU_ASSERT("" == util::quote_string(balloc, StringRef::from_lit("")));
+ CU_ASSERT("\\\"alpha\\\"" ==
+ util::quote_string(balloc, StringRef::from_lit("\"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));
+
+ std::array<char, 30> http_buf;
+
+ CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" ==
+ util::format_http_date(http_buf.data(),
+ std::chrono::system_clock::time_point()));
+ CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" ==
+ util::format_http_date(http_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::seconds(1330506916))));
+}
+
+void test_util_select_h2(void) {
+ const unsigned char *out = nullptr;
+ unsigned char outlen = 0;
+
+ // Check single entry and select it.
+ const unsigned char t1[] = "\x2h2";
+ 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 = nullptr;
+ 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 is located after bogus ID.
+ const unsigned char t3[] = "\x2h3\x2h2";
+ 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 = nullptr;
+ 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(util::streq(NGHTTP2_H2_16, StringRef{out, 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(void) {
+ uint8_t buf[32];
+
+ CU_ASSERT(("0" == StringRef{buf, util::utos(buf, 0)}));
+ CU_ASSERT(("123" == StringRef{buf, util::utos(buf, 123)}));
+ CU_ASSERT(("18446744073709551615" ==
+ StringRef{buf, util::utos(buf, 18446744073709551615ULL)}));
+}
+
+void test_util_make_string_ref_uint(void) {
+ BlockAllocator balloc(1024, 1024);
+
+ CU_ASSERT("0" == util::make_string_ref_uint(balloc, 0));
+ CU_ASSERT("123" == util::make_string_ref_uint(balloc, 123));
+ CU_ASSERT("18446744073709551615" ==
+ util::make_string_ref_uint(balloc, 18446744073709551615ULL));
+}
+
+void test_util_utos_unit(void) {
+ CU_ASSERT("0" == util::utos_unit(0));
+ CU_ASSERT("1023" == util::utos_unit(1023));
+ CU_ASSERT("1K" == util::utos_unit(1024));
+ CU_ASSERT("1K" == util::utos_unit(1025));
+ CU_ASSERT("1M" == util::utos_unit(1 << 20));
+ CU_ASSERT("1G" == util::utos_unit(1 << 30));
+ CU_ASSERT("1024G" == util::utos_unit(1LL << 40));
+}
+
+void test_util_utos_funit(void) {
+ CU_ASSERT("0" == util::utos_funit(0));
+ CU_ASSERT("1023" == util::utos_funit(1023));
+ CU_ASSERT("1.00K" == util::utos_funit(1024));
+ CU_ASSERT("1.00K" == util::utos_funit(1025));
+ CU_ASSERT("1.09K" == util::utos_funit(1119));
+ CU_ASSERT("1.27K" == util::utos_funit(1300));
+ CU_ASSERT("1.00M" == util::utos_funit(1 << 20));
+ CU_ASSERT("1.18M" == util::utos_funit(1234567));
+ CU_ASSERT("1.00G" == util::utos_funit(1 << 30));
+ CU_ASSERT("4492450797.23G" == util::utos_funit(4823732313248234343LL));
+ CU_ASSERT("1024.00G" == util::utos_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"));
+ CU_ASSERT(180 == util::parse_duration_with_unit("3m"));
+ CU_ASSERT(3600 * 5 == util::parse_duration_with_unit("5h"));
+
+ 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));
+ CU_ASSERT("2m" == util::duration_str(120.));
+ CU_ASSERT("121s" == util::duration_str(121.));
+ CU_ASSERT("1h" == util::duration_str(3600.));
+}
+
+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)));
+
+ CU_ASSERT("0us" == util::format_duration(0.));
+ CU_ASSERT("999us" == util::format_duration(0.000999));
+ CU_ASSERT("1.00ms" == util::format_duration(0.001));
+ CU_ASSERT("1.09ms" == util::format_duration(0.00109));
+ CU_ASSERT("1.01ms" == util::format_duration(0.001009));
+ CU_ASSERT("999.99ms" == util::format_duration(0.99999));
+ CU_ASSERT("1.00s" == util::format_duration(1.));
+ CU_ASSERT("1.05s" == util::format_duration(1.05));
+}
+
+void test_util_starts_with(void) {
+ CU_ASSERT(util::starts_with(StringRef::from_lit("foo"),
+ StringRef::from_lit("foo")));
+ CU_ASSERT(util::starts_with(StringRef::from_lit("fooo"),
+ StringRef::from_lit("foo")));
+ CU_ASSERT(util::starts_with(StringRef::from_lit("ofoo"), StringRef{}));
+ CU_ASSERT(!util::starts_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("foo")));
+
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("FOO"),
+ StringRef::from_lit("fOO")));
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("ofoo"), StringRef{}));
+ CU_ASSERT(util::istarts_with(StringRef::from_lit("fOOo"),
+ StringRef::from_lit("Foo")));
+ CU_ASSERT(!util::istarts_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("foo")));
+
+ CU_ASSERT(util::istarts_with_l(StringRef::from_lit("fOOo"), "Foo"));
+ CU_ASSERT(!util::istarts_with_l(StringRef::from_lit("ofoo"), "foo"));
+}
+
+void test_util_ends_with(void) {
+ CU_ASSERT(
+ util::ends_with(StringRef::from_lit("foo"), StringRef::from_lit("foo")));
+ CU_ASSERT(util::ends_with(StringRef::from_lit("foo"), StringRef{}));
+ CU_ASSERT(
+ util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("foo")));
+ CU_ASSERT(
+ !util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("fo")));
+
+ CU_ASSERT(
+ util::iends_with(StringRef::from_lit("fOo"), StringRef::from_lit("Foo")));
+ CU_ASSERT(util::iends_with(StringRef::from_lit("foo"), StringRef{}));
+ CU_ASSERT(util::iends_with(StringRef::from_lit("oFoo"),
+ StringRef::from_lit("fOO")));
+ CU_ASSERT(!util::iends_with(StringRef::from_lit("ofoo"),
+ StringRef::from_lit("fo")));
+
+ CU_ASSERT(util::iends_with_l(StringRef::from_lit("oFoo"), "fOO"));
+ CU_ASSERT(!util::iends_with_l(StringRef::from_lit("ofoo"), "fo"));
+}
+
+void test_util_parse_http_date(void) {
+ CU_ASSERT(1001939696 == util::parse_http_date(StringRef::from_lit(
+ "Mon, 1 Oct 2001 12:34:56 GMT")));
+}
+
+void test_util_localtime_date(void) {
+ auto tz = getenv("TZ");
+ if (tz) {
+ tz = strdup(tz);
+ }
+#ifdef __linux__
+ setenv("TZ", "NZST-12:00:00:00", 1);
+#else // !__linux__
+ setenv("TZ", ":Pacific/Auckland", 1);
+#endif // !__linux__
+ tzset();
+
+ CU_ASSERT_STRING_EQUAL("02/Oct/2001:00:34:56 +1200",
+ util::common_log_date(1001939696).c_str());
+ CU_ASSERT_STRING_EQUAL("2001-10-02T00:34:56.123+12:00",
+ util::iso8601_date(1001939696000LL + 123).c_str());
+
+ std::array<char, 27> common_buf;
+
+ CU_ASSERT("02/Oct/2001:00:34:56 +1200" ==
+ util::format_common_log(common_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::seconds(1001939696))));
+
+ std::array<char, 30> iso8601_buf;
+
+ CU_ASSERT(
+ "2001-10-02T00:34:56.123+12:00" ==
+ util::format_iso8601(iso8601_buf.data(),
+ std::chrono::system_clock::time_point(
+ std::chrono::milliseconds(1001939696123LL))));
+
+ if (tz) {
+ setenv("TZ", tz, 1);
+ free(tz);
+ } else {
+ unsetenv("TZ");
+ }
+ tzset();
+}
+
+void test_util_get_uint64(void) {
+ {
+ auto v = std::array<unsigned char, 8>{
+ {0x01, 0x12, 0x34, 0x56, 0xff, 0x9a, 0xab, 0xbc}};
+
+ auto n = util::get_uint64(v.data());
+
+ CU_ASSERT(0x01123456ff9aabbcULL == n);
+ }
+ {
+ auto v = std::array<unsigned char, 8>{
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+
+ auto n = util::get_uint64(v.data());
+
+ CU_ASSERT(0xffffffffffffffffULL == n);
+ }
+}
+
+void test_util_parse_config_str_list(void) {
+ auto res = util::parse_config_str_list(StringRef::from_lit("a"));
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("a" == res[0]);
+
+ res = util::parse_config_str_list(StringRef::from_lit("a,"));
+ CU_ASSERT(2 == res.size());
+ CU_ASSERT("a" == res[0]);
+ CU_ASSERT("" == res[1]);
+
+ res = util::parse_config_str_list(StringRef::from_lit(":a::"), ':');
+ CU_ASSERT(4 == res.size());
+ CU_ASSERT("" == res[0]);
+ CU_ASSERT("a" == res[1]);
+ CU_ASSERT("" == res[2]);
+ CU_ASSERT("" == res[3]);
+
+ res = util::parse_config_str_list(StringRef{});
+ CU_ASSERT(1 == res.size());
+ CU_ASSERT("" == res[0]);
+
+ res = util::parse_config_str_list(StringRef::from_lit("alpha,bravo,charlie"));
+ CU_ASSERT(3 == res.size());
+ CU_ASSERT("alpha" == res[0]);
+ CU_ASSERT("bravo" == res[1]);
+ CU_ASSERT("charlie" == res[2]);
+}
+
+void test_util_make_http_hostport(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("localhost" == util::make_http_hostport(
+ balloc, StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]" ==
+ util::make_http_hostport(balloc, StringRef::from_lit("::1"), 443));
+ CU_ASSERT(
+ "localhost:3000" ==
+ util::make_http_hostport(balloc, StringRef::from_lit("localhost"), 3000));
+}
+
+void test_util_make_hostport(void) {
+ std::array<char, util::max_hostport> hostport_buf;
+ CU_ASSERT("localhost:80" ==
+ util::make_hostport(std::begin(hostport_buf),
+ StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]:443" == util::make_hostport(std::begin(hostport_buf),
+ StringRef::from_lit("::1"),
+ 443));
+
+ BlockAllocator balloc(4096, 4096);
+ CU_ASSERT("localhost:80" ==
+ util::make_hostport(balloc, StringRef::from_lit("localhost"), 80));
+ CU_ASSERT("[::1]:443" ==
+ util::make_hostport(balloc, StringRef::from_lit("::1"), 443));
+}
+
+void test_util_strifind(void) {
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("gzip")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("dEflate")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"),
+ StringRef::from_lit("BZIP2")));
+
+ CU_ASSERT(util::strifind(StringRef::from_lit("nghttp2"), StringRef{}));
+
+ // Be aware this fact
+ CU_ASSERT(!util::strifind(StringRef{}, StringRef{}));
+
+ CU_ASSERT(!util::strifind(StringRef::from_lit("nghttp2"),
+ StringRef::from_lit("http1")));
+}
+
+void test_util_random_alpha_digit(void) {
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::array<uint8_t, 19> data;
+
+ auto p = util::random_alpha_digit(std::begin(data), std::end(data), gen);
+
+ CU_ASSERT(std::end(data) == p);
+
+ for (auto b : data) {
+ CU_ASSERT(('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') ||
+ ('0' <= b && b <= '9'));
+ }
+}
+
+void test_util_format_hex(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("0ff0" ==
+ util::format_hex(balloc, StringRef::from_lit("\x0f\xf0")));
+ CU_ASSERT("" == util::format_hex(balloc, StringRef::from_lit("")));
+}
+
+void test_util_is_hex_string(void) {
+ CU_ASSERT(util::is_hex_string(StringRef{}));
+ CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789abcdef")));
+ CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789ABCDEF")));
+ CU_ASSERT(!util::is_hex_string(StringRef::from_lit("000")));
+ CU_ASSERT(!util::is_hex_string(StringRef::from_lit("XX")));
+}
+
+void test_util_decode_hex(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("\x0f\xf0" ==
+ util::decode_hex(balloc, StringRef::from_lit("0ff0")));
+ CU_ASSERT("" == util::decode_hex(balloc, StringRef{}));
+}
+
+void test_util_extract_host(void) {
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo")));
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo:")));
+ CU_ASSERT(StringRef::from_lit("foo") ==
+ util::extract_host(StringRef::from_lit("foo:0")));
+ CU_ASSERT(StringRef::from_lit("[::1]") ==
+ util::extract_host(StringRef::from_lit("[::1]")));
+ CU_ASSERT(StringRef::from_lit("[::1]") ==
+ util::extract_host(StringRef::from_lit("[::1]:")));
+
+ CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty());
+ CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty());
+ CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty());
+ CU_ASSERT(util::extract_host(StringRef{}).empty());
+}
+
+void test_util_split_hostport(void) {
+ CU_ASSERT(std::make_pair(StringRef::from_lit("foo"), StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("foo")));
+ CU_ASSERT(
+ std::make_pair(StringRef::from_lit("foo"), StringRef::from_lit("80")) ==
+ util::split_hostport(StringRef::from_lit("foo:80")));
+ CU_ASSERT(
+ std::make_pair(StringRef::from_lit("::1"), StringRef::from_lit("80")) ==
+ util::split_hostport(StringRef::from_lit("[::1]:80")));
+ CU_ASSERT(std::make_pair(StringRef::from_lit("::1"), StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]")));
+
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef{}));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("foo:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1:")));
+ CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) ==
+ util::split_hostport(StringRef::from_lit("[::1]80")));
+}
+
+void test_util_split_str(void) {
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ','));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("")}) ==
+ util::split_str(StringRef::from_lit("alpha,"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo"), ','));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo"),
+ StringRef::from_lit("charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ','));
+ CU_ASSERT(
+ (std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo"),
+ StringRef::from_lit("charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 0));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ',', 1));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
+ util::split_str(StringRef::from_lit(""), ',', 2));
+ CU_ASSERT(
+ (std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("bravo,charlie")}) ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ',', 2));
+ CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
+ StringRef::from_lit("")}) ==
+ util::split_str(StringRef::from_lit("alpha,"), ',', 2));
+ CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
+ util::split_str(StringRef::from_lit("alpha"), ',', 0));
+ CU_ASSERT(
+ std::vector<StringRef>{StringRef::from_lit("alpha,bravo,charlie")} ==
+ util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 1));
+}
+
+void test_util_rstrip(void) {
+ BlockAllocator balloc(4096, 4096);
+
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha")));
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha ")));
+ CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha \t")));
+ CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit("")));
+ CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit("\t\t\t ")));
+}
+
+} // namespace shrpx
diff --git a/src/util_test.h b/src/util_test.h
new file mode 100644
index 0000000..48925ab
--- /dev/null
+++ b/src/util_test.h
@@ -0,0 +1,75 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_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_to_token68(void);
+void test_util_percent_encode_token(void);
+void test_util_percent_decode(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(void);
+void test_util_make_string_ref_uint(void);
+void test_util_utos_unit(void);
+void test_util_utos_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);
+void test_util_starts_with(void);
+void test_util_ends_with(void);
+void test_util_parse_http_date(void);
+void test_util_localtime_date(void);
+void test_util_get_uint64(void);
+void test_util_parse_config_str_list(void);
+void test_util_make_http_hostport(void);
+void test_util_make_hostport(void);
+void test_util_strifind(void);
+void test_util_random_alpha_digit(void);
+void test_util_format_hex(void);
+void test_util_is_hex_string(void);
+void test_util_decode_hex(void);
+void test_util_extract_host(void);
+void test_util_split_hostport(void);
+void test_util_split_str(void);
+void test_util_rstrip(void);
+
+} // namespace shrpx
+
+#endif // UTIL_TEST_H
diff --git a/src/xsi_strerror.c b/src/xsi_strerror.c
new file mode 100644
index 0000000..d008d4e
--- /dev/null
+++ b/src/xsi_strerror.c
@@ -0,0 +1,50 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 "xsi_strerror.h"
+
+/* Make sure that we get XSI-compliant version of strerror_r */
+#ifdef _POSIX_C_SOURCE
+# undef _POSIX_C_SOURCE
+#endif /* _POSIX_C_SOURCE */
+
+#ifdef _GNU_SOURCE
+# undef _GNU_SOURCE
+#endif /* _GNU_SOURCE */
+
+#include <string.h>
+
+char *xsi_strerror(int errnum, char *buf, size_t buflen) {
+ int rv;
+
+ rv = strerror_r(errnum, buf, buflen);
+
+ if (rv != 0) {
+ if (buflen > 0) {
+ buf[0] = '\0';
+ }
+ }
+
+ return buf;
+}
diff --git a/src/xsi_strerror.h b/src/xsi_strerror.h
new file mode 100644
index 0000000..32cadc3
--- /dev/null
+++ b/src/xsi_strerror.h
@@ -0,0 +1,55 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 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 XSI_STRERROR_H
+#define XSI_STRERROR_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Looks like error message is quite small, but we really don't know
+ how much longer they become. */
+#define STRERROR_BUFSIZE 256
+
+/*
+ * Returns description of error denoted by |errnum|. The description
+ * is written in |buf| of length |buflen| including terminal NULL. If
+ * there is an error, including the case that buffer space is not
+ * sufficient to include error message, and |buflen| > 0, empty string
+ * is written to |buf|. This function returns |buf|.
+ */
+char *xsi_strerror(int errnum, char *buf, size_t buflen);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* XSI_STRERROR_H */
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..42dfe63
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,3 @@
+# tests
+failmalloc
+main
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..b20ccf7
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,55 @@
+# XXX testdata/: EXTRA_DIST = cacert.pem index.html privkey.pem
+if(HAVE_CUNIT)
+ string(REPLACE " " ";" c_flags "${WARNCFLAGS}")
+ add_compile_options(${c_flags})
+
+ include_directories(
+ "${CMAKE_SOURCE_DIR}/lib/includes"
+ "${CMAKE_SOURCE_DIR}/lib"
+ "${CMAKE_BINARY_DIR}/lib/includes"
+ ${CUNIT_INCLUDE_DIRS}
+ )
+
+ set(MAIN_SOURCES
+ 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_alpn_test.c
+ nghttp2_helper_test.c
+ nghttp2_buf_test.c
+ nghttp2_http_test.c
+ nghttp2_extpri_test.c
+ nghttp2_ratelim_test.c
+ )
+
+ add_executable(main EXCLUDE_FROM_ALL
+ ${MAIN_SOURCES}
+ )
+ target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS})
+ target_link_libraries(main
+ nghttp2_static
+ ${CUNIT_LIBRARIES}
+ )
+ add_test(main main)
+ add_dependencies(check main)
+
+ if(ENABLE_FAILMALLOC)
+ set(FAILMALLOC_SOURCES
+ failmalloc.c failmalloc_test.c
+ malloc_wrapper.c
+ nghttp2_test_helper.c
+ )
+ add_executable(failmalloc EXCLUDE_FROM_ALL
+ ${FAILMALLOC_SOURCES}
+ )
+ target_link_libraries(failmalloc
+ nghttp2_static
+ ${CUNIT_LIBRARIES}
+ )
+ add_test(failmalloc failmalloc)
+ add_dependencies(check failmalloc)
+ endif()
+endif()
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..1c506d8
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,93 @@
+# 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
+
+EXTRA_DIST = CMakeLists.txt
+
+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_alpn_test.c \
+ nghttp2_helper_test.c \
+ nghttp2_buf_test.c \
+ nghttp2_http_test.c \
+ nghttp2_extpri_test.c \
+ nghttp2_ratelim_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_alpn_test.h nghttp2_helper_test.h \
+ nghttp2_test_helper.h \
+ nghttp2_buf_test.h \
+ nghttp2_http_test.h \
+ nghttp2_extpri_test.h \
+ nghttp2_ratelim_test.h
+
+main_SOURCES = $(HFILES) $(OBJECTS)
+
+if ENABLE_STATIC
+main_LDADD = ${top_builddir}/lib/libnghttp2.la
+else
+# With static lib disabled and symbol hiding enabled, we have to link object
+# files directly because the tests use symbols not included in public API.
+main_LDADD = ${top_builddir}/lib/.libs/*.o
+endif
+
+main_LDADD += @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 \
+ -DBUILDING_NGHTTP2 \
+ -DNGHTTP2_STATICLIB \
+ @CUNIT_CFLAGS@ @DEFS@
+
+TESTS = main
+
+if ENABLE_FAILMALLOC
+TESTS += failmalloc
+endif # ENABLE_FAILMALLOC
+
+endif # HAVE_CUNIT
diff --git a/tests/failmalloc.c b/tests/failmalloc.c
new file mode 100644
index 0000000..6294cff
--- /dev/null
+++ b/tests/failmalloc.c
@@ -0,0 +1,79 @@
+/*
+ * 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(void) {
+ CU_pSuite pSuite = NULL;
+ unsigned int num_tests_failed;
+
+ /* initialize the CUnit test registry */
+ if (CUE_SUCCESS != CU_initialize_registry())
+ return (int)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 (int)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_send_server",
+ test_nghttp2_session_send_server) ||
+ !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 (int)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 (int)num_tests_failed;
+ } else {
+ printf("CUnit Error: %s\n", CU_get_error_msg());
+ return (int)CU_get_error();
+ }
+}
diff --git a/tests/failmalloc_test.c b/tests/failmalloc_test.c
new file mode 100644
index 0000000..594bc72
--- /dev/null
+++ b/tests/failmalloc_test.c
@@ -0,0 +1,576 @@
+/*
+ * 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 <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.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, const uint8_t *data,
+ size_t len, int flags, void *user_data) {
+ (void)session;
+ (void)data;
+ (void)flags;
+ (void)user_data;
+
+ return (ssize_t)len;
+}
+
+static ssize_t data_feed_recv_callback(nghttp2_session *session, uint8_t *data,
+ size_t len, int flags, void *user_data) {
+ data_feed *df = ((my_user_data *)user_data)->df;
+ size_t avail = (size_t)(df->datalimit - df->datamark);
+ size_t wlen = nghttp2_min(avail, len);
+ (void)session;
+ (void)flags;
+
+ memcpy(data, df->datamark, wlen);
+ df->datamark += wlen;
+ return (ssize_t)wlen;
+}
+
+static ssize_t fixed_length_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ size_t wlen;
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)source;
+
+ 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 (ssize_t)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;
+ }
+ 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_send_server(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks *callbacks;
+ int rv;
+ const uint8_t *txdata;
+ ssize_t txdatalen;
+ const uint8_t origin[] = "nghttp2.org";
+ const uint8_t altsvc_field_value[] = "h2=\":443\"";
+ static const uint8_t nghttp2[] = "https://nghttp2.org";
+ static const nghttp2_origin_entry ov = {
+ (uint8_t *)nghttp2,
+ sizeof(nghttp2) - 1,
+ };
+
+ rv = nghttp2_session_callbacks_new(&callbacks);
+ if (rv != 0) {
+ return;
+ }
+
+ rv = nghttp2_session_server_new3(&session, callbacks, NULL, NULL,
+ nghttp2_mem_fm());
+
+ nghttp2_session_callbacks_del(callbacks);
+
+ if (rv != 0) {
+ return;
+ }
+
+ rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
+ sizeof(origin) - 1, altsvc_field_value,
+ sizeof(altsvc_field_value) - 1);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, &ov, 1);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ txdatalen = nghttp2_session_mem_send(session, &txdata);
+
+ if (txdatalen < 0) {
+ goto fail;
+ }
+
+fail:
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_server(void) {
+ TEST_FAILMALLOC_RUN(run_nghttp2_session_send_server);
+}
+
+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(":method", "GET"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "example.org"),
+ MAKE_NV(":path", "/"),
+ };
+ nghttp2_settings_entry iv[2];
+ my_user_data ud;
+ data_feed df;
+ int rv;
+ nghttp2_nv *nva;
+ size_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();
+ nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm());
+ nghttp2_session_server_new3(&session, &callbacks, &ud, NULL,
+ nghttp2_mem_fm());
+
+ /* Client preface */
+ nghttp2_bufs_add(&bufs, NGHTTP2_CLIENT_MAGIC, NGHTTP2_CLIENT_MAGIC_LEN);
+ data_feed_init(&df, &bufs);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_failmalloc_unpause();
+
+ rv = nghttp2_session_recv(session);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ nghttp2_failmalloc_pause();
+ /* SETTINGS */
+ 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());
+ data_feed_init(&df, &bufs);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_failmalloc_unpause();
+
+ rv = nghttp2_session_recv(session);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ nghttp2_failmalloc_pause();
+ /* HEADERS */
+ nvlen = ARRLEN(nv);
+ nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm());
+ 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;
+ }
+
+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;
+ size_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, nghttp2_mem *mem) {
+ 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, mem);
+
+ 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"), MAKE_NV("foo", "bar")};
+ nghttp2_nv nva2[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"),
+ MAKE_NV(":path", "/style.css"), MAKE_NV("cookie", "nghttp2=FTW"),
+ MAKE_NV("foo", "bar2")};
+
+ 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),
+ nghttp2_mem_fm());
+
+ if (rv != 0) {
+ goto deflate_hd_fail;
+ }
+
+ rv = deflate_inflate(&deflater, &inflater, &bufs, nva2, ARRLEN(nva2),
+ nghttp2_mem_fm());
+
+ 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
index 0000000..576932a
--- /dev/null
+++ b/tests/failmalloc_test.h
@@ -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 FAILMALLOC_TEST_H
+#define FAILMALLOC_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_session_send(void);
+void test_nghttp2_session_send_server(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
index 0000000..6827daf
--- /dev/null
+++ b/tests/main.c
@@ -0,0 +1,473 @@
+/*
+ * 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_alpn_test.h"
+#include "nghttp2_helper_test.h"
+#include "nghttp2_buf_test.h"
+#include "nghttp2_http_test.h"
+#include "nghttp2_extpri_test.h"
+#include "nghttp2_ratelim_test.h"
+
+extern int nghttp2_enable_strict_preface;
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(void) {
+ CU_pSuite pSuite = NULL;
+ unsigned int num_tests_failed;
+
+ nghttp2_enable_strict_preface = 0;
+
+ /* initialize the CUnit test registry */
+ if (CUE_SUCCESS != CU_initialize_registry())
+ return (int)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 (int)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, "pq_remove", test_nghttp2_pq_remove) ||
+ !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, "alpn", test_nghttp2_alpn) ||
+ !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_data_no_auto_flow_control",
+ test_nghttp2_session_recv_data_no_auto_flow_control) ||
+ !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_headers_with_padding",
+ test_nghttp2_session_recv_headers_with_padding) ||
+ !CU_add_test(pSuite, "session_recv_headers_early_response",
+ test_nghttp2_session_recv_headers_early_response) ||
+ !CU_add_test(pSuite, "session_recv_headers_for_closed_stream",
+ test_nghttp2_session_recv_headers_for_closed_stream) ||
+ !CU_add_test(pSuite, "session_recv_headers_with_extpri",
+ test_nghttp2_session_recv_headers_with_extpri) ||
+ !CU_add_test(pSuite, "session_server_recv_push_response",
+ test_nghttp2_session_server_recv_push_response) ||
+ !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_recv_extension",
+ test_nghttp2_session_recv_extension) ||
+ !CU_add_test(pSuite, "session_recv_altsvc",
+ test_nghttp2_session_recv_altsvc) ||
+ !CU_add_test(pSuite, "session_recv_origin",
+ test_nghttp2_session_recv_origin) ||
+ !CU_add_test(pSuite, "session_recv_priority_update",
+ test_nghttp2_session_recv_priority_update) ||
+ !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_on_data_received_fail_fast",
+ test_nghttp2_session_on_data_received_fail_fast) ||
+ !CU_add_test(pSuite, "session_on_altsvc_received",
+ test_nghttp2_session_on_altsvc_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_upgrade2", test_nghttp2_session_upgrade2) ||
+ !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_read_length_smallest",
+ test_nghttp2_submit_data_read_length_smallest) ||
+ !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_response_push_response",
+ test_nghttp2_submit_response_push_response) ||
+ !CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) ||
+ !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_headers_continuation_extra_large",
+ test_nghttp2_submit_headers_continuation_extra_large) ||
+ !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_settings_multiple_times",
+ test_nghttp2_submit_settings_multiple_times) ||
+ !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, "submit_extension", test_nghttp2_submit_extension) ||
+ !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) ||
+ !CU_add_test(pSuite, "submit_origin", test_nghttp2_submit_origin) ||
+ !CU_add_test(pSuite, "submit_priority_update",
+ test_nghttp2_submit_priority_update) ||
+ !CU_add_test(pSuite, "submit_rst_stream",
+ test_nghttp2_submit_rst_stream) ||
+ !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_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_get_state",
+ test_nghttp2_session_stream_get_state) ||
+ !CU_add_test(pSuite, "session_stream_get_something",
+ test_nghttp2_session_stream_get_something) ||
+ !CU_add_test(pSuite, "session_find_stream",
+ test_nghttp2_session_find_stream) ||
+ !CU_add_test(pSuite, "session_keep_closed_stream",
+ test_nghttp2_session_keep_closed_stream) ||
+ !CU_add_test(pSuite, "session_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_magic",
+ test_nghttp2_session_recv_client_magic) ||
+ !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, "session_cancel_reserved_remote",
+ test_nghttp2_session_cancel_reserved_remote) ||
+ !CU_add_test(pSuite, "session_reset_pending_headers",
+ test_nghttp2_session_reset_pending_headers) ||
+ !CU_add_test(pSuite, "session_send_data_callback",
+ test_nghttp2_session_send_data_callback) ||
+ !CU_add_test(pSuite, "session_on_begin_headers_temporal_failure",
+ test_nghttp2_session_on_begin_headers_temporal_failure) ||
+ !CU_add_test(pSuite, "session_defer_then_close",
+ test_nghttp2_session_defer_then_close) ||
+ !CU_add_test(pSuite, "session_detach_item_from_closed_stream",
+ test_nghttp2_session_detach_item_from_closed_stream) ||
+ !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
+ !CU_add_test(pSuite, "session_change_stream_priority",
+ test_nghttp2_session_change_stream_priority) ||
+ !CU_add_test(pSuite, "session_change_extpri_stream_priority",
+ test_nghttp2_session_change_extpri_stream_priority) ||
+ !CU_add_test(pSuite, "session_create_idle_stream",
+ test_nghttp2_session_create_idle_stream) ||
+ !CU_add_test(pSuite, "session_repeated_priority_change",
+ test_nghttp2_session_repeated_priority_change) ||
+ !CU_add_test(pSuite, "session_repeated_priority_submission",
+ test_nghttp2_session_repeated_priority_submission) ||
+ !CU_add_test(pSuite, "session_set_local_window_size",
+ test_nghttp2_session_set_local_window_size) ||
+ !CU_add_test(pSuite, "session_cancel_from_before_frame_send",
+ test_nghttp2_session_cancel_from_before_frame_send) ||
+ !CU_add_test(pSuite, "session_too_many_settings",
+ test_nghttp2_session_too_many_settings) ||
+ !CU_add_test(pSuite, "session_removed_closed_stream",
+ test_nghttp2_session_removed_closed_stream) ||
+ !CU_add_test(pSuite, "session_pause_data",
+ test_nghttp2_session_pause_data) ||
+ !CU_add_test(pSuite, "session_no_closed_streams",
+ test_nghttp2_session_no_closed_streams) ||
+ !CU_add_test(pSuite, "session_set_stream_user_data",
+ test_nghttp2_session_set_stream_user_data) ||
+ !CU_add_test(pSuite, "session_no_rfc7540_priorities",
+ test_nghttp2_session_no_rfc7540_priorities) ||
+ !CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities",
+ test_nghttp2_session_server_fallback_rfc7540_priorities) ||
+ !CU_add_test(pSuite, "session_stream_reset_ratelim",
+ test_nghttp2_session_stream_reset_ratelim) ||
+ !CU_add_test(pSuite, "http_mandatory_headers",
+ test_nghttp2_http_mandatory_headers) ||
+ !CU_add_test(pSuite, "http_content_length",
+ test_nghttp2_http_content_length) ||
+ !CU_add_test(pSuite, "http_content_length_mismatch",
+ test_nghttp2_http_content_length_mismatch) ||
+ !CU_add_test(pSuite, "http_non_final_response",
+ test_nghttp2_http_non_final_response) ||
+ !CU_add_test(pSuite, "http_trailer_headers",
+ test_nghttp2_http_trailer_headers) ||
+ !CU_add_test(pSuite, "http_ignore_regular_header",
+ test_nghttp2_http_ignore_regular_header) ||
+ !CU_add_test(pSuite, "http_ignore_content_length",
+ test_nghttp2_http_ignore_content_length) ||
+ !CU_add_test(pSuite, "http_record_request_method",
+ test_nghttp2_http_record_request_method) ||
+ !CU_add_test(pSuite, "http_push_promise",
+ test_nghttp2_http_push_promise) ||
+ !CU_add_test(pSuite, "http_head_method_upgrade_workaround",
+ test_nghttp2_http_head_method_upgrade_workaround) ||
+ !CU_add_test(
+ pSuite, "http_no_rfc9113_leading_and_trailing_ws_validation",
+ test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation) ||
+ !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_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, "frame_pack_altsvc",
+ test_nghttp2_frame_pack_altsvc) ||
+ !CU_add_test(pSuite, "frame_pack_origin",
+ test_nghttp2_frame_pack_origin) ||
+ !CU_add_test(pSuite, "frame_pack_priority_update",
+ test_nghttp2_frame_pack_priority_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_inflate_expect_table_size_update",
+ test_nghttp2_hd_inflate_expect_table_size_update) ||
+ !CU_add_test(pSuite, "hd_inflate_unexpected_table_size_update",
+ test_nghttp2_hd_inflate_unexpected_table_size_update) ||
+ !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_deflate_hd_vec",
+ test_nghttp2_hd_deflate_hd_vec) ||
+ !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, "hd_huff_decode", test_nghttp2_hd_huff_decode) ||
+ !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, "check_header_value_rfc9113",
+ test_nghttp2_check_header_value_rfc9113) ||
+ !CU_add_test(pSuite, "bufs_add", test_nghttp2_bufs_add) ||
+ !CU_add_test(pSuite, "bufs_add_stack_buffer_overflow_bug",
+ test_nghttp2_bufs_add_stack_buffer_overflow_bug) ||
+ !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_add_test(pSuite, "http_parse_priority",
+ test_nghttp2_http_parse_priority) ||
+ !CU_add_test(pSuite, "extpri_to_uint8", test_nghttp2_extpri_to_uint8) ||
+ !CU_add_test(pSuite, "ratelim_update", test_nghttp2_ratelim_update) ||
+ !CU_add_test(pSuite, "ratelim_drain", test_nghttp2_ratelim_drain)) {
+ CU_cleanup_registry();
+ return (int)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 (int)num_tests_failed;
+ } else {
+ printf("CUnit Error: %s\n", CU_get_error_msg());
+ return (int)CU_get_error();
+ }
+}
diff --git a/tests/malloc_wrapper.c b/tests/malloc_wrapper.c
new file mode 100644
index 0000000..f814c3d
--- /dev/null
+++ b/tests/malloc_wrapper.c
@@ -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 "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) {
+ (void)mud;
+
+ CHECK_PREREQ;
+ return malloc(size);
+}
+
+static void my_free(void *ptr, void *mud) {
+ (void)mud;
+
+ free(ptr);
+}
+
+static void *my_calloc(size_t nmemb, size_t size, void *mud) {
+ (void)mud;
+
+ CHECK_PREREQ;
+ return calloc(nmemb, size);
+}
+
+static void *my_realloc(void *ptr, size_t size, void *mud) {
+ (void)mud;
+
+ 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
index 0000000..a3a3dd7
--- /dev/null
+++ b/tests/malloc_wrapper.h
@@ -0,0 +1,65 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_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_alpn_test.c b/tests/nghttp2_alpn_test.c
new file mode 100644
index 0000000..d471d5f
--- /dev/null
+++ b/tests/nghttp2_alpn_test.c
@@ -0,0 +1,95 @@
+/*
+ * 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_alpn_test.h"
+
+#include <stdio.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', 2,
+ 'h', '2', 6, 's', 'p', 'd', 'y', '/', '3'};
+ unsigned char outlen;
+ const unsigned char *out;
+ CU_ASSERT(1 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen,
+ p, sizeof(p)));
+ CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+ CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0);
+
+ outlen = 0;
+ out = NULL;
+
+ CU_ASSERT(1 == nghttp2_select_alpn(&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;
+ const unsigned char *out;
+ CU_ASSERT(0 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen,
+ spdy, sizeof(spdy)));
+ CU_ASSERT(8 == outlen);
+ CU_ASSERT(memcmp("http/1.1", out, outlen) == 0);
+
+ outlen = 0;
+ out = NULL;
+
+ CU_ASSERT(0 == nghttp2_select_alpn(&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;
+ const unsigned char *out = NULL;
+ CU_ASSERT(-1 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen,
+ spdy, sizeof(spdy)));
+ CU_ASSERT(0 == outlen);
+ CU_ASSERT(NULL == out);
+
+ outlen = 0;
+ out = NULL;
+
+ CU_ASSERT(-1 == nghttp2_select_alpn(&out, &outlen, spdy, sizeof(spdy)));
+ CU_ASSERT(0 == outlen);
+ CU_ASSERT(NULL == out);
+}
+
+void test_nghttp2_alpn(void) {
+ http2();
+ http11();
+ no_overlap();
+}
diff --git a/tests/nghttp2_alpn_test.h b/tests/nghttp2_alpn_test.h
new file mode 100644
index 0000000..f797f9e
--- /dev/null
+++ b/tests/nghttp2_alpn_test.h
@@ -0,0 +1,34 @@
+/*
+ * 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_ALPN_TEST_H
+#define NGHTTP2_ALPN_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_alpn(void);
+
+#endif /* NGHTTP2_ALPN_TEST_H */
diff --git a/tests/nghttp2_buf_test.c b/tests/nghttp2_buf_test.c
new file mode 100644
index 0000000..e3e8a14
--- /dev/null
+++ b/tests/nghttp2_buf_test.c
@@ -0,0 +1,344 @@
+/*
+ * 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 <stdio.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);
+}
+
+/* Test for GH-232, stack-buffer-overflow */
+void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void) {
+ int rv;
+ nghttp2_bufs bufs;
+ uint8_t data[1024];
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ rv = nghttp2_bufs_init(&bufs, 100, 200, mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_bufs_add(&bufs, data, sizeof(data));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(sizeof(data) == nghttp2_bufs_len(&bufs));
+
+ 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((size_t)(i + 2) == nghttp2_buf_len(&bufs.cur->buf));
+ CU_ASSERT((size_t)(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, (size_t)outlen));
+ CU_ASSERT(11 == nghttp2_bufs_len(&bufs));
+
+ mem->free(out, NULL);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_reset(void) {
+ int rv;
+ nghttp2_bufs bufs;
+ nghttp2_buf_chain *ci;
+ size_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((ssize_t)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
index 0000000..714b89f
--- /dev/null
+++ b/tests/nghttp2_buf_test.h
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_bufs_add(void);
+void test_nghttp2_bufs_add_stack_buffer_overflow_bug(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_extpri_test.c b/tests/nghttp2_extpri_test.c
new file mode 100644
index 0000000..0ef59b7
--- /dev/null
+++ b/tests/nghttp2_extpri_test.c
@@ -0,0 +1,52 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_extpri_test.h"
+
+#include <stdio.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_extpri.h"
+#include "nghttp2_test_helper.h"
+
+void test_nghttp2_extpri_to_uint8(void) {
+ {
+ nghttp2_extpri pri = {1, 0};
+ CU_ASSERT(1 == nghttp2_extpri_to_uint8(&pri));
+ }
+ {
+ nghttp2_extpri pri = {1, 1};
+ CU_ASSERT((0x80 | 1) == nghttp2_extpri_to_uint8(&pri));
+ }
+ {
+ nghttp2_extpri pri = {7, 1};
+ CU_ASSERT((0x80 | 7) == nghttp2_extpri_to_uint8(&pri));
+ }
+ {
+ nghttp2_extpri pri = {7, 0};
+ CU_ASSERT(7 == nghttp2_extpri_to_uint8(&pri));
+ }
+}
diff --git a/tests/nghttp2_extpri_test.h b/tests/nghttp2_extpri_test.h
new file mode 100644
index 0000000..a8a93b9
--- /dev/null
+++ b/tests/nghttp2_extpri_test.h
@@ -0,0 +1,35 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_EXTPRI_TEST_H
+#define NGHTTP2_EXTPRI_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_extpri_to_uint8(void);
+
+#endif /* NGHTTP2_EXTPRI_TEST_H */
diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c
new file mode 100644
index 0000000..7ce98dd
--- /dev/null
+++ b/tests/nghttp2_frame_test.c
@@ -0,0 +1,735 @@
+/*
+ * 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(nghttp2_mem *mem) {
+ nghttp2_nv *nva = mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL);
+ 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(void) {
+ nghttp2_hd_deflater deflater;
+ nghttp2_hd_inflater inflater;
+ nghttp2_headers frame, oframe;
+ nghttp2_bufs bufs;
+ nghttp2_nv *nva;
+ nghttp2_priority_spec pri_spec;
+ size_t nvlen;
+ nva_out out;
+ size_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(mem);
+ 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((ssize_t)hdblocklen ==
+ inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem));
+
+ 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, mem);
+ 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((ssize_t)hdblocklen ==
+ inflate_hd(&inflater, &out, &bufs,
+ NGHTTP2_FRAME_HDLEN +
+ nghttp2_frame_priority_len(oframe.hd.flags),
+ mem));
+
+ 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, mem);
+ 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 = mem->malloc(big_vallen + 1, NULL);
+ 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) {
+ mem->free(big_hds[i].value, NULL);
+ }
+ nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_frame_pack_priority(void) {
+ nghttp2_priority frame, oframe;
+ nghttp2_bufs bufs;
+ nghttp2_priority_spec pri_spec;
+
+ 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);
+ nghttp2_frame_pack_priority(&bufs, &frame);
+
+ 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;
+
+ frame_pack_bufs_init(&bufs);
+
+ nghttp2_frame_rst_stream_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR);
+ nghttp2_frame_pack_rst_stream(&bufs, &frame);
+
+ 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;
+ nghttp2_frame_pack_rst_stream(&bufs, &frame);
+
+ 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(void) {
+ 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(void) {
+ nghttp2_hd_deflater deflater;
+ nghttp2_hd_inflater inflater;
+ nghttp2_push_promise frame, oframe;
+ nghttp2_bufs bufs;
+ nghttp2_nv *nva;
+ size_t nvlen;
+ nva_out out;
+ size_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(mem);
+ 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((ssize_t)hdblocklen ==
+ inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + 4, mem));
+
+ CU_ASSERT(7 == out.nvlen);
+ CU_ASSERT(nvnameeq("method", &out.nva[0]));
+ CU_ASSERT(nvvalueeq("GET", &out.nva[0]));
+
+ nva_out_reset(&out, mem);
+ 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";
+
+ frame_pack_bufs_init(&bufs);
+
+ nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data);
+ nghttp2_frame_pack_ping(&bufs, &frame);
+
+ 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(void) {
+ nghttp2_goaway frame, oframe;
+ nghttp2_bufs bufs;
+ size_t opaque_data_len = 16;
+ uint8_t *opaque_data;
+ int rv;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ opaque_data = mem->malloc(opaque_data_len, NULL);
+ 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(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;
+
+ frame_pack_bufs_init(&bufs);
+
+ nghttp2_frame_window_update_init(&frame, NGHTTP2_FLAG_NONE, 1000000007, 4096);
+ nghttp2_frame_pack_window_update(&bufs, &frame);
+
+ 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_frame_pack_altsvc(void) {
+ nghttp2_extension frame, oframe;
+ nghttp2_ext_altsvc altsvc, oaltsvc;
+ nghttp2_bufs bufs;
+ int rv;
+ size_t payloadlen;
+ static const uint8_t origin[] = "nghttp2.org";
+ static const uint8_t field_value[] = "h2=\":443\"";
+ nghttp2_buf buf;
+ uint8_t *rawbuf;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ frame_pack_bufs_init(&bufs);
+
+ frame.payload = &altsvc;
+ oframe.payload = &oaltsvc;
+
+ rawbuf = nghttp2_mem_malloc(mem, 32);
+ nghttp2_buf_wrap_init(&buf, rawbuf, 32);
+
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
+
+ nghttp2_frame_altsvc_init(&frame, 1000000007, buf.pos, sizeof(origin) - 1,
+ buf.pos + sizeof(origin) - 1,
+ sizeof(field_value) - 1);
+
+ payloadlen = 2 + sizeof(origin) - 1 + sizeof(field_value) - 1;
+
+ nghttp2_frame_pack_altsvc(&bufs, &frame);
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs));
+
+ rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ CU_ASSERT(0 == rv);
+
+ check_frame_header(payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1000000007,
+ &oframe.hd);
+
+ CU_ASSERT(sizeof(origin) - 1 == oaltsvc.origin_len);
+ CU_ASSERT(0 == memcmp(origin, oaltsvc.origin, sizeof(origin) - 1));
+ CU_ASSERT(sizeof(field_value) - 1 == oaltsvc.field_value_len);
+ CU_ASSERT(0 ==
+ memcmp(field_value, oaltsvc.field_value, sizeof(field_value) - 1));
+
+ nghttp2_frame_altsvc_free(&oframe, mem);
+ nghttp2_frame_altsvc_free(&frame, mem);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_frame_pack_origin(void) {
+ nghttp2_extension frame, oframe;
+ nghttp2_ext_origin origin, oorigin;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ int rv;
+ size_t payloadlen;
+ static const uint8_t example[] = "https://example.com";
+ static const uint8_t nghttp2[] = "https://nghttp2.org";
+ nghttp2_origin_entry ov[] = {
+ {
+ (uint8_t *)example,
+ sizeof(example) - 1,
+ },
+ {
+ NULL,
+ 0,
+ },
+ {
+ (uint8_t *)nghttp2,
+ sizeof(nghttp2) - 1,
+ },
+ };
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ frame_pack_bufs_init(&bufs);
+
+ frame.payload = &origin;
+ oframe.payload = &oorigin;
+
+ nghttp2_frame_origin_init(&frame, ov, 3);
+
+ payloadlen = 2 + sizeof(example) - 1 + 2 + 2 + sizeof(nghttp2) - 1;
+
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs));
+
+ rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ CU_ASSERT(0 == rv);
+
+ check_frame_header(payloadlen, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0,
+ &oframe.hd);
+
+ CU_ASSERT(2 == oorigin.nov);
+ CU_ASSERT(sizeof(example) - 1 == oorigin.ov[0].origin_len);
+ CU_ASSERT(0 == memcmp(example, oorigin.ov[0].origin, sizeof(example) - 1));
+ CU_ASSERT(sizeof(nghttp2) - 1 == oorigin.ov[1].origin_len);
+ CU_ASSERT(0 == memcmp(nghttp2, oorigin.ov[1].origin, sizeof(nghttp2) - 1));
+
+ nghttp2_frame_origin_free(&oframe, mem);
+
+ /* Check the case where origin length is too large */
+ buf = &bufs.head->buf;
+ nghttp2_put_uint16be(buf->pos + NGHTTP2_FRAME_HDLEN,
+ (uint16_t)(payloadlen - 1));
+
+ rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == rv);
+
+ nghttp2_bufs_reset(&bufs);
+ memset(&oframe, 0, sizeof(oframe));
+ memset(&oorigin, 0, sizeof(oorigin));
+ oframe.payload = &oorigin;
+
+ /* Empty ORIGIN frame */
+ nghttp2_frame_origin_init(&frame, NULL, 0);
+
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN == nghttp2_bufs_len(&bufs));
+
+ rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ CU_ASSERT(0 == rv);
+
+ check_frame_header(0, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, &oframe.hd);
+
+ CU_ASSERT(0 == oorigin.nov);
+ CU_ASSERT(NULL == oorigin.ov);
+
+ nghttp2_frame_origin_free(&oframe, mem);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_frame_pack_priority_update(void) {
+ nghttp2_extension frame, oframe;
+ nghttp2_ext_priority_update priority_update, opriority_update;
+ nghttp2_bufs bufs;
+ int rv;
+ size_t payloadlen;
+ static const uint8_t field_value[] = "i,u=0";
+
+ frame_pack_bufs_init(&bufs);
+
+ frame.payload = &priority_update;
+ oframe.payload = &opriority_update;
+
+ nghttp2_frame_priority_update_init(&frame, 1000000007, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+
+ payloadlen = 4 + sizeof(field_value) - 1;
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs));
+
+ rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs);
+
+ CU_ASSERT(0 == rv);
+
+ check_frame_header(payloadlen, NGHTTP2_PRIORITY_UPDATE, NGHTTP2_FLAG_NONE, 0,
+ &oframe.hd);
+
+ CU_ASSERT(sizeof(field_value) - 1 == opriority_update.field_value_len);
+ CU_ASSERT(0 == memcmp(field_value, opriority_update.field_value,
+ sizeof(field_value) - 1));
+
+ nghttp2_bufs_free(&bufs);
+}
+
+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 = mem->malloc(bignv.valuelen, NULL);
+ bignv.flags = NGHTTP2_NV_FLAG_NONE;
+ 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);
+
+ mem->free(bignv.value, NULL);
+}
+
+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));
+
+ /* Full size SETTINGS_HEADER_TABLE_SIZE (UINT32_MAX) must be
+ accepted */
+ 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
index 0000000..dc07625
--- /dev/null
+++ b/tests/nghttp2_frame_test.h
@@ -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 NGHTTP2_FRAME_TEST_H
+#define NGHTTP2_FRAME_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_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_frame_pack_altsvc(void);
+void test_nghttp2_frame_pack_origin(void);
+void test_nghttp2_frame_pack_priority_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
index 0000000..657d895
--- /dev/null
+++ b/tests/nghttp2_hd_test.c
@@ -0,0 +1,1577 @@
+/*
+ * 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"
+
+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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(3 == out.nvlen);
+ assert_nv_equal(nva1, out.nva, 3, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Second headers */
+ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2));
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(2 == out.nvlen);
+ assert_nv_equal(nva2, out.nva, 2, mem);
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(3 == out.nvlen);
+ assert_nv_equal(nva3, out.nva, 3, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Fourth headers, including duplicate header fields. */
+ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva4, ARRLEN(nva4));
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(3 == out.nvlen);
+ assert_nv_equal(nva4, out.nva, 3, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Fifth headers includes empty value */
+ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva5, ARRLEN(nva5));
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(2 == out.nvlen);
+ assert_nv_equal(nva5, out.nva, 2, mem);
+
+ nva_out_reset(&out, mem);
+ 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("host", "alpha"), MAKE_NV("host", "alpha")};
+ nghttp2_nv nva2[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha"),
+ MAKE_NV("host", "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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(2 == out.nvlen);
+ assert_nv_equal(nva1, out.nva, 2, mem);
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen == 3);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(3 == out.nvlen);
+ assert_nv_equal(nva2, out.nva, 3, mem);
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(1 == blocklen);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+
+ assert_nv_equal(&nv, out.nva, 1, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ /* index = 0 is error */
+ nghttp2_bufs_addb(&bufs, 1 << 7);
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(1 == blocklen);
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ 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],
+ NGHTTP2_HD_WITHOUT_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv[i], out.nva, 1, mem);
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+ CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+
+ nva_out_reset(&out, mem);
+ 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,
+ NGHTTP2_HD_WITH_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+ CU_ASSERT(1 == inflater.ctx.hd_table.len);
+ CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+ assert_nv_equal(
+ &nv,
+ nghttp2_hd_inflate_get_table_entry(
+ &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len),
+ 1, mem);
+
+ nva_out_reset(&out, mem);
+ 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[1025];
+ 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));
+ value[sizeof(value) - 1] = '\0';
+ nv.value = value;
+ nv.valuelen = sizeof(value) - 1;
+
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv,
+ NGHTTP2_HD_WITH_INDEXING));
+ CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv,
+ NGHTTP2_HD_WITH_INDEXING));
+ CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv,
+ NGHTTP2_HD_WITH_INDEXING));
+ CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv,
+ NGHTTP2_HD_WITH_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ 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) - 1 == out.nva[0].valuelen);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ CU_ASSERT(3 == inflater.ctx.hd_table.len);
+ CU_ASSERT(64 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+
+ 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],
+ NGHTTP2_HD_WITHOUT_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv[i], out.nva, 1, mem);
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+ nva_out_reset(&out, mem);
+ 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, NGHTTP2_HD_WITH_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+ CU_ASSERT(1 == inflater.ctx.hd_table.len);
+ assert_nv_equal(
+ &nv,
+ nghttp2_hd_inflate_get_table_entry(
+ &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len),
+ 1, mem);
+
+ nva_out_reset(&out, mem);
+ 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[4061];
+ 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));
+ value[sizeof(value) - 1] = '\0';
+ nv.value = value;
+ nv.valuelen = sizeof(value) - 1;
+
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ nghttp2_hd_inflate_init(&inflater, mem);
+
+ CU_ASSERT(
+ 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+ nva_out_reset(&out, mem);
+
+ /* Do it again */
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+
+ /* This time, 4096 bytes space required, which is just fits in the
+ header table */
+ nv.valuelen = sizeof(value) - 2;
+
+ CU_ASSERT(
+ 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING));
+
+ blocklen = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+ CU_ASSERT(1 == inflater.ctx.hd_table.len);
+
+ nva_out_reset(&out, mem);
+ 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, mem));
+
+ 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, mem);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_expect_table_size_update(void) {
+ nghttp2_hd_inflater inflater;
+ nghttp2_bufs bufs;
+ nghttp2_mem *mem;
+ /* Indexed Header: :method: GET */
+ uint8_t data[] = {0x82};
+ nva_out out;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+ nva_out_init(&out);
+
+ nghttp2_bufs_add(&bufs, data, sizeof(data));
+ nghttp2_hd_inflate_init(&inflater, mem);
+ /* This will make inflater require table size update in the next
+ inflation. */
+ nghttp2_hd_inflate_change_table_size(&inflater, 4095);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4096);
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* This does not require for encoder to emit table size update since
+ * size is not changed. */
+ nghttp2_hd_inflate_init(&inflater, mem);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4096);
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* This does not require for encodre to emit table size update since
+ new size is larger than current size. */
+ nghttp2_hd_inflate_init(&inflater, mem);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4097);
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* Received table size is strictly larger than minimum table size */
+ nghttp2_hd_inflate_init(&inflater, mem);
+ nghttp2_hd_inflate_change_table_size(&inflater, 111);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4096);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_emit_table_size(&bufs, 112);
+
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* Receiving 2 table size updates, min and last value */
+ nghttp2_hd_inflate_init(&inflater, mem);
+ nghttp2_hd_inflate_change_table_size(&inflater, 111);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4096);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_emit_table_size(&bufs, 111);
+ nghttp2_hd_emit_table_size(&bufs, 4096);
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* 2nd update is larger than last value */
+ nghttp2_hd_inflate_init(&inflater, mem);
+ nghttp2_hd_inflate_change_table_size(&inflater, 111);
+ nghttp2_hd_inflate_change_table_size(&inflater, 4095);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_emit_table_size(&bufs, 111);
+ nghttp2_hd_emit_table_size(&bufs, 4096);
+
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ nghttp2_hd_inflate_free(&inflater);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_hd_inflate_unexpected_table_size_update(void) {
+ nghttp2_hd_inflater inflater;
+ nghttp2_bufs bufs;
+ nghttp2_mem *mem;
+ /* Indexed Header: :method: GET, followed by table size update.
+ This violates RFC 7541. */
+ uint8_t data[] = {0x82, 0x20};
+ nva_out out;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+ nva_out_init(&out);
+
+ nghttp2_bufs_add(&bufs, data, sizeof(data));
+ nghttp2_hd_inflate_init(&inflater, mem);
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP ==
+ inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ nva_out_reset(&out, mem);
+ 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 = mem->malloc(nv.valuelen + 1, NULL);
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(1 == out.nvlen);
+ assert_nv_equal(&nv, out.nva, 1, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_reset(&bufs);
+ }
+
+ nghttp2_bufs_free(&bufs);
+ nghttp2_hd_inflate_free(&inflater);
+ nghttp2_hd_deflate_free(&deflater);
+
+ mem->free(nv.value, NULL);
+}
+
+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(4096 == 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(2 == deflater.ctx.hd_table.len);
+ CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater));
+ CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max);
+
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+ CU_ASSERT(2 == inflater.ctx.hd_table.len);
+ CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+ CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(2 == deflater.ctx.hd_table.len);
+ CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater));
+ CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max);
+
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+ CU_ASSERT(2 == inflater.ctx.hd_table.len);
+ CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+ CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max);
+
+ nva_out_reset(&out, mem);
+ 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(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater));
+ CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max);
+
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+ CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(0 == deflater.ctx.hd_table.len);
+ CU_ASSERT(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater));
+ CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max);
+
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+ CU_ASSERT(0 == inflater.ctx.hd_table.len);
+ CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
+ CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max);
+
+ nva_out_reset(&out, mem);
+ 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 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater));
+ CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(4096 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater));
+ CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+ blocklen = (ssize_t)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, mem));
+ 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, mem);
+ 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(8192 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater));
+
+ CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(8000 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater));
+ CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max);
+
+ rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+ blocklen = (ssize_t)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, mem));
+ 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, mem);
+ 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, mem));
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)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, mem));
+ 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, mem);
+ 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 = (ssize_t)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, mem));
+ CU_ASSERT(UINT32_MAX == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(UINT32_MAX == inflater.settings_hd_table_bufsize_max);
+
+ nva_out_reset(&out, mem);
+ 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 = (ssize_t)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, mem));
+ CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max);
+
+ nva_out_reset(&out, mem);
+ 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_mem *mem) {
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen >= 0);
+
+ CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(nvlen == out.nvlen);
+ assert_nv_equal(nva, out.nva, nvlen, mem);
+
+ nva_out_reset(&out, mem);
+ 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), mem);
+ check_deflate_inflate(&deflater, &inflater, nv2, ARRLEN(nv2), mem);
+ check_deflate_inflate(&deflater, &inflater, nv3, ARRLEN(nv3), mem);
+ check_deflate_inflate(&deflater, &inflater, nv4, ARRLEN(nv4), mem);
+ check_deflate_inflate(&deflater, &inflater, nv5, ARRLEN(nv5), mem);
+ check_deflate_inflate(&deflater, &inflater, nv6, ARRLEN(nv6), mem);
+ check_deflate_inflate(&deflater, &inflater, nv7, ARRLEN(nv7), mem);
+ check_deflate_inflate(&deflater, &inflater, nv8, ARRLEN(nv8), mem);
+ check_deflate_inflate(&deflater, &inflater, nv9, ARRLEN(nv9), mem);
+ check_deflate_inflate(&deflater, &inflater, nv10, ARRLEN(nv10), mem);
+
+ 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 = (ssize_t)nghttp2_bufs_len(&bufs);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(blocklen > 0);
+ CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(ARRLEN(nva) == out.nvlen);
+ assert_nv_equal(nva, out.nva, ARRLEN(nva), mem);
+
+ 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, mem);
+
+ 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, (size_t)blocklen, mem);
+ bufs.head->buf.last += blocklen;
+
+ CU_ASSERT(blocklen == inflate_hd(inflater, NULL, &bufs, 0, mem));
+
+ 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, (size_t)(blocklen - 1), nva,
+ ARRLEN(nva));
+
+ CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen);
+
+ nghttp2_hd_deflate_del(deflater);
+}
+
+void test_nghttp2_hd_deflate_hd_vec(void) {
+ nghttp2_hd_deflater *deflater;
+ nghttp2_hd_inflater *inflater;
+ nghttp2_nv nva[] = {
+ MAKE_NV(":method", "PUT"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "localhost:3000"),
+ MAKE_NV(":path", "/usr/foo/alpha/bravo"),
+ MAKE_NV("content-type", "image/png"),
+ MAKE_NV("content-length", "1000000007"),
+ };
+ uint8_t buf[4096];
+ ssize_t blocklen;
+ nghttp2_mem *mem;
+ nghttp2_vec vec[256];
+ size_t buflen;
+ nghttp2_bufs bufs;
+ nva_out out;
+ size_t i;
+
+ mem = nghttp2_mem_default();
+
+ nva_out_init(&out);
+
+ nghttp2_hd_deflate_new(&deflater, 4096);
+ nghttp2_hd_inflate_new(&inflater);
+
+ buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva));
+
+ vec[0].base = &buf[0];
+ vec[0].len = buflen / 2;
+ vec[1].base = &buf[buflen / 2];
+ vec[1].len = buflen / 2;
+
+ blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva));
+
+ CU_ASSERT(blocklen > 0);
+
+ nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem);
+ bufs.head->buf.last += blocklen;
+
+ CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem));
+
+ CU_ASSERT(ARRLEN(nva) == out.nvlen);
+ assert_nv_equal(nva, out.nva, ARRLEN(nva), mem);
+
+ nghttp2_bufs_wrap_free(&bufs);
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+ nva_out_reset(&out, mem);
+
+ /* check the case when veclen is 0 */
+ nghttp2_hd_deflate_new(&deflater, 4096);
+ nghttp2_hd_inflate_new(&inflater);
+
+ blocklen = nghttp2_hd_deflate_hd_vec(deflater, NULL, 0, nva, ARRLEN(nva));
+
+ CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen);
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+
+ /* check the case when chunk length is 0 */
+ vec[0].base = NULL;
+ vec[0].len = 0;
+ vec[1].base = NULL;
+ vec[1].len = 0;
+
+ nghttp2_hd_deflate_new(&deflater, 4096);
+ nghttp2_hd_inflate_new(&inflater);
+
+ blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva));
+
+ CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen);
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+
+ /* check the case where chunk size differs in each chunk */
+ nghttp2_hd_deflate_new(&deflater, 4096);
+ nghttp2_hd_inflate_new(&inflater);
+
+ buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva));
+
+ vec[0].base = &buf[0];
+ vec[0].len = buflen / 2;
+ vec[1].base = &buf[buflen / 2];
+ vec[1].len = (buflen / 2) + 1;
+
+ blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva));
+
+ CU_ASSERT(blocklen > 0);
+
+ nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem);
+ bufs.head->buf.last += blocklen;
+
+ CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem));
+ CU_ASSERT(ARRLEN(nva) == out.nvlen);
+ assert_nv_equal(nva, out.nva, ARRLEN(nva), mem);
+
+ nghttp2_bufs_wrap_free(&bufs);
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+ nva_out_reset(&out, mem);
+
+ /* check the case where chunk size is 1 */
+ nghttp2_hd_deflate_new(&deflater, 4096);
+ nghttp2_hd_inflate_new(&inflater);
+
+ buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva));
+
+ assert(buflen <= ARRLEN(vec));
+
+ for (i = 0; i < buflen; ++i) {
+ vec[i].base = &buf[i];
+ vec[i].len = 1;
+ }
+
+ blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, buflen, nva, ARRLEN(nva));
+
+ CU_ASSERT(blocklen > 0);
+
+ nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem);
+ bufs.head->buf.last += blocklen;
+
+ CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem));
+ CU_ASSERT(ARRLEN(nva) == out.nvlen);
+ assert_nv_equal(nva, out.nva, ARRLEN(nva), mem);
+
+ nghttp2_bufs_wrap_free(&bufs);
+
+ nghttp2_hd_inflate_del(inflater);
+ nghttp2_hd_deflate_del(deflater);
+ nva_out_reset(&out, mem);
+}
+
+static size_t encode_length(uint8_t *buf, uint64_t n, size_t prefix) {
+ size_t k = (size_t)((1 << prefix) - 1);
+ size_t len = 0;
+ *buf = (uint8_t)(*buf & ~k);
+ if (n >= k) {
+ *buf = (uint8_t)(*buf | k);
+ ++buf;
+ n -= k;
+ ++len;
+ } else {
+ *buf = (uint8_t)(*buf | n);
+ ++buf;
+ return 1;
+ }
+ do {
+ ++len;
+ if (n >= 128) {
+ *buf = (uint8_t)((1 << 7) | (n & 0x7f));
+ ++buf;
+ 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 fin;
+ 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, &fin, 0, 0, buf, buf + len, 7);
+
+ CU_ASSERT((ssize_t)len == rv);
+ CU_ASSERT(0 != fin);
+ CU_ASSERT(UINT32_MAX == out);
+
+ /* Make sure that we can decode integer if we feed 1 byte at a
+ time */
+ out = 0;
+ shift = 0;
+ fin = 0;
+ bufp = buf;
+
+ for (i = 0; i < len; ++i, ++bufp) {
+ rv = nghttp2_hd_decode_length(&out, &shift, &fin, out, shift, bufp,
+ bufp + 1, 7);
+
+ CU_ASSERT(rv == 1);
+
+ if (fin) {
+ break;
+ }
+ }
+
+ CU_ASSERT(i == len - 1);
+ CU_ASSERT(0 != fin);
+ 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, &fin, 0, 0, buf, buf + len, 7);
+
+ CU_ASSERT(-1 == rv);
+
+ /* Check the case that shift goes beyond 32 bits */
+ buf[0] = 255;
+ buf[1] = 128;
+ buf[2] = 128;
+ buf[3] = 128;
+ buf[4] = 128;
+ buf[5] = 128;
+ buf[6] = 1;
+
+ rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + 7, 8);
+
+ CU_ASSERT(-1 == rv);
+}
+
+void test_nghttp2_hd_huff_encode(void) {
+ int rv;
+ ssize_t len;
+ nghttp2_buf outbuf;
+ nghttp2_bufs bufs;
+ 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};
+ uint8_t b[256];
+
+ nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
+ frame_pack_bufs_init(&bufs);
+
+ 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, &outbuf, bufs.cur->buf.pos,
+ nghttp2_bufs_len(&bufs), 1);
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == len);
+ CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_buf_len(&outbuf));
+
+ CU_ASSERT(0 == memcmp(t1, outbuf.pos, sizeof(t1)));
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_hd_huff_decode(void) {
+ const uint8_t e[] = {0x1f, 0xff, 0xff, 0xff, 0xff, 0xff};
+ nghttp2_hd_huff_decode_context ctx;
+ nghttp2_buf outbuf;
+ uint8_t b[256];
+ ssize_t len;
+
+ nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
+ nghttp2_hd_huff_decode_context_init(&ctx);
+ len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 1, 1);
+
+ CU_ASSERT(1 == len);
+ CU_ASSERT(0 == memcmp("a", outbuf.pos, 1));
+
+ /* Premature sequence must elicit decoding error */
+ nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
+ nghttp2_hd_huff_decode_context_init(&ctx);
+ len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 1);
+
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len);
+
+ /* Fully decoding EOS is error */
+ nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
+ nghttp2_hd_huff_decode_context_init(&ctx);
+ len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 6);
+
+ CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len);
+
+ /* Check failure state */
+ nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
+ nghttp2_hd_huff_decode_context_init(&ctx);
+ len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 5, 0);
+
+ CU_ASSERT(5 == len);
+ CU_ASSERT(nghttp2_hd_huff_decode_failure_state(&ctx));
+}
diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h
new file mode 100644
index 0000000..ab0117c
--- /dev/null
+++ b/tests/nghttp2_hd_test.h
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_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_inflate_expect_table_size_update(void);
+void test_nghttp2_hd_inflate_unexpected_table_size_update(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_deflate_hd_vec(void);
+void test_nghttp2_hd_decode_length(void);
+void test_nghttp2_hd_huff_encode(void);
+void test_nghttp2_hd_huff_decode(void);
+
+#endif /* NGHTTP2_HD_TEST_H */
diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c
new file mode 100644
index 0000000..377f49d
--- /dev/null
+++ b/tests/nghttp2_helper_test.c
@@ -0,0 +1,195 @@
+/*
+ * 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 <stdio.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));
+ CU_ASSERT(check_header_value(""));
+ CU_ASSERT(check_header_value(" "));
+ CU_ASSERT(check_header_value("\t"));
+}
+
+#define check_header_value_rfc9113(S) \
+ nghttp2_check_header_value_rfc9113((const uint8_t *)S, sizeof(S) - 1)
+
+void test_nghttp2_check_header_value_rfc9113(void) {
+ uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd'};
+ uint8_t badval1[] = {'a', 0x1fu, 'b'};
+ uint8_t badval2[] = {'a', 0x7fu, 'b'};
+
+ CU_ASSERT(check_header_value_rfc9113("!|}~"));
+ CU_ASSERT(!check_header_value_rfc9113(" !|}~"));
+ CU_ASSERT(!check_header_value_rfc9113("!|}~ "));
+ CU_ASSERT(!check_header_value_rfc9113("\t!|}~"));
+ CU_ASSERT(!check_header_value_rfc9113("!|}~\t"));
+ CU_ASSERT(check_header_value_rfc9113(goodval));
+ CU_ASSERT(!check_header_value_rfc9113(badval1));
+ CU_ASSERT(!check_header_value_rfc9113(badval2));
+ CU_ASSERT(check_header_value_rfc9113(""));
+ CU_ASSERT(!check_header_value_rfc9113(" "));
+ CU_ASSERT(!check_header_value_rfc9113("\t"));
+}
diff --git a/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h
new file mode 100644
index 0000000..8790dcf
--- /dev/null
+++ b/tests/nghttp2_helper_test.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_adjust_local_window_size(void);
+void test_nghttp2_check_header_name(void);
+void test_nghttp2_check_header_value(void);
+void test_nghttp2_check_header_value_rfc9113(void);
+
+#endif /* NGHTTP2_HELPER_TEST_H */
diff --git a/tests/nghttp2_http_test.c b/tests/nghttp2_http_test.c
new file mode 100644
index 0000000..19f345b
--- /dev/null
+++ b/tests/nghttp2_http_test.c
@@ -0,0 +1,206 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_http_test.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_http.h"
+#include "nghttp2_test_helper.h"
+
+void test_nghttp2_http_parse_priority(void) {
+ int rv;
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)-1 == pri.urgency);
+ CU_ASSERT(-1 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=7,i";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)7 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0,i=?0";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)0 == pri.urgency);
+ CU_ASSERT(0 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=3, i";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)3 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0, i, i=?0, u=6";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)6 == pri.urgency);
+ CU_ASSERT(0 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0,";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0, ";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?1";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)-1 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?2";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=-1";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=8";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] =
+ "i=?0, u=1, a=(x y z), u=2; i=?0;foo=\",,,\", i=?1;i=?0; u=6";
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)2 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp2_extpri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = {'u', '='};
+
+ rv = nghttp2_http_parse_priority(&pri, v, sizeof(v));
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+ }
+}
diff --git a/tests/nghttp2_http_test.h b/tests/nghttp2_http_test.h
new file mode 100644
index 0000000..e616cdc
--- /dev/null
+++ b/tests/nghttp2_http_test.h
@@ -0,0 +1,35 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2022 nghttp3 contributors
+ * Copyright (c) 2022 nghttp2 contributors
+ *
+ * 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_HTTP_TEST_H
+#define NGHTTP2_HTTP_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_http_parse_priority(void);
+
+#endif /* NGHTTP2_HTTP_TEST_H */
diff --git a/tests/nghttp2_map_test.c b/tests/nghttp2_map_test.c
new file mode 100644
index 0000000..7ba9bd6
--- /dev/null
+++ b/tests/nghttp2_map_test.c
@@ -0,0 +1,208 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * 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 <stdio.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_map.h"
+
+typedef struct strentry {
+ nghttp2_map_key_type key;
+ const char *str;
+} strentry;
+
+static void strentry_init(strentry *entry, nghttp2_map_key_type key,
+ const char *str) {
+ entry->key = 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.key, &foo));
+ 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.key, &FOO));
+
+ 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.key, &bar));
+ CU_ASSERT(2 == nghttp2_map_size(&map));
+
+ CU_ASSERT(0 == nghttp2_map_insert(&map, baz.key, &baz));
+ CU_ASSERT(3 == nghttp2_map_size(&map));
+
+ CU_ASSERT(0 == nghttp2_map_insert(&map, shrubbery.key, &shrubbery));
+ 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 = (size_t)((double)(i + 1) * rand() / (RAND_MAX + 1.0));
+ int t = a[j];
+ a[j] = a[i];
+ a[i] = t;
+ }
+}
+
+static int eachfun(void *data, void *ptr) {
+ (void)data;
+ (void)ptr;
+
+ return 0;
+}
+
+#define NUM_ENT 6000
+static strentry arr[NUM_ENT];
+static int order[NUM_ENT];
+
+void test_nghttp2_map_functional(void) {
+ nghttp2_map map;
+ int i;
+ strentry *ent;
+
+ nghttp2_map_init(&map, nghttp2_mem_default());
+ for (i = 0; i < NUM_ENT; ++i) {
+ strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo");
+ order[i] = i + 1;
+ }
+ /* insertion */
+ shuffle(order, NUM_ENT);
+ for (i = 0; i < NUM_ENT; ++i) {
+ ent = &arr[order[i] - 1];
+ CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent));
+ }
+
+ CU_ASSERT(NUM_ENT == nghttp2_map_size(&map));
+
+ /* traverse */
+ nghttp2_map_each(&map, eachfun, NULL);
+ /* find */
+ shuffle(order, NUM_ENT);
+ for (i = 0; i < NUM_ENT; ++i) {
+ CU_ASSERT(NULL != nghttp2_map_find(&map, (nghttp2_map_key_type)order[i]));
+ }
+ /* remove */
+ for (i = 0; i < NUM_ENT; ++i) {
+ CU_ASSERT(0 == nghttp2_map_remove(&map, (nghttp2_map_key_type)order[i]));
+ }
+
+ /* each_free (but no op function for testing purpose) */
+ for (i = 0; i < NUM_ENT; ++i) {
+ strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo");
+ }
+ /* insert once again */
+ for (i = 0; i < NUM_ENT; ++i) {
+ ent = &arr[i];
+ CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent));
+ }
+ nghttp2_map_each_free(&map, eachfun, NULL);
+ nghttp2_map_free(&map);
+}
+
+static int entry_free(void *data, void *ptr) {
+ const nghttp2_mem *mem = ptr;
+
+ mem->free(data, NULL);
+ return 0;
+}
+
+void test_nghttp2_map_each_free(void) {
+ const nghttp2_mem *mem = nghttp2_mem_default();
+ strentry *foo = mem->malloc(sizeof(strentry), NULL),
+ *bar = mem->malloc(sizeof(strentry), NULL),
+ *baz = mem->malloc(sizeof(strentry), NULL),
+ *shrubbery = mem->malloc(sizeof(strentry), NULL);
+ 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->key, foo);
+ nghttp2_map_insert(&map, bar->key, bar);
+ nghttp2_map_insert(&map, baz->key, baz);
+ nghttp2_map_insert(&map, shrubbery->key, shrubbery);
+
+ nghttp2_map_each_free(&map, entry_free, (void *)mem);
+ nghttp2_map_free(&map);
+}
+
+void test_nghttp2_map_clear(void) {
+ nghttp2_mem *mem = nghttp2_mem_default();
+ nghttp2_map map;
+ strentry foo;
+
+ strentry_init(&foo, 1, "foo");
+
+ nghttp2_map_init(&map, mem);
+
+ CU_ASSERT(0 == nghttp2_map_insert(&map, foo.key, &foo));
+
+ nghttp2_map_clear(&map);
+
+ CU_ASSERT(0 == nghttp2_map_size(&map));
+
+ nghttp2_map_free(&map);
+}
diff --git a/tests/nghttp2_map_test.h b/tests/nghttp2_map_test.h
new file mode 100644
index 0000000..235624d
--- /dev/null
+++ b/tests/nghttp2_map_test.h
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_map(void);
+void test_nghttp2_map_functional(void);
+void test_nghttp2_map_each_free(void);
+void test_nghttp2_map_clear(void);
+
+#endif /* NGHTTP2_MAP_TEST_H */
diff --git a/tests/nghttp2_pq_test.c b/tests/nghttp2_pq_test.c
new file mode 100644
index 0000000..90db26d
--- /dev/null
+++ b/tests/nghttp2_pq_test.c
@@ -0,0 +1,228 @@
+/*
+ * 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 <stdio.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_pq.h"
+
+typedef struct {
+ nghttp2_pq_entry ent;
+ const char *s;
+} string_entry;
+
+static string_entry *string_entry_new(const char *s) {
+ nghttp2_mem *mem;
+ string_entry *ent;
+
+ mem = nghttp2_mem_default();
+
+ ent = nghttp2_mem_malloc(mem, sizeof(string_entry));
+ ent->s = s;
+
+ return ent;
+}
+
+static void string_entry_del(string_entry *ent) { free(ent); }
+
+static int pq_less(const void *lhs, const void *rhs) {
+ return strcmp(((string_entry *)lhs)->s, ((string_entry *)rhs)->s) < 0;
+}
+
+void test_nghttp2_pq(void) {
+ int i;
+ nghttp2_pq pq;
+ string_entry *top;
+
+ nghttp2_pq_init(&pq, pq_less, nghttp2_mem_default());
+ CU_ASSERT(nghttp2_pq_empty(&pq));
+ CU_ASSERT(0 == nghttp2_pq_size(&pq));
+ CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("foo")->ent));
+ CU_ASSERT(0 == nghttp2_pq_empty(&pq));
+ CU_ASSERT(1 == nghttp2_pq_size(&pq));
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("foo", top->s) == 0);
+ CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("bar")->ent));
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("bar", top->s) == 0);
+ CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("baz")->ent));
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("bar", top->s) == 0);
+ CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("C")->ent));
+ CU_ASSERT(4 == nghttp2_pq_size(&pq));
+
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("C", top->s) == 0);
+ string_entry_del(top);
+ nghttp2_pq_pop(&pq);
+
+ CU_ASSERT(3 == nghttp2_pq_size(&pq));
+
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("bar", top->s) == 0);
+ nghttp2_pq_pop(&pq);
+ string_entry_del(top);
+
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("baz", top->s) == 0);
+ nghttp2_pq_pop(&pq);
+ string_entry_del(top);
+
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(strcmp("foo", top->s) == 0);
+ nghttp2_pq_pop(&pq);
+ string_entry_del(top);
+
+ 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, &string_entry_new("foo")->ent));
+ CU_ASSERT((size_t)(i + 1) == nghttp2_pq_size(&pq));
+ }
+ for (i = 10000; i > 0; --i) {
+ top = (string_entry *)nghttp2_pq_top(&pq);
+ CU_ASSERT(NULL != top);
+ nghttp2_pq_pop(&pq);
+ string_entry_del(top);
+ CU_ASSERT((size_t)(i - 1) == nghttp2_pq_size(&pq));
+ }
+
+ nghttp2_pq_free(&pq);
+}
+
+typedef struct {
+ nghttp2_pq_entry ent;
+ int key;
+ int val;
+} node;
+
+static int node_less(const void *lhs, const void *rhs) {
+ node *ln = (node *)lhs;
+ node *rn = (node *)rhs;
+ return ln->key < rn->key;
+}
+
+static int node_update(nghttp2_pq_entry *item, void *arg) {
+ node *nd = (node *)item;
+ (void)arg;
+
+ 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_less, 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].ent);
+ }
+
+ nghttp2_pq_update(&pq, node_update, NULL);
+
+ for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) {
+ nd = (node *)nghttp2_pq_top(&pq);
+ CU_ASSERT(ans[i] == nd->key);
+ nghttp2_pq_pop(&pq);
+ }
+
+ nghttp2_pq_free(&pq);
+}
+
+static void push_nodes(nghttp2_pq *pq, node *dest, size_t n) {
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ dest[i].key = (int)i;
+ dest[i].val = (int)i;
+ nghttp2_pq_push(pq, &dest[i].ent);
+ }
+}
+
+static void check_nodes(nghttp2_pq *pq, size_t n, int *ans_key, int *ans_val) {
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ node *nd = (node *)nghttp2_pq_top(pq);
+ CU_ASSERT(ans_key[i] == nd->key);
+ CU_ASSERT(ans_val[i] == nd->val);
+ nghttp2_pq_pop(pq);
+ }
+}
+
+void test_nghttp2_pq_remove(void) {
+ nghttp2_pq pq;
+ node nodes[10];
+ int ans_key1[] = {1, 2, 3, 4, 5};
+ int ans_val1[] = {1, 2, 3, 4, 5};
+ int ans_key2[] = {0, 1, 2, 4, 5};
+ int ans_val2[] = {0, 1, 2, 4, 5};
+ int ans_key3[] = {0, 1, 2, 3, 4};
+ int ans_val3[] = {0, 1, 2, 3, 4};
+
+ nghttp2_pq_init(&pq, node_less, nghttp2_mem_default());
+
+ push_nodes(&pq, nodes, 6);
+
+ nghttp2_pq_remove(&pq, &nodes[0].ent);
+
+ check_nodes(&pq, 5, ans_key1, ans_val1);
+
+ nghttp2_pq_free(&pq);
+
+ nghttp2_pq_init(&pq, node_less, nghttp2_mem_default());
+
+ push_nodes(&pq, nodes, 6);
+
+ nghttp2_pq_remove(&pq, &nodes[3].ent);
+
+ check_nodes(&pq, 5, ans_key2, ans_val2);
+
+ nghttp2_pq_free(&pq);
+
+ nghttp2_pq_init(&pq, node_less, nghttp2_mem_default());
+
+ push_nodes(&pq, nodes, 6);
+
+ nghttp2_pq_remove(&pq, &nodes[5].ent);
+
+ check_nodes(&pq, 5, ans_key3, ans_val3);
+
+ nghttp2_pq_free(&pq);
+}
diff --git a/tests/nghttp2_pq_test.h b/tests/nghttp2_pq_test.h
new file mode 100644
index 0000000..969662a
--- /dev/null
+++ b/tests/nghttp2_pq_test.h
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_pq(void);
+void test_nghttp2_pq_update(void);
+void test_nghttp2_pq_remove(void);
+
+#endif /* NGHTTP2_PQ_TEST_H */
diff --git a/tests/nghttp2_queue_test.c b/tests/nghttp2_queue_test.c
new file mode 100644
index 0000000..cb993a8
--- /dev/null
+++ b/tests/nghttp2_queue_test.c
@@ -0,0 +1,50 @@
+/*
+ * 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 <stdio.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
index 0000000..64f8ce8
--- /dev/null
+++ b/tests/nghttp2_queue_test.h
@@ -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_QUEUE_TEST_H
+#define NGHTTP2_QUEUE_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_queue(void);
+
+#endif /* NGHTTP2_QUEUE_TEST_H */
diff --git a/tests/nghttp2_ratelim_test.c b/tests/nghttp2_ratelim_test.c
new file mode 100644
index 0000000..6abece9
--- /dev/null
+++ b/tests/nghttp2_ratelim_test.c
@@ -0,0 +1,101 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_ratelim_test.h"
+
+#include <stdio.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_ratelim.h"
+
+void test_nghttp2_ratelim_update(void) {
+ nghttp2_ratelim rl;
+
+ nghttp2_ratelim_init(&rl, 1000, 21);
+
+ CU_ASSERT(1000 == rl.val);
+ CU_ASSERT(1000 == rl.burst);
+ CU_ASSERT(21 == rl.rate);
+ CU_ASSERT(0 == rl.tstamp);
+
+ nghttp2_ratelim_update(&rl, 999);
+
+ CU_ASSERT(1000 == rl.val);
+ CU_ASSERT(999 == rl.tstamp);
+
+ nghttp2_ratelim_drain(&rl, 100);
+
+ CU_ASSERT(900 == rl.val);
+
+ nghttp2_ratelim_update(&rl, 1000);
+
+ CU_ASSERT(921 == rl.val);
+
+ nghttp2_ratelim_update(&rl, 1002);
+
+ CU_ASSERT(963 == rl.val);
+
+ nghttp2_ratelim_update(&rl, 1004);
+
+ CU_ASSERT(1000 == rl.val);
+ CU_ASSERT(1004 == rl.tstamp);
+
+ /* timer skew */
+ nghttp2_ratelim_init(&rl, 1000, 21);
+ nghttp2_ratelim_update(&rl, 1);
+
+ CU_ASSERT(1000 == rl.val);
+
+ nghttp2_ratelim_update(&rl, 0);
+
+ CU_ASSERT(1000 == rl.val);
+
+ /* rate * duration overflow */
+ nghttp2_ratelim_init(&rl, 1000, 100);
+ nghttp2_ratelim_drain(&rl, 999);
+
+ CU_ASSERT(1 == rl.val);
+
+ nghttp2_ratelim_update(&rl, UINT64_MAX);
+
+ CU_ASSERT(1000 == rl.val);
+
+ /* val + rate * duration overflow */
+ nghttp2_ratelim_init(&rl, UINT64_MAX - 1, 2);
+ nghttp2_ratelim_update(&rl, 1);
+
+ CU_ASSERT(UINT64_MAX - 1 == rl.val);
+}
+
+void test_nghttp2_ratelim_drain(void) {
+ nghttp2_ratelim rl;
+
+ nghttp2_ratelim_init(&rl, 100, 7);
+
+ CU_ASSERT(-1 == nghttp2_ratelim_drain(&rl, 101));
+ CU_ASSERT(0 == nghttp2_ratelim_drain(&rl, 51));
+ CU_ASSERT(0 == nghttp2_ratelim_drain(&rl, 49));
+ CU_ASSERT(-1 == nghttp2_ratelim_drain(&rl, 1));
+}
diff --git a/tests/nghttp2_ratelim_test.h b/tests/nghttp2_ratelim_test.h
new file mode 100644
index 0000000..02b2f2b
--- /dev/null
+++ b/tests/nghttp2_ratelim_test.h
@@ -0,0 +1,35 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2023 nghttp2 contributors
+ *
+ * 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_RATELIM_TEST_H
+#define NGHTTP2_RATELIM_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp2_ratelim_update(void);
+void test_nghttp2_ratelim_drain(void);
+
+#endif /* NGHTTP2_RATELIM_TEST_H */
diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c
new file mode 100644
index 0000000..9f6a667
--- /dev/null
+++ b/tests/nghttp2_session_test.c
@@ -0,0 +1,13438 @@
+/*
+ * 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"
+#include "nghttp2_extpri.h"
+
+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;
+ nghttp2_frame_hd recv_frame_hd;
+ int frame_send_cb_called;
+ uint8_t sent_frame_type;
+ int before_frame_send_cb_called;
+ int frame_not_send_cb_called;
+ uint8_t not_sent_frame_type;
+ int not_sent_error;
+ int stream_close_cb_called;
+ uint32_t stream_close_error_code;
+ 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 invalid_header_cb_called;
+ int begin_headers_cb_called;
+ nghttp2_nv nv;
+ size_t data_chunk_len;
+ size_t padlen;
+ int begin_frame_cb_called;
+ nghttp2_buf scratchbuf;
+ size_t data_source_read_cb_paused;
+} my_user_data;
+
+static const nghttp2_nv reqnv[] = {
+ MAKE_NV(":method", "GET"),
+ MAKE_NV(":path", "/"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "localhost"),
+};
+
+static const nghttp2_nv resnv[] = {
+ MAKE_NV(":status", "200"),
+};
+
+static const nghttp2_nv trailernv[] = {
+ // from http://tools.ietf.org/html/rfc6249#section-7
+ MAKE_NV("digest", "SHA-256="
+ "MWVkMWQxYTRiMzk5MDQ0MzI3NGU5NDEyZTk5OWY1ZGFmNzgyZTJlODYz"
+ "YjRjYzFhOTlmNTQwYzI2M2QwM2U2MQ=="),
+};
+
+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, const uint8_t *data,
+ size_t len, int flags, void *user_data) {
+ (void)session;
+ (void)data;
+ (void)flags;
+ (void)user_data;
+
+ return (ssize_t)len;
+}
+
+static ssize_t fail_send_callback(nghttp2_session *session, const uint8_t *data,
+ size_t len, int flags, void *user_data) {
+ (void)session;
+ (void)data;
+ (void)len;
+ (void)flags;
+ (void)user_data;
+
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+static ssize_t fixed_bytes_send_callback(nghttp2_session *session,
+ const uint8_t *data, size_t len,
+ int flags, void *user_data) {
+ size_t fixed_sendlen = ((my_user_data *)user_data)->fixed_sendlen;
+ (void)session;
+ (void)data;
+ (void)flags;
+
+ return (ssize_t)(fixed_sendlen < len ? fixed_sendlen : len);
+}
+
+static ssize_t scripted_recv_callback(nghttp2_session *session, uint8_t *data,
+ size_t len, int flags, 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];
+ (void)session;
+ (void)flags;
+
+ memcpy(data, df->datamark, wlen);
+ df->datamark += wlen;
+ df->feedseq[df->seqidx] -= wlen;
+ if (df->feedseq[df->seqidx] == 0) {
+ ++df->seqidx;
+ }
+ return (ssize_t)wlen;
+}
+
+static ssize_t eof_recv_callback(nghttp2_session *session, uint8_t *data,
+ size_t len, int flags, void *user_data) {
+ (void)session;
+ (void)data;
+ (void)len;
+ (void)flags;
+ (void)user_data;
+
+ return NGHTTP2_ERR_EOF;
+}
+
+static ssize_t accumulator_send_callback(nghttp2_session *session,
+ const uint8_t *buf, size_t len,
+ int flags, void *user_data) {
+ accumulator *acc = ((my_user_data *)user_data)->acc;
+ (void)session;
+ (void)flags;
+
+ assert(acc->length + len < sizeof(acc->buf));
+ memcpy(acc->buf + acc->length, buf, len);
+ acc->length += len;
+ return (ssize_t)len;
+}
+
+static int on_begin_frame_callback(nghttp2_session *session,
+ const nghttp2_frame_hd *hd,
+ void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)hd;
+
+ ++ud->begin_frame_cb_called;
+ return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+
+ ++ud->frame_recv_cb_called;
+ ud->recv_frame_type = frame->hd.type;
+ ud->recv_frame_hd = frame->hd;
+
+ return 0;
+}
+
+static int on_invalid_frame_recv_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ int lib_error_code, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)frame;
+ (void)lib_error_code;
+
+ ++ud->invalid_frame_recv_cb_called;
+ return 0;
+}
+
+static int on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+
+ ++ud->frame_send_cb_called;
+ ud->sent_frame_type = frame->hd.type;
+ return 0;
+}
+
+static int on_frame_not_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame, int lib_error,
+ void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+
+ ++ud->frame_not_send_cb_called;
+ ud->not_sent_frame_type = frame->hd.type;
+ ud->not_sent_error = lib_error;
+ return 0;
+}
+
+static int cancel_before_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)frame;
+
+ ++ud->before_frame_send_cb_called;
+ return NGHTTP2_ERR_CANCEL;
+}
+
+static 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) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)flags;
+ (void)stream_id;
+ (void)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,
+ uint8_t flags, int32_t stream_id,
+ const uint8_t *data, size_t len,
+ void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)flags;
+ (void)stream_id;
+ (void)data;
+ (void)len;
+
+ ++ud->data_chunk_recv_cb_called;
+ return NGHTTP2_ERR_PAUSE;
+}
+
+static ssize_t select_padding_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ size_t max_payloadlen, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+
+ return (ssize_t)nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen);
+}
+
+static ssize_t too_large_data_source_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) {
+ (void)session;
+ (void)frame_type;
+ (void)stream_id;
+ (void)session_remote_window_size;
+ (void)stream_remote_window_size;
+ (void)remote_max_frame_size;
+ (void)user_data;
+
+ return NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
+}
+
+static ssize_t smallest_length_data_source_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) {
+ (void)session;
+ (void)frame_type;
+ (void)stream_id;
+ (void)session_remote_window_size;
+ (void)stream_remote_window_size;
+ (void)remote_max_frame_size;
+ (void)user_data;
+
+ return 1;
+}
+
+static ssize_t fixed_length_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ size_t wlen;
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)source;
+
+ 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 (ssize_t)wlen;
+}
+
+static ssize_t temporal_failure_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)len;
+ (void)data_flags;
+ (void)source;
+ (void)user_data;
+
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+
+static ssize_t fail_data_source_read_callback(nghttp2_session *session,
+ int32_t stream_id, uint8_t *buf,
+ size_t len, uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *user_data) {
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)len;
+ (void)data_flags;
+ (void)source;
+ (void)user_data;
+
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+static ssize_t no_end_stream_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)len;
+ (void)source;
+ (void)user_data;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ return 0;
+}
+
+static ssize_t no_copy_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ size_t wlen;
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)source;
+
+ if (len < ud->data_source_length) {
+ wlen = len;
+ } else {
+ wlen = ud->data_source_length;
+ }
+
+ ud->data_source_length -= wlen;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+
+ if (ud->data_source_length == 0) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return (ssize_t)wlen;
+}
+
+static int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
+ const uint8_t *framehd, size_t length,
+ nghttp2_data_source *source, void *user_data) {
+ accumulator *acc = ((my_user_data *)user_data)->acc;
+ (void)session;
+ (void)source;
+
+ memcpy(acc->buf + acc->length, framehd, NGHTTP2_FRAME_HDLEN);
+ acc->length += NGHTTP2_FRAME_HDLEN;
+
+ if (frame->data.padlen) {
+ *(acc->buf + acc->length++) = (uint8_t)(frame->data.padlen - 1);
+ }
+
+ acc->length += length;
+
+ if (frame->data.padlen) {
+ acc->length += frame->data.padlen - 1;
+ }
+
+ return 0;
+}
+
+static ssize_t block_count_send_callback(nghttp2_session *session,
+ const uint8_t *data, size_t len,
+ int flags, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)data;
+ (void)flags;
+
+ if (ud->block_count == 0) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ --ud->block_count;
+ return (ssize_t)len;
+}
+
+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, void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ ++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_invalid_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) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)flags;
+
+ ++ud->invalid_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_invalid_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_invalid_header_callback(session, frame, name, namelen, value, valuelen,
+ flags, user_data);
+ return NGHTTP2_ERR_PAUSE;
+}
+
+static int reset_on_invalid_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_invalid_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,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ my_user_data *ud = (my_user_data *)user_data;
+ (void)session;
+ (void)frame;
+
+ ++ud->begin_headers_cb_called;
+ return 0;
+}
+
+static int temporal_failure_on_begin_headers_callback(
+ nghttp2_session *session, const nghttp2_frame *frame, void *user_data) {
+ on_begin_headers_callback(session, frame, user_data);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+
+static ssize_t defer_data_source_read_callback(nghttp2_session *session,
+ int32_t stream_id, uint8_t *buf,
+ size_t len, uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *user_data) {
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)len;
+ (void)data_flags;
+ (void)source;
+ (void)user_data;
+
+ return NGHTTP2_ERR_DEFERRED;
+}
+
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ my_user_data *my_data = (my_user_data *)user_data;
+ (void)session;
+ (void)stream_id;
+ (void)error_code;
+
+ ++my_data->stream_close_cb_called;
+ my_data->stream_close_error_code = error_code;
+
+ return 0;
+}
+
+static int fatal_error_on_stream_close_callback(nghttp2_session *session,
+ int32_t stream_id,
+ uint32_t error_code,
+ void *user_data) {
+ on_stream_close_callback(session, stream_id, error_code, user_data);
+
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+static ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf,
+ size_t len, const nghttp2_frame *frame,
+ void *user_data) {
+ nghttp2_buf *p = frame->ext.payload;
+ (void)session;
+ (void)len;
+ (void)user_data;
+
+ memcpy(buf, p->pos, nghttp2_buf_len(p));
+
+ return (ssize_t)nghttp2_buf_len(p);
+}
+
+static int on_extension_chunk_recv_callback(nghttp2_session *session,
+ const nghttp2_frame_hd *hd,
+ const uint8_t *data, size_t len,
+ void *user_data) {
+ my_user_data *my_data = (my_user_data *)user_data;
+ nghttp2_buf *buf = &my_data->scratchbuf;
+ (void)session;
+ (void)hd;
+
+ buf->last = nghttp2_cpymem(buf->last, data, len);
+
+ return 0;
+}
+
+static int cancel_on_extension_chunk_recv_callback(nghttp2_session *session,
+ const nghttp2_frame_hd *hd,
+ const uint8_t *data,
+ size_t len,
+ void *user_data) {
+ (void)session;
+ (void)hd;
+ (void)data;
+ (void)len;
+ (void)user_data;
+
+ return NGHTTP2_ERR_CANCEL;
+}
+
+static int unpack_extension_callback(nghttp2_session *session, void **payload,
+ const nghttp2_frame_hd *hd,
+ void *user_data) {
+ my_user_data *my_data = (my_user_data *)user_data;
+ nghttp2_buf *buf = &my_data->scratchbuf;
+ (void)session;
+ (void)hd;
+
+ *payload = buf;
+
+ return 0;
+}
+
+static int cancel_unpack_extension_callback(nghttp2_session *session,
+ void **payload,
+ const nghttp2_frame_hd *hd,
+ void *user_data) {
+ (void)session;
+ (void)payload;
+ (void)hd;
+ (void)user_data;
+
+ return NGHTTP2_ERR_CANCEL;
+}
+
+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;
+ nghttp2_bufs bufs;
+ size_t framelen;
+ nghttp2_frame frame;
+ size_t i;
+ nghttp2_outbound_item *item;
+ nghttp2_nv *nva;
+ size_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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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 (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);
+
+ /* Receive PRIORITY */
+ nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default);
+
+ nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+ 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);
+
+ nghttp2_frame_pack_ping(&bufs, &frame.ping);
+
+ /* 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;
+ nghttp2_nv *nva;
+ size_t nvlen;
+
+ 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);
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+ 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);
+ 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;
+ nghttp2_bufs bufs;
+ nghttp2_frame frame;
+ nghttp2_nv *nva;
+ size_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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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 error, because it has
+ * pseudo headers and without END_STREAM flag set */
+ scripted_data_feed_init2(&df, &bufs);
+
+ CU_ASSERT(0 == nghttp2_session_recv(session));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(1 == user_data.frame_send_cb_called);
+ CU_ASSERT(NGHTTP2_RST_STREAM == user_data.sent_frame_type);
+
+ 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;
+ callbacks.on_frame_send_callback = on_frame_send_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 = open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING);
+
+ /* Set initial window size 16383 to check stream flow control,
+ isolating it from the connection 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);
+
+ /* Check window_update_queued flag in both session and stream */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ hd.length = 4096;
+ hd.type = NGHTTP2_DATA;
+ hd.flags = NGHTTP2_FLAG_NONE;
+ hd.stream_id = 1;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+
+ stream = open_recv_stream(session, 1);
+
+ /* Send 32767 bytes of DATA. In our current flow control algorithm,
+ it triggers first WINDOW_UPDATE of window_size_increment
+ 32767. */
+ for (i = 0; i < 7; ++i) {
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+ }
+
+ hd.length = 4095;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv);
+
+ /* Now 2 WINDOW_UPDATEs for session and stream should be queued. */
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(1 == stream->window_update_queued);
+ CU_ASSERT(1 == session->window_update_queued);
+
+ /* Then send 32768 bytes of DATA. Since we have not sent queued
+ WINDOW_UDPATE frame, recv_window_size should not be decreased */
+ hd.length = 4096;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+
+ for (i = 0; i < 8; ++i) {
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+ }
+
+ /* WINDOW_UPDATE is blocked for session and stream, so
+ recv_window_size must not be decreased. */
+ CU_ASSERT(32768 == stream->recv_window_size);
+ CU_ASSERT(32768 == session->recv_window_size);
+ CU_ASSERT(1 == stream->window_update_queued);
+ CU_ASSERT(1 == session->window_update_queued);
+
+ ud.frame_send_cb_called = 0;
+
+ /* This sends queued WINDOW_UPDATES. And then check
+ recv_window_size, and queue WINDOW_UPDATEs for both session and
+ stream, and send them at once. */
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ CU_ASSERT(4 == ud.frame_send_cb_called);
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(0 == stream->window_update_queued);
+ CU_ASSERT(0 == session->window_update_queued);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_data_no_auto_flow_control(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_option *option;
+ nghttp2_frame_hd hd;
+ size_t padlen;
+ uint8_t data[8192];
+ ssize_t rv;
+ size_t sendlen;
+ nghttp2_stream *stream;
+ size_t i;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ callbacks.on_frame_send_callback = on_frame_send_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_window_update(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ /* Create DATA frame with length 4KiB + 11 bytes padding*/
+ padlen = 11;
+ memset(data, 0, sizeof(data));
+ hd.length = 4096 + 1 + padlen;
+ hd.type = NGHTTP2_DATA;
+ hd.flags = NGHTTP2_FLAG_PADDED;
+ hd.stream_id = 1;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+ data[NGHTTP2_FRAME_HDLEN] = (uint8_t)padlen;
+
+ /* First create stream 1, then close it. Check that data is
+ consumed for connection in this situation */
+ open_recv_stream(session, 1);
+
+ /* Receive first 100 bytes */
+ sendlen = 100;
+ rv = nghttp2_session_mem_recv(session, data, sendlen);
+ CU_ASSERT((ssize_t)sendlen == rv);
+
+ /* We consumed pad length field (1 byte) */
+ CU_ASSERT(1 == session->consumed_size);
+
+ /* close stream here */
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, NGHTTP2_NO_ERROR);
+ nghttp2_session_send(session);
+
+ /* stream 1 has been closed, and we disabled auto flow-control, so
+ data must be immediately consumed for connection. */
+ rv = nghttp2_session_mem_recv(session, data + sendlen,
+ NGHTTP2_FRAME_HDLEN + hd.length - sendlen);
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen) == rv);
+
+ /* We already consumed pad length field (1 byte), so do +1 here */
+ CU_ASSERT((int32_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen + 1) ==
+ session->consumed_size);
+
+ nghttp2_session_del(session);
+
+ /* Reuse DATA created previously. */
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ /* Now we are expecting final response header, which means receiving
+ DATA for that stream is illegal. */
+ stream = open_recv_stream(session, 1);
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE;
+
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length);
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == rv);
+
+ /* Whole payload must be consumed now because HTTP messaging rule
+ was not honored. */
+ CU_ASSERT((int32_t)hd.length == session->consumed_size);
+
+ nghttp2_session_del(session);
+
+ /* Check window_update_queued flag in both session and stream */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ stream = open_recv_stream(session, 1);
+
+ hd.length = 4096;
+ hd.type = NGHTTP2_DATA;
+ hd.flags = NGHTTP2_FLAG_NONE;
+ hd.stream_id = 1;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+
+ /* Receive up to 65535 bytes of DATA */
+ for (i = 0; i < 15; ++i) {
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+ }
+
+ hd.length = 4095;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+
+ rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv);
+
+ CU_ASSERT(65535 == session->recv_window_size);
+ CU_ASSERT(65535 == stream->recv_window_size);
+
+ /* The first call of nghttp2_session_consume_connection() will queue
+ WINDOW_UPDATE. Next call does not. */
+ nghttp2_session_consume_connection(session, 32767);
+ nghttp2_session_consume_connection(session, 32768);
+
+ CU_ASSERT(32768 == session->recv_window_size);
+ CU_ASSERT(65535 == stream->recv_window_size);
+ CU_ASSERT(1 == session->window_update_queued);
+ CU_ASSERT(0 == stream->window_update_queued);
+
+ ud.frame_send_cb_called = 0;
+
+ /* This will send WINDOW_UPDATE, and check whether we should send
+ WINDOW_UPDATE, and queue and send it at once. */
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(65535 == stream->recv_window_size);
+ CU_ASSERT(0 == session->window_update_queued);
+ CU_ASSERT(0 == stream->window_update_queued);
+ CU_ASSERT(2 == ud.frame_send_cb_called);
+
+ /* Do the same for stream */
+ nghttp2_session_consume_stream(session, 1, 32767);
+ nghttp2_session_consume_stream(session, 1, 32768);
+
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(32768 == stream->recv_window_size);
+ CU_ASSERT(0 == session->window_update_queued);
+ CU_ASSERT(1 == stream->window_update_queued);
+
+ ud.frame_send_cb_called = 0;
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(0 == session->window_update_queued);
+ CU_ASSERT(0 == stream->window_update_queued);
+ CU_ASSERT(2 == ud.frame_send_cb_called);
+
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_recv_continuation(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ 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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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, (uint32_t)((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(4 == ud.header_cb_called);
+ CU_ASSERT(3 == ud.begin_frame_cb_called);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* HEADERS with padding followed by CONTINUATION */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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);
+
+ 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));
+
+ /* HEADERS payload is 3 byte (1 for padding field, 1 for padding) */
+ memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN);
+ nghttp2_put_uint32be(data, (uint32_t)((3 << 8) + data[3]));
+ data[4] |= NGHTTP2_FLAG_PADDED;
+ /* padding field */
+ data[NGHTTP2_FRAME_HDLEN] = 1;
+ data[NGHTTP2_FRAME_HDLEN + 1] = buf->pos[NGHTTP2_FRAME_HDLEN];
+ /* padding */
+ data[NGHTTP2_FRAME_HDLEN + 2] = 0;
+ datalen = NGHTTP2_FRAME_HDLEN + 3;
+ buf->pos += NGHTTP2_FRAME_HDLEN + 1;
+
+ /* 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(4 == ud.header_cb_called);
+ CU_ASSERT(2 == 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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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);
+
+ nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+ 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;
+ 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_recv_stream(session, 1);
+
+ /* With NGHTTP2_FLAG_PRIORITY without exclusive flag set */
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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((ssize_t)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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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_bufs_len(&bufs) > NGHTTP2_FRAME_HDLEN + 5);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ buf = &bufs.head->buf;
+ /* Make payload shorter than required length to store priority
+ group */
+ nghttp2_put_uint32be(buf->pos, (uint32_t)((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((ssize_t)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(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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((ssize_t)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_headers_with_padding(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_frame_hd hd;
+ nghttp2_outbound_item *item;
+ my_user_data ud;
+ ssize_t rv;
+
+ 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;
+
+ /* HEADERS: Wrong padding length */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+ nghttp2_session_send(session);
+
+ nghttp2_frame_hd_init(&hd, 10, NGHTTP2_HEADERS,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY |
+ NGHTTP2_FLAG_PADDED,
+ 1);
+ buf = &bufs.head->buf;
+ nghttp2_frame_pack_frame_hd(buf->last, &hd);
+ buf->last += NGHTTP2_FRAME_HDLEN;
+ /* padding is 6 bytes */
+ *buf->last++ = 5;
+ /* priority field */
+ nghttp2_put_uint32be(buf->last, 3);
+ buf->last += sizeof(uint32_t);
+ *buf->last++ = 1;
+ /* rest is garbage */
+ memset(buf->last, 0, 4);
+ buf->last += 4;
+
+ ud.frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_session_del(session);
+
+ /* PUSH_PROMISE: Wrong padding length */
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+ nghttp2_session_send(session);
+
+ open_sent_stream(session, 1);
+
+ nghttp2_frame_hd_init(&hd, 9, NGHTTP2_PUSH_PROMISE,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED, 1);
+ buf = &bufs.head->buf;
+ nghttp2_frame_pack_frame_hd(buf->last, &hd);
+ buf->last += NGHTTP2_FRAME_HDLEN;
+ /* padding is 6 bytes */
+ *buf->last++ = 5;
+ /* promised stream ID field */
+ nghttp2_put_uint32be(buf->last, 2);
+ buf->last += sizeof(uint32_t);
+ /* rest is garbage */
+ memset(buf->last, 0, 4);
+ buf->last += 4;
+
+ ud.frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_bufs_free(&bufs);
+ nghttp2_session_del(session);
+}
+
+static int response_on_begin_frame_callback(nghttp2_session *session,
+ const nghttp2_frame_hd *hd,
+ void *user_data) {
+ int rv;
+ (void)user_data;
+
+ if (hd->type != NGHTTP2_HEADERS) {
+ return 0;
+ }
+
+ rv = nghttp2_submit_response(session, hd->stream_id, resnv, ARRLEN(resnv),
+ NULL);
+
+ CU_ASSERT(0 == rv);
+
+ return 0;
+}
+
+void test_nghttp2_session_recv_headers_early_response(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_nv *nva;
+ size_t nvlen;
+ nghttp2_frame frame;
+ ssize_t rv;
+ nghttp2_stream *stream;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ callbacks.on_begin_frame_callback = response_on_begin_frame_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+ nghttp2_frame_headers_init(&frame.headers,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ 1, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
+
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ buf = &bufs.head->buf;
+
+ /* Only receive 9 bytes headers, and invoke
+ on_begin_frame_callback */
+ rv = nghttp2_session_mem_recv(session, buf->pos, 9);
+
+ CU_ASSERT(9 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ rv =
+ nghttp2_session_mem_recv(session, buf->pos + 9, nghttp2_buf_len(buf) - 9);
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - 9 == rv);
+
+ stream = nghttp2_session_get_stream_raw(session, 1);
+
+ CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_CLOSED);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_recv_headers_for_closed_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ 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_stream *stream;
+ nghttp2_mem *mem;
+ const uint8_t *data;
+
+ 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.on_header_callback = on_header_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* Make sure that on_header callback never be invoked for closed
+ stream */
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, 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));
+
+ ud.header_cb_called = 0;
+ ud.frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, NGHTTP2_FRAME_HDLEN);
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv);
+ CU_ASSERT(0 == ud.header_cb_called);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NULL != stream);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(rv > 0);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NULL == stream);
+
+ ud.header_cb_called = 0;
+ ud.frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos + NGHTTP2_FRAME_HDLEN,
+ nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN);
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN == rv);
+ CU_ASSERT(0 == ud.header_cb_called);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_bufs_free(&bufs);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_headers_with_extpri(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_nv *nva;
+ size_t nvlen;
+ nghttp2_frame frame;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ ssize_t rv;
+ nghttp2_hd_deflater deflater;
+ nghttp2_stream *stream;
+ nghttp2_mem *mem;
+ const nghttp2_nv extpri_reqnv[] = {
+ MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"),
+ MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV("priority", "i,u=2"),
+ };
+ nghttp2_settings_entry iv;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 1;
+
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(extpri_reqnv);
+ nghttp2_nv_array_copy(&nva, extpri_reqnv, 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));
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Client should ignore priority header field included in
+ PUSH_PROMISE. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ open_sent_stream(session, 1);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(extpri_reqnv);
+ nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem);
+
+ nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+ 1, 2, nva, nvlen);
+
+ rv = nghttp2_frame_pack_push_promise(&bufs, &frame.push_promise, &deflater);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+ nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+
+ buf = &bufs.head->buf;
+ assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ stream = nghttp2_session_get_stream(session, 2);
+
+ CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY ==
+ nghttp2_extpri_uint8_urgency(stream->http_extpri));
+ CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY ==
+ nghttp2_extpri_uint8_urgency(stream->extpri));
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_server_recv_push_response(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ ssize_t rv;
+ my_user_data ud;
+ nghttp2_mem *mem;
+ nghttp2_frame frame;
+ nghttp2_hd_deflater deflater;
+ nghttp2_nv *nva;
+ size_t nvlen;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ nvlen = ARRLEN(resnv);
+ nghttp2_nv_array_copy(&nva, resnv, nvlen, mem);
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+ NGHTTP2_HCAT_HEADERS, &pri_spec_default, 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;
+
+ ud.invalid_frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
+
+ 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;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ ssize_t rv;
+ my_user_data ud;
+ nghttp2_hd_deflater deflater;
+ nghttp2_outbound_item *item;
+ nghttp2_mem *mem;
+ uint32_t payloadlen;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+
+ buf = &bufs.head->buf;
+ /* Intentionally feed payload cutting last 1 byte off */
+ payloadlen = nghttp2_get_uint32(buf->pos) >> 8;
+ nghttp2_put_uint32be(buf->pos, ((payloadlen - 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);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* Test for PUSH_PROMISE */
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ open_sent_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default,
+ NGHTTP2_STREAM_OPENING, NULL);
+
+ rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2,
+ reqnv, ARRLEN(reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ buf = &bufs.head->buf;
+ payloadlen = nghttp2_get_uint32(buf->pos) >> 8;
+ /* Intentionally feed payload cutting last 1 byte off */
+ nghttp2_put_uint32be(buf->pos, ((payloadlen - 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);
+ CU_ASSERT(2 == item->frame.hd.stream_id);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+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_recv_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((ssize_t)nghttp2_buf_len(buf) == rv);
+ 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((ssize_t)(nghttp2_buf_len(buf)) == rv);
+ 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((ssize_t)nghttp2_buf_len(buf) == rv);
+ 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((ssize_t)nghttp2_buf_len(buf) == rv);
+ 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_recv_extension(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_buf buf;
+ nghttp2_frame_hd hd;
+ nghttp2_mem *mem;
+ const char data[] = "Hello World!";
+ ssize_t rv;
+ nghttp2_option *option;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback;
+ callbacks.unpack_extension_callback = unpack_extension_callback;
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_user_recv_extension_type(option, 111);
+
+ nghttp2_buf_init2(&ud.scratchbuf, 4096, mem);
+ nghttp2_buf_init2(&buf, 4096, mem);
+
+ nghttp2_frame_hd_init(&hd, sizeof(data), 111, 0xab, 1000000007);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ buf.last = nghttp2_cpymem(buf.last, data, sizeof(data));
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&ud.recv_frame_hd, 0, 0, 0, 0);
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv);
+ CU_ASSERT(111 == ud.recv_frame_hd.type);
+ CU_ASSERT(0xab == ud.recv_frame_hd.flags);
+ CU_ASSERT(1000000007 == ud.recv_frame_hd.stream_id);
+ CU_ASSERT(0 == memcmp(data, ud.scratchbuf.pos, sizeof(data)));
+
+ nghttp2_session_del(session);
+
+ /* cancel in on_extension_chunk_recv_callback */
+ nghttp2_buf_reset(&ud.scratchbuf);
+
+ callbacks.on_extension_chunk_recv_callback =
+ cancel_on_extension_chunk_recv_callback;
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* cancel in unpack_extension_callback */
+ nghttp2_buf_reset(&ud.scratchbuf);
+
+ callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback;
+ callbacks.unpack_extension_callback = cancel_unpack_extension_callback;
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ nghttp2_buf_free(&buf, mem);
+ nghttp2_buf_free(&ud.scratchbuf, mem);
+
+ nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_recv_altsvc(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_buf buf;
+ nghttp2_frame_hd hd;
+ nghttp2_mem *mem;
+ ssize_t rv;
+ nghttp2_option *option;
+ static const uint8_t origin[] = "nghttp2.org";
+ static const uint8_t field_value[] = "h2=\":443\"";
+
+ mem = nghttp2_mem_default();
+
+ nghttp2_buf_init2(&buf, NGHTTP2_FRAME_HDLEN + NGHTTP2_MAX_FRAME_SIZE_MIN,
+ mem);
+
+ 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_option_new(&option);
+ nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1,
+ NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags);
+ CU_ASSERT(0 == ud.recv_frame_hd.stream_id);
+
+ nghttp2_session_del(session);
+
+ /* size of origin is larger than frame length */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 - 1, NGHTTP2_ALTSVC,
+ NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1 - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* zero-length value */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1, NGHTTP2_ALTSVC,
+ NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+
+ ud.invalid_frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* non-empty origin to a stream other than 0 */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ open_sent_stream(session, 1);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1,
+ NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
+
+ ud.invalid_frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* empty origin to stream 0 */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(field_value) - 1, NGHTTP2_ALTSVC,
+ NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, 0);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
+
+ ud.invalid_frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* send large frame (16KiB) */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN, NGHTTP2_ALTSVC,
+ NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ memset(buf.last, 0, nghttp2_buf_avail(&buf));
+ buf.last += nghttp2_buf_avail(&buf);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_MAX_FRAME_SIZE_MIN == ud.recv_frame_hd.length);
+
+ nghttp2_session_del(session);
+
+ /* send too large frame */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ session->local_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN - 1;
+
+ nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_ALTSVC,
+ NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ memset(buf.last, 0, nghttp2_buf_avail(&buf));
+ buf.last += nghttp2_buf_avail(&buf);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* received by server */
+ nghttp2_buf_reset(&buf);
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1,
+ NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0);
+ nghttp2_frame_pack_frame_hd(buf.last, &hd);
+ buf.last += NGHTTP2_FRAME_HDLEN;
+ nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
+ buf.last += 2;
+ buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
+ buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ nghttp2_buf_free(&buf, mem);
+ nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_recv_origin(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ nghttp2_option *option;
+ nghttp2_extension frame;
+ nghttp2_ext_origin origin;
+ nghttp2_origin_entry ov;
+ static const uint8_t nghttp2[] = "https://nghttp2.org";
+
+ frame_pack_bufs_init(&bufs);
+
+ frame.payload = &origin;
+
+ ov.origin = (uint8_t *)nghttp2;
+ ov.origin_len = sizeof(nghttp2) - 1;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, &ov, 1);
+
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags);
+ CU_ASSERT(0 == ud.recv_frame_hd.stream_id);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* The length of origin is larger than payload length. */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, &ov, 1);
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_put_uint16be(bufs.head->buf.pos + NGHTTP2_FRAME_HDLEN,
+ (uint16_t)sizeof(nghttp2));
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* A frame should be ignored if it is sent to a stream other than
+ stream 0. */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, &ov, 1);
+ frame.hd.stream_id = 1;
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* A frame should be ignored if the reserved flag is set */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, &ov, 1);
+ frame.hd.flags = 0xf0;
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* A frame should be ignored if it is received by a server. */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, &ov, 1);
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Receiving empty ORIGIN frame */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ nghttp2_frame_origin_init(&frame, NULL, 0);
+ rv = nghttp2_frame_pack_origin(&bufs, &frame);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type);
+
+ nghttp2_session_del(session);
+
+ nghttp2_option_del(option);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_recv_priority_update(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ nghttp2_option *option;
+ nghttp2_extension frame;
+ nghttp2_ext_priority_update priority_update;
+ nghttp2_stream *stream;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ uint8_t large_field_value[sizeof(session->iframe.raw_sbuf) + 1];
+ nghttp2_outbound_item *item;
+ size_t i;
+ int32_t stream_id;
+ static const uint8_t field_value[] = "u=2,i";
+
+ mem = nghttp2_mem_default();
+
+ memset(large_field_value, ' ', sizeof(large_field_value));
+ memcpy(large_field_value, field_value, sizeof(field_value) - 1);
+
+ frame_pack_bufs_init(&bufs);
+
+ frame.payload = &priority_update;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_builtin_recv_extension_type(option,
+ NGHTTP2_PRIORITY_UPDATE);
+
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ open_recv_stream(session, 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags);
+ CU_ASSERT(0 == ud.recv_frame_hd.stream_id);
+
+ stream = nghttp2_session_get_stream_raw(session, 1);
+
+ CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Check that priority which is received in idle state is
+ retained. */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags);
+ CU_ASSERT(0 == ud.recv_frame_hd.stream_id);
+
+ stream = nghttp2_session_get_stream_raw(session, 1);
+
+ CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
+ CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+ nghttp2_bufs_reset(&bufs);
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_HEADERS == ud.recv_frame_hd.type);
+ CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+ CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* PRIORITY_UPDATE with too large field_value is discarded */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ nghttp2_frame_priority_update_init(&frame, 1, large_field_value,
+ sizeof(large_field_value));
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ open_recv_stream(session, 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ stream = nghttp2_session_get_stream_raw(session, 1);
+
+ CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_reset(&bufs);
+
+ /* Connection error if client receives PRIORITY_UPDATE. */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ open_sent_stream(session, 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ 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);
+ nghttp2_bufs_reset(&bufs);
+
+ /* The number of idle streams exceeds the maximum. */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+ session->local_settings.max_concurrent_streams = 100;
+
+ for (i = 0; i < 101; ++i) {
+ stream_id = (int32_t)(i * 2 + 1);
+ nghttp2_frame_priority_update_init(
+ &frame, stream_id, (uint8_t *)field_value, sizeof(field_value) - 1);
+
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ if (i < 100) {
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type);
+ } else {
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+ }
+
+ nghttp2_bufs_reset(&bufs);
+ }
+
+ 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);
+ nghttp2_option_del(option);
+ nghttp2_bufs_free(&bufs);
+}
+
+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;
+ size_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);
+ /* disable strict HTTP layering checks */
+ session->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING;
+
+ 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;
+ nghttp2_outbound_item *item;
+ nghttp2_frame *frame;
+ nghttp2_nv *nva;
+ size_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 = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+
+ nghttp2_frame_headers_init(
+ &frame->headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+ (int32_t)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(NULL != nghttp2_outbound_queue_top(&session->ob_syn));
+ 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_DEFAULT_MAX_CONCURRENT_STREAMS;
+
+ /* Stream ID less than or equal to the previously 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;
+ session->last_sent_stream_id = 3;
+
+ /* 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);
+
+ nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+ /* HEADERS to closed stream */
+ stream = open_recv_stream(session, 1);
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
+
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+ NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+ CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+ nghttp2_session_on_request_headers_received(session, &frame));
+ CU_ASSERT(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 = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ 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 = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENED);
+ 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 = open_sent_stream2(session, 3, NGHTTP2_STREAM_CLOSING);
+ 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 = open_recv_stream(session, 2);
+
+ 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_OPENED == 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 = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+ 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(1 == session->num_incoming_reserved_streams);
+ CU_ASSERT(0 == nghttp2_session_on_push_response_headers_received(
+ session, &frame, stream));
+ CU_ASSERT(1 == user_data.begin_headers_cb_called);
+ CU_ASSERT(0 == session->num_incoming_reserved_streams);
+ CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+ CU_ASSERT(1 == session->num_incoming_streams);
+ CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH));
+
+ /* If un-ACKed max concurrent streams limit is exceeded,
+ RST_STREAMed */
+ session->pending_local_max_concurrent_stream = 1;
+ stream = open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED);
+ 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(1 == session->num_incoming_reserved_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 = open_recv_stream2(session, 6, NGHTTP2_STREAM_RESERVED);
+ 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);
+ CU_ASSERT(1 == session->num_incoming_reserved_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 = open_recv_stream(session, 1);
+
+ 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 = open_sent_stream(session, 2);
+ dep_stream = open_recv_stream(session, 3);
+
+ 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);
+ open_recv_stream(session, 1);
+
+ 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);
+
+ /* Check again dep_stream_id == stream_id, and stream_id is idle */
+ nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+ nghttp2_priority_spec_init(&pri_spec, 1, 16, 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);
+ open_recv_stream(session, 1);
+
+ 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;
+ nghttp2_option *option;
+ uint8_t data[2048];
+ nghttp2_frame_hd hd;
+ int rv;
+ ssize_t nread;
+ nghttp2_stream *stream;
+
+ 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 = open_sent_stream(session, 1);
+ stream2 = open_recv_stream(session, 2);
+
+ /* 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);
+ 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 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 that remote SETTINGS_MAX_CONCURRENT_STREAMS is set to a value set by
+ nghttp2_option_set_peer_max_concurrent_streams() and reset to the default
+ value (unlimited) after receiving initial SETTINGS frame from the peer. */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_peer_max_concurrent_streams(option, 1000);
+ nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+ CU_ASSERT(1000 == session->remote_settings.max_concurrent_streams);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
+ CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+ CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS ==
+ session->remote_settings.max_concurrent_streams);
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+
+ /* 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);
+
+ /* Check the case where stream window size overflows */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ stream1 = open_recv_stream(session, 1);
+
+ /* This will increment window size by 1 */
+ nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1,
+ 1);
+
+ CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
+
+ nghttp2_frame_window_update_free(&frame.window_update);
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[0].value = NGHTTP2_MAX_WINDOW_SIZE;
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1),
+ 1);
+
+ /* Now window size gets NGHTTP2_MAX_WINDOW_SIZE + 1, which is
+ unacceptable situation in protocol spec. */
+ CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type);
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream1->state);
+
+ nghttp2_session_del(session);
+
+ /* It is invalid that peer disables ENABLE_CONNECT_PROTOCOL once it
+ has been enabled. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ session->remote_settings.enable_connect_protocol = 1;
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ iv[0].value = 0;
+
+ 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));
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_session_del(session);
+
+ /* Should send WINDOW_UPDATE with no_auto_window_update option on if
+ the initial window size is decreased and becomes smaller than or
+ equal to the amount of data that has already received. */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_window_update(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[0].value = 1024;
+
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = open_recv_stream(session, 1);
+
+ memset(data, 0, sizeof(data));
+ hd.length = 1024;
+ hd.type = NGHTTP2_DATA;
+ hd.flags = NGHTTP2_FLAG_NONE;
+ hd.stream_id = 1;
+ nghttp2_frame_pack_frame_hd(data, &hd);
+
+ nread =
+ nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length);
+
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == nread);
+
+ rv = nghttp2_session_consume(session, 1, hd.length);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((int32_t)hd.length == stream->recv_window_size);
+ CU_ASSERT((int32_t)hd.length == stream->consumed_size);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
+
+ rv = nghttp2_session_on_settings_received(session, &frame, 0);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1024 == stream->local_window_size);
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(0 == stream->consumed_size);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+ CU_ASSERT((int32_t)hd.length ==
+ item->frame.window_update.window_size_increment);
+
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+
+ /* It is invalid to change SETTINGS_NO_RFC7540_PRIORITIES in the
+ following SETTINGS. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ session->remote_settings.no_rfc7540_priorities = 1;
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[0].value = 0;
+
+ 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));
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ 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;
+ nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_ENABLE_PUSH, 0};
+
+ 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 = open_sent_stream(session, 1);
+
+ 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);
+ CU_ASSERT(1 == session->num_incoming_reserved_streams);
+ 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(1 == session->num_incoming_reserved_streams);
+ CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(4 == session->last_recv_stream_id);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+ stream = open_sent_stream(session, 1);
+
+ /* Attempt to PUSH_PROMISE against stream in closing state */
+ 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(0 == session->num_incoming_reserved_streams);
+ 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_CANCEL == item->frame.rst_stream.error_code);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ /* Attempt to PUSH_PROMISE against idle 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(0 == session->num_incoming_reserved_streams);
+ 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 = open_sent_stream(session, 1);
+
+ /* Same ID twice */
+ frame.hd.stream_id = 1;
+ frame.push_promise.promised_stream_id = 2;
+
+ 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(1 == session->num_incoming_reserved_streams);
+ CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2));
+
+ 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 == session->num_incoming_reserved_streams);
+ 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(1 == session->num_incoming_reserved_streams);
+ 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);
+
+ open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ /* 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);
+ CU_ASSERT(1 == session->num_incoming_reserved_streams);
+
+ nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+ nghttp2_session_del(session);
+
+ /* Disable PUSH */
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+ open_sent_stream(session, 1);
+
+ 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);
+ CU_ASSERT(0 == session->num_incoming_reserved_streams);
+
+ 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);
+
+ open_sent_stream(session, 1);
+
+ 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);
+
+ /* If local_settings.enable_push = 0 is pending, but not acked from
+ peer, incoming PUSH_PROMISE is rejected */
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+ open_sent_stream(session, 1);
+
+ /* Submit settings with ENABLE_PUSH = 0 (thus disabling push) */
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+ 1, 2, NULL, 0);
+
+ CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+ nghttp2_session_on_push_promise_received(session, &frame));
+
+ CU_ASSERT(0 == session->num_incoming_reserved_streams);
+
+ nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+ nghttp2_session_del(session);
+
+ /* Check max_incoming_reserved_streams */
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+ session->max_incoming_reserved_streams = 1;
+
+ open_sent_stream(session, 1);
+ open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ CU_ASSERT(1 == session->num_incoming_reserved_streams);
+
+ nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+ 1, 4, NULL, 0);
+
+ CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+ nghttp2_session_on_push_promise_received(session, &frame));
+
+ CU_ASSERT(1 == session->num_incoming_reserved_streams);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
+
+ 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";
+ nghttp2_option *option;
+
+ 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 ACK flag set, no further action is
+ performed. */
+ CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent));
+
+ /* 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_outbound_queue_top(&session->ob_urgent);
+ 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);
+
+ /* Use nghttp2_option_set_no_auto_ping_ack() */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_ping_ack(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, &user_data, option);
+ nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
+
+ user_data.frame_recv_cb_called = 0;
+
+ CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame));
+ CU_ASSERT(1 == user_data.frame_recv_cb_called);
+ CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent));
+
+ nghttp2_frame_ping_free(&frame.ping);
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+}
+
+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;
+ const uint8_t *data;
+ ssize_t datalen;
+
+ 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) {
+ if (nghttp2_session_is_my_stream_id(session, i)) {
+ open_sent_stream(session, i);
+ } else {
+ open_recv_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);
+
+ /* Make sure that no memory leak when stream_close callback fails
+ with a fatal error */
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_stream_close_callback = fatal_error_on_stream_close_callback;
+
+ memset(&user_data, 0, sizeof(user_data));
+
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+ nghttp2_frame_goaway_init(&frame.goaway, 0, NGHTTP2_NO_ERROR, NULL, 0);
+
+ CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame));
+
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ datalen = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == datalen);
+ CU_ASSERT(1 == user_data.stream_close_cb_called);
+
+ 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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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 = open_sent_stream(session, 1);
+
+ data_item = create_data_ob_item(mem);
+
+ CU_ASSERT(0 == nghttp2_stream_attach_item(stream, data_item));
+
+ 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);
+
+ nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
+
+ 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 */
+ open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ 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 = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ 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 = open_recv_stream(session, 2);
+
+ 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. */
+ open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING);
+
+ frame.hd.flags = NGHTTP2_FLAG_NONE;
+ frame.hd.stream_id = 1;
+
+ CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+ CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_reg));
+
+ /* Check INVALID_STREAM case: DATA frame with stream ID which does
+ not exist. */
+
+ frame.hd.stream_id = 3;
+
+ CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+ top = nghttp2_outbound_queue_top(&session->ob_reg);
+ /* DATA against nonexistent stream is just ignored for now. */
+ CU_ASSERT(top == NULL);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_data_received_fail_fast(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ uint8_t buf[9];
+ nghttp2_stream *stream;
+ nghttp2_frame_hd hd;
+ nghttp2_outbound_item *item;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1);
+ nghttp2_frame_pack_frame_hd(buf, &hd);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* DATA to closed (remote) */
+ stream = open_recv_stream(session, 1);
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+
+ CU_ASSERT((ssize_t)sizeof(buf) ==
+ nghttp2_session_mem_recv(session, buf, sizeof(buf)));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* DATA to closed stream with explicit closed (remote) */
+ stream = open_recv_stream(session, 1);
+ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+ nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
+
+ CU_ASSERT((ssize_t)sizeof(buf) ==
+ nghttp2_session_mem_recv(session, buf, sizeof(buf)));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_altsvc_received(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_frame frame;
+ nghttp2_option *option;
+ uint8_t origin[] = "nghttp2.org";
+ uint8_t field_value[] = "h2=\":443\"";
+ int rv;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ frame.ext.payload = &session->iframe.ext_frame_payload;
+
+ /* We just pass the strings without making a copy. This is OK,
+ since we never call nghttp2_frame_altsvc_free(). */
+ nghttp2_frame_altsvc_init(&frame.ext, 0, origin, sizeof(origin) - 1,
+ field_value, sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_on_altsvc_received(session, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* Receiving empty origin with stream ID == 0 */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ frame.ext.payload = &session->iframe.ext_frame_payload;
+
+ nghttp2_frame_altsvc_init(&frame.ext, 0, origin, 0, field_value,
+ sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_on_altsvc_received(session, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* Receiving non-empty origin with stream ID != 0 */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ frame.ext.payload = &session->iframe.ext_frame_payload;
+
+ open_sent_stream(session, 1);
+
+ nghttp2_frame_altsvc_init(&frame.ext, 1, origin, sizeof(origin) - 1,
+ field_value, sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_on_altsvc_received(session, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* Receiving empty origin with stream ID != 0; this is OK */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ frame.ext.payload = &session->iframe.ext_frame_payload;
+
+ open_sent_stream(session, 1);
+
+ nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value,
+ sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_on_altsvc_received(session, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ /* Stream does not exist; ALTSVC will be ignored. */
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ frame.ext.payload = &session->iframe.ext_frame_payload;
+
+ nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value,
+ sizeof(field_value) - 1);
+
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_on_altsvc_received(session, &frame);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ nghttp2_session_del(session);
+
+ nghttp2_option_del(option);
+}
+
+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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+
+ nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS,
+ (int32_t)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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+
+ nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 1,
+ 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, 1);
+ 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;
+ size_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 = mem->malloc(vallen + 1, NULL);
+ 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 = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+
+ nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS,
+ (int32_t)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) {
+ mem->free(nv[i].value, NULL);
+ }
+ 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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
+ open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(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);
+ CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH));
+ 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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ nghttp2_session_client_new(&session, &callbacks, &user_data);
+ open_sent_stream(session, 1);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(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);
+ open_recv_stream(session, 1);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(item);
+
+ frame = &item->frame;
+
+ nghttp2_frame_push_promise_init(&frame->push_promise,
+ NGHTTP2_FLAG_END_HEADERS, 1,
+ (int32_t)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 = mem->malloc(sizeof(nghttp2_frame), NULL);
+ 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);
+ mem->free(frame, NULL);
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(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);
+ open_sent_stream(session, 1);
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+
+ nghttp2_outbound_item_init(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_upgrade2(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;
+ ssize_t rv;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_hd_deflater deflater;
+ 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;
+ 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 = (size_t)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_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, &callbacks));
+ CU_ASSERT(1 == session->last_sent_stream_id);
+ 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_upgrade2() again is error */
+ CU_ASSERT(NGHTTP2_ERR_PROTO ==
+ nghttp2_session_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, &callbacks));
+ nghttp2_session_del(session);
+
+ /* Make sure that response from server can be received */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, &callbacks));
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, resnv,
+ ARRLEN(resnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ buf = &bufs.head->buf;
+
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(buf));
+ CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Check server side */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+ CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, &callbacks));
+ CU_ASSERT(1 == session->last_recv_stream_id);
+ 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_upgrade2() again is error */
+ CU_ASSERT(NGHTTP2_ERR_PROTO ==
+ nghttp2_session_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, &callbacks));
+ nghttp2_session_del(session);
+
+ /* Empty SETTINGS is OK */
+ settings_payloadlen = (size_t)nghttp2_pack_settings_payload(
+ settings_payload, sizeof(settings_payload), NULL, 0);
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+ CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload,
+ settings_payloadlen, 0, NULL));
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_reprioritize_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream;
+ nghttp2_stream *dep_stream;
+ nghttp2_priority_spec pri_spec;
+ int rv;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = block_count_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ stream = open_recv_stream(session, 1);
+
+ nghttp2_priority_spec_init(&pri_spec, 0, 10, 0);
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(10 == stream->weight);
+ CU_ASSERT(&session->root == stream->dep_prev);
+
+ /* If dependency to idle stream which is not in dependency tree yet */
+
+ nghttp2_priority_spec_init(&pri_spec, 3, 99, 0);
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ 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_recv_stream(session, 3);
+
+ /* Change weight */
+ pri_spec.weight = 128;
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(128 == stream->weight);
+ CU_ASSERT(dep_stream == stream->dep_prev);
+
+ /* Change weight again to test short-path case */
+ pri_spec.weight = 100;
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(100 == stream->weight);
+ CU_ASSERT(dep_stream == stream->dep_prev);
+ CU_ASSERT(100 == dep_stream->sum_dep_weight);
+
+ /* 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);
+
+ rv = nghttp2_session_reprioritize_stream(session, dep_stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ 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);
+
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* circular dependency; in case of stream which is not a direct
+ descendant of root. Use exclusive dependency. */
+ stream = open_recv_stream(session, 1);
+ stream = open_recv_stream_with_dep(session, 3, stream);
+ stream = open_recv_stream_with_dep(session, 5, stream);
+ stream = open_recv_stream_with_dep(session, 7, stream);
+ open_recv_stream_with_dep(session, 9, stream);
+
+ nghttp2_priority_spec_init(&pri_spec, 7, 1, 1);
+
+ stream = nghttp2_session_get_stream(session, 3);
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(7 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 7);
+
+ CU_ASSERT(1 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 9);
+
+ CU_ASSERT(3 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 5);
+
+ CU_ASSERT(3 == stream->dep_prev->stream_id);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* circular dependency; in case of stream which is not a direct
+ descendant of root. Without exclusive dependency. */
+ stream = open_recv_stream(session, 1);
+ stream = open_recv_stream_with_dep(session, 3, stream);
+ stream = open_recv_stream_with_dep(session, 5, stream);
+ stream = open_recv_stream_with_dep(session, 7, stream);
+ open_recv_stream_with_dep(session, 9, stream);
+
+ nghttp2_priority_spec_init(&pri_spec, 7, 1, 0);
+
+ stream = nghttp2_session_get_stream(session, 3);
+ rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(7 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 7);
+
+ CU_ASSERT(1 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 9);
+
+ CU_ASSERT(7 == stream->dep_prev->stream_id);
+
+ stream = nghttp2_session_get_stream(session, 5);
+
+ CU_ASSERT(3 == stream->dep_prev->stream_id);
+
+ 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 = open_recv_stream(session, 1);
+
+ 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;
+
+ open_sent_stream(session, 1);
+
+ 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;
+
+ open_sent_stream(session, 1);
+
+ 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;
+
+ open_sent_stream(session, 1);
+
+ 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;
+
+ open_sent_stream(session, 1);
+
+ 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, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ (void)session;
+ (void)stream_id;
+ (void)buf;
+ (void)source;
+ (void)user_data;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ return (ssize_t)nghttp2_min(len, 16);
+}
+
+static int submit_data_twice_on_frame_send_callback(nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *user_data) {
+ static int called = 0;
+ int rv;
+ nghttp2_data_provider data_prd;
+ (void)user_data;
+
+ 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));
+
+ open_sent_stream(session, 1);
+
+ 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_data_provider data_prd;
+ my_user_data ud;
+ nghttp2_outbound_item *item;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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, reqnv, ARRLEN(reqnv),
+ &data_prd, NULL));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen);
+ assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(0 == ud.data_source_length);
+
+ nghttp2_session_del(session);
+
+ /* nghttp2_submit_request() with server session is error */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ CU_ASSERT(NGHTTP2_ERR_PROTO == nghttp2_submit_request(session, NULL, reqnv,
+ ARRLEN(reqnv), NULL,
+ NULL));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_request_without_data(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ accumulator acc;
+ 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;
+ nghttp2_priority_spec pri_spec;
+
+ 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, reqnv, ARRLEN(reqnv),
+ &data_prd, NULL));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen);
+ assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ 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, mem);
+
+ CU_ASSERT(ARRLEN(reqnv) == out.nvlen);
+ assert_nv_equal(reqnv, out.nva, out.nvlen, mem);
+ nghttp2_frame_headers_free(&frame.headers, mem);
+ nva_out_reset(&out, mem);
+
+ nghttp2_bufs_free(&bufs);
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* Try to depend on itself is error */
+ nghttp2_priority_spec_init(&pri_spec, (int32_t)session->next_stream_id, 16,
+ 0);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_request(session, &pri_spec, reqnv, ARRLEN(reqnv),
+ NULL, NULL));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_response_with_data(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_data_provider data_prd;
+ my_user_data ud;
+ nghttp2_outbound_item *item;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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));
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv),
+ &data_prd));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen);
+ assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(0 == ud.data_source_length);
+
+ nghttp2_session_del(session);
+
+ /* Various error cases */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ /* Calling nghttp2_submit_response() with client session is error */
+ CU_ASSERT(NGHTTP2_ERR_PROTO ==
+ nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL));
+
+ /* Stream ID <= 0 is error */
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_response(session, 0, resnv, ARRLEN(resnv), NULL));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_response_without_data(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ accumulator acc;
+ 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);
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv),
+ &data_prd));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen);
+ assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ 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, mem);
+
+ CU_ASSERT(ARRLEN(resnv) == out.nvlen);
+ assert_nv_equal(resnv, out.nva, out.nvlen, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_frame_headers_free(&frame.headers, mem);
+ nghttp2_hd_inflate_free(&inflater);
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_response_push_response(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_not_send_callback = on_frame_not_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
+
+ CU_ASSERT(0 ==
+ nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL));
+
+ ud.frame_not_send_cb_called = 0;
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(1 == ud.frame_not_send_cb_called);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_trailer(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ accumulator acc;
+ nghttp2_data_provider data_prd;
+ 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);
+
+ data_prd.read_callback = no_end_stream_data_source_read_callback;
+ nva_out_init(&out);
+ acc.length = 0;
+ ud.acc = &acc;
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+
+ nghttp2_hd_inflate_init(&inflater, mem);
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv),
+ &data_prd));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ CU_ASSERT(0 ==
+ nghttp2_submit_trailer(session, 1, trailernv, ARRLEN(trailernv)));
+
+ session->callbacks.send_callback = accumulator_send_callback;
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_HCAT_HEADERS == item->frame.headers.cat);
+ 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, mem);
+
+ CU_ASSERT(ARRLEN(trailernv) == out.nvlen);
+ assert_nv_equal(trailernv, out.nva, out.nvlen, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_frame_headers_free(&frame.headers, mem);
+ nghttp2_hd_inflate_free(&inflater);
+ nghttp2_session_del(session);
+
+ /* Specifying stream ID <= 0 is error */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+ open_recv_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_trailer(session, 0, trailernv, ARRLEN(trailernv)));
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_trailer(session, -1, trailernv, ARRLEN(trailernv)));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers_start_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_outbound_item *item;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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, reqnv, ARRLEN(reqnv), NULL));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen);
+ assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ 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;
+ my_user_data ud;
+ nghttp2_outbound_item *item;
+ nghttp2_stream *stream;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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, resnv, ARRLEN(resnv), NULL));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen);
+ assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ 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 = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+ NULL, resnv, ARRLEN(resnv), 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;
+ 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 = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL,
+ resnv, ARRLEN(resnv), &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));
+ open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL,
+ reqnv, ARRLEN(reqnv), 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;
+ 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;
+ nghttp2_priority_spec pri_spec;
+
+ 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, reqnv, ARRLEN(reqnv), NULL));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen);
+ assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+ 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 = open_sent_stream(session, 1);
+
+ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+ NULL, reqnv, ARRLEN(reqnv), 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, mem);
+
+ CU_ASSERT(ARRLEN(reqnv) == out.nvlen);
+ assert_nv_equal(reqnv, out.nva, out.nvlen, mem);
+
+ nva_out_reset(&out, mem);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ nghttp2_hd_inflate_free(&inflater);
+
+ /* Try to depend on itself */
+ nghttp2_priority_spec_init(&pri_spec, 3, 16, 0);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, &pri_spec,
+ reqnv, ARRLEN(reqnv), NULL));
+
+ session->next_stream_id = 5;
+ nghttp2_priority_spec_init(&pri_spec, 5, 16, 0);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, &pri_spec,
+ reqnv, ARRLEN(reqnv), NULL));
+
+ nghttp2_session_del(session);
+
+ /* Error cases with invalid stream ID */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* Sending nghttp2_submit_headers() with stream_id == 1 and server
+ session is error */
+ CU_ASSERT(NGHTTP2_ERR_PROTO ==
+ nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, reqnv,
+ ARRLEN(reqnv), NULL));
+
+ /* Sending stream ID <= 0 is error */
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 0, NULL, resnv,
+ ARRLEN(resnv), NULL));
+
+ 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_headers_continuation_extra_large(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", ""),
+ };
+ nghttp2_outbound_item *item;
+ uint8_t data[16384];
+ size_t i;
+ my_user_data ud;
+ nghttp2_option *opt;
+
+ 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;
+
+ /* The default size of max send header block length is too small to
+ send these header fields. Expand it. */
+ nghttp2_option_new(&opt);
+ nghttp2_option_set_max_send_header_block_length(opt, 102400);
+
+ CU_ASSERT(0 == nghttp2_session_client_new2(&session, &callbacks, &ud, opt));
+ 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);
+ nghttp2_option_del(opt);
+}
+
+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 = open_sent_stream(session, 1);
+
+ 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 = 111;
+
+ iv[4].settings_id = UNKNOWN_ID;
+ iv[4].value = 999;
+
+ iv[5].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[5].value = 1023;
+
+ iv[6].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[6].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, 7));
+
+ /* Make sure that local settings are not changed */
+ CU_ASSERT(NGHTTP2_DEFAULT_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, 6));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type);
+
+ frame = &item->frame;
+ CU_ASSERT(6 == 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);
+
+ /* before receiving SETTINGS ACK, local settings have still default
+ values */
+ CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS ==
+ nghttp2_session_get_local_settings(
+ session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));
+ CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+ nghttp2_session_get_local_settings(
+ session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
+
+ 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(111 == session->hd_inflater.ctx.hd_table_bufsize_max);
+ CU_ASSERT(111 == session->hd_inflater.min_hd_table_bufsize_max);
+ CU_ASSERT(50 == session->local_settings.max_concurrent_streams);
+
+ CU_ASSERT(50 == nghttp2_session_get_local_settings(
+ session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));
+ CU_ASSERT(16 * 1024 == nghttp2_session_get_local_settings(
+ session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));
+
+ /* We just keep the last seen value */
+ CU_ASSERT(50 == session->pending_local_max_concurrent_stream);
+
+ nghttp2_session_del(session);
+
+ /* Bail out if there are contradicting
+ SETTINGS_NO_RFC7540_PRIORITIES in one SETTINGS. */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[0].value = 1;
+ iv[1].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[1].value = 0;
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2));
+
+ nghttp2_session_del(session);
+
+ /* Attempt to change SETTINGS_NO_RFC7540_PRIORITIES in the 2nd
+ SETTINGS. */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[0].value = 1;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv[0].value = 0;
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
+
+ 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;
+ nghttp2_option *option;
+
+ 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 = open_recv_stream(session, 1);
+ stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100;
+ stream->recv_window_size = 32768;
+
+ open_recv_stream(session, 3);
+
+ 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);
+
+ /* Without auto-window update */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_window_update(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ nghttp2_option_del(option);
+
+ stream = open_recv_stream(session, 1);
+ stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100;
+ stream->recv_window_size = 32768;
+
+ 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(32768 == stream->recv_window_size);
+ CU_ASSERT(16 * 1024 + 100 == stream->local_window_size);
+ /* Check that we can handle the case where local_window_size <
+ recv_window_size */
+ CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1));
+
+ nghttp2_session_del(session);
+
+ /* Check overflow case */
+ iv[0].value = 128 * 1024;
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+ stream = open_recv_stream(session, 1);
+ 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_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_FLOW_CONTROL_ERROR == item->frame.rst_stream.error_code);
+
+ nghttp2_session_del(session);
+ nghttp2_frame_settings_free(&ack_frame.settings, mem);
+}
+
+void test_nghttp2_submit_settings_multiple_times(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_settings_entry iv[4];
+ nghttp2_frame frame;
+ nghttp2_inflight_settings *inflight_settings;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ /* first SETTINGS */
+ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[0].value = 100;
+
+ iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[1].value = 0;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2));
+
+ inflight_settings = session->inflight_settings_head;
+
+ CU_ASSERT(NULL != inflight_settings);
+ CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
+ inflight_settings->iv[0].settings_id);
+ CU_ASSERT(100 == inflight_settings->iv[0].value);
+ CU_ASSERT(2 == inflight_settings->niv);
+ CU_ASSERT(NULL == inflight_settings->next);
+
+ CU_ASSERT(100 == session->pending_local_max_concurrent_stream);
+ CU_ASSERT(0 == session->pending_enable_push);
+
+ /* second SETTINGS */
+ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[0].value = 99;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
+
+ inflight_settings = session->inflight_settings_head->next;
+
+ CU_ASSERT(NULL != inflight_settings);
+ CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
+ inflight_settings->iv[0].settings_id);
+ CU_ASSERT(99 == inflight_settings->iv[0].value);
+ CU_ASSERT(1 == inflight_settings->niv);
+ CU_ASSERT(NULL == inflight_settings->next);
+
+ CU_ASSERT(99 == session->pending_local_max_concurrent_stream);
+ CU_ASSERT(0 == session->pending_enable_push);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
+
+ /* receive SETTINGS ACK */
+ CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+ inflight_settings = session->inflight_settings_head;
+
+ /* first inflight SETTINGS was removed */
+ CU_ASSERT(NULL != inflight_settings);
+ CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
+ inflight_settings->iv[0].settings_id);
+ CU_ASSERT(99 == inflight_settings->iv[0].value);
+ CU_ASSERT(1 == inflight_settings->niv);
+ CU_ASSERT(NULL == inflight_settings->next);
+
+ CU_ASSERT(100 == session->local_settings.max_concurrent_streams);
+
+ /* receive SETTINGS ACK again */
+ CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+ CU_ASSERT(NULL == session->inflight_settings_head);
+ CU_ASSERT(99 == session->local_settings.max_concurrent_streams);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_push_promise(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ 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;
+ callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+
+ CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+ open_recv_stream(session, 1);
+ CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
+ reqnv, ARRLEN(reqnv), &ud));
+
+ stream = nghttp2_session_get_stream(session, 2);
+
+ CU_ASSERT(NULL != stream);
+ CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
+ CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
+
+ 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));
+
+ /* submit PUSH_PROMISE while associated stream is not opened */
+ CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED ==
+ nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv,
+ ARRLEN(reqnv), NULL));
+
+ /* Stream ID <= 0 is error */
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 0, reqnv,
+ ARRLEN(reqnv), NULL));
+
+ 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 = open_recv_stream(session, 2);
+ 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 = open_recv_stream(session, 2);
+ 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_response */
+ CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv,
+ ARRLEN(empty_name_nv), NULL));
+
+ /* nghttp2_submit_push_promise */
+ open_recv_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);
+
+ CU_ASSERT(0 == nghttp2_session_client_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_headers */
+ CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL,
+ empty_name_nv, ARRLEN(empty_name_nv),
+ NULL));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_extension(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ accumulator acc;
+ nghttp2_mem *mem;
+ const char data[] = "Hello World!";
+ size_t len;
+ int32_t stream_id;
+ int rv;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ callbacks.pack_extension_callback = pack_extension_callback;
+ callbacks.send_callback = accumulator_send_callback;
+
+ nghttp2_buf_init2(&ud.scratchbuf, 4096, mem);
+
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ ud.scratchbuf.last = nghttp2_cpymem(ud.scratchbuf.last, data, sizeof(data));
+ ud.acc = &acc;
+
+ rv = nghttp2_submit_extension(session, 211, 0x01, 3, &ud.scratchbuf);
+
+ CU_ASSERT(0 == rv);
+
+ acc.length = 0;
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(NGHTTP2_FRAME_HDLEN + sizeof(data) == acc.length);
+
+ len = nghttp2_get_uint32(acc.buf) >> 8;
+
+ CU_ASSERT(sizeof(data) == len);
+ CU_ASSERT(211 == acc.buf[3]);
+ CU_ASSERT(0x01 == acc.buf[4]);
+
+ stream_id = (int32_t)nghttp2_get_uint32(acc.buf + 5);
+
+ CU_ASSERT(3 == stream_id);
+ CU_ASSERT(0 == memcmp(data, &acc.buf[NGHTTP2_FRAME_HDLEN], sizeof(data)));
+
+ nghttp2_session_del(session);
+
+ /* submitting standard HTTP/2 frame is error */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = nghttp2_submit_extension(session, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0,
+ NULL);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ nghttp2_session_del(session);
+ nghttp2_buf_free(&ud.scratchbuf, mem);
+}
+
+void test_nghttp2_submit_altsvc(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ int rv;
+ ssize_t len;
+ const uint8_t *data;
+ nghttp2_frame_hd hd;
+ size_t origin_len;
+ const uint8_t origin[] = "nghttp2.org";
+ const uint8_t field_value[] = "h2=\":443\"";
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
+ sizeof(origin) - 1, field_value,
+ sizeof(field_value) - 1);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_send_cb_called = 0;
+
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len == NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1 +
+ sizeof(field_value) - 1);
+
+ nghttp2_frame_unpack_frame_hd(&hd, data);
+
+ CU_ASSERT(2 + sizeof(origin) - 1 + sizeof(field_value) - 1 == hd.length);
+ CU_ASSERT(NGHTTP2_ALTSVC == hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+
+ origin_len = nghttp2_get_uint16(data + NGHTTP2_FRAME_HDLEN);
+
+ CU_ASSERT(sizeof(origin) - 1 == origin_len);
+ CU_ASSERT(0 ==
+ memcmp(origin, data + NGHTTP2_FRAME_HDLEN + 2, sizeof(origin) - 1));
+ CU_ASSERT(0 == memcmp(field_value,
+ data + NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1,
+ hd.length - (sizeof(origin) - 1) - 2));
+
+ /* submitting empty origin with stream_id == 0 is error */
+ rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, NULL, 0,
+ field_value, sizeof(field_value) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* submitting non-empty origin with stream_id != 0 is error */
+ rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 1, origin,
+ sizeof(origin) - 1, field_value,
+ sizeof(field_value) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ nghttp2_session_del(session);
+
+ /* submitting from client side session is error */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
+ sizeof(origin) - 1, field_value,
+ sizeof(field_value) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_origin(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ int rv;
+ ssize_t len;
+ const uint8_t *data;
+ static const uint8_t nghttp2[] = "https://nghttp2.org";
+ static const uint8_t examples[] = "https://examples.com";
+ static const nghttp2_origin_entry ov[] = {
+ {
+ (uint8_t *)nghttp2,
+ sizeof(nghttp2) - 1,
+ },
+ {
+ (uint8_t *)examples,
+ sizeof(examples) - 1,
+ },
+ };
+ nghttp2_frame frame;
+ nghttp2_ext_origin origin;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_frame_send_callback = on_frame_send_callback;
+
+ frame.ext.payload = &origin;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 2);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_send_cb_called = 0;
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len > 0);
+ CU_ASSERT(1 == ud.frame_send_cb_called);
+
+ nghttp2_frame_unpack_frame_hd(&frame.hd, data);
+ rv = nghttp2_frame_unpack_origin_payload(
+ &frame.ext, data + NGHTTP2_FRAME_HDLEN, (size_t)len - NGHTTP2_FRAME_HDLEN,
+ mem);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type);
+ CU_ASSERT(2 == origin.nov);
+ CU_ASSERT(0 == memcmp(nghttp2, origin.ov[0].origin, sizeof(nghttp2) - 1));
+ CU_ASSERT(sizeof(nghttp2) - 1 == origin.ov[0].origin_len);
+ CU_ASSERT(0 == memcmp(examples, origin.ov[1].origin, sizeof(examples) - 1));
+ CU_ASSERT(sizeof(examples) - 1 == origin.ov[1].origin_len);
+
+ nghttp2_frame_origin_free(&frame.ext, mem);
+
+ nghttp2_session_del(session);
+
+ /* Submitting ORIGIN frame from client session is error */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv);
+
+ nghttp2_session_del(session);
+
+ /* Submitting empty ORIGIN frame */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, NULL, 0);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_send_cb_called = 0;
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len == NGHTTP2_FRAME_HDLEN);
+ CU_ASSERT(1 == ud.frame_send_cb_called);
+
+ nghttp2_frame_unpack_frame_hd(&frame.hd, data);
+
+ CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_priority_update(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ const uint8_t field_value[] = "i";
+ my_user_data ud;
+ const uint8_t *data;
+ int rv;
+ nghttp2_frame frame;
+ nghttp2_ext_priority_update priority_update;
+ ssize_t len;
+ int32_t stream_id;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_frame_send_callback = on_frame_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ stream_id =
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(1 == stream_id);
+
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len > 0);
+
+ rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id,
+ field_value, sizeof(field_value) - 1);
+
+ CU_ASSERT(0 == rv);
+
+ frame.ext.payload = &priority_update;
+
+ ud.frame_send_cb_called = 0;
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len > 0);
+ CU_ASSERT(1 == ud.frame_send_cb_called);
+
+ nghttp2_frame_unpack_frame_hd(&frame.hd, data);
+ nghttp2_frame_unpack_priority_update_payload(
+ &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN),
+ (size_t)len - NGHTTP2_FRAME_HDLEN);
+
+ CU_ASSERT(0 == frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type);
+ CU_ASSERT(stream_id == priority_update.stream_id);
+ CU_ASSERT(sizeof(field_value) - 1 == priority_update.field_value_len);
+ CU_ASSERT(0 == memcmp(field_value, priority_update.field_value,
+ sizeof(field_value) - 1));
+
+ nghttp2_session_del(session);
+
+ /* Submitting PRIORITY_UPDATE frame from server session is error */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ open_recv_stream(session, 1);
+
+ rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, 1,
+ field_value, sizeof(field_value) - 1);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv);
+
+ nghttp2_session_del(session);
+
+ /* Submitting PRIORITY_UPDATE with empty field_value */
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ stream_id =
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(1 == stream_id);
+
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len > 0);
+
+ rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id,
+ NULL, 0);
+
+ CU_ASSERT(0 == rv);
+
+ frame.ext.payload = &priority_update;
+
+ len = nghttp2_session_mem_send(session, &data);
+
+ CU_ASSERT(len > 0);
+
+ nghttp2_frame_unpack_frame_hd(&frame.hd, data);
+ nghttp2_frame_unpack_priority_update_payload(
+ &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN),
+ (size_t)len - NGHTTP2_FRAME_HDLEN);
+
+ CU_ASSERT(0 == frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type);
+ CU_ASSERT(stream_id == priority_update.stream_id);
+ CU_ASSERT(0 == priority_update.field_value_len);
+ CU_ASSERT(NULL == priority_update.field_value);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_rst_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_outbound_item *item;
+ int rv;
+ int32_t stream_id;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ /* Sending RST_STREAM to idle stream (local) is ignored */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_session_del(session);
+
+ /* Sending RST_STREAM to idle stream (remote) is ignored */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_session_del(session);
+
+ /* Sending RST_STREAM to non-idle stream (local) */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ open_sent_stream(session, 1);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+
+ nghttp2_session_del(session);
+
+ /* Sending RST_STREAM to non-idle stream (remote) */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ open_recv_stream(session, 2);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ item = nghttp2_outbound_queue_top(&session->ob_reg);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(2 == item->frame.hd.stream_id);
+
+ nghttp2_session_del(session);
+
+ /* Sending RST_STREAM to pending stream */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ stream_id =
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(stream_id > 0);
+
+ item = nghttp2_outbound_queue_top(&session->ob_syn);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ CU_ASSERT(0 == item->aux_data.headers.canceled);
+
+ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+ NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == rv);
+
+ item = nghttp2_outbound_queue_top(&session->ob_syn);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ CU_ASSERT(1 == item->aux_data.headers.canceled);
+
+ 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(&session->root == 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(&session->root == 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(&session->root == 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(&session->root == stream->dep_prev);
+
+ /* 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);
+ CU_ASSERT(&session->root == stream->dep_prev);
+
+ 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(&session->root == 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_client_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);
+
+ CU_ASSERT(1 == 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. */
+ open_recv_stream(session, 2);
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0);
+
+ CU_ASSERT(3 ==
+ 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));
+
+ CU_ASSERT(5 ==
+ 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);
+
+ /* 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;
+ open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+ CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2,
+ NULL, NULL, 0, NULL));
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+ CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_syn));
+ 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_mem *mem;
+
+ mem = nghttp2_mem_default();
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_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);
+ mem->free(item, NULL);
+
+ item = nghttp2_session_pop_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ nghttp2_outbound_item_free(item, mem);
+ mem->free(item, NULL);
+
+ CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
+
+ /* Incoming stream does not affect the number of outgoing max
+ concurrent streams. */
+ open_recv_stream(session, 4);
+ /* In-flight outgoing stream */
+ open_sent_stream(session, 1);
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0);
+
+ nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL);
+
+ 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);
+ mem->free(item, NULL);
+
+ 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;
+ open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+ 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_outbound_queue_size(&session->ob_syn));
+ 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));
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ 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);
+ open_recv_stream(session, 1);
+
+ /* 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_outbound_queue_top(&session->ob_reg);
+ 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_outbound_queue_top(&session->ob_reg);
+ 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);
+}
+
+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);
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ 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;
+ nghttp2_stream *stream;
+
+ 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);
+ stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ session->remote_window_size = 1 << 20;
+ stream->remote_window_size = 1 << 20;
+
+ 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 = stream->item;
+ 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 16KiB 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->aux_data.data.data_prd.read_callback =
+ fixed_length_data_source_read_callback;
+ ud.block_count = 1;
+ /* Reads 2 16KiB 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 -
+ ((int32_t)session->remote_settings.initial_window_size -
+ stream->remote_window_size);
+ session->remote_settings.initial_window_size =
+ (uint32_t)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 = open_sent_stream(session, 1);
+
+ 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 = open_sent_stream(session, 1);
+
+ /* Create DATA frame */
+ memset(data, 0, sizeof(data));
+ nghttp2_frame_hd_init(&hd, 357, NGHTTP2_DATA, 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);
+ CU_ASSERT(357 == session->recv_window_size);
+ CU_ASSERT(357 == stream->recv_window_size);
+
+ /* Receive the same DATA frame, but in 2 parts: first 9 + 1 + 102
+ bytes which includes 1st padding byte, and remainder */
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 103) ==
+ nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 103));
+ CU_ASSERT(258 == session->consumed_size);
+ CU_ASSERT(258 == stream->consumed_size);
+ CU_ASSERT(460 == session->recv_window_size);
+ CU_ASSERT(460 == stream->recv_window_size);
+
+ /* 357 - 103 = 254 bytes left */
+ CU_ASSERT(254 == nghttp2_session_mem_recv(session, data, 254));
+ CU_ASSERT(512 == session->consumed_size);
+ CU_ASSERT(512 == stream->consumed_size);
+ CU_ASSERT(714 == session->recv_window_size);
+ CU_ASSERT(714 == stream->recv_window_size);
+
+ /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 101
+ bytes which only includes data without padding, 2nd part is
+ padding only */
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 102) ==
+ nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 102));
+ CU_ASSERT(513 == session->consumed_size);
+ CU_ASSERT(513 == stream->consumed_size);
+ CU_ASSERT(816 == session->recv_window_size);
+ CU_ASSERT(816 == stream->recv_window_size);
+
+ /* 357 - 102 = 255 bytes left */
+ CU_ASSERT(255 == nghttp2_session_mem_recv(session, data, 255));
+ CU_ASSERT(768 == session->consumed_size);
+ CU_ASSERT(768 == stream->consumed_size);
+ CU_ASSERT(1071 == session->recv_window_size);
+ CU_ASSERT(1071 == stream->recv_window_size);
+
+ /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 50
+ bytes which includes byte up to middle of data, 2nd part is the
+ remainder */
+ CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 51) ==
+ nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 51));
+ CU_ASSERT(769 == session->consumed_size);
+ CU_ASSERT(769 == stream->consumed_size);
+ CU_ASSERT(1122 == session->recv_window_size);
+ CU_ASSERT(1122 == stream->recv_window_size);
+
+ /* 357 - 51 = 306 bytes left */
+ CU_ASSERT(306 == nghttp2_session_mem_recv(session, data, 306));
+ CU_ASSERT(1024 == session->consumed_size);
+ CU_ASSERT(1024 == stream->consumed_size);
+ CU_ASSERT(1428 == session->recv_window_size);
+ CU_ASSERT(1428 == stream->recv_window_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_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 =
+ open_sent_stream3(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 =
+ open_recv_stream3(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 = open_sent_stream(session, 1);
+
+ 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(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_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(-50 == session->recv_window_size);
+ CU_ASSERT(50 == session->recv_reduction);
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 ==
+ nghttp2_session_get_effective_local_window_size(session));
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_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 */
+
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 ==
+ nghttp2_session_get_local_window_size(session));
+
+ nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 100);
+ CU_ASSERT(50 == session->recv_window_size);
+ CU_ASSERT(0 == session->recv_reduction);
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1050 ==
+ nghttp2_session_get_effective_local_window_size(session));
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_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(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_stream_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(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_stream_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(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 ==
+ nghttp2_session_get_stream_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_hd_deflater *deflater;
+ int rv;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ /* Test for nghttp2_option_set_no_auto_window_update */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_window_update(option, 1);
+
+ 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_del(option);
+
+ /* Test for nghttp2_option_set_peer_max_concurrent_streams */
+ nghttp2_option_new(&option);
+ 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);
+
+ /* Test for nghttp2_option_set_max_reserved_remote_streams */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_max_reserved_remote_streams(option, 99);
+
+ nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+ CU_ASSERT(99 == session->max_incoming_reserved_streams);
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+
+ /* Test for nghttp2_option_set_no_auto_ping_ack */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_auto_ping_ack(option, 1);
+
+ nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+ CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK);
+
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+
+ /* Test for nghttp2_option_set_max_deflate_dynamic_table_size */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_max_deflate_dynamic_table_size(option, 0);
+
+ nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+ deflater = &session->hd_deflater;
+
+ rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(1 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == deflater->deflate_hd_table_bufsize_max);
+ CU_ASSERT(0 == deflater->ctx.hd_table_bufsize);
+
+ 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);
+
+ session->remote_window_size = 1 << 20;
+
+ ud.block_count = 2;
+ /* Sends request HEADERS + DATA[0] */
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ stream = nghttp2_session_get_stream(session, 1);
+ stream->remote_window_size = 1 << 20;
+
+ 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));
+
+ 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_mem *mem) {
+ 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);
+
+ open_recv_stream(session, 1);
+
+ inlen = (size_t)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);
+
+ mem->free(in, NULL);
+ 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;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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, mem);
+
+ 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;
+
+ 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, reqnv, ARRLEN(reqnv),
+ 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,
+ (size_t)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);
+
+ mem->free(resiv, NULL);
+
+ 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, *root;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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(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, root, b, NULL, NULL);
+ check_stream_dep_sib(b, a, NULL, NULL, c);
+ check_stream_dep_sib(c, a, d, b, NULL);
+ check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+ CU_ASSERT(a == session->root.dep_next);
+
+ e = open_stream_with_dep_excl(session, 9, a);
+
+ /* a
+ * |
+ * e
+ * |
+ * b--c
+ * |
+ * d
+ */
+
+ 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, root, 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, e, d, b, NULL);
+ check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+ CU_ASSERT(a == session->root.dep_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, *root;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ /* Remove root */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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:
+ * c b
+ * |
+ * d
+ */
+
+ 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, root, NULL, c, NULL);
+ check_stream_dep_sib(c, root, d, NULL, b);
+ check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+ CU_ASSERT(c == session->root.dep_next);
+
+ nghttp2_session_del(session);
+
+ /* Remove right most stream */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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(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, root, 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(a == session->root.dep_next);
+
+ nghttp2_session_del(session);
+
+ /* Remove left most stream */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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_with_dep(session, 9, c);
+
+ /* a
+ * |
+ * c--b
+ * |
+ * e--d
+ */
+
+ nghttp2_stream_dep_remove(c);
+
+ /* becomes:
+ * a
+ * |
+ * e--d--b
+ */
+
+ 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);
+ CU_ASSERT(0 == e->sum_dep_weight);
+
+ check_stream_dep_sib(a, root, e, NULL, NULL);
+ check_stream_dep_sib(b, a, NULL, d, NULL);
+ check_stream_dep_sib(c, NULL, NULL, NULL, NULL);
+ check_stream_dep_sib(d, a, NULL, e, b);
+ check_stream_dep_sib(e, a, NULL, NULL, d);
+
+ nghttp2_session_del(session);
+
+ /* Remove middle stream */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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(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
+ */
+
+ /* 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, root, d, NULL, NULL);
+ check_stream_dep_sib(b, a, NULL, e, NULL);
+ check_stream_dep_sib(c, NULL, NULL, NULL, NULL);
+ check_stream_dep_sib(e, a, NULL, f, b);
+ check_stream_dep_sib(f, a, 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, *root;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ /* dep_stream has dep_next */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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_remove_subtree(e);
+ nghttp2_stream_dep_add_subtree(a, e);
+
+ /* becomes
+ * a
+ * |
+ * e--c--b
+ * | |
+ * f d
+ */
+
+ 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, root, e, NULL, NULL);
+ check_stream_dep_sib(b, a, NULL, c, NULL);
+ check_stream_dep_sib(c, a, 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);
+
+ root = &session->root;
+
+ 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_remove_subtree(e);
+ nghttp2_stream_dep_insert_subtree(a, e);
+
+ /* becomes
+ * a
+ * |
+ * e
+ * |
+ * f--c--b
+ * |
+ * d
+ */
+
+ 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, root, 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, e, NULL, c, NULL);
+ check_stream_dep_sib(c, e, 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, *root;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ /* Remove left most stream */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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(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, root, 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);
+
+ root = &session->root;
+
+ 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(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, root, 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);
+
+ root = &session->root;
+
+ 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(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, root, b, NULL, NULL);
+ check_stream_dep_sib(b, a, NULL, NULL, e);
+ check_stream_dep_sib(e, a, 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, *root;
+ nghttp2_outbound_item *db, *dc;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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_insert_subtree(&session->root, c));
+
+ /*
+ * c
+ * |
+ * a
+ * |
+ * b
+ */
+
+ 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);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+
+ check_stream_dep_sib(c, root, 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);
+
+ root = &session->root;
+
+ 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_insert_subtree(&session->root, c));
+
+ /*
+ * c
+ * |
+ * b--a
+ */
+
+ CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight);
+ CU_ASSERT(0 == b->sum_dep_weight);
+ CU_ASSERT(0 == a->sum_dep_weight);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+
+ check_stream_dep_sib(c, root, b, NULL, NULL);
+ check_stream_dep_sib(b, c, NULL, NULL, a);
+ check_stream_dep_sib(a, c, NULL, b, NULL);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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_insert_subtree(&session->root, c));
+
+ /*
+ * c
+ * |
+ * d--a
+ * |
+ * b
+ */
+
+ 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);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ check_stream_dep_sib(c, root, d, NULL, NULL);
+ check_stream_dep_sib(d, c, NULL, NULL, a);
+ check_stream_dep_sib(a, c, b, d, NULL);
+ check_stream_dep_sib(b, a, NULL, NULL, NULL);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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
+ */
+
+ db = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(b, db);
+
+ nghttp2_stream_dep_remove_subtree(c);
+ CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c));
+
+ /*
+ * c
+ * |
+ * d--a
+ * |
+ * b
+ */
+
+ CU_ASSERT(c->queued);
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ check_stream_dep_sib(c, root, d, NULL, NULL);
+ check_stream_dep_sib(d, c, NULL, NULL, a);
+ check_stream_dep_sib(a, c, b, d, NULL);
+ check_stream_dep_sib(b, a, NULL, NULL, NULL);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ root = &session->root;
+
+ 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
+ */
+
+ db = create_data_ob_item(mem);
+ dc = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(b, db);
+ nghttp2_stream_attach_item(c, dc);
+
+ nghttp2_stream_dep_remove_subtree(c);
+ CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c));
+
+ /*
+ * c
+ * |
+ * d--a
+ * |
+ * b
+ */
+
+ CU_ASSERT(c->queued);
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!d->queued);
+
+ check_stream_dep_sib(c, root, d, NULL, NULL);
+ check_stream_dep_sib(d, c, NULL, NULL, a);
+ check_stream_dep_sib(a, c, b, d, NULL);
+ 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, *e;
+ nghttp2_outbound_item *da, *db, *dc, *dd;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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(mem);
+
+ nghttp2_stream_attach_item(b, db);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+
+ /* Attach item to c */
+ dc = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(c, dc);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+
+ /* Attach item to a */
+ da = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(a, da);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+
+ /* Detach item from a */
+ nghttp2_stream_detach_item(a);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+
+ /* Attach item to d */
+ dd = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(d, dd);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+
+ /* Detach item from c */
+ nghttp2_stream_detach_item(c);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+
+ /* Detach item from b */
+ nghttp2_stream_detach_item(b);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(!b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+
+ /* exercises insertion */
+ e = open_stream_with_dep_excl(session, 9, a);
+
+ /* a
+ * |
+ * e
+ * |
+ * c--b
+ * |
+ * d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ /* exercises deletion */
+ nghttp2_stream_dep_remove(e);
+
+ /* a
+ * |
+ * c--b
+ * |
+ * d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(!b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ /* e's weight 16 is distributed equally among c and b, both now have
+ weight 8 each. */
+ CU_ASSERT(8 == b->weight);
+ CU_ASSERT(8 == c->weight);
+
+ /* da, db, dc have been detached */
+ nghttp2_outbound_item_free(da, mem);
+ nghttp2_outbound_item_free(db, mem);
+ nghttp2_outbound_item_free(dc, mem);
+ free(da);
+ free(db);
+ free(dc);
+
+ 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_with_dep(session, 5, a);
+ d = open_stream_with_dep(session, 7, c);
+
+ /* a
+ * |
+ * c--b
+ * |
+ * d
+ */
+
+ da = create_data_ob_item(mem);
+ db = create_data_ob_item(mem);
+ dc = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(a, da);
+ nghttp2_stream_attach_item(b, db);
+ nghttp2_stream_attach_item(c, dc);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ /* Detach item from a */
+ nghttp2_stream_detach_item(a);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(!d->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+
+ /* da has been detached */
+ nghttp2_outbound_item_free(da, mem);
+ free(da);
+
+ 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 *da, *db, *dd, *de;
+ nghttp2_mem *mem;
+
+ mem = nghttp2_mem_default();
+
+ 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_with_dep_weight(session, 9, 32, &session->root);
+ f = open_stream_with_dep(session, 11, e);
+
+ /*
+ * a e
+ * | |
+ * c--b f
+ * |
+ * d
+ */
+
+ de = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(e, de);
+
+ db = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(b, db);
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Insert subtree e under a */
+
+ nghttp2_stream_dep_remove_subtree(e);
+ nghttp2_stream_dep_insert_subtree(a, e);
+
+ /*
+ * a
+ * |
+ * e
+ * |
+ * f--c--b
+ * |
+ * d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree b */
+
+ nghttp2_stream_dep_remove_subtree(b);
+
+ CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b));
+
+ /*
+ * a b
+ * |
+ * e
+ * |
+ * f--c
+ * |
+ * d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree a, and add it to root again */
+
+ nghttp2_stream_dep_remove_subtree(a);
+
+ CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, a));
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree c */
+
+ nghttp2_stream_dep_remove_subtree(c);
+
+ CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, c));
+
+ /*
+ * a b c
+ * | |
+ * e d
+ * |
+ * f
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(!c->queued);
+ CU_ASSERT(!d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(nghttp2_pq_empty(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ dd = create_data_ob_item(mem);
+
+ nghttp2_stream_attach_item(d, dd);
+
+ /* Add subtree c to a */
+
+ nghttp2_stream_dep_remove_subtree(c);
+ nghttp2_stream_dep_add_subtree(a, c);
+
+ /*
+ * a b
+ * |
+ * c--e
+ * | |
+ * d f
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(nghttp2_pq_empty(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Insert b under a */
+
+ nghttp2_stream_dep_remove_subtree(b);
+ nghttp2_stream_dep_insert_subtree(a, b);
+
+ /*
+ * a
+ * |
+ * b
+ * |
+ * c--e
+ * | |
+ * d f
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
+ CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree b */
+
+ nghttp2_stream_dep_remove_subtree(b);
+ CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b));
+
+ /*
+ * b a
+ * |
+ * e--c
+ * | |
+ * f d
+ */
+
+ CU_ASSERT(!a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree c, and detach item from b, and then re-add
+ subtree c under b */
+
+ nghttp2_stream_dep_remove_subtree(c);
+ nghttp2_stream_detach_item(b);
+ nghttp2_stream_dep_add_subtree(b, c);
+
+ /*
+ * b a
+ * |
+ * e--c
+ * | |
+ * f d
+ */
+
+ CU_ASSERT(!a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Attach data to a, and add subtree a under b */
+
+ da = create_data_ob_item(mem);
+ nghttp2_stream_attach_item(a, da);
+ nghttp2_stream_dep_remove_subtree(a);
+ nghttp2_stream_dep_add_subtree(b, a);
+
+ /*
+ * b
+ * |
+ * a--e--c
+ * | |
+ * f d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(!f->queued);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(3 == nghttp2_pq_size(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(nghttp2_pq_empty(&e->obq));
+ CU_ASSERT(nghttp2_pq_empty(&f->obq));
+
+ /* Remove subtree c, and add under f */
+ nghttp2_stream_dep_remove_subtree(c);
+ nghttp2_stream_dep_insert_subtree(f, c);
+
+ /*
+ * b
+ * |
+ * a--e
+ * |
+ * f
+ * |
+ * c
+ * |
+ * d
+ */
+
+ CU_ASSERT(a->queued);
+ CU_ASSERT(b->queued);
+ CU_ASSERT(c->queued);
+ CU_ASSERT(d->queued);
+ CU_ASSERT(e->queued);
+ CU_ASSERT(f->queued);
+
+ CU_ASSERT(nghttp2_pq_empty(&a->obq));
+ CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
+ CU_ASSERT(nghttp2_pq_empty(&d->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&e->obq));
+ CU_ASSERT(1 == nghttp2_pq_size(&f->obq));
+
+ /* db has been detached */
+ nghttp2_outbound_item_free(db, mem);
+ free(db);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_get_state(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_mem *mem;
+ nghttp2_hd_deflater deflater;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_stream *stream;
+ ssize_t rv;
+ nghttp2_data_provider data_prd;
+ nghttp2_frame frame;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+ memset(&data_prd, 0, sizeof(data_prd));
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE ==
+ nghttp2_stream_get_state(nghttp2_session_get_root_stream(session)));
+
+ /* stream 1 HEADERS; without END_STREAM flag set */
+ pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ stream = nghttp2_session_find_stream(session, 1);
+
+ CU_ASSERT(NULL != stream);
+ CU_ASSERT(1 == stream->stream_id);
+ CU_ASSERT(NGHTTP2_STREAM_STATE_OPEN == nghttp2_stream_get_state(stream));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* stream 3 HEADERS; with END_STREAM flag set */
+ pack_headers(&bufs, &deflater, 3,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv,
+ ARRLEN(reqnv), mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ stream = nghttp2_session_find_stream(session, 3);
+
+ CU_ASSERT(NULL != stream);
+ CU_ASSERT(3 == stream->stream_id);
+ CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE ==
+ nghttp2_stream_get_state(stream));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Respond to stream 1 */
+ nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL ==
+ nghttp2_stream_get_state(stream));
+
+ /* Respond to stream 3 */
+ nghttp2_submit_response(session, 3, resnv, ARRLEN(resnv), NULL);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 3);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream));
+
+ /* stream 5 HEADERS; with END_STREAM flag set */
+ pack_headers(&bufs, &deflater, 5,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv,
+ ARRLEN(reqnv), mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Push stream 2 associated to stream 5 */
+ rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv,
+ ARRLEN(reqnv), NULL);
+
+ CU_ASSERT(2 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 2);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL ==
+ nghttp2_stream_get_state(stream));
+
+ /* Send response to push stream 2 with END_STREAM set */
+ nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 2);
+
+ /* At server, pushed stream object is not retained after closed */
+ CU_ASSERT(NULL == stream);
+
+ /* Push stream 4 associated to stream 5 */
+ rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv,
+ ARRLEN(reqnv), NULL);
+
+ CU_ASSERT(4 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 4);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL ==
+ nghttp2_stream_get_state(stream));
+
+ /* Send response to push stream 4 without closing */
+ data_prd.read_callback = defer_data_source_read_callback;
+
+ nghttp2_submit_response(session, 4, resnv, ARRLEN(resnv), &data_prd);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_find_stream(session, 4);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE ==
+ nghttp2_stream_get_state(stream));
+
+ /* Create idle stream by PRIORITY frame */
+ nghttp2_frame_priority_init(&frame.priority, 7, &pri_spec_default);
+
+ nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+ nghttp2_frame_priority_free(&frame.priority);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ stream = nghttp2_session_find_stream(session, 7);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == nghttp2_stream_get_state(stream));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* Test for client side */
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ /* Receive PUSH_PROMISE 2 associated to stream 1 */
+ pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, reqnv,
+ ARRLEN(reqnv), mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ stream = nghttp2_session_find_stream(session, 2);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_REMOTE ==
+ nghttp2_stream_get_state(stream));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Receive push response for stream 2 without END_STREAM set */
+ pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv,
+ ARRLEN(resnv), mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ stream = nghttp2_session_find_stream(session, 2);
+
+ CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL ==
+ nghttp2_stream_get_state(stream));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_stream_get_something(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *a, *b, *c;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ a = open_stream(session, 1);
+
+ CU_ASSERT(nghttp2_session_get_root_stream(session) ==
+ nghttp2_stream_get_parent(a));
+ CU_ASSERT(NULL == nghttp2_stream_get_previous_sibling(a));
+ CU_ASSERT(NULL == nghttp2_stream_get_next_sibling(a));
+ CU_ASSERT(NULL == nghttp2_stream_get_first_child(a));
+
+ b = open_stream_with_dep(session, 3, a);
+ c = open_stream_with_dep_weight(session, 5, 11, a);
+
+ CU_ASSERT(a == nghttp2_stream_get_parent(c));
+ CU_ASSERT(a == nghttp2_stream_get_parent(b));
+
+ CU_ASSERT(c == nghttp2_stream_get_first_child(a));
+
+ CU_ASSERT(b == nghttp2_stream_get_next_sibling(c));
+ CU_ASSERT(c == nghttp2_stream_get_previous_sibling(b));
+
+ CU_ASSERT(27 == nghttp2_stream_get_sum_dependency_weight(a));
+
+ CU_ASSERT(11 == nghttp2_stream_get_weight(c));
+ CU_ASSERT(5 == nghttp2_stream_get_stream_id(c));
+ CU_ASSERT(0 == nghttp2_stream_get_stream_id(&session->root));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_find_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ open_recv_stream(session, 1);
+
+ stream = nghttp2_session_find_stream(session, 1);
+
+ CU_ASSERT(NULL != stream);
+ CU_ASSERT(1 == stream->stream_id);
+
+ stream = nghttp2_session_find_stream(session, 0);
+
+ CU_ASSERT(&session->root == stream);
+ CU_ASSERT(0 == stream->stream_id);
+
+ stream = nghttp2_session_find_stream(session, 2);
+
+ CU_ASSERT(NULL == stream);
+
+ 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,
+ (uint32_t)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_recv_stream(session, (int32_t)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_recv_stream(session, 11);
+ nghttp2_session_adjust_closed_stream(session);
+
+ 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_recv_stream(session, 13);
+ nghttp2_session_adjust_closed_stream(session);
+
+ CU_ASSERT(0 == session->num_closed_streams);
+ CU_ASSERT(NULL == session->closed_stream_tail);
+ CU_ASSERT(NULL == session->closed_stream_head);
+
+ nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(1 == session->num_closed_streams);
+ CU_ASSERT(3 == session->closed_stream_head->stream_id);
+
+ /* server initiated stream is not counted to max concurrent limit */
+ open_sent_stream(session, 2);
+ nghttp2_session_adjust_closed_stream(session);
+
+ CU_ASSERT(1 == session->num_closed_streams);
+ CU_ASSERT(3 == session->closed_stream_head->stream_id);
+
+ nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(1 == session->num_closed_streams);
+ CU_ASSERT(3 == session->closed_stream_head->stream_id);
+
+ 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,
+ (uint32_t)max_concurrent_streams};
+ int i;
+ int32_t stream_id;
+
+ 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 NGHTTP2_MIN_IDLE_STREAM idle streams even if
+ max concurrent streams is very low. */
+ for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) {
+ open_recv_stream2(session, i * 2 + 1, NGHTTP2_STREAM_IDLE);
+ nghttp2_session_adjust_idle_stream(session);
+ }
+
+ CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
+
+ stream_id = (NGHTTP2_MIN_IDLE_STREAMS - 1) * 2 + 1;
+ CU_ASSERT(1 == session->idle_stream_head->stream_id);
+ CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
+
+ stream_id += 2;
+
+ open_recv_stream2(session, stream_id, NGHTTP2_STREAM_IDLE);
+ nghttp2_session_adjust_idle_stream(session);
+
+ CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
+ CU_ASSERT(3 == session->idle_stream_head->stream_id);
+ CU_ASSERT(stream_id == 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 *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 < 250; ++i, stream_id += 2) {
+ dep_stream = open_stream_with_dep(session, stream_id, dep_stream);
+ }
+
+ stream_id = 1;
+ for (i = 0; i < 250; ++i, stream_id += 2) {
+ stream = nghttp2_session_get_stream(session, stream_id);
+ CU_ASSERT(nghttp2_stream_dep_find_ancestor(stream, &session->root));
+ CU_ASSERT(nghttp2_stream_in_dep_tree(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_recv_stream(session, 301);
+ open_sent_stream(session, 302);
+ open_recv_stream(session, 309);
+ open_recv_stream(session, 311);
+ open_recv_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, reqnv, ARRLEN(reqnv), mem);
+
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1,
+ NGHTTP2_HCAT_REQUEST, NULL, nva, ARRLEN(reqnv));
+ 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);
+
+ ud.header_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.header_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+
+ /* Make sure no header decompression error occurred */
+ CU_ASSERT(NGHTTP2_GOAWAY_NONE == session->goaway_flags);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Check for PUSH_PROMISE */
+ nghttp2_hd_deflate_init(&deflater, mem);
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ open_sent_stream(session, 1);
+
+ rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2,
+ reqnv, ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ ud.header_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(1 == ud.header_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(2 == item->frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code);
+
+ nghttp2_session_del(session);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_recv_client_magic(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ ssize_t rv;
+ nghttp2_frame ping_frame;
+ uint8_t buf[16];
+
+ /* enable global nghttp2_enable_strict_preface here */
+ nghttp2_enable_strict_preface = 1;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ /* Check success case */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC,
+ NGHTTP2_CLIENT_MAGIC_LEN);
+
+ CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN);
+ CU_ASSERT(NGHTTP2_IB_READ_FIRST_SETTINGS == session->iframe.state);
+
+ /* Receiving PING is error because we want SETTINGS. */
+ 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_ALL == session->iframe.state);
+ CU_ASSERT(0 == session->iframe.payloadleft);
+
+ nghttp2_frame_ping_free(&ping_frame.ping);
+
+ nghttp2_session_del(session);
+
+ /* Check bad case */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* Feed magic with one byte less */
+ rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC,
+ NGHTTP2_CLIENT_MAGIC_LEN - 1);
+
+ CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN - 1);
+ CU_ASSERT(NGHTTP2_IB_READ_CLIENT_MAGIC == 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_CLIENT_MAGIC == rv);
+
+ nghttp2_session_del(session);
+
+ /* disable global nghttp2_enable_strict_preface here */
+ nghttp2_enable_strict_preface = 0;
+}
+
+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_recv_stream(session, 1);
+ open_recv_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;
+
+ CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &prd));
+ 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 = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ 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);
+}
+
+void test_nghttp2_session_cancel_reserved_remote(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream;
+ nghttp2_frame frame;
+ nghttp2_nv *nva;
+ size_t nvlen;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ stream = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);
+
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL);
+
+ CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nvlen = ARRLEN(resnv);
+ nghttp2_nv_array_copy(&nva, resnv, nvlen, mem);
+
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+ NGHTTP2_HCAT_PUSH_RESPONSE, NULL, nva, nvlen);
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ /* stream is not dangling, so assign NULL */
+ stream = NULL;
+
+ /* No RST_STREAM or GOAWAY is generated since stream should be in
+ NGHTTP2_STREAM_CLOSING and push response should be ignored. */
+ CU_ASSERT(0 == nghttp2_outbound_queue_size(&session->ob_reg));
+
+ /* Check that we can receive push response HEADERS while RST_STREAM
+ is just queued. */
+ open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED);
+
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL);
+
+ nghttp2_bufs_reset(&bufs);
+
+ frame.hd.stream_id = 4;
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_reg));
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_reset_pending_headers(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream;
+ int32_t stream_id;
+ 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;
+ callbacks.on_stream_close_callback = on_stream_close_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ stream_id = nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL);
+ CU_ASSERT(stream_id >= 1);
+
+ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+ NGHTTP2_CANCEL);
+
+ session->remote_settings.max_concurrent_streams = 0;
+
+ /* RST_STREAM cancels pending HEADERS and is not actually sent. */
+ ud.frame_send_cb_called = 0;
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ CU_ASSERT(0 == ud.frame_send_cb_called);
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+
+ CU_ASSERT(NULL == stream);
+
+ /* See HEADERS is not sent. on_stream_close is called just like
+ transmission failure. */
+ session->remote_settings.max_concurrent_streams = 1;
+
+ ud.frame_not_send_cb_called = 0;
+ ud.stream_close_error_code = 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_CANCEL == ud.stream_close_error_code);
+
+ stream = nghttp2_session_get_stream(session, stream_id);
+
+ CU_ASSERT(NULL == stream);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_data_callback(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_data_provider data_prd;
+ my_user_data ud;
+ accumulator acc;
+ nghttp2_frame_hd hd;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = accumulator_send_callback;
+ callbacks.send_data_callback = send_data_callback;
+
+ data_prd.read_callback = no_copy_data_source_read_callback;
+
+ acc.length = 0;
+ ud.acc = &acc;
+
+ ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
+
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ open_sent_stream(session, 1);
+
+ nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ CU_ASSERT((NGHTTP2_FRAME_HDLEN + NGHTTP2_DATA_PAYLOADLEN) * 2 == acc.length);
+
+ nghttp2_frame_unpack_frame_hd(&hd, acc.buf);
+
+ CU_ASSERT(16384 == hd.length);
+ CU_ASSERT(NGHTTP2_DATA == hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+
+ nghttp2_frame_unpack_frame_hd(&hd, acc.buf + NGHTTP2_FRAME_HDLEN + hd.length);
+
+ CU_ASSERT(16384 == hd.length);
+ CU_ASSERT(NGHTTP2_DATA == hd.type);
+ CU_ASSERT(NGHTTP2_FLAG_END_STREAM == hd.flags);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_begin_headers_temporal_failure(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_bufs bufs;
+ nghttp2_mem *mem;
+ ssize_t rv;
+ nghttp2_hd_deflater deflater;
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.on_begin_headers_callback =
+ temporal_failure_on_begin_headers_callback;
+ callbacks.on_header_callback = on_header_callback;
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+ callbacks.send_callback = null_send_callback;
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ ud.header_cb_called = 0;
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.header_cb_called);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code);
+
+ nghttp2_session_del(session);
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_bufs_reset(&bufs);
+ /* check for PUSH_PROMISE */
+ nghttp2_hd_deflate_init(&deflater, mem);
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ open_sent_stream(session, 1);
+
+ rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2,
+ reqnv, ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ ud.header_cb_called = 0;
+ ud.frame_recv_cb_called = 0;
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv);
+ CU_ASSERT(0 == ud.header_cb_called);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(2 == item->frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code);
+
+ nghttp2_session_del(session);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_defer_then_close(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_data_provider prd;
+ int rv;
+ const uint8_t *datap;
+ ssize_t datalen;
+ nghttp2_frame frame;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ prd.read_callback = defer_data_source_read_callback;
+
+ rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), &prd, NULL);
+ CU_ASSERT(rv > 0);
+
+ /* This sends HEADERS */
+ datalen = nghttp2_session_mem_send(session, &datap);
+
+ CU_ASSERT(datalen > 0);
+
+ /* This makes DATA item deferred */
+ datalen = nghttp2_session_mem_send(session, &datap);
+
+ CU_ASSERT(datalen == 0);
+
+ nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL);
+
+ /* Assertion failure; GH-264 */
+ rv = nghttp2_session_on_rst_stream_received(session, &frame);
+
+ CU_ASSERT(rv == 0);
+
+ nghttp2_session_del(session);
+}
+
+static int submit_response_on_stream_close(nghttp2_session *session,
+ int32_t stream_id,
+ uint32_t error_code,
+ void *user_data) {
+ nghttp2_data_provider data_prd;
+ (void)error_code;
+ (void)user_data;
+
+ data_prd.read_callback = temporal_failure_data_source_read_callback;
+
+ // Attempt to submit response or data to the stream being closed
+ switch (stream_id) {
+ case 1:
+ CU_ASSERT(0 == nghttp2_submit_response(session, stream_id, resnv,
+ ARRLEN(resnv), &data_prd));
+ break;
+ case 3:
+ CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, stream_id,
+ &data_prd));
+ break;
+ }
+
+ return 0;
+}
+
+void test_nghttp2_session_detach_item_from_closed_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.send_callback = null_send_callback;
+ callbacks.on_stream_close_callback = submit_response_on_stream_close;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ open_recv_stream(session, 1);
+ open_recv_stream(session, 3);
+
+ nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
+ nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_flooding(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_frame frame;
+ nghttp2_mem *mem;
+ size_t i;
+
+ mem = nghttp2_mem_default();
+
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ /* PING ACK */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
+ nghttp2_frame_pack_ping(&bufs, &frame.ping);
+ nghttp2_frame_ping_free(&frame.ping);
+
+ buf = &bufs.head->buf;
+
+ for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
+ CU_ASSERT(
+ (ssize_t)nghttp2_buf_len(buf) ==
+ nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
+ }
+
+ CU_ASSERT(NGHTTP2_ERR_FLOODED ==
+ nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
+
+ nghttp2_session_del(session);
+
+ /* SETTINGS ACK */
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
+ nghttp2_frame_pack_settings(&bufs, &frame.settings);
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ buf = &bufs.head->buf;
+
+ for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
+ CU_ASSERT(
+ (ssize_t)nghttp2_buf_len(buf) ==
+ nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
+ }
+
+ CU_ASSERT(NGHTTP2_ERR_FLOODED ==
+ nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_change_stream_priority(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream1, *stream2, *stream3, *stream5;
+ nghttp2_priority_spec pri_spec;
+ int rv;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ stream1 = open_recv_stream(session, 1);
+ stream3 = open_recv_stream_with_dep_weight(session, 3, 199, stream1);
+ stream2 = open_sent_stream_with_dep_weight(session, 2, 101, stream3);
+
+ nghttp2_priority_spec_init(&pri_spec, 1, 256, 0);
+
+ rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ CU_ASSERT(stream1 == stream2->dep_prev);
+ CU_ASSERT(256 == stream2->weight);
+
+ /* Cannot change stream which does not exist */
+ rv = nghttp2_session_change_stream_priority(session, 5, &pri_spec);
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* It is an error to depend on itself */
+ rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* It is an error to change priority of root stream (0) */
+ rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* Depends on the non-existing idle stream. This creates that idle
+ stream. */
+ nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
+
+ rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ stream5 = nghttp2_session_get_stream_raw(session, 5);
+
+ CU_ASSERT(NULL != stream5);
+ CU_ASSERT(&session->root == stream5->dep_prev);
+ CU_ASSERT(stream5 == stream2->dep_prev);
+ CU_ASSERT(9 == stream2->weight);
+
+ nghttp2_session_del(session);
+
+ /* Check that this works in client session too */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ stream1 = open_sent_stream(session, 1);
+
+ nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
+
+ rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ stream5 = nghttp2_session_get_stream_raw(session, 5);
+
+ CU_ASSERT(NULL != stream5);
+ CU_ASSERT(&session->root == stream5->dep_prev);
+ CU_ASSERT(stream5 == stream1->dep_prev);
+ CU_ASSERT(9 == stream1->weight);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_change_extpri_stream_priority(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ ssize_t rv;
+ nghttp2_option *option;
+ nghttp2_extension frame;
+ nghttp2_ext_priority_update priority_update;
+ nghttp2_extpri extpri, nextpri;
+ nghttp2_stream *stream;
+ static const uint8_t field_value[] = "u=2";
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ frame_pack_bufs_init(&bufs);
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_builtin_recv_extension_type(option,
+ NGHTTP2_PRIORITY_UPDATE);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ session->pending_no_rfc7540_priorities = 1;
+
+ open_recv_stream(session, 1);
+
+ extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW + 1;
+ extpri.inc = 1;
+
+ rv = nghttp2_session_change_extpri_stream_priority(
+ session, 1, &extpri, /* ignore_client_signal = */ 0);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW ==
+ nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ rv = nghttp2_session_get_extpri_stream_priority(session, &nextpri, 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == nextpri.urgency);
+ CU_ASSERT(1 == nextpri.inc);
+
+ /* Client can still update stream priority. */
+ frame.payload = &priority_update;
+ nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+ nghttp2_frame_pack_priority_update(&bufs, &frame);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(2 == stream->extpri);
+
+ /* Start to ignore client priority signal for this stream. */
+ rv = nghttp2_session_change_extpri_stream_priority(
+ session, 1, &extpri, /* ignore_client_signal = */ 1);
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW ==
+ nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW ==
+ nghttp2_extpri_uint8_urgency(stream->extpri));
+ CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri));
+
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_create_idle_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_stream *stream2, *stream4, *stream8, *stream10;
+ nghttp2_priority_spec pri_spec;
+ int rv;
+ int i;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ stream2 = open_sent_stream(session, 2);
+
+ nghttp2_priority_spec_init(&pri_spec, 2, 111, 1);
+
+ rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ stream4 = nghttp2_session_get_stream_raw(session, 4);
+
+ CU_ASSERT(4 == stream4->stream_id);
+ CU_ASSERT(111 == stream4->weight);
+ CU_ASSERT(stream2 == stream4->dep_prev);
+ CU_ASSERT(stream4 == stream2->dep_next);
+
+ /* If pri_spec->stream_id does not exist, and it is idle stream, it
+ is created too */
+ nghttp2_priority_spec_init(&pri_spec, 10, 109, 0);
+
+ rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ stream8 = nghttp2_session_get_stream_raw(session, 8);
+ stream10 = nghttp2_session_get_stream_raw(session, 10);
+
+ CU_ASSERT(8 == stream8->stream_id);
+ CU_ASSERT(109 == stream8->weight);
+ CU_ASSERT(10 == stream10->stream_id);
+ CU_ASSERT(16 == stream10->weight);
+ CU_ASSERT(stream10 == stream8->dep_prev);
+ CU_ASSERT(&session->root == stream10->dep_prev);
+
+ /* It is an error to attempt to create already existing idle
+ stream */
+ rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* It is an error to depend on itself */
+ pri_spec.stream_id = 6;
+
+ rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec);
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* It is an error to create root stream (0) as idle stream */
+ rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec);
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ /* It is an error to create non-idle stream */
+ session->last_sent_stream_id = 20;
+ pri_spec.stream_id = 2;
+
+ rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec);
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+ nghttp2_session_del(session);
+
+ /* Check that this works in client session too */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_priority_spec_init(&pri_spec, 4, 99, 1);
+
+ rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ stream4 = nghttp2_session_get_stream_raw(session, 4);
+ stream2 = nghttp2_session_get_stream_raw(session, 2);
+
+ CU_ASSERT(NULL != stream4);
+ CU_ASSERT(NULL != stream2);
+ CU_ASSERT(&session->root == stream4->dep_prev);
+ CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight);
+ CU_ASSERT(stream4 == stream2->dep_prev);
+ CU_ASSERT(99 == stream2->weight);
+
+ nghttp2_session_del(session);
+
+ /* Check that idle stream is reduced when nghttp2_session_send() is
+ called. */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ session->local_settings.max_concurrent_streams = 30;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
+ for (i = 0; i < 100; ++i) {
+ rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
+ }
+
+ CU_ASSERT(100 == session->num_idle_streams);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(30 == session->num_idle_streams);
+ CU_ASSERT(141 == session->idle_stream_head->stream_id);
+
+ nghttp2_session_del(session);
+
+ /* Check that idle stream is reduced when nghttp2_session_mem_recv() is
+ called. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ session->local_settings.max_concurrent_streams = 30;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
+ for (i = 0; i < 100; ++i) {
+ rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
+ }
+
+ CU_ASSERT(100 == session->num_idle_streams);
+ CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0));
+ CU_ASSERT(30 == session->num_idle_streams);
+ CU_ASSERT(141 == session->idle_stream_head->stream_id);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_repeated_priority_change(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_frame frame;
+ nghttp2_priority_spec pri_spec;
+ int32_t stream_id, last_stream_id;
+ int32_t max_streams = 20;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ session->local_settings.max_concurrent_streams = (uint32_t)max_streams;
+
+ /* 1 -> 0 */
+ nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
+ nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+ nghttp2_frame_priority_free(&frame.priority);
+
+ last_stream_id = max_streams * 2 + 1;
+
+ for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
+ /* 1 -> stream_id */
+ nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
+ nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+ nghttp2_frame_priority_free(&frame.priority);
+ }
+
+ CU_ASSERT(20 == session->num_idle_streams);
+ CU_ASSERT(1 == session->idle_stream_head->stream_id);
+
+ /* 1 -> last_stream_id */
+ nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
+ nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+ nghttp2_frame_priority_free(&frame.priority);
+
+ CU_ASSERT(20 == session->num_idle_streams);
+ CU_ASSERT(3 == session->idle_stream_head->stream_id);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_repeated_priority_submission(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_priority_spec pri_spec;
+ int32_t stream_id, last_stream_id;
+ uint32_t max_streams = NGHTTP2_MIN_IDLE_STREAMS;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ session->local_settings.max_concurrent_streams = max_streams;
+
+ /* 1 -> 0 */
+ nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
+
+ CU_ASSERT(0 ==
+ nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
+
+ last_stream_id = (int32_t)(max_streams * 2 + 1);
+
+ for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
+ /* 1 -> stream_id */
+ nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
+
+ CU_ASSERT(
+ 0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
+ }
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(max_streams == session->num_idle_streams);
+ CU_ASSERT(1 == session->idle_stream_head->stream_id);
+
+ /* 1 -> last_stream_id */
+ nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
+
+ CU_ASSERT(0 ==
+ nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(max_streams == session->num_idle_streams);
+ CU_ASSERT(3 == session->idle_stream_head->stream_id);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_set_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 = open_sent_stream(session, 1);
+ stream->recv_window_size = 4096;
+
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 1, 65536));
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 ==
+ stream->local_window_size);
+ CU_ASSERT(4096 == stream->recv_window_size);
+ CU_ASSERT(65536 - 4096 ==
+ nghttp2_session_get_stream_local_window_size(session, 1));
+
+ 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(1 == item->frame.window_update.window_size_increment);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ /* Go decrement part */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 1, 32768));
+ CU_ASSERT(32768 == stream->local_window_size);
+ CU_ASSERT(-28672 == stream->recv_window_size);
+ CU_ASSERT(32768 == stream->recv_reduction);
+ CU_ASSERT(65536 - 4096 ==
+ nghttp2_session_get_stream_local_window_size(session, 1));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(item == NULL);
+
+ /* Increase local window size */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 1, 49152));
+ CU_ASSERT(49152 == stream->local_window_size);
+ CU_ASSERT(-12288 == stream->recv_window_size);
+ CU_ASSERT(16384 == stream->recv_reduction);
+ CU_ASSERT(65536 - 4096 ==
+ nghttp2_session_get_stream_local_window_size(session, 1));
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ /* Increase local window again */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 1, 65537));
+ CU_ASSERT(65537 == stream->local_window_size);
+ CU_ASSERT(4096 == stream->recv_window_size);
+ CU_ASSERT(0 == stream->recv_reduction);
+ CU_ASSERT(65537 - 4096 ==
+ nghttp2_session_get_stream_local_window_size(session, 1));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(1 == item->frame.window_update.window_size_increment);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ /* Check connection-level flow control */
+ session->recv_window_size = 4096;
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 0, 65536));
+ CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 ==
+ session->local_window_size);
+ CU_ASSERT(4096 == session->recv_window_size);
+ CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session));
+
+ 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(1 == item->frame.window_update.window_size_increment);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ /* Go decrement part */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 0, 32768));
+ CU_ASSERT(32768 == session->local_window_size);
+ CU_ASSERT(-28672 == session->recv_window_size);
+ CU_ASSERT(32768 == session->recv_reduction);
+ CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(item == NULL);
+
+ /* Increase local window size */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 0, 49152));
+ CU_ASSERT(49152 == session->local_window_size);
+ CU_ASSERT(-12288 == session->recv_window_size);
+ CU_ASSERT(16384 == session->recv_reduction);
+ CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session));
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ /* Increase local window again */
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 0, 65537));
+ CU_ASSERT(65537 == session->local_window_size);
+ CU_ASSERT(4096 == session->recv_window_size);
+ CU_ASSERT(0 == session->recv_reduction);
+ CU_ASSERT(65537 - 4096 == nghttp2_session_get_local_window_size(session));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(1 == item->frame.window_update.window_size_increment);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_session_del(session);
+
+ /* Make sure that nghttp2_session_set_local_window_size submits
+ WINDOW_UPDATE if necessary to increase stream-level window. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+ stream = open_sent_stream(session, 1);
+ stream->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
+
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 1, 0));
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1));
+ /* This should submit WINDOW_UPDATE frame because stream-level
+ receiving window is now full. */
+ CU_ASSERT(0 ==
+ nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 1,
+ NGHTTP2_INITIAL_WINDOW_SIZE));
+ CU_ASSERT(0 == stream->recv_window_size);
+ CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+ nghttp2_session_get_stream_local_window_size(session, 1));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+ CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+ item->frame.window_update.window_size_increment);
+
+ nghttp2_session_del(session);
+
+ /* Make sure that nghttp2_session_set_local_window_size submits
+ WINDOW_UPDATE if necessary to increase connection-level
+ window. */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+ session->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
+
+ CU_ASSERT(0 == nghttp2_session_set_local_window_size(
+ session, NGHTTP2_FLAG_NONE, 0, 0));
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(0 == nghttp2_session_get_local_window_size(session));
+ /* This should submit WINDOW_UPDATE frame because connection-level
+ receiving window is now full. */
+ CU_ASSERT(0 ==
+ nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
+ NGHTTP2_INITIAL_WINDOW_SIZE));
+ CU_ASSERT(0 == session->recv_window_size);
+ CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+ nghttp2_session_get_local_window_size(session));
+
+ 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_INITIAL_WINDOW_SIZE ==
+ item->frame.window_update.window_size_increment);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_cancel_from_before_frame_send(void) {
+ int rv;
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ my_user_data ud;
+ nghttp2_settings_entry iv;
+ nghttp2_data_provider data_prd;
+ int32_t stream_id;
+ nghttp2_stream *stream;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.before_frame_send_callback = cancel_before_frame_send_callback;
+ callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, &ud);
+
+ iv.settings_id = 0;
+ iv.value = 1000000009;
+
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ CU_ASSERT(0 == rv);
+
+ ud.frame_send_cb_called = 0;
+ ud.before_frame_send_cb_called = 0;
+ ud.frame_not_send_cb_called = 0;
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_send_cb_called);
+ CU_ASSERT(1 == ud.before_frame_send_cb_called);
+ CU_ASSERT(1 == ud.frame_not_send_cb_called);
+
+ data_prd.source.ptr = NULL;
+ data_prd.read_callback = temporal_failure_data_source_read_callback;
+
+ stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv),
+ &data_prd, NULL);
+
+ CU_ASSERT(stream_id > 0);
+
+ ud.frame_send_cb_called = 0;
+ ud.before_frame_send_cb_called = 0;
+ ud.frame_not_send_cb_called = 0;
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_send_cb_called);
+ CU_ASSERT(1 == ud.before_frame_send_cb_called);
+ CU_ASSERT(1 == ud.frame_not_send_cb_called);
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+
+ CU_ASSERT(NULL == stream);
+
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ open_recv_stream(session, 1);
+
+ stream_id = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, reqnv,
+ ARRLEN(reqnv), NULL);
+
+ CU_ASSERT(stream_id > 0);
+
+ ud.frame_send_cb_called = 0;
+ ud.before_frame_send_cb_called = 0;
+ ud.frame_not_send_cb_called = 0;
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == ud.frame_send_cb_called);
+ CU_ASSERT(1 == ud.before_frame_send_cb_called);
+ CU_ASSERT(1 == ud.frame_not_send_cb_called);
+
+ stream = nghttp2_session_get_stream_raw(session, stream_id);
+
+ CU_ASSERT(NULL == stream);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_too_many_settings(void) {
+ nghttp2_session *session;
+ nghttp2_option *option;
+ 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_mem *mem;
+ nghttp2_outbound_item *item;
+
+ 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_option_new(&option);
+ nghttp2_option_set_max_settings(option, 1);
+
+ nghttp2_session_client_new2(&session, &callbacks, &ud, option);
+
+ CU_ASSERT(1 == session->max_settings);
+
+ nghttp2_option_del(option);
+
+ 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((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_session_del(session);
+}
+
+static void
+prepare_session_removed_closed_stream(nghttp2_session *session,
+ nghttp2_hd_deflater *deflater) {
+ int rv;
+ nghttp2_settings_entry iv;
+ nghttp2_bufs bufs;
+ nghttp2_mem *mem;
+ ssize_t nread;
+ int i;
+ nghttp2_stream *stream;
+ nghttp2_frame_hd hd;
+
+ mem = nghttp2_mem_default();
+
+ frame_pack_bufs_init(&bufs);
+
+ iv.settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv.value = 2;
+
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ for (i = 1; i <= 3; i += 2) {
+ rv = pack_headers(&bufs, deflater, i,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv,
+ ARRLEN(reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+
+ nghttp2_bufs_reset(&bufs);
+ }
+
+ nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);
+
+ rv = pack_headers(&bufs, deflater, 5,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv,
+ ARRLEN(reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ /* Receiving stream 5 will erase stream 3 from closed stream list */
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+
+ stream = nghttp2_session_get_stream_raw(session, 3);
+
+ CU_ASSERT(NULL == stream);
+
+ /* Since the current max concurrent streams is
+ NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, receiving frame on stream
+ 3 is ignored. */
+ nghttp2_bufs_reset(&bufs);
+ rv = pack_headers(&bufs, deflater, 3,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ trailernv, ARRLEN(trailernv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ /* Now server receives SETTINGS ACK */
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_SETTINGS, NGHTTP2_FLAG_ACK, 0);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_removed_closed_stream(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ int rv;
+ nghttp2_hd_deflater deflater;
+ nghttp2_bufs bufs;
+ nghttp2_mem *mem;
+ ssize_t nread;
+ nghttp2_frame_hd hd;
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(callbacks));
+
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ /* Now local max concurrent streams is still unlimited, pending max
+ concurrent streams is now 2. */
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ prepare_session_removed_closed_stream(session, &deflater);
+
+ /* Now current max concurrent streams is 2. Receiving frame on
+ stream 3 is ignored because we have no stream object for stream
+ 3. */
+ nghttp2_bufs_reset(&bufs);
+ rv = pack_headers(&bufs, &deflater, 3,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ trailernv, ARRLEN(trailernv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+ nghttp2_hd_deflate_init(&deflater, mem);
+ /* Same setup, and then receive DATA instead of HEADERS */
+
+ prepare_session_removed_closed_stream(session, &deflater);
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_bufs_len(&bufs));
+
+ CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+static ssize_t pause_once_data_source_read_callback(
+ nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
+ uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
+ my_user_data *ud = user_data;
+ if (ud->data_source_read_cb_paused == 0) {
+ ++ud->data_source_read_cb_paused;
+ return NGHTTP2_ERR_PAUSE;
+ }
+
+ return fixed_length_data_source_read_callback(session, stream_id, buf, len,
+ data_flags, source, user_data);
+}
+
+void test_nghttp2_session_pause_data(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 = null_send_callback;
+ callbacks.on_frame_send_callback = on_frame_send_callback;
+
+ data_prd.read_callback = pause_once_data_source_read_callback;
+ ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ open_recv_stream(session, 1);
+
+ CU_ASSERT(
+ 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
+
+ ud.frame_send_cb_called = 0;
+ ud.data_source_read_cb_paused = 0;
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(0 == ud.frame_send_cb_called);
+ CU_ASSERT(NULL == session->aob.item);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(1 == ud.frame_send_cb_called);
+ CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type);
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_no_closed_streams(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_option *option;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_closed_streams(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ open_recv_stream(session, 1);
+
+ nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
+
+ CU_ASSERT(0 == session->num_closed_streams);
+
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_set_stream_user_data(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ int32_t stream_id;
+ int user_data1, user_data2;
+ int rv;
+ const uint8_t *datap;
+ ssize_t datalen;
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL,
+ &user_data1);
+
+ rv = nghttp2_session_set_stream_user_data(session, stream_id, &user_data2);
+
+ CU_ASSERT(0 == rv);
+
+ datalen = nghttp2_session_mem_send(session, &datap);
+
+ CU_ASSERT(datalen > 0);
+
+ CU_ASSERT(&user_data2 ==
+ nghttp2_session_get_stream_user_data(session, stream_id));
+
+ CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+ nghttp2_session_set_stream_user_data(session, 2, NULL));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_no_rfc7540_priorities(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_data_provider data_prd;
+ my_user_data ud;
+ nghttp2_outbound_item *item;
+ nghttp2_mem *mem;
+ nghttp2_settings_entry iv;
+ nghttp2_priority_spec pri_spec;
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ /* Do not use a dependency tree if SETTINGS_NO_RFC7540_PRIORITIES =
+ 1. */
+ data_prd.read_callback = fixed_length_data_source_read_callback;
+
+ ud.data_source_length = 128 * 1024;
+ CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 1;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+ CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv),
+ &data_prd));
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen);
+ assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen,
+ mem);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ CU_ASSERT(1 == nghttp2_pq_size(
+ &session->sched[NGHTTP2_EXTPRI_DEFAULT_URGENCY].ob_data));
+ CU_ASSERT(nghttp2_pq_empty(&session->root.obq));
+
+ nghttp2_session_del(session);
+
+ /* Priorities are sent as is before client receives
+ SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */
+ CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 1;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
+
+ pri_spec.stream_id = 5;
+ pri_spec.weight = 111;
+ pri_spec.exclusive = 1;
+
+ CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv,
+ ARRLEN(reqnv), NULL, NULL));
+
+ item = nghttp2_outbound_queue_top(&session->ob_syn);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ CU_ASSERT(pri_spec.stream_id == item->frame.headers.pri_spec.stream_id);
+ CU_ASSERT(pri_spec.weight == item->frame.headers.pri_spec.weight);
+ CU_ASSERT(pri_spec.exclusive == item->frame.headers.pri_spec.exclusive);
+
+ nghttp2_session_del(session);
+
+ /* Priorities are defaulted if client received
+ SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */
+ CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 1;
+
+ CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
+
+ session->remote_settings.no_rfc7540_priorities = 1;
+
+ pri_spec.stream_id = 5;
+ pri_spec.weight = 111;
+ pri_spec.exclusive = 1;
+
+ CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv,
+ ARRLEN(reqnv), NULL, NULL));
+
+ item = nghttp2_outbound_queue_top(&session->ob_syn);
+
+ CU_ASSERT(NULL != item);
+ CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+ CU_ASSERT(nghttp2_priority_spec_check_default(&item->frame.headers.pri_spec));
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_server_fallback_rfc7540_priorities(void) {
+ nghttp2_session *session;
+ nghttp2_option *option;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_frame frame;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ ssize_t rv;
+ nghttp2_settings_entry iv;
+ nghttp2_mem *mem;
+ nghttp2_hd_deflater deflater;
+ nghttp2_nv *nva;
+ size_t nvlen;
+ nghttp2_priority_spec pri_spec;
+ nghttp2_stream *anchor_stream, *stream;
+ my_user_data ud;
+ nghttp2_ext_priority_update priority_update;
+ static const uint8_t field_value[] = "u=0";
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_server_fallback_rfc7540_priorities(option, 1);
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 1;
+
+ /* Server falls back to RFC 7540 priorities. */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
+ rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(1 == session->fallback_rfc7540_priorities);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+ nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
+ nghttp2_frame_headers_init(&frame.headers,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+ 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
+ nghttp2_bufs_reset(&bufs);
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ anchor_stream = nghttp2_session_get_stream_raw(session, 3);
+
+ CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
+ CU_ASSERT(
+ !(anchor_stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
+ CU_ASSERT(&session->root == anchor_stream->dep_prev);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+ CU_ASSERT(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
+ CU_ASSERT(anchor_stream == stream->dep_prev);
+
+ /* Make sure that PRIORITY frame updates stream priority. */
+ nghttp2_priority_spec_init(&pri_spec, 5, 1, 0);
+ nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+ nghttp2_frame_priority_free(&frame.priority);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ anchor_stream = nghttp2_session_get_stream_raw(session, 5);
+
+ CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
+ CU_ASSERT(&session->root == anchor_stream->dep_prev);
+ CU_ASSERT(anchor_stream == stream->dep_prev);
+
+ /* Make sure that PRIORITY_UPDATE frame is ignored. */
+ frame.ext.payload = &priority_update;
+ nghttp2_frame_priority_update_init(&frame.ext, 1, (uint8_t *)field_value,
+ sizeof(field_value) - 1);
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_frame_pack_priority_update(&bufs, &frame.ext);
+
+ ud.frame_recv_cb_called = 0;
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(0 == ud.frame_recv_cb_called);
+ CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* Server does not fallback to RFC 7540 priorities. */
+ nghttp2_session_server_new2(&session, &callbacks, &ud, option);
+
+ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
+ iv.value = 0;
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
+ dup_iv(&iv, 1), 1);
+ nghttp2_bufs_reset(&bufs);
+ rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(0 == session->fallback_rfc7540_priorities);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+ nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
+ nghttp2_frame_headers_init(&frame.headers,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+ 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
+ nghttp2_bufs_reset(&bufs);
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+ CU_ASSERT(NULL == nghttp2_session_get_stream_raw(session, 3));
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+ CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ nghttp2_option_del(option);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_session_stream_reset_ratelim(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_frame frame;
+ ssize_t rv;
+ nghttp2_bufs bufs;
+ nghttp2_buf *buf;
+ nghttp2_mem *mem;
+ size_t i;
+ nghttp2_hd_deflater deflater;
+ size_t nvlen;
+ nghttp2_nv *nva;
+ int32_t stream_id;
+ nghttp2_outbound_item *item;
+ nghttp2_option *option;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_option_new(&option);
+ nghttp2_option_set_stream_reset_rate_limit(
+ option, NGHTTP2_DEFAULT_STREAM_RESET_BURST, 0);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
+ rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_settings_free(&frame.settings, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ /* Send SETTINGS ACK */
+ rv = nghttp2_session_send(session);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ for (i = 0; i < NGHTTP2_DEFAULT_STREAM_RESET_BURST + 2; ++i) {
+ stream_id = (int32_t)(i * 2 + 1);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* HEADERS */
+ nvlen = ARRLEN(reqnv);
+ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
+ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS,
+ stream_id, NGHTTP2_HCAT_HEADERS, NULL, nva,
+ nvlen);
+ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* RST_STREAM */
+ nghttp2_frame_rst_stream_init(&frame.rst_stream, stream_id,
+ NGHTTP2_NO_ERROR);
+ nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream);
+ nghttp2_frame_rst_stream_free(&frame.rst_stream);
+
+ buf = &bufs.head->buf;
+ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
+
+ if (i < NGHTTP2_DEFAULT_STREAM_RESET_BURST) {
+ CU_ASSERT(0 == nghttp2_outbound_queue_size(&session->ob_reg));
+
+ continue;
+ }
+
+ CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_reg));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+ CU_ASSERT(NGHTTP2_DEFAULT_STREAM_RESET_BURST * 2 + 1 ==
+ item->frame.goaway.last_stream_id);
+ }
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+ nghttp2_option_del(option);
+}
+
+static void check_nghttp2_http_recv_headers_fail(
+ nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
+ int stream_state, const nghttp2_nv *nva, size_t nvlen) {
+ nghttp2_mem *mem;
+ ssize_t rv;
+ nghttp2_outbound_item *item;
+ nghttp2_bufs bufs;
+ my_user_data *ud;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ ud = session->user_data;
+
+ if (stream_state != -1) {
+ if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+ open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state);
+ } else {
+ open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state);
+ }
+ }
+
+ rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva,
+ nvlen, mem);
+ CU_ASSERT(0 == rv);
+
+ ud->invalid_frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(1 == ud->invalid_frame_recv_cb_called);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_free(&bufs);
+}
+
+static void check_nghttp2_http_recv_headers_ok(
+ nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
+ int stream_state, const nghttp2_nv *nva, size_t nvlen) {
+ nghttp2_mem *mem;
+ ssize_t rv;
+ nghttp2_bufs bufs;
+ my_user_data *ud;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ ud = session->user_data;
+
+ if (stream_state != -1) {
+ if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+ open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state);
+ } else {
+ open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state);
+ }
+ }
+
+ rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva,
+ nvlen, mem);
+ CU_ASSERT(0 == rv);
+
+ ud->frame_recv_cb_called = 0;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+ CU_ASSERT(1 == ud->frame_recv_cb_called);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_mandatory_headers(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ my_user_data ud;
+ /* test case for response */
+ const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")};
+ const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV(":status", "200")};
+ const nghttp2_nv badpseudo_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV(":scheme", "https")};
+ const nghttp2_nv latepseudo_resnv[] = {MAKE_NV("server", "foo"),
+ MAKE_NV(":status", "200")};
+ const nghttp2_nv badstatus_resnv[] = {MAKE_NV(":status", "2000")};
+ const nghttp2_nv badcl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "-1")};
+ const nghttp2_nv dupcl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "0"),
+ MAKE_NV("content-length", "0")};
+ const nghttp2_nv badhd_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("connection", "close")};
+ const nghttp2_nv cl1xx_resnv[] = {MAKE_NV(":status", "100"),
+ MAKE_NV("content-length", "0")};
+ const nghttp2_nv cl204_resnv[] = {MAKE_NV(":status", "204"),
+ MAKE_NV("content-length", "0")};
+ const nghttp2_nv clnonzero204_resnv[] = {MAKE_NV(":status", "204"),
+ MAKE_NV("content-length", "100")};
+ const nghttp2_nv status101_resnv[] = {MAKE_NV(":status", "101")};
+ const nghttp2_nv unexpectedhost_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("host", "/localhost")};
+
+ /* test case for request */
+ const nghttp2_nv nopath_reqnv[] = {MAKE_NV(":scheme", "https"),
+ MAKE_NV(":method", "GET"),
+ MAKE_NV(":authority", "localhost")};
+ const nghttp2_nv earlyconnect_reqnv[] = {
+ MAKE_NV(":method", "CONNECT"), MAKE_NV(":scheme", "https"),
+ MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost")};
+ const nghttp2_nv lateconnect_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")};
+ const nghttp2_nv duppath_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
+ MAKE_NV(":path", "/")};
+ const nghttp2_nv badcl_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
+ MAKE_NV("content-length", "-1")};
+ const nghttp2_nv dupcl_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
+ MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0")};
+ const nghttp2_nv badhd_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"),
+ MAKE_NV("connection", "close")};
+ const nghttp2_nv badauthority_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"),
+ MAKE_NV(":authority", "\x0d\x0alocalhost"), MAKE_NV(":path", "/")};
+ const nghttp2_nv badhdbtw_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"),
+ MAKE_NV("foo", "\x0d\x0a"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":path", "/")};
+ const nghttp2_nv asteriskget1_reqnv[] = {
+ MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "GET")};
+ const nghttp2_nv asteriskget2_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":method", "GET"), MAKE_NV(":path", "*")};
+ const nghttp2_nv asteriskoptions1_reqnv[] = {
+ MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "OPTIONS")};
+ const nghttp2_nv asteriskoptions2_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":method", "OPTIONS"), MAKE_NV(":path", "*")};
+ const nghttp2_nv connectproto_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":protocol", "websocket")};
+ const nghttp2_nv connectprotoget_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":protocol", "websocket")};
+ const nghttp2_nv connectprotonopath_reqnv[] = {
+ MAKE_NV(":scheme", "https"), MAKE_NV(":method", "CONNECT"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":protocol", "websocket")};
+ const nghttp2_nv connectprotonoauth_reqnv[] = {
+ MAKE_NV(":scheme", "http"), MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "CONNECT"), MAKE_NV("host", "localhost"),
+ MAKE_NV(":protocol", "websocket")};
+ const nghttp2_nv regularconnect_reqnv[] = {
+ MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")};
+
+ mem = nghttp2_mem_default();
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ 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, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* response header lacks :status */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 1,
+ NGHTTP2_STREAM_OPENING, nostatus_resnv,
+ ARRLEN(nostatus_resnv));
+
+ /* response header has 2 :status */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 3,
+ NGHTTP2_STREAM_OPENING, dupstatus_resnv,
+ ARRLEN(dupstatus_resnv));
+
+ /* response header has bad pseudo header :scheme */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 5,
+ NGHTTP2_STREAM_OPENING, badpseudo_resnv,
+ ARRLEN(badpseudo_resnv));
+
+ /* response header has :status after regular header field */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 7,
+ NGHTTP2_STREAM_OPENING, latepseudo_resnv,
+ ARRLEN(latepseudo_resnv));
+
+ /* response header has bad status code */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 9,
+ NGHTTP2_STREAM_OPENING, badstatus_resnv,
+ ARRLEN(badstatus_resnv));
+
+ /* response header has bad content-length */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 11,
+ NGHTTP2_STREAM_OPENING, badcl_resnv,
+ ARRLEN(badcl_resnv));
+
+ /* response header has multiple content-length */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 13,
+ NGHTTP2_STREAM_OPENING, dupcl_resnv,
+ ARRLEN(dupcl_resnv));
+
+ /* response header has disallowed header field */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 15,
+ NGHTTP2_STREAM_OPENING, badhd_resnv,
+ ARRLEN(badhd_resnv));
+
+ /* response header has content-length with 100 status code */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 17,
+ NGHTTP2_STREAM_OPENING, cl1xx_resnv,
+ ARRLEN(cl1xx_resnv));
+
+ /* response header has 0 content-length with 204 status code */
+ check_nghttp2_http_recv_headers_ok(session, &deflater, 19,
+ NGHTTP2_STREAM_OPENING, cl204_resnv,
+ ARRLEN(cl204_resnv));
+
+ /* response header has nonzero content-length with 204 status
+ code */
+ check_nghttp2_http_recv_headers_fail(
+ session, &deflater, 21, NGHTTP2_STREAM_OPENING, clnonzero204_resnv,
+ ARRLEN(clnonzero204_resnv));
+
+ /* status code 101 should not be used in HTTP/2 because it is used
+ for HTTP Upgrade which HTTP/2 removes. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 23,
+ NGHTTP2_STREAM_OPENING, status101_resnv,
+ ARRLEN(status101_resnv));
+
+ /* Specific characters check for host field in response header
+ should not be done as its use is undefined. */
+ check_nghttp2_http_recv_headers_ok(
+ session, &deflater, 25, NGHTTP2_STREAM_OPENING, unexpectedhost_resnv,
+ ARRLEN(unexpectedhost_resnv));
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ /* check server side */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* request header has no :path */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 1, -1, nopath_reqnv,
+ ARRLEN(nopath_reqnv));
+
+ /* request header has CONNECT method, but followed by :path */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1,
+ earlyconnect_reqnv,
+ ARRLEN(earlyconnect_reqnv));
+
+ /* request header has CONNECT method following :path */
+ check_nghttp2_http_recv_headers_fail(
+ session, &deflater, 5, -1, lateconnect_reqnv, ARRLEN(lateconnect_reqnv));
+
+ /* request header has multiple :path */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, duppath_reqnv,
+ ARRLEN(duppath_reqnv));
+
+ /* request header has bad content-length */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 9, -1, badcl_reqnv,
+ ARRLEN(badcl_reqnv));
+
+ /* request header has multiple content-length */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 11, -1, dupcl_reqnv,
+ ARRLEN(dupcl_reqnv));
+
+ /* request header has disallowed header field */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 13, -1, badhd_reqnv,
+ ARRLEN(badhd_reqnv));
+
+ /* request header has :authority header field containing illegal
+ characters */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 15, -1,
+ badauthority_reqnv,
+ ARRLEN(badauthority_reqnv));
+
+ /* request header has regular header field containing illegal
+ character before all mandatory header fields are seen. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 17, -1,
+ badhdbtw_reqnv, ARRLEN(badhdbtw_reqnv));
+
+ /* request header has "*" in :path header field while method is GET.
+ :path is received before :method */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 19, -1,
+ asteriskget1_reqnv,
+ ARRLEN(asteriskget1_reqnv));
+
+ /* request header has "*" in :path header field while method is GET.
+ :method is received before :path */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 21, -1,
+ asteriskget2_reqnv,
+ ARRLEN(asteriskget2_reqnv));
+
+ /* OPTIONS method can include "*" in :path header field. :path is
+ received before :method. */
+ check_nghttp2_http_recv_headers_ok(session, &deflater, 23, -1,
+ asteriskoptions1_reqnv,
+ ARRLEN(asteriskoptions1_reqnv));
+
+ /* OPTIONS method can include "*" in :path header field. :method is
+ received before :path. */
+ check_nghttp2_http_recv_headers_ok(session, &deflater, 25, -1,
+ asteriskoptions2_reqnv,
+ ARRLEN(asteriskoptions2_reqnv));
+
+ /* :protocol is not allowed unless it is enabled by the local
+ endpoint. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 27, -1,
+ connectproto_reqnv,
+ ARRLEN(connectproto_reqnv));
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ /* enable SETTINGS_CONNECT_PROTOCOL */
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ session->pending_enable_connect_protocol = 1;
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by
+ the local endpoint. */
+ check_nghttp2_http_recv_headers_ok(session, &deflater, 1, -1,
+ connectproto_reqnv,
+ ARRLEN(connectproto_reqnv));
+
+ /* :protocol is only allowed with CONNECT method. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1,
+ connectprotoget_reqnv,
+ ARRLEN(connectprotoget_reqnv));
+
+ /* CONNECT method with :protocol requires :path. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 5, -1,
+ connectprotonopath_reqnv,
+ ARRLEN(connectprotonopath_reqnv));
+
+ /* CONNECT method with :protocol requires :authority. */
+ check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1,
+ connectprotonoauth_reqnv,
+ ARRLEN(connectprotonoauth_reqnv));
+
+ /* regular CONNECT method should succeed with
+ SETTINGS_CONNECT_PROTOCOL */
+ check_nghttp2_http_recv_headers_ok(session, &deflater, 9, -1,
+ regularconnect_reqnv,
+ ARRLEN(regularconnect_reqnv));
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_http_content_length(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ nghttp2_stream *stream;
+ const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("te", "trailers"),
+ MAKE_NV("content-length", "9000000000")};
+ const nghttp2_nv cl_reqnv[] = {
+ MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"),
+ MAKE_NV(":scheme", "https"), MAKE_NV("te", "trailers"),
+ MAKE_NV("host", "localhost"), MAKE_NV("content-length", "9000000000")};
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv,
+ ARRLEN(cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+ CU_ASSERT(9000000000LL == stream->content_length);
+ CU_ASSERT(200 == stream->status_code);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* check server side */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_reqnv,
+ ARRLEN(cl_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+ CU_ASSERT(9000000000LL == stream->content_length);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_content_length_mismatch(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ const nghttp2_nv cl_reqnv[] = {
+ MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"),
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"),
+ MAKE_NV("content-length", "20")};
+ const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "20")};
+ nghttp2_outbound_item *item;
+ nghttp2_frame_hd hd;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* header says content-length: 20, but HEADERS has END_STREAM flag set */
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ cl_reqnv, ARRLEN(cl_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* header says content-length: 20, but DATA has 0 byte */
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_reqnv,
+ ARRLEN(cl_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* header says content-length: 20, but DATA has 21 bytes */
+ rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_reqnv,
+ ARRLEN(cl_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ /* Check for client */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* header says content-length: 20, but HEADERS has END_STREAM flag set */
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ cl_resnv, ARRLEN(cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ /* After sending RST_STREAM, stream must be closed */
+ CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* header says content-length: 20, but DATA has 0 byte */
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_resnv,
+ ARRLEN(cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ /* After sending RST_STREAM, stream must be closed */
+ CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* header says content-length: 20, but DATA has 21 bytes */
+ nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_resnv,
+ ARRLEN(cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(NULL != nghttp2_session_get_stream(session, 5));
+ CU_ASSERT(0 == nghttp2_session_send(session));
+ /* After sending RST_STREAM, stream must be closed */
+ CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_bufs_free(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+}
+
+void test_nghttp2_http_non_final_response(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ const nghttp2_nv nonfinal_resnv[] = {
+ MAKE_NV(":status", "100"),
+ };
+ nghttp2_outbound_item *item;
+ nghttp2_frame_hd hd;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* non-final HEADERS with END_STREAM is illegal */
+ open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ nonfinal_resnv, ARRLEN(nonfinal_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* non-final HEADERS followed by non-empty DATA is illegal */
+ open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS,
+ nonfinal_resnv, ARRLEN(nonfinal_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 10, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 10;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* non-final HEADERS followed by empty DATA (without END_STREAM) is
+ ok */
+ open_sent_stream2(session, 5, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS,
+ nonfinal_resnv, ARRLEN(nonfinal_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 5);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* non-final HEADERS followed by empty DATA (with END_STREAM) is
+ illegal */
+ open_sent_stream2(session, 7, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 7, NGHTTP2_FLAG_END_HEADERS,
+ nonfinal_resnv, ARRLEN(nonfinal_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 7);
+ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd);
+ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* non-final HEADERS followed by final HEADERS is OK */
+ open_sent_stream2(session, 9, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS,
+ nonfinal_resnv, ARRLEN(nonfinal_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, resnv,
+ ARRLEN(resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_trailer_headers(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ const nghttp2_nv trailer_reqnv[] = {
+ MAKE_NV("foo", "bar"),
+ };
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* good trailer header */
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ trailer_reqnv, ARRLEN(trailer_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* trailer header without END_STREAM is illegal */
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS,
+ trailer_reqnv, ARRLEN(trailer_reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* trailer header including pseudo header field is illegal */
+ rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ nghttp2_bufs_reset(&bufs);
+
+ rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv,
+ ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ nghttp2_session_del(session);
+
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_ignore_regular_header(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ my_user_data ud;
+ const nghttp2_nv bad_reqnv[] = {
+ MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "GET"),
+ MAKE_NV("foo", "\x0zzz"),
+ MAKE_NV("bar", "buzz"),
+ };
+ const nghttp2_nv bad_ansnv[] = {
+ MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"),
+ MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")};
+ size_t proclen;
+ size_t i;
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+ callbacks.on_header_callback = pause_on_header_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ bad_reqnv, ARRLEN(bad_reqnv), mem);
+
+ CU_ASSERT_FATAL(0 == rv);
+
+ nghttp2_hd_deflate_free(&deflater);
+
+ proclen = 0;
+
+ for (i = 0; i < 4; ++i) {
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
+ nghttp2_buf_len(&bufs.head->buf) - proclen);
+ CU_ASSERT_FATAL(rv > 0);
+ proclen += (size_t)rv;
+ CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv));
+ }
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
+ nghttp2_buf_len(&bufs.head->buf) - proclen);
+ CU_ASSERT_FATAL(rv > 0);
+ /* Without on_invalid_frame_recv_callback, bad header causes stream
+ reset */
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+ proclen += (size_t)rv;
+
+ CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen);
+
+ nghttp2_session_del(session);
+
+ /* use on_invalid_header_callback */
+ callbacks.on_invalid_header_callback = pause_on_invalid_header_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ proclen = 0;
+
+ ud.invalid_header_cb_called = 0;
+
+ for (i = 0; i < 4; ++i) {
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
+ nghttp2_buf_len(&bufs.head->buf) - proclen);
+ CU_ASSERT_FATAL(rv > 0);
+ proclen += (size_t)rv;
+ CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv));
+ }
+
+ CU_ASSERT(0 == ud.invalid_header_cb_called);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
+ nghttp2_buf_len(&bufs.head->buf) - proclen);
+
+ CU_ASSERT_FATAL(rv > 0);
+ CU_ASSERT(1 == ud.invalid_header_cb_called);
+ CU_ASSERT(nghttp2_nv_equal(&bad_reqnv[4], &ud.nv));
+
+ proclen += (size_t)rv;
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
+ nghttp2_buf_len(&bufs.head->buf) - proclen);
+
+ CU_ASSERT(rv > 0);
+ CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[4], &ud.nv));
+
+ nghttp2_session_del(session);
+
+ /* make sure that we can reset stream from
+ on_invalid_header_callback */
+ callbacks.on_header_callback = on_header_callback;
+ callbacks.on_invalid_header_callback = reset_on_invalid_header_callback;
+
+ nghttp2_session_server_new(&session, &callbacks, &ud);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(&bufs.head->buf));
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(1 == item->frame.hd.stream_id);
+
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_ignore_content_length(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "304"),
+ MAKE_NV("content-length", "20")};
+ const nghttp2_nv conn_reqnv[] = {MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":method", "CONNECT"),
+ MAKE_NV("content-length", "999999")};
+ const nghttp2_nv conn_cl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "0")};
+ nghttp2_stream *stream;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ /* If status 304, content-length must be ignored */
+ open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ cl_resnv, ARRLEN(cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* Content-Length in 200 response to CONNECT is ignored */
+ stream = open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING);
+ stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
+
+ rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS,
+ conn_cl_resnv, ARRLEN(conn_cl_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+ CU_ASSERT(-1 == stream->content_length);
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* If request method is CONNECT, content-length must be ignored */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_reqnv,
+ ARRLEN(conn_reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(-1 == stream->content_length);
+ CU_ASSERT((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) > 0);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_record_request_method(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ const nghttp2_nv conn_reqnv[] = {MAKE_NV(":method", "CONNECT"),
+ MAKE_NV(":authority", "localhost")};
+ const nghttp2_nv conn_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "9999")};
+ nghttp2_stream *stream;
+ ssize_t rv;
+ nghttp2_bufs bufs;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ CU_ASSERT(1 == nghttp2_submit_request(session, NULL, conn_reqnv,
+ ARRLEN(conn_reqnv), NULL, NULL));
+
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(NGHTTP2_HTTP_FLAG_METH_CONNECT == stream->http_flags);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_resnv,
+ ARRLEN(conn_resnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT((NGHTTP2_HTTP_FLAG_METH_CONNECT & stream->http_flags) > 0);
+ CU_ASSERT(-1 == stream->content_length);
+
+ /* content-length is ignored in 200 response to a CONNECT request */
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_push_promise(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ nghttp2_stream *stream;
+ const nghttp2_nv bad_reqnv[] = {MAKE_NV(":method", "GET")};
+ nghttp2_outbound_item *item;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ /* good PUSH_PROMISE case */
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);
+
+ rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2,
+ reqnv, ARRLEN(reqnv), mem);
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ stream = nghttp2_session_get_stream(session, 2);
+ CU_ASSERT(NULL != stream);
+
+ nghttp2_bufs_reset(&bufs);
+
+ rv = pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv,
+ ARRLEN(resnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+ CU_ASSERT(200 == stream->status_code);
+
+ nghttp2_bufs_reset(&bufs);
+
+ /* PUSH_PROMISE lacks mandatory header */
+ rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 4,
+ bad_reqnv, ARRLEN(bad_reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ 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);
+
+ nghttp2_bufs_reset(&bufs);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_head_method_upgrade_workaround(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"),
+ MAKE_NV("content-length", "1000000007")};
+ nghttp2_bufs bufs;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ ssize_t rv;
+ nghttp2_stream *stream;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ nghttp2_session_client_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ nghttp2_session_upgrade(session, NULL, 0, NULL);
+
+ rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv,
+ ARRLEN(cl_resnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ stream = nghttp2_session_get_stream(session, 1);
+
+ CU_ASSERT(-1 == stream->content_length);
+
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void) {
+ nghttp2_session *session;
+ nghttp2_session_callbacks callbacks;
+ nghttp2_hd_deflater deflater;
+ nghttp2_mem *mem;
+ nghttp2_bufs bufs;
+ ssize_t rv;
+ const nghttp2_nv ws_reqnv[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV(":method", "GET"),
+ MAKE_NV(":authority", "localhost"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV("foo", "bar "),
+ };
+ nghttp2_outbound_item *item;
+ nghttp2_option *option;
+
+ mem = nghttp2_mem_default();
+ frame_pack_bufs_init(&bufs);
+
+ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+ callbacks.send_callback = null_send_callback;
+
+ /* By default, the leading and trailing white spaces validation is
+ enabled as per RFC 9113. */
+ nghttp2_session_server_new(&session, &callbacks, NULL);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ ws_reqnv, ARRLEN(ws_reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+ CU_ASSERT(0 == nghttp2_session_send(session));
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+
+ /* Turn off the validation */
+ nghttp2_option_new(&option);
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(option, 1);
+
+ nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+ nghttp2_hd_deflate_init(&deflater, mem);
+
+ rv = pack_headers(&bufs, &deflater, 1,
+ NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
+ ws_reqnv, ARRLEN(ws_reqnv), mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
+ nghttp2_buf_len(&bufs.head->buf));
+
+ CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
+
+ item = nghttp2_session_get_next_ob_item(session);
+
+ CU_ASSERT(NULL == item);
+
+ nghttp2_bufs_reset(&bufs);
+ nghttp2_hd_deflate_free(&deflater);
+ nghttp2_session_del(session);
+ nghttp2_option_del(option);
+
+ nghttp2_bufs_free(&bufs);
+}
diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h
new file mode 100644
index 0000000..be4fdf8
--- /dev/null
+++ b/tests/nghttp2_session_test.h
@@ -0,0 +1,184 @@
+/*
+ * 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
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_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_data_no_auto_flow_control(void);
+void test_nghttp2_session_recv_continuation(void);
+void test_nghttp2_session_recv_headers_with_priority(void);
+void test_nghttp2_session_recv_headers_with_padding(void);
+void test_nghttp2_session_recv_headers_early_response(void);
+void test_nghttp2_session_recv_headers_for_closed_stream(void);
+void test_nghttp2_session_recv_headers_with_extpri(void);
+void test_nghttp2_session_server_recv_push_response(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_recv_extension(void);
+void test_nghttp2_session_recv_altsvc(void);
+void test_nghttp2_session_recv_origin(void);
+void test_nghttp2_session_recv_priority_update(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_on_data_received_fail_fast(void);
+void test_nghttp2_session_on_altsvc_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_upgrade2(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_response_push_response(void);
+void test_nghttp2_submit_trailer(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_headers_continuation_extra_large(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_settings_multiple_times(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_submit_extension(void);
+void test_nghttp2_submit_altsvc(void);
+void test_nghttp2_submit_origin(void);
+void test_nghttp2_submit_priority_update(void);
+void test_nghttp2_submit_rst_stream(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_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_stream_get_state(void);
+void test_nghttp2_session_stream_get_something(void);
+void test_nghttp2_session_find_stream(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_magic(void);
+void test_nghttp2_session_delete_data_item(void);
+void test_nghttp2_session_open_idle_stream(void);
+void test_nghttp2_session_cancel_reserved_remote(void);
+void test_nghttp2_session_reset_pending_headers(void);
+void test_nghttp2_session_send_data_callback(void);
+void test_nghttp2_session_on_begin_headers_temporal_failure(void);
+void test_nghttp2_session_defer_then_close(void);
+void test_nghttp2_session_detach_item_from_closed_stream(void);
+void test_nghttp2_session_flooding(void);
+void test_nghttp2_session_change_stream_priority(void);
+void test_nghttp2_session_change_extpri_stream_priority(void);
+void test_nghttp2_session_create_idle_stream(void);
+void test_nghttp2_session_repeated_priority_change(void);
+void test_nghttp2_session_repeated_priority_submission(void);
+void test_nghttp2_session_set_local_window_size(void);
+void test_nghttp2_session_cancel_from_before_frame_send(void);
+void test_nghttp2_session_too_many_settings(void);
+void test_nghttp2_session_removed_closed_stream(void);
+void test_nghttp2_session_pause_data(void);
+void test_nghttp2_session_no_closed_streams(void);
+void test_nghttp2_session_set_stream_user_data(void);
+void test_nghttp2_session_no_rfc7540_priorities(void);
+void test_nghttp2_session_server_fallback_rfc7540_priorities(void);
+void test_nghttp2_session_stream_reset_ratelim(void);
+void test_nghttp2_http_mandatory_headers(void);
+void test_nghttp2_http_content_length(void);
+void test_nghttp2_http_content_length_mismatch(void);
+void test_nghttp2_http_non_final_response(void);
+void test_nghttp2_http_trailer_headers(void);
+void test_nghttp2_http_ignore_regular_header(void);
+void test_nghttp2_http_ignore_content_length(void);
+void test_nghttp2_http_record_request_method(void);
+void test_nghttp2_http_push_promise(void);
+void test_nghttp2_http_head_method_upgrade_workaround(void);
+void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void);
+
+#endif /* NGHTTP2_SESSION_TEST_H */
diff --git a/tests/nghttp2_stream_test.c b/tests/nghttp2_stream_test.c
new file mode 100644
index 0000000..75b4d68
--- /dev/null
+++ b/tests/nghttp2_stream_test.c
@@ -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.
+ */
+#include "nghttp2_stream_test.h"
+
+#include <stdio.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
index 0000000..ad7be64
--- /dev/null
+++ b/tests/nghttp2_stream_test.h
@@ -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_STREAM_TEST_H
+#define NGHTTP2_STREAM_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#endif /* NGHTTP2_STREAM_TEST_H */
diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c
new file mode 100644
index 0000000..1bd4a63
--- /dev/null
+++ b/tests/nghttp2_test_helper.c
@@ -0,0 +1,435 @@
+/*
+ * 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 <stdio.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);
+ nghttp2_frame_unpack_headers_payload(&frame->headers, payload + payloadoff);
+ break;
+ case NGHTTP2_PRIORITY:
+ nghttp2_frame_unpack_priority_payload(&frame->priority, payload);
+ break;
+ case NGHTTP2_RST_STREAM:
+ nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, payload);
+ break;
+ case NGHTTP2_SETTINGS:
+ rv = nghttp2_frame_unpack_settings_payload2(
+ &frame->settings.iv, &frame->settings.niv, payload, payloadlen, mem);
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, payload);
+ break;
+ case NGHTTP2_PING:
+ nghttp2_frame_unpack_ping_payload(&frame->ping, payload);
+ 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);
+ break;
+ case NGHTTP2_ALTSVC:
+ assert(payloadlen > 2);
+ nghttp2_frame_unpack_altsvc_payload2(&frame->ext, payload, payloadlen, mem);
+ break;
+ case NGHTTP2_ORIGIN:
+ rv = nghttp2_frame_unpack_origin_payload(&frame->ext, payload, payloadlen,
+ mem);
+ break;
+ case NGHTTP2_PRIORITY_UPDATE:
+ assert(payloadlen >= 4);
+ nghttp2_frame_unpack_priority_update_payload(
+ &frame->ext, (uint8_t *)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, nghttp2_mem *mem) {
+ size_t i;
+ for (i = 0; i < out->nvlen; ++i) {
+ mem->free(out->nva[i].name, NULL);
+ mem->free(out->nva[i].value, NULL);
+ }
+ memset(out->nva, 0, sizeof(out->nva));
+ out->nvlen = 0;
+}
+
+void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem) {
+ nghttp2_nv *onv = &out->nva[out->nvlen];
+ if (nv->namelen) {
+ onv->name = mem->malloc(nv->namelen, NULL);
+ memcpy(onv->name, nv->name, nv->namelen);
+ } else {
+ onv->name = NULL;
+ }
+ if (nv->valuelen) {
+ onv->value = mem->malloc(nv->valuelen, NULL);
+ 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, nghttp2_mem *mem) {
+ ssize_t rv;
+ nghttp2_nv nv;
+ int inflate_flags;
+ nghttp2_buf_chain *ci;
+ nghttp2_buf *buf;
+ nghttp2_buf bp;
+ int fin;
+ size_t processed;
+
+ processed = 0;
+
+ for (ci = bufs->head; ci; ci = ci->next) {
+ buf = &ci->buf;
+ fin = nghttp2_buf_len(buf) == 0 || ci->next == NULL;
+ bp = *buf;
+
+ if (offset) {
+ size_t n;
+
+ n = nghttp2_min(offset, nghttp2_buf_len(&bp));
+ bp.pos += n;
+ offset -= n;
+ }
+
+ for (;;) {
+ inflate_flags = 0;
+ rv = nghttp2_hd_inflate_hd2(inflater, &nv, &inflate_flags, bp.pos,
+ nghttp2_buf_len(&bp), fin);
+
+ if (rv < 0) {
+ return rv;
+ }
+
+ bp.pos += rv;
+ processed += (size_t)rv;
+
+ if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ if (out) {
+ add_out(out, &nv, mem);
+ }
+ }
+ if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+ break;
+ }
+ if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 &&
+ nghttp2_buf_len(&bp) == 0) {
+ break;
+ }
+ }
+ }
+
+ nghttp2_hd_inflate_end_headers(inflater);
+
+ return (ssize_t)processed;
+}
+
+int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater,
+ int32_t stream_id, uint8_t flags, const nghttp2_nv *nva,
+ size_t nvlen, nghttp2_mem *mem) {
+ nghttp2_nv *dnva;
+ nghttp2_frame frame;
+ int rv;
+
+ nghttp2_nv_array_copy(&dnva, nva, nvlen, mem);
+
+ nghttp2_frame_headers_init(&frame.headers, flags, stream_id,
+ NGHTTP2_HCAT_HEADERS, NULL, dnva, nvlen);
+ rv = nghttp2_frame_pack_headers(bufs, &frame.headers, deflater);
+
+ nghttp2_frame_headers_free(&frame.headers, mem);
+
+ return rv;
+}
+
+int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater,
+ int32_t stream_id, uint8_t flags,
+ int32_t promised_stream_id, const nghttp2_nv *nva,
+ size_t nvlen, nghttp2_mem *mem) {
+ nghttp2_nv *dnva;
+ nghttp2_frame frame;
+ int rv;
+
+ nghttp2_nv_array_copy(&dnva, nva, nvlen, mem);
+
+ nghttp2_frame_push_promise_init(&frame.push_promise, flags, stream_id,
+ promised_stream_id, dnva, nvlen);
+ rv = nghttp2_frame_pack_push_promise(bufs, &frame.push_promise, deflater);
+
+ nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+
+ return rv;
+}
+
+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(nghttp2_mem *mem) {
+ nghttp2_outbound_item *item;
+
+ item = mem->malloc(sizeof(nghttp2_outbound_item), NULL);
+ memset(item, 0, sizeof(nghttp2_outbound_item));
+
+ return item;
+}
+
+nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0);
+ return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec,
+ NGHTTP2_STREAM_OPENED, NULL);
+}
+
+nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id,
+ nghttp2_stream_state initial_state) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0);
+ return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec,
+ initial_state, NULL);
+}
+
+nghttp2_stream *open_sent_stream3(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) {
+ nghttp2_stream *stream;
+
+ assert(nghttp2_session_is_my_stream_id(session, stream_id));
+
+ stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in,
+ initial_state, stream_user_data);
+ session->last_sent_stream_id =
+ nghttp2_max(session->last_sent_stream_id, stream_id);
+ session->next_stream_id =
+ nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2);
+
+ return stream;
+}
+
+nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session,
+ int32_t stream_id,
+ nghttp2_stream *dep_stream) {
+ return open_sent_stream_with_dep_weight(session, stream_id,
+ NGHTTP2_DEFAULT_WEIGHT, dep_stream);
+}
+
+nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session,
+ int32_t stream_id,
+ int32_t weight,
+ nghttp2_stream *dep_stream) {
+ nghttp2_stream *stream;
+
+ assert(nghttp2_session_is_my_stream_id(session, stream_id));
+
+ stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream);
+
+ session->last_sent_stream_id =
+ nghttp2_max(session->last_sent_stream_id, stream_id);
+ session->next_stream_id =
+ nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2);
+
+ return stream;
+}
+
+nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0);
+ return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec,
+ NGHTTP2_STREAM_OPENED, NULL);
+}
+
+nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id,
+ nghttp2_stream_state initial_state) {
+ nghttp2_priority_spec pri_spec;
+
+ nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0);
+ return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec,
+ initial_state, NULL);
+}
+
+nghttp2_stream *open_recv_stream3(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) {
+ nghttp2_stream *stream;
+
+ assert(!nghttp2_session_is_my_stream_id(session, stream_id));
+
+ stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in,
+ initial_state, stream_user_data);
+ session->last_recv_stream_id =
+ nghttp2_max(session->last_recv_stream_id, stream_id);
+
+ return stream;
+}
+
+nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session,
+ int32_t stream_id,
+ nghttp2_stream *dep_stream) {
+ return open_recv_stream_with_dep_weight(session, stream_id,
+ NGHTTP2_DEFAULT_WEIGHT, dep_stream);
+}
+
+nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session,
+ int32_t stream_id,
+ int32_t weight,
+ nghttp2_stream *dep_stream) {
+ nghttp2_stream *stream;
+
+ assert(!nghttp2_session_is_my_stream_id(session, stream_id));
+
+ stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream);
+
+ session->last_recv_stream_id =
+ nghttp2_max(session->last_recv_stream_id, stream_id);
+
+ return stream;
+}
diff --git a/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h
new file mode 100644
index 0000000..c66298a
--- /dev/null
+++ b/tests/nghttp2_test_helper.h
@@ -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 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), sizeof((NAME)) - 1, \
+ sizeof((VALUE)) - 1, NGHTTP2_NV_FLAG_NONE \
+ }
+#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0]))
+
+#define assert_nv_equal(A, B, len, mem) \
+ do { \
+ size_t alloclen = sizeof(nghttp2_nv) * len; \
+ const nghttp2_nv *sa = A, *sb = B; \
+ nghttp2_nv *a = mem->malloc(alloclen, NULL); \
+ nghttp2_nv *b = mem->malloc(alloclen, NULL); \
+ 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_])); \
+ } \
+ mem->free(b, NULL); \
+ mem->free(a, NULL); \
+ } 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, nghttp2_mem *mem);
+
+void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem);
+
+ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out,
+ nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem);
+
+int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater,
+ int32_t stream_id, uint8_t flags, const nghttp2_nv *nva,
+ size_t nvlen, nghttp2_mem *mem);
+
+int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater,
+ int32_t stream_id, uint8_t flags,
+ int32_t promised_stream_id, const nghttp2_nv *nva,
+ size_t nvlen, nghttp2_mem *mem);
+
+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(nghttp2_mem *mem);
+
+/* Opens stream. This stream is assumed to be sent from |session|,
+ and session->last_sent_stream_id and session->next_stream_id will
+ be adjusted accordingly. */
+nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id);
+
+nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id,
+ nghttp2_stream_state initial_state);
+
+nghttp2_stream *open_sent_stream3(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);
+
+nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session,
+ int32_t stream_id,
+ nghttp2_stream *dep_stream);
+
+nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session,
+ int32_t stream_id,
+ int32_t weight,
+ nghttp2_stream *dep_stream);
+
+/* Opens stream. This stream is assumed to be received by |session|,
+ and session->last_recv_stream_id will be adjusted accordingly. */
+nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id);
+
+nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id,
+ nghttp2_stream_state initial_state);
+
+nghttp2_stream *open_recv_stream3(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);
+
+nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session,
+ int32_t stream_id,
+ nghttp2_stream *dep_stream);
+
+nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session,
+ int32_t stream_id,
+ int32_t weight,
+ nghttp2_stream *dep_stream);
+
+#endif /* NGHTTP2_TEST_HELPER_H */
diff --git a/tests/testdata/Makefile.am b/tests/testdata/Makefile.am
new file mode 100644
index 0000000..ee38113
--- /dev/null
+++ b/tests/testdata/Makefile.am
@@ -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
index 0000000..e462790
--- /dev/null
+++ b/tests/testdata/cacert.pem
@@ -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
index 0000000..cdd75fc
--- /dev/null
+++ b/tests/testdata/index.html
@@ -0,0 +1 @@
+<html><body>small</body></html>
diff --git a/tests/testdata/privkey.pem b/tests/testdata/privkey.pem
new file mode 100644
index 0000000..0ab825b
--- /dev/null
+++ b/tests/testdata/privkey.pem
@@ -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/CMakeLists.txt b/third-party/CMakeLists.txt
new file mode 100644
index 0000000..b0ef060
--- /dev/null
+++ b/third-party/CMakeLists.txt
@@ -0,0 +1,81 @@
+if(ENABLE_THIRD_PARTY)
+ set(LIBLLHTTP_SOURCES
+ llhttp/src/api.c
+ llhttp/src/http.c
+ llhttp/src/llhttp.c
+ )
+ add_library(llhttp OBJECT ${LIBLLHTTP_SOURCES})
+ target_include_directories(llhttp PRIVATE
+ "${CMAKE_CURRENT_SOURCE_DIR}/llhttp/include"
+ )
+ set_target_properties(llhttp PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ )
+
+ set(LIBURL_PARSER_SOURCES
+ url-parser/url_parser.c
+ )
+ add_library(url-parser OBJECT ${LIBURL_PARSER_SOURCES})
+ set_target_properties(url-parser PROPERTIES
+ POSITION_INDEPENDENT_CODE ON)
+
+ if(HAVE_NEVERBLEED)
+ set(NEVERBLEED_SOURCES
+ neverbleed/neverbleed.c
+ )
+ add_library(neverbleed ${NEVERBLEED_SOURCES})
+ target_include_directories(neverbleed PRIVATE ${OPENSSL_INCLUDE_DIRS})
+ target_include_directories(neverbleed INTERFACE
+ "${CMAKE_SOURCE_DIR}/third-party/neverbleed"
+ )
+ target_link_libraries(neverbleed ${OPENSSL_LIBRARIES})
+ endif()
+
+ if(HAVE_MRUBY)
+ # EXTRA_DIST = build_config.rb mruby/*
+
+ set(MRUBY_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/mruby/build")
+ set(MRUBY_LIBRARY
+ "${CMAKE_STATIC_LIBRARY_PREFIX}mruby${CMAKE_STATIC_LIBRARY_SUFFIX}"
+ )
+
+ # The mruby build needs some env vars. Alternatively, look at cmake -P
+ if(CMAKE_VERSION VERSION_LESS "3.1")
+ # XXX works only for Unixes?
+ set(ENV_COMMAND env)
+ else()
+ set(ENV_COMMAND ${CMAKE_COMMAND} -E env)
+ endif()
+ # Required for the Ninja generator. For older CMake, you first have to
+ # invoke 'ninja mruby' before building dependents.
+ if(CMAKE_VERSION VERSION_LESS "3.2")
+ set(_byproducts)
+ else()
+ set(_byproducts BYPRODUCTS "mruby/build/lib/${MRUBY_LIBRARY}")
+ endif()
+ add_custom_target(mruby
+ COMMAND ${ENV_COMMAND}
+ "MRUBY_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/build_config.rb"
+ "BUILD_DIR=${MRUBY_BUILD_DIR}"
+ "INSTALL_DIR=${MRUBY_BUILD_DIR}/install/bin"
+ "MRUBY_CC=${CMAKE_C_COMPILER}" "MRUBY_CXX=${CMAKE_CXX_COMPILER}"
+ "${CMAKE_CURRENT_SOURCE_DIR}/mruby/minirake"
+ -f "${CMAKE_CURRENT_SOURCE_DIR}/mruby/Rakefile"
+ ${_byproducts}
+ VERBATIM
+ )
+
+ # Make the mruby library available to others in this project without them
+ # having to worry about include dirs and the mruby location.
+ add_library(mruby-lib STATIC IMPORTED GLOBAL)
+ set_target_properties(mruby-lib PROPERTIES
+ IMPORTED_LOCATION "${MRUBY_BUILD_DIR}/lib/${MRUBY_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/mruby/include"
+ )
+ add_dependencies(mruby-lib mruby)
+
+ set_directory_properties(PROPERTIES
+ ADDITIONAL_MAKE_CLEAN_FILES mruby/build
+ )
+ endif()
+endif()
diff --git a/third-party/Makefile.am b/third-party/Makefile.am
new file mode 100644
index 0000000..8b47532
--- /dev/null
+++ b/third-party/Makefile.am
@@ -0,0 +1,593 @@
+# 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 = @DEFS@
+
+EXTRA_DIST = CMakeLists.txt build_config.rb
+
+# Enumerate all mruby files with the following command:
+# find mruby -type f ! -ipath 'mruby/.*' | awk '{print "\t"$0" \\"}'
+EXTRA_DIST += \
+ mruby/AUTHORS \
+ mruby/codespell.txt \
+ mruby/CONTRIBUTING.md \
+ mruby/NEWS \
+ mruby/CODEOWNERS \
+ mruby/appveyor.yml \
+ mruby/benchmark/plot.gpl \
+ mruby/benchmark/bm_ao_render.rb \
+ mruby/benchmark/bm_fib.rb \
+ mruby/benchmark/bm_so_lists.rb \
+ mruby/benchmark/bm_app_lc_fizzbuzz.rb \
+ mruby/Makefile \
+ mruby/LEGAL \
+ mruby/tasks/libmruby.rake \
+ mruby/tasks/core.rake \
+ mruby/tasks/toolchains/visualcpp.rake \
+ mruby/tasks/toolchains/openwrt.rake \
+ mruby/tasks/toolchains/android.rake \
+ mruby/tasks/toolchains/gcc.rake \
+ mruby/tasks/toolchains/clang.rake \
+ mruby/tasks/doc.rake \
+ mruby/tasks/benchmark.rake \
+ mruby/tasks/mrbgems.rake \
+ mruby/tasks/presym.rake \
+ mruby/tasks/bin.rake \
+ mruby/tasks/test.rake \
+ mruby/tasks/mrblib.rake \
+ mruby/README.md \
+ mruby/oss-fuzz/ruby.proto \
+ mruby/oss-fuzz/proto_to_ruby.cpp \
+ mruby/oss-fuzz/mruby_proto_fuzzer.cpp \
+ mruby/oss-fuzz/config/mruby.dict \
+ mruby/oss-fuzz/config/mruby_proto_fuzzer.options \
+ mruby/oss-fuzz/config/mruby_fuzzer.options \
+ mruby/oss-fuzz/mruby_fuzzer.c \
+ mruby/oss-fuzz/proto_to_ruby.h \
+ mruby/build_config/cross-mingw-winetest.rb \
+ mruby/build_config/host-f32.rb \
+ mruby/build_config/chipKITMax32.rb \
+ mruby/build_config/gameboyadvance.rb \
+ mruby/build_config/minimal.rb \
+ mruby/build_config/host-m32.rb \
+ mruby/build_config/host-shared.rb \
+ mruby/build_config/host-debug.rb \
+ mruby/build_config/bench.rb \
+ mruby/build_config/host-cxx.rb \
+ mruby/build_config/mrbc.rb \
+ mruby/build_config/ArduinoDue.rb \
+ mruby/build_config/IntelEdison.rb \
+ mruby/build_config/boxing.rb \
+ mruby/build_config/IntelGalileo.rb \
+ mruby/build_config/ci/msvc.rb \
+ mruby/build_config/ci/gcc-clang.rb \
+ mruby/build_config/nintendo_switch.rb \
+ mruby/build_config/serenity.rb \
+ mruby/build_config/dreamcast_shelf.rb \
+ mruby/build_config/default.rb \
+ mruby/build_config/android_armeabi_v7a_neon_hard.rb \
+ mruby/build_config/host-gprof.rb \
+ mruby/build_config/clang-asan.rb \
+ mruby/build_config/RX630.rb \
+ mruby/build_config/host-nofloat.rb \
+ mruby/build_config/android_armeabi.rb \
+ mruby/build_config/android_arm64_v8a.rb \
+ mruby/build_config/cross-32bit.rb \
+ mruby/build_config/cross-mingw.rb \
+ mruby/build_config/helpers/wine_runner.rb \
+ mruby/lib/mruby/build/command.rb \
+ mruby/lib/mruby/build/load_gems.rb \
+ mruby/lib/mruby/lockfile.rb \
+ mruby/lib/mruby/build.rb \
+ mruby/lib/mruby/core_ext.rb \
+ mruby/lib/mruby/doc.rb \
+ mruby/lib/mruby/gem.rb \
+ mruby/lib/mruby/presym.rb \
+ mruby/lib/mruby/source.rb \
+ mruby/examples/mrbgems/ruby_extension_example/mrbgem.rake \
+ mruby/examples/mrbgems/ruby_extension_example/README.md \
+ mruby/examples/mrbgems/ruby_extension_example/test/example.rb \
+ mruby/examples/mrbgems/ruby_extension_example/mrblib/example.rb \
+ mruby/examples/mrbgems/cdata_extension_example/mrbgem.rake \
+ mruby/examples/mrbgems/cdata_extension_example/README.md \
+ mruby/examples/mrbgems/cdata_extension_example/test/example.c \
+ mruby/examples/mrbgems/cdata_extension_example/src/example.c \
+ mruby/examples/mrbgems/c_and_ruby_extension_example/mrbgem.rake \
+ mruby/examples/mrbgems/c_and_ruby_extension_example/README.md \
+ mruby/examples/mrbgems/c_and_ruby_extension_example/test/example.rb \
+ mruby/examples/mrbgems/c_and_ruby_extension_example/mrblib/example.rb \
+ mruby/examples/mrbgems/c_and_ruby_extension_example/src/example.c \
+ mruby/examples/mrbgems/c_extension_example/mrbgem.rake \
+ mruby/examples/mrbgems/c_extension_example/README.md \
+ mruby/examples/mrbgems/c_extension_example/test/example.rb \
+ mruby/examples/mrbgems/c_extension_example/test/example.c \
+ mruby/examples/mrbgems/c_extension_example/src/example.c \
+ mruby/examples/mrbgems/mruby-YOUR-bigint/TODO-HINT.md \
+ mruby/examples/mrbgems/mruby-YOUR-bigint/core/bigint.c \
+ mruby/examples/mrbgems/mruby-YOUR-bigint/mrbgem.rake \
+ mruby/test/bintest.rb \
+ mruby/test/t/enumerable.rb \
+ mruby/test/t/comparable.rb \
+ mruby/test/t/false.rb \
+ mruby/test/t/exception.rb \
+ mruby/test/t/rangeerror.rb \
+ mruby/test/t/bs_literal.rb \
+ mruby/test/t/regexperror.rb \
+ mruby/test/t/class.rb \
+ mruby/test/t/iterations.rb \
+ mruby/test/t/true.rb \
+ mruby/test/t/lang.rb \
+ mruby/test/t/range.rb \
+ mruby/test/t/kernel.rb \
+ mruby/test/t/unicode.rb \
+ mruby/test/t/localjumperror.rb \
+ mruby/test/t/hash.rb \
+ mruby/test/t/syntax.rb \
+ mruby/test/t/nameerror.rb \
+ mruby/test/t/module.rb \
+ mruby/test/t/argumenterror.rb \
+ mruby/test/t/methods.rb \
+ mruby/test/t/ensure.rb \
+ mruby/test/t/indexerror.rb \
+ mruby/test/t/runtimeerror.rb \
+ mruby/test/t/gc.rb \
+ mruby/test/t/array.rb \
+ mruby/test/t/string.rb \
+ mruby/test/t/proc.rb \
+ mruby/test/t/basicobject.rb \
+ mruby/test/t/integer.rb \
+ mruby/test/t/nomethoderror.rb \
+ mruby/test/t/codegen.rb \
+ mruby/test/t/literals.rb \
+ mruby/test/t/nil.rb \
+ mruby/test/t/typeerror.rb \
+ mruby/test/t/symbol.rb \
+ mruby/test/t/object.rb \
+ mruby/test/t/vformat.rb \
+ mruby/test/t/numeric.rb \
+ mruby/test/t/bs_block.rb \
+ mruby/test/t/float.rb \
+ mruby/test/t/standarderror.rb \
+ mruby/test/t/superclass.rb \
+ mruby/test/assert.rb \
+ mruby/super-linter.report/.keep \
+ mruby/TODO.md \
+ mruby/Doxyfile \
+ mruby/mrblib/00kernel.rb \
+ mruby/mrblib/range.rb \
+ mruby/mrblib/kernel.rb \
+ mruby/mrblib/hash.rb \
+ mruby/mrblib/00class.rb \
+ mruby/mrblib/10error.rb \
+ mruby/mrblib/array.rb \
+ mruby/mrblib/string.rb \
+ mruby/mrblib/compar.rb \
+ mruby/mrblib/symbol.rb \
+ mruby/mrblib/enum.rb \
+ mruby/mrblib/numeric.rb \
+ mruby/build_config.rb \
+ mruby/LICENSE \
+ mruby/src/etc.c \
+ mruby/src/variable.c \
+ mruby/src/cdump.c \
+ mruby/src/init.c \
+ mruby/src/opcode.h \
+ mruby/src/gc.c \
+ mruby/src/load.c \
+ mruby/src/codedump.c \
+ mruby/src/error.c \
+ mruby/src/readfloat.c \
+ mruby/src/state.c \
+ mruby/src/string.c \
+ mruby/src/numeric.c \
+ mruby/src/readnum.c \
+ mruby/src/enum.c \
+ mruby/src/print.c \
+ mruby/src/readint.c \
+ mruby/src/numops.c \
+ mruby/src/hash.c \
+ mruby/src/version.c \
+ mruby/src/backtrace.c \
+ mruby/src/fmt_fp.c \
+ mruby/src/range.c \
+ mruby/src/vm.c \
+ mruby/src/debug.c \
+ mruby/src/symbol.c \
+ mruby/src/error.h \
+ mruby/src/kernel.c \
+ mruby/src/object.c \
+ mruby/src/proc.c \
+ mruby/src/array.c \
+ mruby/src/dump.c \
+ mruby/src/class.c \
+ mruby/src/compar.c \
+ mruby/src/value_array.h \
+ mruby/src/pool.c \
+ mruby/mruby-source.gemspec \
+ mruby/mrbgems/mruby-eval/mrbgem.rake \
+ mruby/mrbgems/mruby-eval/test/eval.rb \
+ mruby/mrbgems/mruby-eval/src/eval.c \
+ mruby/mrbgems/stdlib.gembox \
+ mruby/mrbgems/mruby-string-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-string-ext/test/range.rb \
+ mruby/mrbgems/mruby-string-ext/test/string.rb \
+ mruby/mrbgems/mruby-string-ext/test/numeric.rb \
+ mruby/mrbgems/mruby-string-ext/mrblib/string.rb \
+ mruby/mrbgems/mruby-string-ext/src/string.c \
+ mruby/mrbgems/default-no-stdio.gembox \
+ mruby/mrbgems/mruby-set/mrbgem.rake \
+ mruby/mrbgems/mruby-set/README.md \
+ mruby/mrbgems/mruby-set/test/set.rb \
+ mruby/mrbgems/mruby-set/mrblib/set.rb \
+ mruby/mrbgems/mruby-set/LICENSE \
+ mruby/mrbgems/mruby-set/mruby-set.gem \
+ mruby/mrbgems/mruby-method/mrbgem.rake \
+ mruby/mrbgems/mruby-method/README.md \
+ mruby/mrbgems/mruby-method/test/method.rb \
+ mruby/mrbgems/mruby-method/mrblib/kernel.rb \
+ mruby/mrbgems/mruby-method/mrblib/method.rb \
+ mruby/mrbgems/mruby-method/src/method.c \
+ mruby/mrbgems/mruby-random/mrbgem.rake \
+ mruby/mrbgems/mruby-random/test/random.rb \
+ mruby/mrbgems/mruby-random/src/random.c \
+ mruby/mrbgems/mruby-catch/mrbgem.rake \
+ mruby/mrbgems/mruby-catch/test/catch.rb \
+ mruby/mrbgems/mruby-catch/mrblib/catch.rb \
+ mruby/mrbgems/mruby-catch/src/catch.c \
+ mruby/mrbgems/mruby-cmath/mrbgem.rake \
+ mruby/mrbgems/mruby-cmath/test/cmath.rb \
+ mruby/mrbgems/mruby-cmath/src/cmath.c \
+ mruby/mrbgems/mruby-compar-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-compar-ext/mrblib/compar.rb \
+ mruby/mrbgems/mruby-pack/mrbgem.rake \
+ mruby/mrbgems/mruby-pack/README.md \
+ mruby/mrbgems/mruby-pack/test/pack.rb \
+ mruby/mrbgems/mruby-pack/src/pack.c \
+ mruby/mrbgems/mruby-struct/mrbgem.rake \
+ mruby/mrbgems/mruby-struct/test/struct.rb \
+ mruby/mrbgems/mruby-struct/mrblib/struct.rb \
+ mruby/mrbgems/mruby-struct/src/struct.c \
+ mruby/mrbgems/mruby-bigint/core/bigint.c \
+ mruby/mrbgems/mruby-bigint/core/bigint.h \
+ mruby/mrbgems/mruby-bigint/mrbgem.rake \
+ mruby/mrbgems/mruby-bigint/README.md \
+ mruby/mrbgems/mruby-bigint/test/bigint.rb \
+ mruby/mrbgems/mruby-bigint/README-fgmp.md \
+ mruby/mrbgems/mruby-symbol-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-symbol-ext/test/symbol.rb \
+ mruby/mrbgems/mruby-symbol-ext/mrblib/symbol.rb \
+ mruby/mrbgems/mruby-symbol-ext/src/symbol.c \
+ mruby/mrbgems/mruby-io/mrbgem.rake \
+ mruby/mrbgems/mruby-io/README.md \
+ mruby/mrbgems/mruby-io/test/mruby_io_test.c \
+ mruby/mrbgems/mruby-io/test/io.rb \
+ mruby/mrbgems/mruby-io/test/file.rb \
+ mruby/mrbgems/mruby-io/test/file_test.rb \
+ mruby/mrbgems/mruby-io/mrblib/io.rb \
+ mruby/mrbgems/mruby-io/mrblib/file.rb \
+ mruby/mrbgems/mruby-io/mrblib/file_constants.rb \
+ mruby/mrbgems/mruby-io/mrblib/kernel.rb \
+ mruby/mrbgems/mruby-io/src/mruby_io_gem.c \
+ mruby/mrbgems/mruby-io/src/file_test.c \
+ mruby/mrbgems/mruby-io/src/io.c \
+ mruby/mrbgems/mruby-io/src/file.c \
+ mruby/mrbgems/mruby-io/include/mruby/ext/io.h \
+ mruby/mrbgems/mruby-compiler/core/parse.y \
+ mruby/mrbgems/mruby-compiler/core/y.tab.c \
+ mruby/mrbgems/mruby-compiler/core/node.h \
+ mruby/mrbgems/mruby-compiler/core/keywords \
+ mruby/mrbgems/mruby-compiler/core/codegen.c \
+ mruby/mrbgems/mruby-compiler/core/lex.def \
+ mruby/mrbgems/mruby-compiler/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-config/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-config/mruby-config \
+ mruby/mrbgems/mruby-bin-config/mruby-config.bat \
+ mruby/mrbgems/mruby-proc-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-proc-ext/test/proc.rb \
+ mruby/mrbgems/mruby-proc-ext/test/proc.c \
+ mruby/mrbgems/mruby-proc-ext/mrblib/proc.rb \
+ mruby/mrbgems/mruby-proc-ext/src/proc.c \
+ mruby/mrbgems/mruby-data/mrbgem.rake \
+ mruby/mrbgems/mruby-data/test/data.rb \
+ mruby/mrbgems/mruby-data/src/data.c \
+ mruby/mrbgems/mruby-dir/mrbgem.rake \
+ mruby/mrbgems/mruby-dir/README.md \
+ mruby/mrbgems/mruby-dir/test/dir.rb \
+ mruby/mrbgems/mruby-dir/test/dirtest.c \
+ mruby/mrbgems/mruby-dir/mrblib/dir.rb \
+ mruby/mrbgems/mruby-dir/src/Win/dirent.c \
+ mruby/mrbgems/mruby-dir/src/dir.c \
+ mruby/mrbgems/mruby-object-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-object-ext/test/nil.rb \
+ mruby/mrbgems/mruby-object-ext/test/object.rb \
+ mruby/mrbgems/mruby-object-ext/mrblib/object.rb \
+ mruby/mrbgems/mruby-object-ext/src/object.c \
+ mruby/mrbgems/mruby-kernel-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-kernel-ext/test/kernel.rb \
+ mruby/mrbgems/mruby-kernel-ext/src/kernel.c \
+ mruby/mrbgems/mruby-class-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-class-ext/test/class.rb \
+ mruby/mrbgems/mruby-class-ext/test/module.rb \
+ mruby/mrbgems/mruby-class-ext/mrblib/module.rb \
+ mruby/mrbgems/mruby-class-ext/src/class.c \
+ mruby/mrbgems/mruby-binding/mrbgem.rake \
+ mruby/mrbgems/mruby-binding/test/binding.rb \
+ mruby/mrbgems/mruby-binding/test/binding.c \
+ mruby/mrbgems/mruby-binding/src/binding.c \
+ mruby/mrbgems/mruby-print/mrbgem.rake \
+ mruby/mrbgems/mruby-print/mrblib/print.rb \
+ mruby/mrbgems/mruby-print/src/print.c \
+ mruby/mrbgems/mruby-hash-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-hash-ext/test/hash.rb \
+ mruby/mrbgems/mruby-hash-ext/mrblib/hash.rb \
+ mruby/mrbgems/mruby-hash-ext/src/hash-ext.c \
+ mruby/mrbgems/mruby-sleep/example/sleep.rb \
+ mruby/mrbgems/mruby-sleep/mrbgem.rake \
+ mruby/mrbgems/mruby-sleep/README.md \
+ mruby/mrbgems/mruby-sleep/test/sleep_test.rb \
+ mruby/mrbgems/mruby-sleep/src/sleep.c \
+ mruby/mrbgems/mruby-sprintf/mrbgem.rake \
+ mruby/mrbgems/mruby-sprintf/test/sprintf.rb \
+ mruby/mrbgems/mruby-sprintf/mrblib/string.rb \
+ mruby/mrbgems/mruby-sprintf/src/sprintf.c \
+ mruby/mrbgems/mruby-enum-chain/mrbgem.rake \
+ mruby/mrbgems/mruby-enum-chain/test/enum_chain.rb \
+ mruby/mrbgems/mruby-enum-chain/mrblib/chain.rb \
+ mruby/mrbgems/mruby-range-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-range-ext/test/range.rb \
+ mruby/mrbgems/mruby-range-ext/mrblib/range.rb \
+ mruby/mrbgems/mruby-range-ext/src/range.c \
+ mruby/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c \
+ mruby/mrbgems/mruby-bin-mirb/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-mirb/bintest/mirb.rb \
+ mruby/mrbgems/mruby-metaprog/mrbgem.rake \
+ mruby/mrbgems/mruby-metaprog/test/metaprog.rb \
+ mruby/mrbgems/mruby-metaprog/src/metaprog.c \
+ mruby/mrbgems/mruby-test-inline-struct/mrbgem.rake \
+ mruby/mrbgems/mruby-test-inline-struct/test/inline.c \
+ mruby/mrbgems/mruby-test-inline-struct/test/inline.rb \
+ mruby/mrbgems/mruby-time/mrbgem.rake \
+ mruby/mrbgems/mruby-time/test/time.rb \
+ mruby/mrbgems/mruby-time/mrblib/time.rb \
+ mruby/mrbgems/mruby-time/src/time.c \
+ mruby/mrbgems/mruby-time/include/mruby/time.h \
+ mruby/mrbgems/mruby-proc-binding/mrbgem.rake \
+ mruby/mrbgems/mruby-proc-binding/test/proc-binding.c \
+ mruby/mrbgems/mruby-proc-binding/test/proc-binding.rb \
+ mruby/mrbgems/mruby-proc-binding/src/proc-binding.c \
+ mruby/mrbgems/stdlib-ext.gembox \
+ mruby/mrbgems/metaprog.gembox \
+ mruby/mrbgems/mruby-enumerator/mrbgem.rake \
+ mruby/mrbgems/mruby-enumerator/test/enumerator.rb \
+ mruby/mrbgems/mruby-enumerator/mrblib/enumerator.rb \
+ mruby/mrbgems/math.gembox \
+ mruby/mrbgems/mruby-socket/mrbgem.rake \
+ mruby/mrbgems/mruby-socket/README.md \
+ mruby/mrbgems/mruby-socket/test/socket.rb \
+ mruby/mrbgems/mruby-socket/test/udpsocket.rb \
+ mruby/mrbgems/mruby-socket/test/tcpsocket.rb \
+ mruby/mrbgems/mruby-socket/test/sockettest.c \
+ mruby/mrbgems/mruby-socket/test/basicsocket.rb \
+ mruby/mrbgems/mruby-socket/test/addrinfo.rb \
+ mruby/mrbgems/mruby-socket/test/unix.rb \
+ mruby/mrbgems/mruby-socket/test/ipsocket.rb \
+ mruby/mrbgems/mruby-socket/mrblib/socket.rb \
+ mruby/mrbgems/mruby-socket/src/socket.c \
+ mruby/mrbgems/mruby-socket/src/gen.rb \
+ mruby/mrbgems/mruby-socket/src/const.cstub \
+ mruby/mrbgems/mruby-socket/src/const.def \
+ mruby/mrbgems/mruby-objectspace/mrbgem.rake \
+ mruby/mrbgems/mruby-objectspace/test/objectspace.rb \
+ mruby/mrbgems/mruby-objectspace/src/mruby_objectspace.c \
+ mruby/mrbgems/full-core.gembox \
+ mruby/mrbgems/mruby-rational/mrbgem.rake \
+ mruby/mrbgems/mruby-rational/test/rational.rb \
+ mruby/mrbgems/mruby-rational/mrblib/rational.rb \
+ mruby/mrbgems/mruby-rational/src/rational.c \
+ mruby/mrbgems/mruby-numeric-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-numeric-ext/test/numeric.rb \
+ mruby/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb \
+ mruby/mrbgems/mruby-numeric-ext/src/numeric_ext.c \
+ mruby/mrbgems/mruby-binding-core/mrbgem.rake \
+ mruby/mrbgems/mruby-binding-core/test/binding-core.rb \
+ mruby/mrbgems/mruby-binding-core/src/binding-core.c \
+ mruby/mrbgems/mruby-fiber/mrbgem.rake \
+ mruby/mrbgems/mruby-fiber/test/fiber.rb \
+ mruby/mrbgems/mruby-fiber/src/fiber.c \
+ mruby/mrbgems/mruby-complex/mrbgem.rake \
+ mruby/mrbgems/mruby-complex/test/complex.rb \
+ mruby/mrbgems/mruby-complex/mrblib/complex.rb \
+ mruby/mrbgems/mruby-complex/src/complex.c \
+ mruby/mrbgems/mruby-exit/mrbgem.rake \
+ mruby/mrbgems/mruby-exit/src/mruby-exit.c \
+ mruby/mrbgems/mruby-error/mrbgem.rake \
+ mruby/mrbgems/mruby-error/test/exception.rb \
+ mruby/mrbgems/mruby-error/test/exception.c \
+ mruby/mrbgems/mruby-error/src/exception.c \
+ mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c \
+ mruby/mrbgems/mruby-bin-mruby/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-mruby/bintest/mruby.rb \
+ mruby/mrbgems/mruby-test/mrbgem.rake \
+ mruby/mrbgems/mruby-test/vformat.c \
+ mruby/mrbgems/mruby-test/README.md \
+ mruby/mrbgems/mruby-test/driver.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c \
+ mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c \
+ mruby/mrbgems/mruby-bin-debugger/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-debugger/bintest/mrdb.rb \
+ mruby/mrbgems/mruby-bin-debugger/bintest/print.rb \
+ mruby/mrbgems/mruby-array-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-array-ext/test/array.rb \
+ mruby/mrbgems/mruby-array-ext/mrblib/array.rb \
+ mruby/mrbgems/mruby-array-ext/src/array.c \
+ mruby/mrbgems/mruby-enum-lazy/mrbgem.rake \
+ mruby/mrbgems/mruby-enum-lazy/test/lazy.rb \
+ mruby/mrbgems/mruby-enum-lazy/mrblib/lazy.rb \
+ mruby/mrbgems/mruby-bin-strip/tools/mruby-strip/mruby-strip.c \
+ mruby/mrbgems/mruby-bin-strip/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-strip/bintest/mruby-strip.rb \
+ mruby/mrbgems/mruby-errno/mrbgem.rake \
+ mruby/mrbgems/mruby-errno/README.md \
+ mruby/mrbgems/mruby-errno/test/errno.rb \
+ mruby/mrbgems/mruby-errno/mrblib/errno.rb \
+ mruby/mrbgems/mruby-errno/src/gen.rb \
+ mruby/mrbgems/mruby-errno/src/known_errors_def.cstub \
+ mruby/mrbgems/mruby-errno/src/known_errors.def \
+ mruby/mrbgems/mruby-errno/src/errno.c \
+ mruby/mrbgems/mruby-toplevel-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-toplevel-ext/test/toplevel.rb \
+ mruby/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb \
+ mruby/mrbgems/mruby-math/mrbgem.rake \
+ mruby/mrbgems/mruby-math/test/math.rb \
+ mruby/mrbgems/mruby-math/src/math.c \
+ mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c \
+ mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c \
+ mruby/mrbgems/mruby-bin-mrbc/mrbgem.rake \
+ mruby/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb \
+ mruby/mrbgems/stdlib-io.gembox \
+ mruby/mrbgems/default.gembox \
+ mruby/mrbgems/mruby-os-memsize/mrbgem.rake \
+ mruby/mrbgems/mruby-os-memsize/test/memsize.rb \
+ mruby/mrbgems/mruby-os-memsize/src/memsize.c \
+ mruby/mrbgems/default-no-fpu.gembox \
+ mruby/mrbgems/mruby-enum-ext/mrbgem.rake \
+ mruby/mrbgems/mruby-enum-ext/test/enum.rb \
+ mruby/mrbgems/mruby-enum-ext/mrblib/enum.rb \
+ mruby/Rakefile \
+ mruby/SECURITY.md \
+ mruby/doc/mruby_logo_red_icon.png \
+ mruby/doc/mruby3.0.md \
+ mruby/doc/mruby3.1.md \
+ mruby/doc/internal/opcode.md \
+ mruby/doc/internal/boxing.md \
+ mruby/doc/limitations.md \
+ mruby/doc/mruby3.2.md \
+ mruby/doc/guides/compile.md \
+ mruby/doc/guides/mrbgems.md \
+ mruby/doc/guides/debugger.md \
+ mruby/doc/guides/link.md \
+ mruby/doc/guides/symbol.md \
+ mruby/doc/guides/gc-arena-howto.md \
+ mruby/doc/guides/mrbconf.md \
+ mruby/minirake \
+ mruby/include/mruby/opcode.h \
+ mruby/include/mruby/re.h \
+ mruby/include/mruby/hash.h \
+ mruby/include/mruby/string.h \
+ mruby/include/mruby/presym.h \
+ mruby/include/mruby/object.h \
+ mruby/include/mruby/class.h \
+ mruby/include/mruby/ops.h \
+ mruby/include/mruby/irep.h \
+ mruby/include/mruby/compile.h \
+ mruby/include/mruby/array.h \
+ mruby/include/mruby/range.h \
+ mruby/include/mruby/throw.h \
+ mruby/include/mruby/variable.h \
+ mruby/include/mruby/boxing_word.h \
+ mruby/include/mruby/istruct.h \
+ mruby/include/mruby/data.h \
+ mruby/include/mruby/common.h \
+ mruby/include/mruby/error.h \
+ mruby/include/mruby/debug.h \
+ mruby/include/mruby/boxing_no.h \
+ mruby/include/mruby/numeric.h \
+ mruby/include/mruby/boxing_nan.h \
+ mruby/include/mruby/value.h \
+ mruby/include/mruby/endian.h \
+ mruby/include/mruby/internal.h \
+ mruby/include/mruby/dump.h \
+ mruby/include/mruby/version.h \
+ mruby/include/mruby/khash.h \
+ mruby/include/mruby/gc.h \
+ mruby/include/mruby/presym/enable.h \
+ mruby/include/mruby/presym/disable.h \
+ mruby/include/mruby/presym/scanning.h \
+ mruby/include/mruby/proc.h \
+ mruby/include/mrbconf.h \
+ mruby/include/mruby.h
+
+if ENABLE_THIRD_PARTY
+
+noinst_LTLIBRARIES = liburl-parser.la
+liburl_parser_la_SOURCES = \
+ url-parser/url_parser.c \
+ url-parser/url_parser.h
+
+noinst_LTLIBRARIES += libllhttp.la
+libllhttp_la_SOURCES = \
+ llhttp/src/api.c \
+ llhttp/src/http.c \
+ llhttp/src/llhttp.c \
+ llhttp/include/llhttp.h
+libllhttp_la_CPPFLAGS = -I${srcdir}/llhttp/include
+
+if HAVE_NEVERBLEED
+noinst_LTLIBRARIES += libneverbleed.la
+libneverbleed_la_CPPFLAGS = @OPENSSL_CFLAGS@
+libneverbleed_la_LIBADD = @OPENSSL_LIBS@
+libneverbleed_la_SOURCES = neverbleed/neverbleed.c neverbleed/neverbleed.h
+endif # HAVE_NEVERBLEED
+
+if HAVE_MRUBY
+
+.PHONY: all-local clean mruby
+
+mruby:
+ mkdir -p "${abs_builddir}/mruby/build"
+ diff "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build/build_config.rb" >& /dev/null || \
+ cp "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build"
+ MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \
+ BUILD_DIR="${abs_builddir}/mruby/build" \
+ INSTALL_DIR="${abs_builddir}/mruby/build/install/bin" \
+ MRUBY_CC="${CC}" MRUBY_CXX="$(firstword $(CXX))" MRUBY_LD="${LD}" \
+ MRUBY_AR="${AR}" \
+ HOST="${host}" BUILD="${build}" \
+ "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile"
+
+all-local: mruby
+
+clean-local:
+ [ ! -f "${abs_builddir}/mruby/build/build_config.rb" ] || \
+ MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \
+ BUILD_DIR="${abs_builddir}/mruby/build" \
+ MRUBY_CC="${CC}" \
+ "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile" clean
+
+endif # HAVE_MRUBY
+
+endif # ENABLE_THIRD_PARTY
diff --git a/third-party/build_config.rb b/third-party/build_config.rb
new file mode 100644
index 0000000..e06a4cc
--- /dev/null
+++ b/third-party/build_config.rb
@@ -0,0 +1,34 @@
+def config(conf)
+ toolchain :clang if ENV['MRUBY_CC'].include? "clang"
+ toolchain :gcc if ENV['MRUBY_CC'].include? "gcc"
+
+ conf.cc.command = ENV['MRUBY_CC']
+ conf.cxx.command = ENV['MRUBY_CXX']
+
+ if ENV['MRUBY_LD']
+ conf.linker.command = ENV['MRUBY_LD']
+ end
+ if ENV['MRUBY_AR']
+ conf.archiver.command = ENV['MRUBY_AR']
+ end
+
+ # C++ project needs this. Without this, mruby exception does not
+ # properly destroy C++ object allocated on stack.
+ conf.enable_cxx_exception
+
+ conf.build_dir = ENV['BUILD_DIR']
+
+ # include the default GEMs
+ conf.gembox 'default'
+ conf.gem :core => 'mruby-eval'
+end
+
+if ENV['BUILD'] == ENV['HOST'] then
+ MRuby::Build.new do |conf|
+ config(conf)
+ end
+else
+ MRuby::CrossBuild.new(ENV['HOST']) do |conf|
+ config(conf)
+ end
+end
diff --git a/third-party/llhttp/LICENSE-MIT b/third-party/llhttp/LICENSE-MIT
new file mode 100644
index 0000000..6c1512d
--- /dev/null
+++ b/third-party/llhttp/LICENSE-MIT
@@ -0,0 +1,22 @@
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2018.
+
+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/llhttp/README.md b/third-party/llhttp/README.md
new file mode 100644
index 0000000..e982ed0
--- /dev/null
+++ b/third-party/llhttp/README.md
@@ -0,0 +1,482 @@
+# llhttp
+[![CI](https://github.com/nodejs/llhttp/workflows/CI/badge.svg)](https://github.com/nodejs/llhttp/actions?query=workflow%3ACI)
+
+Port of [http_parser][0] to [llparse][1].
+
+## Why?
+
+Let's face it, [http_parser][0] is practically unmaintainable. Even
+introduction of a single new method results in a significant code churn.
+
+This project aims to:
+
+* Make it maintainable
+* Verifiable
+* Improving benchmarks where possible
+
+More details in [Fedor Indutny's talk at JSConf EU 2019](https://youtu.be/x3k_5Mi66sY)
+
+## How?
+
+Over time, different approaches for improving [http_parser][0]'s code base
+were tried. However, all of them failed due to resulting significant performance
+degradation.
+
+This project is a port of [http_parser][0] to TypeScript. [llparse][1] is used
+to generate the output C source file, which could be compiled and
+linked with the embedder's program (like [Node.js][7]).
+
+## Performance
+
+So far llhttp outperforms http_parser:
+
+| | input size | bandwidth | reqs/sec | time |
+|:----------------|-----------:|-------------:|-----------:|--------:|
+| **llhttp** | 8192.00 mb | 1777.24 mb/s | 3583799.39 req/sec | 4.61 s |
+| **http_parser** | 8192.00 mb | 694.66 mb/s | 1406180.33 req/sec | 11.79 s |
+
+llhttp is faster by approximately **156%**.
+
+## Maintenance
+
+llhttp project has about 1400 lines of TypeScript code describing the parser
+itself and around 450 lines of C code and headers providing the helper methods.
+The whole [http_parser][0] is implemented in approximately 2500 lines of C, and
+436 lines of headers.
+
+All optimizations and multi-character matching in llhttp are generated
+automatically, and thus doesn't add any extra maintenance cost. On the contrary,
+most of http_parser's code is hand-optimized and unrolled. Instead describing
+"how" it should parse the HTTP requests/responses, a maintainer should
+implement the new features in [http_parser][0] cautiously, considering
+possible performance degradation and manually optimizing the new code.
+
+## Verification
+
+The state machine graph is encoded explicitly in llhttp. The [llparse][1]
+automatically checks the graph for absence of loops and correct reporting of the
+input ranges (spans) like header names and values. In the future, additional
+checks could be performed to get even stricter verification of the llhttp.
+
+## Usage
+
+```C
+#include "stdio.h"
+#include "llhttp.h"
+#include "string.h"
+
+int handle_on_message_complete(llhttp_t* parser) {
+ fprintf(stdout, "Message completed!\n");
+ return 0;
+}
+
+int main() {
+ llhttp_t parser;
+ llhttp_settings_t settings;
+
+ /*Initialize user callbacks and settings */
+ llhttp_settings_init(&settings);
+
+ /*Set user callback */
+ settings.on_message_complete = handle_on_message_complete;
+
+ /*Initialize the parser in HTTP_BOTH mode, meaning that it will select between
+ *HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
+ *input.
+ */
+ llhttp_init(&parser, HTTP_BOTH, &settings);
+
+ /*Parse request! */
+ const char* request = "GET / HTTP/1.1\r\n\r\n";
+ int request_len = strlen(request);
+
+ enum llhttp_errno err = llhttp_execute(&parser, request, request_len);
+ if (err == HPE_OK) {
+ fprintf(stdout, "Successfully parsed!\n");
+ } else {
+ fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason);
+ }
+}
+```
+For more information on API usage, please refer to [src/native/api.h](https://github.com/nodejs/llhttp/blob/main/src/native/api.h).
+
+## API
+
+### llhttp_settings_t
+
+The settings object contains a list of callbacks that the parser will invoke.
+
+The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_PAUSED` (pause the parser):
+
+* `on_message_begin`: Invoked when a new request/response starts.
+* `on_message_complete`: Invoked when a request/response has been completedly parsed.
+* `on_url_complete`: Invoked after the URL has been parsed.
+* `on_method_complete`: Invoked after the HTTP method has been parsed.
+* `on_version_complete`: Invoked after the HTTP version has been parsed.
+* `on_status_complete`: Invoked after the status code has been parsed.
+* `on_header_field_complete`: Invoked after a header name has been parsed.
+* `on_header_value_complete`: Invoked after a header value has been parsed.
+* `on_chunk_header`: Invoked after a new chunk is started. The current chunk length is stored in `parser->content_length`.
+* `on_chunk_extension_name_complete`: Invoked after a chunk extension name is started.
+* `on_chunk_extension_value_complete`: Invoked after a chunk extension value is started.
+* `on_chunk_complete`: Invoked after a new chunk is received.
+* `on_reset`: Invoked after `on_message_complete` and before `on_message_begin` when a new message
+ is received on the same parser. This is not invoked for the first message of the parser.
+
+The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_USER` (error from the callback):
+
+* `on_url`: Invoked when another character of the URL is received.
+* `on_status`: Invoked when another character of the status is received.
+* `on_method`: Invoked when another character of the method is received.
+ When parser is created with `HTTP_BOTH` and the input is a response, this also invoked for the sequence `HTTP/`
+ of the first message.
+* `on_version`: Invoked when another character of the version is received.
+* `on_header_field`: Invoked when another character of a header name is received.
+* `on_header_value`: Invoked when another character of a header value is received.
+* `on_chunk_extension_name`: Invoked when another character of a chunk extension name is received.
+* `on_chunk_extension_value`: Invoked when another character of a extension value is received.
+
+The callback `on_headers_complete`, invoked when headers are completed, can return:
+
+* `0`: Proceed normally.
+* `1`: Assume that request/response has no body, and proceed to parsing the next message.
+* `2`: Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE`.
+* `-1`: Error
+* `HPE_PAUSED`: Pause the parser.
+
+### `void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)`
+
+Initialize the parser with specific type and user settings.
+
+### `uint8_t llhttp_get_type(llhttp_t* parser)`
+
+Returns the type of the parser.
+
+### `uint8_t llhttp_get_http_major(llhttp_t* parser)`
+
+Returns the major version of the HTTP protocol of the current request/response.
+
+### `uint8_t llhttp_get_http_minor(llhttp_t* parser)`
+
+Returns the minor version of the HTTP protocol of the current request/response.
+
+### `uint8_t llhttp_get_method(llhttp_t* parser)`
+
+Returns the method of the current request.
+
+### `int llhttp_get_status_code(llhttp_t* parser)`
+
+Returns the method of the current response.
+
+### `uint8_t llhttp_get_upgrade(llhttp_t* parser)`
+
+Returns `1` if request includes the `Connection: upgrade` header.
+
+### `void llhttp_reset(llhttp_t* parser)`
+
+Reset an already initialized parser back to the start state, preserving the
+existing parser type, callback settings, user data, and lenient flags.
+
+### `void llhttp_settings_init(llhttp_settings_t* settings)`
+
+Initialize the settings object.
+
+### `llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)`
+
+Parse full or partial request/response, invoking user callbacks along the way.
+
+If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing interrupts,
+and such errno is returned from `llhttp_execute()`. If `HPE_PAUSED` was used as a errno,
+the execution can be resumed with `llhttp_resume()` call.
+
+In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` is returned
+after fully parsing the request/response. If the user wishes to continue parsing,
+they need to invoke `llhttp_resume_after_upgrade()`.
+
+**if this function ever returns a non-pause type error, it will continue to return
+the same error upon each successive call up until `llhttp_init()` is called.**
+
+### `llhttp_errno_t llhttp_finish(llhttp_t* parser)`
+
+This method should be called when the other side has no further bytes to
+send (e.g. shutdown of readable side of the TCP connection.)
+
+Requests without `Content-Length` and other messages might require treating
+all incoming bytes as the part of the body, up to the last byte of the
+connection.
+
+This method will invoke `on_message_complete()` callback if the
+request was terminated safely. Otherwise a error code would be returned.
+
+
+### `int llhttp_message_needs_eof(const llhttp_t* parser)`
+
+Returns `1` if the incoming message is parsed until the last byte, and has to be completed by calling `llhttp_finish()` on EOF.
+
+### `int llhttp_should_keep_alive(const llhttp_t* parser)`
+
+Returns `1` if there might be any other messages following the last that was
+successfully parsed.
+
+### `void llhttp_pause(llhttp_t* parser)`
+
+Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set
+appropriate error reason.
+
+**Do not call this from user callbacks! User callbacks must return
+`HPE_PAUSED` if pausing is required.**
+
+### `void llhttp_resume(llhttp_t* parser)`
+
+Might be called to resume the execution after the pause in user's callback.
+
+See `llhttp_execute()` above for details.
+
+**Call this only if `llhttp_execute()` returns `HPE_PAUSED`.**
+
+### `void llhttp_resume_after_upgrade(llhttp_t* parser)`
+
+Might be called to resume the execution after the pause in user's callback.
+See `llhttp_execute()` above for details.
+
+**Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`**
+
+### `llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)`
+
+Returns the latest error.
+
+### `const char* llhttp_get_error_reason(const llhttp_t* parser)`
+
+Returns the verbal explanation of the latest returned error.
+
+**User callback should set error reason when returning the error. See
+`llhttp_set_error_reason()` for details.**
+
+### `void llhttp_set_error_reason(llhttp_t* parser, const char* reason)`
+
+Assign verbal description to the returned error. Must be called in user
+callbacks right before returning the errno.
+
+**`HPE_USER` error code might be useful in user callbacks.**
+
+### `const char* llhttp_get_error_pos(const llhttp_t* parser)`
+
+Returns the pointer to the last parsed byte before the returned error. The
+pointer is relative to the `data` argument of `llhttp_execute()`.
+
+**This method might be useful for counting the number of parsed bytes.**
+
+### `const char* llhttp_errno_name(llhttp_errno_t err)`
+
+Returns textual name of error code.
+
+### `const char* llhttp_method_name(llhttp_method_t method)`
+
+Returns textual name of HTTP method.
+
+### `const char* llhttp_status_name(llhttp_status_t status)`
+
+Returns textual name of HTTP status.
+
+### `void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient header value parsing (disabled by default).
+Lenient parsing disables header value token checks, extending llhttp's
+protocol support to highly non-compliant clients/server.
+
+No `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when
+lenient parsing is "on".
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of conflicting `Transfer-Encoding` and
+`Content-Length` headers (disabled by default).
+
+Normally `llhttp` would error when `Transfer-Encoding` is present in
+conjunction with `Content-Length`.
+
+This error is important to prevent HTTP request smuggling, but may be less desirable
+for small number of cases involving legacy servers.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of `Connection: close` and HTTP/1.0
+requests responses.
+
+Normally `llhttp` would error the HTTP request/response
+after the request/response with `Connection: close` and `Content-Length`.
+
+This is important to prevent cache poisoning attacks,
+but might interact badly with outdated and insecure clients.
+
+With this flag the extra request/response will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of `Transfer-Encoding` header.
+
+Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value
+and another value after it (either in a single header or in multiple
+headers whose value are internally joined using `, `).
+
+This is mandated by the spec to reliably determine request body size and thus
+avoid request smuggling.
+
+With this flag the extra value will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_version(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of HTTP version.
+
+Normally `llhttp` would error when the HTTP version in the request or status line
+is not `0.9`, `1.0`, `1.1` or `2.0`.
+With this flag the extra value will be parsed normally.
+
+**Enabling this flag can pose a security issue since you will allow unsupported HTTP versions. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of additional data received after a message ends
+and keep-alive is disabled.
+
+Normally `llhttp` would error when additional unexpected data is received if the message
+contains the `Connection` header with `close` value.
+With this flag the extra data will discarded without throwing an error.
+
+**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of incomplete CRLF sequences.
+
+Normally `llhttp` would error when a CR is not followed by LF when terminating the
+request line, the status line, the headers or a chunk header.
+With this flag only a CR is required to terminate such sections.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+### `void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled)`
+
+Enables/disables lenient handling of chunks not separated via CRLF.
+
+Normally `llhttp` would error when after a chunk data a CRLF is missing before
+starting a new chunk.
+With this flag the new chunk can start immediately after the previous one.
+
+**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!**
+
+## Build Instructions
+
+Make sure you have [Node.js](https://nodejs.org/), npm and npx installed. Then under project directory run:
+
+```sh
+npm install
+make
+```
+
+---
+
+### Bindings to other languages
+
+* Lua: [MunifTanjim/llhttp.lua][11]
+* Python: [pallas/pyllhttp][8]
+* Ruby: [metabahn/llhttp][9]
+* Rust: [JackLiar/rust-llhttp][10]
+
+### Using with CMake
+
+If you want to use this library in a CMake project as a shared library, you can use the snippet below.
+
+```
+FetchContent_Declare(llhttp
+ URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz")
+
+FetchContent_MakeAvailable(llhttp)
+
+# Link with the llhttp_shared target
+target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_shared ${PROJECT_NAME})
+```
+
+If you want to use this library in a CMake project as a static library, you can set some cache variables first.
+
+```
+FetchContent_Declare(llhttp
+ URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz")
+
+set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
+set(BUILD_STATIC_LIBS ON CACHE INTERNAL "")
+FetchContent_MakeAvailable(llhttp)
+
+# Link with the llhttp_static target
+target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_static ${PROJECT_NAME})
+```
+
+_Note that using the git repo directly (e.g., via a git repo url and tag) will not work with FetchContent_Declare because [CMakeLists.txt](./CMakeLists.txt) requires string replacements (e.g., `_RELEASE_`) before it will build._
+
+## Building on Windows
+
+### Installation
+
+* `choco install git`
+* `choco install node`
+* `choco install llvm` (or install the `C++ Clang tools for Windows` optional package from the Visual Studio 2019 installer)
+* `choco install make` (or if you have MinGW, it comes bundled)
+
+1. Ensure that `Clang` and `make` are in your system path.
+2. Using Git Bash, clone the repo to your preferred location.
+3. Cd into the cloned directory and run `npm install`
+5. Run `make`
+6. Your `repo/build` directory should now have `libllhttp.a` and `libllhttp.so` static and dynamic libraries.
+7. When building your executable, you can link to these libraries. Make sure to set the build folder as an include path when building so you can reference the declarations in `repo/build/llhttp.h`.
+
+### A simple example on linking with the library:
+
+Assuming you have an executable `main.cpp` in your current working directory, you would run: `clang++ -Os -g3 -Wall -Wextra -Wno-unused-parameter -I/path/to/llhttp/build main.cpp /path/to/llhttp/build/libllhttp.a -o main.exe`.
+
+If you are getting `unresolved external symbol` linker errors you are likely attempting to build `llhttp.c` without linking it with object files from `api.c` and `http.c`.
+
+#### LICENSE
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2018.
+
+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.
+
+[0]: https://github.com/nodejs/http-parser
+[1]: https://github.com/nodejs/llparse
+[2]: https://en.wikipedia.org/wiki/Register_allocation#Spilling
+[3]: https://en.wikipedia.org/wiki/Tail_call
+[4]: https://llvm.org/docs/LangRef.html
+[5]: https://llvm.org/docs/LangRef.html#call-instruction
+[6]: https://clang.llvm.org/
+[7]: https://github.com/nodejs/node
+[8]: https://github.com/pallas/pyllhttp
+[9]: https://github.com/metabahn/llhttp
+[10]: https://github.com/JackLiar/rust-llhttp
+[11]: https://github.com/MunifTanjim/llhttp.lua
diff --git a/third-party/llhttp/common.gypi b/third-party/llhttp/common.gypi
new file mode 100644
index 0000000..ef7549f
--- /dev/null
+++ b/third-party/llhttp/common.gypi
@@ -0,0 +1,46 @@
+{
+ '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': {
+ # Compile as C++. llhttp.c is actually C99, but C++ is
+ # close enough in this case.
+ 'CompileAs': 2,
+ },
+ 'VCLibrarianTool': {
+ },
+ 'VCLinkerTool': {
+ 'GenerateDebugInformation': 'true',
+ },
+ },
+ 'conditions': [
+ ['OS == "win"', {
+ 'defines': [
+ 'WIN32'
+ ],
+ }]
+ ],
+ },
+}
diff --git a/third-party/llhttp/include/llhttp.h b/third-party/llhttp/include/llhttp.h
new file mode 100644
index 0000000..6588ae5
--- /dev/null
+++ b/third-party/llhttp/include/llhttp.h
@@ -0,0 +1,871 @@
+
+#ifndef INCLUDE_LLHTTP_H_
+#define INCLUDE_LLHTTP_H_
+
+#define LLHTTP_VERSION_MAJOR 9
+#define LLHTTP_VERSION_MINOR 0
+#define LLHTTP_VERSION_PATCH 1
+
+#ifndef INCLUDE_LLHTTP_ITSELF_H_
+#define INCLUDE_LLHTTP_ITSELF_H_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+typedef struct llhttp__internal_s llhttp__internal_t;
+struct llhttp__internal_s {
+ int32_t _index;
+ void* _span_pos0;
+ void* _span_cb0;
+ int32_t error;
+ const char* reason;
+ const char* error_pos;
+ void* data;
+ void* _current;
+ uint64_t content_length;
+ uint8_t type;
+ uint8_t method;
+ uint8_t http_major;
+ uint8_t http_minor;
+ uint8_t header_state;
+ uint8_t lenient_flags;
+ uint8_t upgrade;
+ uint8_t finish;
+ uint16_t flags;
+ uint16_t status_code;
+ uint8_t initial_message_completed;
+ void* settings;
+};
+
+int llhttp__internal_init(llhttp__internal_t* s);
+int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+#endif /* INCLUDE_LLHTTP_ITSELF_H_ */
+
+
+#ifndef LLLLHTTP_C_HEADERS_
+#define LLLLHTTP_C_HEADERS_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum llhttp_errno {
+ HPE_OK = 0,
+ HPE_INTERNAL = 1,
+ HPE_STRICT = 2,
+ HPE_CR_EXPECTED = 25,
+ HPE_LF_EXPECTED = 3,
+ HPE_UNEXPECTED_CONTENT_LENGTH = 4,
+ HPE_UNEXPECTED_SPACE = 30,
+ HPE_CLOSED_CONNECTION = 5,
+ HPE_INVALID_METHOD = 6,
+ HPE_INVALID_URL = 7,
+ HPE_INVALID_CONSTANT = 8,
+ HPE_INVALID_VERSION = 9,
+ HPE_INVALID_HEADER_TOKEN = 10,
+ HPE_INVALID_CONTENT_LENGTH = 11,
+ HPE_INVALID_CHUNK_SIZE = 12,
+ HPE_INVALID_STATUS = 13,
+ HPE_INVALID_EOF_STATE = 14,
+ HPE_INVALID_TRANSFER_ENCODING = 15,
+ HPE_CB_MESSAGE_BEGIN = 16,
+ HPE_CB_HEADERS_COMPLETE = 17,
+ HPE_CB_MESSAGE_COMPLETE = 18,
+ HPE_CB_CHUNK_HEADER = 19,
+ HPE_CB_CHUNK_COMPLETE = 20,
+ HPE_PAUSED = 21,
+ HPE_PAUSED_UPGRADE = 22,
+ HPE_PAUSED_H2_UPGRADE = 23,
+ HPE_USER = 24,
+ HPE_CB_URL_COMPLETE = 26,
+ HPE_CB_STATUS_COMPLETE = 27,
+ HPE_CB_METHOD_COMPLETE = 32,
+ HPE_CB_VERSION_COMPLETE = 33,
+ HPE_CB_HEADER_FIELD_COMPLETE = 28,
+ HPE_CB_HEADER_VALUE_COMPLETE = 29,
+ HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34,
+ HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35,
+ HPE_CB_RESET = 31
+};
+typedef enum llhttp_errno llhttp_errno_t;
+
+enum llhttp_flags {
+ F_CONNECTION_KEEP_ALIVE = 0x1,
+ F_CONNECTION_CLOSE = 0x2,
+ F_CONNECTION_UPGRADE = 0x4,
+ F_CHUNKED = 0x8,
+ F_UPGRADE = 0x10,
+ F_CONTENT_LENGTH = 0x20,
+ F_SKIPBODY = 0x40,
+ F_TRAILING = 0x80,
+ F_TRANSFER_ENCODING = 0x200
+};
+typedef enum llhttp_flags llhttp_flags_t;
+
+enum llhttp_lenient_flags {
+ LENIENT_HEADERS = 0x1,
+ LENIENT_CHUNKED_LENGTH = 0x2,
+ LENIENT_KEEP_ALIVE = 0x4,
+ LENIENT_TRANSFER_ENCODING = 0x8,
+ LENIENT_VERSION = 0x10,
+ LENIENT_DATA_AFTER_CLOSE = 0x20,
+ LENIENT_OPTIONAL_LF_AFTER_CR = 0x40,
+ LENIENT_OPTIONAL_CRLF_AFTER_CHUNK = 0x80
+};
+typedef enum llhttp_lenient_flags llhttp_lenient_flags_t;
+
+enum llhttp_type {
+ HTTP_BOTH = 0,
+ HTTP_REQUEST = 1,
+ HTTP_RESPONSE = 2
+};
+typedef enum llhttp_type llhttp_type_t;
+
+enum llhttp_finish {
+ HTTP_FINISH_SAFE = 0,
+ HTTP_FINISH_SAFE_WITH_CB = 1,
+ HTTP_FINISH_UNSAFE = 2
+};
+typedef enum llhttp_finish llhttp_finish_t;
+
+enum llhttp_method {
+ HTTP_DELETE = 0,
+ HTTP_GET = 1,
+ HTTP_HEAD = 2,
+ HTTP_POST = 3,
+ HTTP_PUT = 4,
+ HTTP_CONNECT = 5,
+ HTTP_OPTIONS = 6,
+ HTTP_TRACE = 7,
+ HTTP_COPY = 8,
+ HTTP_LOCK = 9,
+ HTTP_MKCOL = 10,
+ HTTP_MOVE = 11,
+ HTTP_PROPFIND = 12,
+ HTTP_PROPPATCH = 13,
+ HTTP_SEARCH = 14,
+ HTTP_UNLOCK = 15,
+ HTTP_BIND = 16,
+ HTTP_REBIND = 17,
+ HTTP_UNBIND = 18,
+ HTTP_ACL = 19,
+ HTTP_REPORT = 20,
+ HTTP_MKACTIVITY = 21,
+ HTTP_CHECKOUT = 22,
+ HTTP_MERGE = 23,
+ HTTP_MSEARCH = 24,
+ HTTP_NOTIFY = 25,
+ HTTP_SUBSCRIBE = 26,
+ HTTP_UNSUBSCRIBE = 27,
+ HTTP_PATCH = 28,
+ HTTP_PURGE = 29,
+ HTTP_MKCALENDAR = 30,
+ HTTP_LINK = 31,
+ HTTP_UNLINK = 32,
+ HTTP_SOURCE = 33,
+ HTTP_PRI = 34,
+ HTTP_DESCRIBE = 35,
+ HTTP_ANNOUNCE = 36,
+ HTTP_SETUP = 37,
+ HTTP_PLAY = 38,
+ HTTP_PAUSE = 39,
+ HTTP_TEARDOWN = 40,
+ HTTP_GET_PARAMETER = 41,
+ HTTP_SET_PARAMETER = 42,
+ HTTP_REDIRECT = 43,
+ HTTP_RECORD = 44,
+ HTTP_FLUSH = 45
+};
+typedef enum llhttp_method llhttp_method_t;
+
+enum llhttp_status {
+ HTTP_STATUS_CONTINUE = 100,
+ HTTP_STATUS_SWITCHING_PROTOCOLS = 101,
+ HTTP_STATUS_PROCESSING = 102,
+ HTTP_STATUS_EARLY_HINTS = 103,
+ HTTP_STATUS_RESPONSE_IS_STALE = 110,
+ HTTP_STATUS_REVALIDATION_FAILED = 111,
+ HTTP_STATUS_DISCONNECTED_OPERATION = 112,
+ HTTP_STATUS_HEURISTIC_EXPIRATION = 113,
+ HTTP_STATUS_MISCELLANEOUS_WARNING = 199,
+ HTTP_STATUS_OK = 200,
+ HTTP_STATUS_CREATED = 201,
+ HTTP_STATUS_ACCEPTED = 202,
+ HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203,
+ HTTP_STATUS_NO_CONTENT = 204,
+ HTTP_STATUS_RESET_CONTENT = 205,
+ HTTP_STATUS_PARTIAL_CONTENT = 206,
+ HTTP_STATUS_MULTI_STATUS = 207,
+ HTTP_STATUS_ALREADY_REPORTED = 208,
+ HTTP_STATUS_TRANSFORMATION_APPLIED = 214,
+ HTTP_STATUS_IM_USED = 226,
+ HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299,
+ HTTP_STATUS_MULTIPLE_CHOICES = 300,
+ HTTP_STATUS_MOVED_PERMANENTLY = 301,
+ HTTP_STATUS_FOUND = 302,
+ HTTP_STATUS_SEE_OTHER = 303,
+ HTTP_STATUS_NOT_MODIFIED = 304,
+ HTTP_STATUS_USE_PROXY = 305,
+ HTTP_STATUS_SWITCH_PROXY = 306,
+ HTTP_STATUS_TEMPORARY_REDIRECT = 307,
+ HTTP_STATUS_PERMANENT_REDIRECT = 308,
+ HTTP_STATUS_BAD_REQUEST = 400,
+ HTTP_STATUS_UNAUTHORIZED = 401,
+ HTTP_STATUS_PAYMENT_REQUIRED = 402,
+ HTTP_STATUS_FORBIDDEN = 403,
+ HTTP_STATUS_NOT_FOUND = 404,
+ HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
+ HTTP_STATUS_NOT_ACCEPTABLE = 406,
+ HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407,
+ HTTP_STATUS_REQUEST_TIMEOUT = 408,
+ HTTP_STATUS_CONFLICT = 409,
+ HTTP_STATUS_GONE = 410,
+ HTTP_STATUS_LENGTH_REQUIRED = 411,
+ HTTP_STATUS_PRECONDITION_FAILED = 412,
+ HTTP_STATUS_PAYLOAD_TOO_LARGE = 413,
+ HTTP_STATUS_URI_TOO_LONG = 414,
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
+ HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416,
+ HTTP_STATUS_EXPECTATION_FAILED = 417,
+ HTTP_STATUS_IM_A_TEAPOT = 418,
+ HTTP_STATUS_PAGE_EXPIRED = 419,
+ HTTP_STATUS_ENHANCE_YOUR_CALM = 420,
+ HTTP_STATUS_MISDIRECTED_REQUEST = 421,
+ HTTP_STATUS_UNPROCESSABLE_ENTITY = 422,
+ HTTP_STATUS_LOCKED = 423,
+ HTTP_STATUS_FAILED_DEPENDENCY = 424,
+ HTTP_STATUS_TOO_EARLY = 425,
+ HTTP_STATUS_UPGRADE_REQUIRED = 426,
+ HTTP_STATUS_PRECONDITION_REQUIRED = 428,
+ HTTP_STATUS_TOO_MANY_REQUESTS = 429,
+ HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430,
+ HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ HTTP_STATUS_LOGIN_TIMEOUT = 440,
+ HTTP_STATUS_NO_RESPONSE = 444,
+ HTTP_STATUS_RETRY_WITH = 449,
+ HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450,
+ HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+ HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460,
+ HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463,
+ HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494,
+ HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495,
+ HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496,
+ HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497,
+ HTTP_STATUS_INVALID_TOKEN = 498,
+ HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499,
+ HTTP_STATUS_INTERNAL_SERVER_ERROR = 500,
+ HTTP_STATUS_NOT_IMPLEMENTED = 501,
+ HTTP_STATUS_BAD_GATEWAY = 502,
+ HTTP_STATUS_SERVICE_UNAVAILABLE = 503,
+ HTTP_STATUS_GATEWAY_TIMEOUT = 504,
+ HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505,
+ HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506,
+ HTTP_STATUS_INSUFFICIENT_STORAGE = 507,
+ HTTP_STATUS_LOOP_DETECTED = 508,
+ HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509,
+ HTTP_STATUS_NOT_EXTENDED = 510,
+ HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511,
+ HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520,
+ HTTP_STATUS_WEB_SERVER_IS_DOWN = 521,
+ HTTP_STATUS_CONNECTION_TIMEOUT = 522,
+ HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523,
+ HTTP_STATUS_TIMEOUT_OCCURED = 524,
+ HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525,
+ HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526,
+ HTTP_STATUS_RAILGUN_ERROR = 527,
+ HTTP_STATUS_SITE_IS_OVERLOADED = 529,
+ HTTP_STATUS_SITE_IS_FROZEN = 530,
+ HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561,
+ HTTP_STATUS_NETWORK_READ_TIMEOUT = 598,
+ HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599
+};
+typedef enum llhttp_status llhttp_status_t;
+
+#define HTTP_ERRNO_MAP(XX) \
+ XX(0, OK, OK) \
+ XX(1, INTERNAL, INTERNAL) \
+ XX(2, STRICT, STRICT) \
+ XX(25, CR_EXPECTED, CR_EXPECTED) \
+ XX(3, LF_EXPECTED, LF_EXPECTED) \
+ XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \
+ XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \
+ XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \
+ XX(6, INVALID_METHOD, INVALID_METHOD) \
+ XX(7, INVALID_URL, INVALID_URL) \
+ XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \
+ XX(9, INVALID_VERSION, INVALID_VERSION) \
+ XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \
+ XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \
+ XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \
+ XX(13, INVALID_STATUS, INVALID_STATUS) \
+ XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \
+ XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \
+ XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \
+ XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \
+ XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \
+ XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \
+ XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \
+ XX(21, PAUSED, PAUSED) \
+ XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \
+ XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \
+ XX(24, USER, USER) \
+ XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \
+ XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \
+ XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \
+ XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \
+ XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \
+ XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \
+ XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \
+ XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \
+ XX(31, CB_RESET, CB_RESET) \
+
+
+#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) \
+ XX(5, CONNECT, CONNECT) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(7, TRACE, TRACE) \
+ 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) \
+ XX(16, BIND, BIND) \
+ XX(17, REBIND, REBIND) \
+ XX(18, UNBIND, UNBIND) \
+ XX(19, ACL, ACL) \
+ XX(20, REPORT, REPORT) \
+ XX(21, MKACTIVITY, MKACTIVITY) \
+ XX(22, CHECKOUT, CHECKOUT) \
+ XX(23, MERGE, MERGE) \
+ XX(24, MSEARCH, M-SEARCH) \
+ XX(25, NOTIFY, NOTIFY) \
+ XX(26, SUBSCRIBE, SUBSCRIBE) \
+ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
+ XX(28, PATCH, PATCH) \
+ XX(29, PURGE, PURGE) \
+ XX(30, MKCALENDAR, MKCALENDAR) \
+ XX(31, LINK, LINK) \
+ XX(32, UNLINK, UNLINK) \
+ XX(33, SOURCE, SOURCE) \
+
+
+#define RTSP_METHOD_MAP(XX) \
+ XX(1, GET, GET) \
+ XX(3, POST, POST) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(35, DESCRIBE, DESCRIBE) \
+ XX(36, ANNOUNCE, ANNOUNCE) \
+ XX(37, SETUP, SETUP) \
+ XX(38, PLAY, PLAY) \
+ XX(39, PAUSE, PAUSE) \
+ XX(40, TEARDOWN, TEARDOWN) \
+ XX(41, GET_PARAMETER, GET_PARAMETER) \
+ XX(42, SET_PARAMETER, SET_PARAMETER) \
+ XX(43, REDIRECT, REDIRECT) \
+ XX(44, RECORD, RECORD) \
+ XX(45, FLUSH, FLUSH) \
+
+
+#define HTTP_ALL_METHOD_MAP(XX) \
+ XX(0, DELETE, DELETE) \
+ XX(1, GET, GET) \
+ XX(2, HEAD, HEAD) \
+ XX(3, POST, POST) \
+ XX(4, PUT, PUT) \
+ XX(5, CONNECT, CONNECT) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(7, TRACE, TRACE) \
+ 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) \
+ XX(16, BIND, BIND) \
+ XX(17, REBIND, REBIND) \
+ XX(18, UNBIND, UNBIND) \
+ XX(19, ACL, ACL) \
+ XX(20, REPORT, REPORT) \
+ XX(21, MKACTIVITY, MKACTIVITY) \
+ XX(22, CHECKOUT, CHECKOUT) \
+ XX(23, MERGE, MERGE) \
+ XX(24, MSEARCH, M-SEARCH) \
+ XX(25, NOTIFY, NOTIFY) \
+ XX(26, SUBSCRIBE, SUBSCRIBE) \
+ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
+ XX(28, PATCH, PATCH) \
+ XX(29, PURGE, PURGE) \
+ XX(30, MKCALENDAR, MKCALENDAR) \
+ XX(31, LINK, LINK) \
+ XX(32, UNLINK, UNLINK) \
+ XX(33, SOURCE, SOURCE) \
+ XX(34, PRI, PRI) \
+ XX(35, DESCRIBE, DESCRIBE) \
+ XX(36, ANNOUNCE, ANNOUNCE) \
+ XX(37, SETUP, SETUP) \
+ XX(38, PLAY, PLAY) \
+ XX(39, PAUSE, PAUSE) \
+ XX(40, TEARDOWN, TEARDOWN) \
+ XX(41, GET_PARAMETER, GET_PARAMETER) \
+ XX(42, SET_PARAMETER, SET_PARAMETER) \
+ XX(43, REDIRECT, REDIRECT) \
+ XX(44, RECORD, RECORD) \
+ XX(45, FLUSH, FLUSH) \
+
+
+#define HTTP_STATUS_MAP(XX) \
+ XX(100, CONTINUE, CONTINUE) \
+ XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \
+ XX(102, PROCESSING, PROCESSING) \
+ XX(103, EARLY_HINTS, EARLY_HINTS) \
+ XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \
+ XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \
+ XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \
+ XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \
+ XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \
+ XX(200, OK, OK) \
+ XX(201, CREATED, CREATED) \
+ XX(202, ACCEPTED, ACCEPTED) \
+ XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \
+ XX(204, NO_CONTENT, NO_CONTENT) \
+ XX(205, RESET_CONTENT, RESET_CONTENT) \
+ XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \
+ XX(207, MULTI_STATUS, MULTI_STATUS) \
+ XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \
+ XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \
+ XX(226, IM_USED, IM_USED) \
+ XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \
+ XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \
+ XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \
+ XX(302, FOUND, FOUND) \
+ XX(303, SEE_OTHER, SEE_OTHER) \
+ XX(304, NOT_MODIFIED, NOT_MODIFIED) \
+ XX(305, USE_PROXY, USE_PROXY) \
+ XX(306, SWITCH_PROXY, SWITCH_PROXY) \
+ XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \
+ XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \
+ XX(400, BAD_REQUEST, BAD_REQUEST) \
+ XX(401, UNAUTHORIZED, UNAUTHORIZED) \
+ XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \
+ XX(403, FORBIDDEN, FORBIDDEN) \
+ XX(404, NOT_FOUND, NOT_FOUND) \
+ XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \
+ XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \
+ XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \
+ XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \
+ XX(409, CONFLICT, CONFLICT) \
+ XX(410, GONE, GONE) \
+ XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \
+ XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \
+ XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \
+ XX(414, URI_TOO_LONG, URI_TOO_LONG) \
+ XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \
+ XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \
+ XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \
+ XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \
+ XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \
+ XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \
+ XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \
+ XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \
+ XX(423, LOCKED, LOCKED) \
+ XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \
+ XX(425, TOO_EARLY, TOO_EARLY) \
+ XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \
+ XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \
+ XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \
+ XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \
+ XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \
+ XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \
+ XX(444, NO_RESPONSE, NO_RESPONSE) \
+ XX(449, RETRY_WITH, RETRY_WITH) \
+ XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \
+ XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \
+ XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \
+ XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \
+ XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \
+ XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \
+ XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \
+ XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \
+ XX(498, INVALID_TOKEN, INVALID_TOKEN) \
+ XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \
+ XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \
+ XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \
+ XX(502, BAD_GATEWAY, BAD_GATEWAY) \
+ XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \
+ XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \
+ XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \
+ XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \
+ XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \
+ XX(508, LOOP_DETECTED, LOOP_DETECTED) \
+ XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \
+ XX(510, NOT_EXTENDED, NOT_EXTENDED) \
+ XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \
+ XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \
+ XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \
+ XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \
+ XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \
+ XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \
+ XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \
+ XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \
+ XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \
+ XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \
+ XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \
+ XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \
+ XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \
+ XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+#endif /* LLLLHTTP_C_HEADERS_ */
+
+
+#ifndef INCLUDE_LLHTTP_API_H_
+#define INCLUDE_LLHTTP_API_H_
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <stddef.h>
+
+#if defined(__wasm__)
+#define LLHTTP_EXPORT __attribute__((visibility("default")))
+#else
+#define LLHTTP_EXPORT
+#endif
+
+typedef llhttp__internal_t llhttp_t;
+typedef struct llhttp_settings_s llhttp_settings_t;
+
+typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length);
+typedef int (*llhttp_cb)(llhttp_t*);
+
+struct llhttp_settings_s {
+ /* Possible return values 0, -1, `HPE_PAUSED` */
+ llhttp_cb on_message_begin;
+
+ /* Possible return values 0, -1, HPE_USER */
+ llhttp_data_cb on_url;
+ llhttp_data_cb on_status;
+ llhttp_data_cb on_method;
+ llhttp_data_cb on_version;
+ llhttp_data_cb on_header_field;
+ llhttp_data_cb on_header_value;
+ llhttp_data_cb on_chunk_extension_name;
+ llhttp_data_cb on_chunk_extension_value;
+
+ /* Possible return values:
+ * 0 - Proceed normally
+ * 1 - Assume that request/response has no body, and proceed to parsing the
+ * next message
+ * 2 - Assume absence of body (as above) and make `llhttp_execute()` return
+ * `HPE_PAUSED_UPGRADE`
+ * -1 - Error
+ * `HPE_PAUSED`
+ */
+ llhttp_cb on_headers_complete;
+
+ /* Possible return values 0, -1, HPE_USER */
+ llhttp_data_cb on_body;
+
+ /* Possible return values 0, -1, `HPE_PAUSED` */
+ llhttp_cb on_message_complete;
+ llhttp_cb on_url_complete;
+ llhttp_cb on_status_complete;
+ llhttp_cb on_method_complete;
+ llhttp_cb on_version_complete;
+ llhttp_cb on_header_field_complete;
+ llhttp_cb on_header_value_complete;
+ llhttp_cb on_chunk_extension_name_complete;
+ llhttp_cb on_chunk_extension_value_complete;
+
+ /* When on_chunk_header is called, the current chunk length is stored
+ * in parser->content_length.
+ * Possible return values 0, -1, `HPE_PAUSED`
+ */
+ llhttp_cb on_chunk_header;
+ llhttp_cb on_chunk_complete;
+ llhttp_cb on_reset;
+};
+
+/* Initialize the parser with specific type and user settings.
+ *
+ * NOTE: lifetime of `settings` has to be at least the same as the lifetime of
+ * the `parser` here. In practice, `settings` has to be either a static
+ * variable or be allocated with `malloc`, `new`, etc.
+ */
+LLHTTP_EXPORT
+void llhttp_init(llhttp_t* parser, llhttp_type_t type,
+ const llhttp_settings_t* settings);
+
+LLHTTP_EXPORT
+llhttp_t* llhttp_alloc(llhttp_type_t type);
+
+LLHTTP_EXPORT
+void llhttp_free(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_type(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_http_major(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_http_minor(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_method(llhttp_t* parser);
+
+LLHTTP_EXPORT
+int llhttp_get_status_code(llhttp_t* parser);
+
+LLHTTP_EXPORT
+uint8_t llhttp_get_upgrade(llhttp_t* parser);
+
+/* Reset an already initialized parser back to the start state, preserving the
+ * existing parser type, callback settings, user data, and lenient flags.
+ */
+LLHTTP_EXPORT
+void llhttp_reset(llhttp_t* parser);
+
+/* Initialize the settings object */
+LLHTTP_EXPORT
+void llhttp_settings_init(llhttp_settings_t* settings);
+
+/* Parse full or partial request/response, invoking user callbacks along the
+ * way.
+ *
+ * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing
+ * interrupts, and such errno is returned from `llhttp_execute()`. If
+ * `HPE_PAUSED` was used as a errno, the execution can be resumed with
+ * `llhttp_resume()` call.
+ *
+ * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE`
+ * is returned after fully parsing the request/response. If the user wishes to
+ * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`.
+ *
+ * NOTE: if this function ever returns a non-pause type error, it will continue
+ * to return the same error upon each successive call up until `llhttp_init()`
+ * is called.
+ */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len);
+
+/* This method should be called when the other side has no further bytes to
+ * send (e.g. shutdown of readable side of the TCP connection.)
+ *
+ * Requests without `Content-Length` and other messages might require treating
+ * all incoming bytes as the part of the body, up to the last byte of the
+ * connection. This method will invoke `on_message_complete()` callback if the
+ * request was terminated safely. Otherwise a error code would be returned.
+ */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_finish(llhttp_t* parser);
+
+/* Returns `1` if the incoming message is parsed until the last byte, and has
+ * to be completed by calling `llhttp_finish()` on EOF
+ */
+LLHTTP_EXPORT
+int llhttp_message_needs_eof(const llhttp_t* parser);
+
+/* Returns `1` if there might be any other messages following the last that was
+ * successfully parsed.
+ */
+LLHTTP_EXPORT
+int llhttp_should_keep_alive(const llhttp_t* parser);
+
+/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set
+ * appropriate error reason.
+ *
+ * Important: do not call this from user callbacks! User callbacks must return
+ * `HPE_PAUSED` if pausing is required.
+ */
+LLHTTP_EXPORT
+void llhttp_pause(llhttp_t* parser);
+
+/* Might be called to resume the execution after the pause in user's callback.
+ * See `llhttp_execute()` above for details.
+ *
+ * Call this only if `llhttp_execute()` returns `HPE_PAUSED`.
+ */
+LLHTTP_EXPORT
+void llhttp_resume(llhttp_t* parser);
+
+/* Might be called to resume the execution after the pause in user's callback.
+ * See `llhttp_execute()` above for details.
+ *
+ * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`
+ */
+LLHTTP_EXPORT
+void llhttp_resume_after_upgrade(llhttp_t* parser);
+
+/* Returns the latest return error */
+LLHTTP_EXPORT
+llhttp_errno_t llhttp_get_errno(const llhttp_t* parser);
+
+/* Returns the verbal explanation of the latest returned error.
+ *
+ * Note: User callback should set error reason when returning the error. See
+ * `llhttp_set_error_reason()` for details.
+ */
+LLHTTP_EXPORT
+const char* llhttp_get_error_reason(const llhttp_t* parser);
+
+/* Assign verbal description to the returned error. Must be called in user
+ * callbacks right before returning the errno.
+ *
+ * Note: `HPE_USER` error code might be useful in user callbacks.
+ */
+LLHTTP_EXPORT
+void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
+
+/* Returns the pointer to the last parsed byte before the returned error. The
+ * pointer is relative to the `data` argument of `llhttp_execute()`.
+ *
+ * Note: this method might be useful for counting the number of parsed bytes.
+ */
+LLHTTP_EXPORT
+const char* llhttp_get_error_pos(const llhttp_t* parser);
+
+/* Returns textual name of error code */
+LLHTTP_EXPORT
+const char* llhttp_errno_name(llhttp_errno_t err);
+
+/* Returns textual name of HTTP method */
+LLHTTP_EXPORT
+const char* llhttp_method_name(llhttp_method_t method);
+
+/* Returns textual name of HTTP status */
+LLHTTP_EXPORT
+const char* llhttp_status_name(llhttp_status_t status);
+
+/* Enables/disables lenient header value parsing (disabled by default).
+ *
+ * Lenient parsing disables header value token checks, extending llhttp's
+ * protocol support to highly non-compliant clients/server. No
+ * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when
+ * lenient parsing is "on".
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_headers(llhttp_t* parser, int enabled);
+
+
+/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and
+ * `Content-Length` headers (disabled by default).
+ *
+ * Normally `llhttp` would error when `Transfer-Encoding` is present in
+ * conjunction with `Content-Length`. This error is important to prevent HTTP
+ * request smuggling, but may be less desirable for small number of cases
+ * involving legacy servers.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled);
+
+
+/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0
+ * requests responses.
+ *
+ * Normally `llhttp` would error on (in strict mode) or discard (in loose mode)
+ * the HTTP request/response after the request/response with `Connection: close`
+ * and `Content-Length`. This is important to prevent cache poisoning attacks,
+ * but might interact badly with outdated and insecure clients. With this flag
+ * the extra request/response will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * poisoning attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of `Transfer-Encoding` header.
+ *
+ * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value
+ * and another value after it (either in a single header or in multiple
+ * headers whose value are internally joined using `, `).
+ * This is mandated by the spec to reliably determine request body size and thus
+ * avoid request smuggling.
+ * With this flag the extra value will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of HTTP version.
+ *
+ * Normally `llhttp` would error when the HTTP version in the request or status line
+ * is not `0.9`, `1.0`, `1.1` or `2.0`.
+ * With this flag the invalid value will be parsed normally.
+ *
+ * **Enabling this flag can pose a security issue since you will allow unsupported
+ * HTTP versions. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_version(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of additional data received after a message ends
+ * and keep-alive is disabled.
+ *
+ * Normally `llhttp` would error when additional unexpected data is received if the message
+ * contains the `Connection` header with `close` value.
+ * With this flag the extra data will discarded without throwing an error.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * poisoning attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of incomplete CRLF sequences.
+ *
+ * Normally `llhttp` would error when a CR is not followed by LF when terminating the
+ * request line, the status line, the headers or a chunk header.
+ * With this flag only a CR is required to terminate such sections.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled);
+
+/* Enables/disables lenient handling of chunks not separated via CRLF.
+ *
+ * Normally `llhttp` would error when after a chunk data a CRLF is missing before
+ * starting a new chunk.
+ * With this flag the new chunk can start immediately after the previous one.
+ *
+ * **Enabling this flag can pose a security issue since you will be exposed to
+ * request smuggling attacks. USE WITH CAUTION!**
+ */
+LLHTTP_EXPORT
+void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+#endif /* INCLUDE_LLHTTP_API_H_ */
+
+
+#endif /* INCLUDE_LLHTTP_H_ */
diff --git a/third-party/llhttp/llhttp.gyp b/third-party/llhttp/llhttp.gyp
new file mode 100644
index 0000000..c7b8800
--- /dev/null
+++ b/third-party/llhttp/llhttp.gyp
@@ -0,0 +1,22 @@
+{
+ 'variables': {
+ 'llhttp_sources': [
+ 'src/llhttp.c',
+ 'src/api.c',
+ 'src/http.c',
+ ]
+ },
+ 'targets': [
+ {
+ 'target_name': 'llhttp',
+ 'type': 'static_library',
+ 'include_dirs': [ '.', 'include' ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [ 'include' ],
+ },
+ 'sources': [
+ '<@(llhttp_sources)',
+ ],
+ },
+ ]
+}
diff --git a/third-party/llhttp/src/api.c b/third-party/llhttp/src/api.c
new file mode 100644
index 0000000..e0d0385
--- /dev/null
+++ b/third-party/llhttp/src/api.c
@@ -0,0 +1,494 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "llhttp.h"
+
+#define CALLBACK_MAYBE(PARSER, NAME) \
+ do { \
+ const llhttp_settings_t* settings; \
+ settings = (const llhttp_settings_t*) (PARSER)->settings; \
+ if (settings == NULL || settings->NAME == NULL) { \
+ err = 0; \
+ break; \
+ } \
+ err = settings->NAME((PARSER)); \
+ } while (0)
+
+#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \
+ do { \
+ const llhttp_settings_t* settings; \
+ settings = (const llhttp_settings_t*) (PARSER)->settings; \
+ if (settings == NULL || settings->NAME == NULL) { \
+ err = 0; \
+ break; \
+ } \
+ err = settings->NAME((PARSER), (START), (LEN)); \
+ if (err == -1) { \
+ err = HPE_USER; \
+ llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \
+ } \
+ } while (0)
+
+void llhttp_init(llhttp_t* parser, llhttp_type_t type,
+ const llhttp_settings_t* settings) {
+ llhttp__internal_init(parser);
+
+ parser->type = type;
+ parser->settings = (void*) settings;
+}
+
+
+#if defined(__wasm__)
+
+extern int wasm_on_message_begin(llhttp_t * p);
+extern int wasm_on_url(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_status(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_headers_complete(llhttp_t * p, int status_code,
+ uint8_t upgrade, int should_keep_alive);
+extern int wasm_on_body(llhttp_t* p, const char* at, size_t length);
+extern int wasm_on_message_complete(llhttp_t * p);
+
+static int wasm_on_headers_complete_wrap(llhttp_t* p) {
+ return wasm_on_headers_complete(p, p->status_code, p->upgrade,
+ llhttp_should_keep_alive(p));
+}
+
+const llhttp_settings_t wasm_settings = {
+ wasm_on_message_begin,
+ wasm_on_url,
+ wasm_on_status,
+ NULL,
+ NULL,
+ wasm_on_header_field,
+ wasm_on_header_value,
+ NULL,
+ NULL,
+ wasm_on_headers_complete_wrap,
+ wasm_on_body,
+ wasm_on_message_complete,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+llhttp_t* llhttp_alloc(llhttp_type_t type) {
+ llhttp_t* parser = malloc(sizeof(llhttp_t));
+ llhttp_init(parser, type, &wasm_settings);
+ return parser;
+}
+
+void llhttp_free(llhttp_t* parser) {
+ free(parser);
+}
+
+#endif // defined(__wasm__)
+
+/* Some getters required to get stuff from the parser */
+
+uint8_t llhttp_get_type(llhttp_t* parser) {
+ return parser->type;
+}
+
+uint8_t llhttp_get_http_major(llhttp_t* parser) {
+ return parser->http_major;
+}
+
+uint8_t llhttp_get_http_minor(llhttp_t* parser) {
+ return parser->http_minor;
+}
+
+uint8_t llhttp_get_method(llhttp_t* parser) {
+ return parser->method;
+}
+
+int llhttp_get_status_code(llhttp_t* parser) {
+ return parser->status_code;
+}
+
+uint8_t llhttp_get_upgrade(llhttp_t* parser) {
+ return parser->upgrade;
+}
+
+
+void llhttp_reset(llhttp_t* parser) {
+ llhttp_type_t type = parser->type;
+ const llhttp_settings_t* settings = parser->settings;
+ void* data = parser->data;
+ uint8_t lenient_flags = parser->lenient_flags;
+
+ llhttp__internal_init(parser);
+
+ parser->type = type;
+ parser->settings = (void*) settings;
+ parser->data = data;
+ parser->lenient_flags = lenient_flags;
+}
+
+
+llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) {
+ return llhttp__internal_execute(parser, data, data + len);
+}
+
+
+void llhttp_settings_init(llhttp_settings_t* settings) {
+ memset(settings, 0, sizeof(*settings));
+}
+
+
+llhttp_errno_t llhttp_finish(llhttp_t* parser) {
+ int err;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (parser->error != 0) {
+ return 0;
+ }
+
+ switch (parser->finish) {
+ case HTTP_FINISH_SAFE_WITH_CB:
+ CALLBACK_MAYBE(parser, on_message_complete);
+ if (err != HPE_OK) return err;
+
+ /* FALLTHROUGH */
+ case HTTP_FINISH_SAFE:
+ return HPE_OK;
+ case HTTP_FINISH_UNSAFE:
+ parser->reason = "Invalid EOF state";
+ return HPE_INVALID_EOF_STATE;
+ default:
+ abort();
+ }
+}
+
+
+void llhttp_pause(llhttp_t* parser) {
+ if (parser->error != HPE_OK) {
+ return;
+ }
+
+ parser->error = HPE_PAUSED;
+ parser->reason = "Paused";
+}
+
+
+void llhttp_resume(llhttp_t* parser) {
+ if (parser->error != HPE_PAUSED) {
+ return;
+ }
+
+ parser->error = 0;
+}
+
+
+void llhttp_resume_after_upgrade(llhttp_t* parser) {
+ if (parser->error != HPE_PAUSED_UPGRADE) {
+ return;
+ }
+
+ parser->error = 0;
+}
+
+
+llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) {
+ return parser->error;
+}
+
+
+const char* llhttp_get_error_reason(const llhttp_t* parser) {
+ return parser->reason;
+}
+
+
+void llhttp_set_error_reason(llhttp_t* parser, const char* reason) {
+ parser->reason = reason;
+}
+
+
+const char* llhttp_get_error_pos(const llhttp_t* parser) {
+ return parser->error_pos;
+}
+
+
+const char* llhttp_errno_name(llhttp_errno_t err) {
+#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME;
+ switch (err) {
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+ default: abort();
+ }
+#undef HTTP_ERRNO_GEN
+}
+
+
+const char* llhttp_method_name(llhttp_method_t method) {
+#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING;
+ switch (method) {
+ HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN)
+ default: abort();
+ }
+#undef HTTP_METHOD_GEN
+}
+
+const char* llhttp_status_name(llhttp_status_t status) {
+#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING;
+ switch (status) {
+ HTTP_STATUS_MAP(HTTP_STATUS_GEN)
+ default: abort();
+ }
+#undef HTTP_STATUS_GEN
+}
+
+
+void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_HEADERS;
+ } else {
+ parser->lenient_flags &= ~LENIENT_HEADERS;
+ }
+}
+
+
+void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_CHUNKED_LENGTH;
+ } else {
+ parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH;
+ }
+}
+
+
+void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_KEEP_ALIVE;
+ } else {
+ parser->lenient_flags &= ~LENIENT_KEEP_ALIVE;
+ }
+}
+
+void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_TRANSFER_ENCODING;
+ } else {
+ parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING;
+ }
+}
+
+void llhttp_set_lenient_version(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_VERSION;
+ } else {
+ parser->lenient_flags &= ~LENIENT_VERSION;
+ }
+}
+
+void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE;
+ } else {
+ parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE;
+ }
+}
+
+void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR;
+ } else {
+ parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR;
+ }
+}
+
+void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) {
+ if (enabled) {
+ parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+ } else {
+ parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK;
+ }
+}
+
+/* Callbacks */
+
+
+int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_message_begin);
+ return err;
+}
+
+
+int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_url_complete);
+ return err;
+}
+
+
+int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_status_complete);
+ return err;
+}
+
+
+int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_method_complete);
+ return err;
+}
+
+
+int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_version_complete);
+ return err;
+}
+
+
+int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_header_field_complete);
+ return err;
+}
+
+
+int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_header_value_complete);
+ return err;
+}
+
+
+int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_headers_complete);
+ return err;
+}
+
+
+int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_message_complete);
+ return err;
+}
+
+
+int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_header);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_extension_name_complete);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p);
+ return err;
+}
+
+
+int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_extension_value_complete);
+ return err;
+}
+
+
+int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_complete);
+ return err;
+}
+
+
+int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_reset);
+ return err;
+}
+
+
+/* Private */
+
+
+void llhttp__debug(llhttp_t* s, const char* p, const char* endp,
+ const char* msg) {
+ if (p == endp) {
+ fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type,
+ s->flags, msg);
+ } else {
+ fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s,
+ s->type, s->flags, *p, msg);
+ }
+}
diff --git a/third-party/llhttp/src/http.c b/third-party/llhttp/src/http.c
new file mode 100644
index 0000000..3a66044
--- /dev/null
+++ b/third-party/llhttp/src/http.c
@@ -0,0 +1,150 @@
+#include <stdio.h>
+#ifndef LLHTTP__TEST
+# include "llhttp.h"
+#else
+# define llhttp_t llparse_t
+#endif /* */
+
+int llhttp_message_needs_eof(const llhttp_t* parser);
+int llhttp_should_keep_alive(const llhttp_t* parser);
+
+int llhttp__before_headers_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ if ((parser->flags & F_UPGRADE) &&
+ (parser->flags & F_CONNECTION_UPGRADE)) {
+ /* For responses, "Upgrade: foo" and "Connection: upgrade" are
+ * mandatory only when it is a 101 Switching Protocols response,
+ * otherwise it is purely informational, to announce support.
+ */
+ parser->upgrade =
+ (parser->type == HTTP_REQUEST || parser->status_code == 101);
+ } else {
+ parser->upgrade = (parser->method == HTTP_CONNECT);
+ }
+ return 0;
+}
+
+
+/* Return values:
+ * 0 - No body, `restart`, message_complete
+ * 1 - CONNECT request, `restart`, message_complete, and pause
+ * 2 - chunk_size_start
+ * 3 - body_identity
+ * 4 - body_identity_eof
+ * 5 - invalid transfer-encoding for request
+ */
+int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ int hasBody;
+
+ hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;
+ if (parser->upgrade && (parser->method == HTTP_CONNECT ||
+ (parser->flags & F_SKIPBODY) || !hasBody)) {
+ /* Exit, the rest of the message is in a different protocol. */
+ return 1;
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ return 0;
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header, prepare for a chunk */
+ return 2;
+ } else if (parser->flags & F_TRANSFER_ENCODING) {
+ if (parser->type == HTTP_REQUEST &&
+ (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 &&
+ (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field
+ * is present in a request and the chunked transfer coding is not
+ * the final encoding, the message body length cannot be determined
+ * reliably; the server MUST respond with the 400 (Bad Request)
+ * status code and then close the connection.
+ */
+ return 5;
+ } else {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field is present in a response and
+ * the chunked transfer coding is not the final encoding, the
+ * message body length is determined by reading the connection until
+ * it is closed by the server.
+ */
+ return 4;
+ }
+ } else {
+ if (!(parser->flags & F_CONTENT_LENGTH)) {
+ if (!llhttp_message_needs_eof(parser)) {
+ /* Assume content-length 0 - read the next */
+ return 0;
+ } else {
+ /* Read body until EOF */
+ return 4;
+ }
+ } else if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ return 0;
+ } else {
+ /* Content-Length header given and non-zero */
+ return 3;
+ }
+ }
+}
+
+
+int llhttp__after_message_complete(llhttp_t* parser, const char* p,
+ const char* endp) {
+ int should_keep_alive;
+
+ should_keep_alive = llhttp_should_keep_alive(parser);
+ parser->finish = HTTP_FINISH_SAFE;
+ parser->flags = 0;
+
+ /* NOTE: this is ignored in loose parsing mode */
+ return should_keep_alive;
+}
+
+
+int llhttp_message_needs_eof(const llhttp_t* 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;
+ }
+
+ /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */
+ if ((parser->flags & F_TRANSFER_ENCODING) &&
+ (parser->flags & F_CHUNKED) == 0) {
+ return 1;
+ }
+
+ if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int llhttp_should_keep_alive(const llhttp_t* 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 !llhttp_message_needs_eof(parser);
+}
diff --git a/third-party/llhttp/src/llhttp.c b/third-party/llhttp/src/llhttp.c
new file mode 100644
index 0000000..03f6d57
--- /dev/null
+++ b/third-party/llhttp/src/llhttp.c
@@ -0,0 +1,9537 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __SSE4_2__
+ #ifdef _MSC_VER
+ #include <nmmintrin.h>
+ #else /* !_MSC_VER */
+ #include <x86intrin.h>
+ #endif /* _MSC_VER */
+#endif /* __SSE4_2__ */
+
+#ifdef _MSC_VER
+ #define ALIGN(n) _declspec(align(n))
+#else /* !_MSC_VER */
+ #define ALIGN(n) __attribute__((aligned(n)))
+#endif /* _MSC_VER */
+
+#include "llhttp.h"
+
+typedef int (*llhttp__internal__span_cb)(
+ llhttp__internal_t*, const char*, const char*);
+
+static const unsigned char llparse_blob0[] = {
+ 0xd, 0xa
+};
+static const unsigned char llparse_blob1[] = {
+ 'o', 'n'
+};
+static const unsigned char llparse_blob2[] = {
+ 'e', 'c', 't', 'i', 'o', 'n'
+};
+static const unsigned char llparse_blob3[] = {
+ 'l', 'o', 's', 'e'
+};
+static const unsigned char llparse_blob4[] = {
+ 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e'
+};
+static const unsigned char llparse_blob5[] = {
+ 'p', 'g', 'r', 'a', 'd', 'e'
+};
+static const unsigned char llparse_blob6[] = {
+ 'c', 'h', 'u', 'n', 'k', 'e', 'd'
+};
+#ifdef __SSE4_2__
+static const unsigned char ALIGN(16) llparse_blob7[] = {
+ 0x9, 0x9, ' ', '~', 0x80, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0
+};
+#endif /* __SSE4_2__ */
+#ifdef __SSE4_2__
+static const unsigned char ALIGN(16) llparse_blob8[] = {
+ '!', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A',
+ 'Z', '^', 'z', '|', '|'
+};
+#endif /* __SSE4_2__ */
+#ifdef __SSE4_2__
+static const unsigned char ALIGN(16) llparse_blob9[] = {
+ '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0
+};
+#endif /* __SSE4_2__ */
+static const unsigned char llparse_blob10[] = {
+ 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h'
+};
+static const unsigned char llparse_blob11[] = {
+ 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c',
+ 't', 'i', 'o', 'n'
+};
+static const unsigned char llparse_blob12[] = {
+ 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c',
+ 'o', 'd', 'i', 'n', 'g'
+};
+static const unsigned char llparse_blob13[] = {
+ 'p', 'g', 'r', 'a', 'd', 'e'
+};
+static const unsigned char llparse_blob14[] = {
+ 'T', 'T', 'P', '/'
+};
+static const unsigned char llparse_blob15[] = {
+ 0xd, 0xa, 0xd, 0xa, 'S', 'M', 0xd, 0xa, 0xd, 0xa
+};
+static const unsigned char llparse_blob16[] = {
+ 'C', 'E', '/'
+};
+static const unsigned char llparse_blob17[] = {
+ 'T', 'S', 'P', '/'
+};
+static const unsigned char llparse_blob18[] = {
+ 'N', 'O', 'U', 'N', 'C', 'E'
+};
+static const unsigned char llparse_blob19[] = {
+ 'I', 'N', 'D'
+};
+static const unsigned char llparse_blob20[] = {
+ 'E', 'C', 'K', 'O', 'U', 'T'
+};
+static const unsigned char llparse_blob21[] = {
+ 'N', 'E', 'C', 'T'
+};
+static const unsigned char llparse_blob22[] = {
+ 'E', 'T', 'E'
+};
+static const unsigned char llparse_blob23[] = {
+ 'C', 'R', 'I', 'B', 'E'
+};
+static const unsigned char llparse_blob24[] = {
+ 'L', 'U', 'S', 'H'
+};
+static const unsigned char llparse_blob25[] = {
+ 'E', 'T'
+};
+static const unsigned char llparse_blob26[] = {
+ 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R'
+};
+static const unsigned char llparse_blob27[] = {
+ 'E', 'A', 'D'
+};
+static const unsigned char llparse_blob28[] = {
+ 'N', 'K'
+};
+static const unsigned char llparse_blob29[] = {
+ 'C', 'K'
+};
+static const unsigned char llparse_blob30[] = {
+ 'S', 'E', 'A', 'R', 'C', 'H'
+};
+static const unsigned char llparse_blob31[] = {
+ 'R', 'G', 'E'
+};
+static const unsigned char llparse_blob32[] = {
+ 'C', 'T', 'I', 'V', 'I', 'T', 'Y'
+};
+static const unsigned char llparse_blob33[] = {
+ 'L', 'E', 'N', 'D', 'A', 'R'
+};
+static const unsigned char llparse_blob34[] = {
+ 'V', 'E'
+};
+static const unsigned char llparse_blob35[] = {
+ 'O', 'T', 'I', 'F', 'Y'
+};
+static const unsigned char llparse_blob36[] = {
+ 'P', 'T', 'I', 'O', 'N', 'S'
+};
+static const unsigned char llparse_blob37[] = {
+ 'C', 'H'
+};
+static const unsigned char llparse_blob38[] = {
+ 'S', 'E'
+};
+static const unsigned char llparse_blob39[] = {
+ 'A', 'Y'
+};
+static const unsigned char llparse_blob40[] = {
+ 'S', 'T'
+};
+static const unsigned char llparse_blob41[] = {
+ 'I', 'N', 'D'
+};
+static const unsigned char llparse_blob42[] = {
+ 'A', 'T', 'C', 'H'
+};
+static const unsigned char llparse_blob43[] = {
+ 'G', 'E'
+};
+static const unsigned char llparse_blob44[] = {
+ 'I', 'N', 'D'
+};
+static const unsigned char llparse_blob45[] = {
+ 'O', 'R', 'D'
+};
+static const unsigned char llparse_blob46[] = {
+ 'I', 'R', 'E', 'C', 'T'
+};
+static const unsigned char llparse_blob47[] = {
+ 'O', 'R', 'T'
+};
+static const unsigned char llparse_blob48[] = {
+ 'R', 'C', 'H'
+};
+static const unsigned char llparse_blob49[] = {
+ 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R'
+};
+static const unsigned char llparse_blob50[] = {
+ 'U', 'R', 'C', 'E'
+};
+static const unsigned char llparse_blob51[] = {
+ 'B', 'S', 'C', 'R', 'I', 'B', 'E'
+};
+static const unsigned char llparse_blob52[] = {
+ 'A', 'R', 'D', 'O', 'W', 'N'
+};
+static const unsigned char llparse_blob53[] = {
+ 'A', 'C', 'E'
+};
+static const unsigned char llparse_blob54[] = {
+ 'I', 'N', 'D'
+};
+static const unsigned char llparse_blob55[] = {
+ 'N', 'K'
+};
+static const unsigned char llparse_blob56[] = {
+ 'C', 'K'
+};
+static const unsigned char llparse_blob57[] = {
+ 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E'
+};
+static const unsigned char llparse_blob58[] = {
+ 'H', 'T', 'T', 'P', '/'
+};
+static const unsigned char llparse_blob59[] = {
+ 'A', 'D'
+};
+static const unsigned char llparse_blob60[] = {
+ 'T', 'P', '/'
+};
+
+enum llparse_match_status_e {
+ kMatchComplete,
+ kMatchPause,
+ kMatchMismatch
+};
+typedef enum llparse_match_status_e llparse_match_status_t;
+
+struct llparse_match_s {
+ llparse_match_status_t status;
+ const unsigned char* current;
+};
+typedef struct llparse_match_s llparse_match_t;
+
+static llparse_match_t llparse__match_sequence_id(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp,
+ const unsigned char* seq, uint32_t seq_len) {
+ uint32_t index;
+ llparse_match_t res;
+
+ index = s->_index;
+ for (; p != endp; p++) {
+ unsigned char current;
+
+ current = *p;
+ if (current == seq[index]) {
+ if (++index == seq_len) {
+ res.status = kMatchComplete;
+ goto reset;
+ }
+ } else {
+ res.status = kMatchMismatch;
+ goto reset;
+ }
+ }
+ s->_index = index;
+ res.status = kMatchPause;
+ res.current = p;
+ return res;
+reset:
+ s->_index = 0;
+ res.current = p;
+ return res;
+}
+
+static llparse_match_t llparse__match_sequence_to_lower(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp,
+ const unsigned char* seq, uint32_t seq_len) {
+ uint32_t index;
+ llparse_match_t res;
+
+ index = s->_index;
+ for (; p != endp; p++) {
+ unsigned char current;
+
+ current = ((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p));
+ if (current == seq[index]) {
+ if (++index == seq_len) {
+ res.status = kMatchComplete;
+ goto reset;
+ }
+ } else {
+ res.status = kMatchMismatch;
+ goto reset;
+ }
+ }
+ s->_index = index;
+ res.status = kMatchPause;
+ res.current = p;
+ return res;
+reset:
+ s->_index = 0;
+ res.current = p;
+ return res;
+}
+
+static llparse_match_t llparse__match_sequence_to_lower_unsafe(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp,
+ const unsigned char* seq, uint32_t seq_len) {
+ uint32_t index;
+ llparse_match_t res;
+
+ index = s->_index;
+ for (; p != endp; p++) {
+ unsigned char current;
+
+ current = ((*p) | 0x20);
+ if (current == seq[index]) {
+ if (++index == seq_len) {
+ res.status = kMatchComplete;
+ goto reset;
+ }
+ } else {
+ res.status = kMatchMismatch;
+ goto reset;
+ }
+ }
+ s->_index = index;
+ res.status = kMatchPause;
+ res.current = p;
+ return res;
+reset:
+ s->_index = 0;
+ res.current = p;
+ return res;
+}
+
+enum llparse_state_e {
+ s_error,
+ s_n_llhttp__internal__n_closed,
+ s_n_llhttp__internal__n_invoke_llhttp__after_message_complete,
+ s_n_llhttp__internal__n_pause_1,
+ s_n_llhttp__internal__n_chunk_data_almost_done,
+ s_n_llhttp__internal__n_consume_content_length,
+ s_n_llhttp__internal__n_span_start_llhttp__on_body,
+ s_n_llhttp__internal__n_invoke_is_equal_content_length,
+ s_n_llhttp__internal__n_chunk_size_almost_done,
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete,
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1,
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete,
+ s_n_llhttp__internal__n_chunk_extension_quoted_value_done,
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1,
+ s_n_llhttp__internal__n_error_21,
+ s_n_llhttp__internal__n_chunk_extension_quoted_value,
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2,
+ s_n_llhttp__internal__n_error_23,
+ s_n_llhttp__internal__n_chunk_extension_value,
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value,
+ s_n_llhttp__internal__n_error_24,
+ s_n_llhttp__internal__n_chunk_extension_name,
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name,
+ s_n_llhttp__internal__n_chunk_extensions,
+ s_n_llhttp__internal__n_chunk_size_otherwise,
+ s_n_llhttp__internal__n_chunk_size,
+ s_n_llhttp__internal__n_chunk_size_digit,
+ s_n_llhttp__internal__n_invoke_update_content_length_1,
+ s_n_llhttp__internal__n_invoke_is_equal_upgrade,
+ s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2,
+ s_n_llhttp__internal__n_consume_content_length_1,
+ s_n_llhttp__internal__n_span_start_llhttp__on_body_1,
+ s_n_llhttp__internal__n_eof,
+ s_n_llhttp__internal__n_span_start_llhttp__on_body_2,
+ s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete,
+ s_n_llhttp__internal__n_error_5,
+ s_n_llhttp__internal__n_headers_almost_done,
+ s_n_llhttp__internal__n_header_field_colon_discard_ws,
+ s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete,
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_value,
+ s_n_llhttp__internal__n_header_value_discard_lws,
+ s_n_llhttp__internal__n_header_value_discard_ws_almost_done,
+ s_n_llhttp__internal__n_header_value_lws,
+ s_n_llhttp__internal__n_header_value_almost_done,
+ s_n_llhttp__internal__n_header_value_lenient,
+ s_n_llhttp__internal__n_error_41,
+ s_n_llhttp__internal__n_header_value_otherwise,
+ s_n_llhttp__internal__n_header_value_connection_token,
+ s_n_llhttp__internal__n_header_value_connection_ws,
+ s_n_llhttp__internal__n_header_value_connection_1,
+ s_n_llhttp__internal__n_header_value_connection_2,
+ s_n_llhttp__internal__n_header_value_connection_3,
+ s_n_llhttp__internal__n_header_value_connection,
+ s_n_llhttp__internal__n_error_43,
+ s_n_llhttp__internal__n_error_44,
+ s_n_llhttp__internal__n_header_value_content_length_ws,
+ s_n_llhttp__internal__n_header_value_content_length,
+ s_n_llhttp__internal__n_error_46,
+ s_n_llhttp__internal__n_error_45,
+ s_n_llhttp__internal__n_header_value_te_token_ows,
+ s_n_llhttp__internal__n_header_value,
+ s_n_llhttp__internal__n_header_value_te_token,
+ s_n_llhttp__internal__n_header_value_te_chunked_last,
+ s_n_llhttp__internal__n_header_value_te_chunked,
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1,
+ s_n_llhttp__internal__n_header_value_discard_ws,
+ s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete,
+ s_n_llhttp__internal__n_header_field_general_otherwise,
+ s_n_llhttp__internal__n_header_field_general,
+ s_n_llhttp__internal__n_header_field_colon,
+ s_n_llhttp__internal__n_header_field_3,
+ s_n_llhttp__internal__n_header_field_4,
+ s_n_llhttp__internal__n_header_field_2,
+ s_n_llhttp__internal__n_header_field_1,
+ s_n_llhttp__internal__n_header_field_5,
+ s_n_llhttp__internal__n_header_field_6,
+ s_n_llhttp__internal__n_header_field_7,
+ s_n_llhttp__internal__n_header_field,
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_field,
+ s_n_llhttp__internal__n_header_field_start,
+ s_n_llhttp__internal__n_headers_start,
+ s_n_llhttp__internal__n_url_to_http_09,
+ s_n_llhttp__internal__n_url_skip_to_http09,
+ s_n_llhttp__internal__n_url_skip_lf_to_http09_1,
+ s_n_llhttp__internal__n_url_skip_lf_to_http09,
+ s_n_llhttp__internal__n_req_pri_upgrade,
+ s_n_llhttp__internal__n_req_http_complete_crlf,
+ s_n_llhttp__internal__n_req_http_complete,
+ s_n_llhttp__internal__n_invoke_load_method_1,
+ s_n_llhttp__internal__n_invoke_llhttp__on_version_complete,
+ s_n_llhttp__internal__n_error_51,
+ s_n_llhttp__internal__n_error_57,
+ s_n_llhttp__internal__n_req_http_minor,
+ s_n_llhttp__internal__n_error_58,
+ s_n_llhttp__internal__n_req_http_dot,
+ s_n_llhttp__internal__n_error_59,
+ s_n_llhttp__internal__n_req_http_major,
+ s_n_llhttp__internal__n_span_start_llhttp__on_version,
+ s_n_llhttp__internal__n_req_http_start_1,
+ s_n_llhttp__internal__n_req_http_start_2,
+ s_n_llhttp__internal__n_req_http_start_3,
+ s_n_llhttp__internal__n_req_http_start,
+ s_n_llhttp__internal__n_url_to_http,
+ s_n_llhttp__internal__n_url_skip_to_http,
+ s_n_llhttp__internal__n_url_fragment,
+ s_n_llhttp__internal__n_span_end_stub_query_3,
+ s_n_llhttp__internal__n_url_query,
+ s_n_llhttp__internal__n_url_query_or_fragment,
+ s_n_llhttp__internal__n_url_path,
+ s_n_llhttp__internal__n_span_start_stub_path_2,
+ s_n_llhttp__internal__n_span_start_stub_path,
+ s_n_llhttp__internal__n_span_start_stub_path_1,
+ s_n_llhttp__internal__n_url_server_with_at,
+ s_n_llhttp__internal__n_url_server,
+ s_n_llhttp__internal__n_url_schema_delim_1,
+ s_n_llhttp__internal__n_url_schema_delim,
+ s_n_llhttp__internal__n_span_end_stub_schema,
+ s_n_llhttp__internal__n_url_schema,
+ s_n_llhttp__internal__n_url_start,
+ s_n_llhttp__internal__n_span_start_llhttp__on_url_1,
+ s_n_llhttp__internal__n_url_entry_normal,
+ s_n_llhttp__internal__n_span_start_llhttp__on_url,
+ s_n_llhttp__internal__n_url_entry_connect,
+ s_n_llhttp__internal__n_req_spaces_before_url,
+ s_n_llhttp__internal__n_req_first_space_before_url,
+ s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1,
+ s_n_llhttp__internal__n_after_start_req_2,
+ s_n_llhttp__internal__n_after_start_req_3,
+ s_n_llhttp__internal__n_after_start_req_1,
+ s_n_llhttp__internal__n_after_start_req_4,
+ s_n_llhttp__internal__n_after_start_req_6,
+ s_n_llhttp__internal__n_after_start_req_8,
+ s_n_llhttp__internal__n_after_start_req_9,
+ s_n_llhttp__internal__n_after_start_req_7,
+ s_n_llhttp__internal__n_after_start_req_5,
+ s_n_llhttp__internal__n_after_start_req_12,
+ s_n_llhttp__internal__n_after_start_req_13,
+ s_n_llhttp__internal__n_after_start_req_11,
+ s_n_llhttp__internal__n_after_start_req_10,
+ s_n_llhttp__internal__n_after_start_req_14,
+ s_n_llhttp__internal__n_after_start_req_17,
+ s_n_llhttp__internal__n_after_start_req_16,
+ s_n_llhttp__internal__n_after_start_req_15,
+ s_n_llhttp__internal__n_after_start_req_18,
+ s_n_llhttp__internal__n_after_start_req_20,
+ s_n_llhttp__internal__n_after_start_req_21,
+ s_n_llhttp__internal__n_after_start_req_19,
+ s_n_llhttp__internal__n_after_start_req_23,
+ s_n_llhttp__internal__n_after_start_req_24,
+ s_n_llhttp__internal__n_after_start_req_26,
+ s_n_llhttp__internal__n_after_start_req_28,
+ s_n_llhttp__internal__n_after_start_req_29,
+ s_n_llhttp__internal__n_after_start_req_27,
+ s_n_llhttp__internal__n_after_start_req_25,
+ s_n_llhttp__internal__n_after_start_req_30,
+ s_n_llhttp__internal__n_after_start_req_22,
+ s_n_llhttp__internal__n_after_start_req_31,
+ s_n_llhttp__internal__n_after_start_req_32,
+ s_n_llhttp__internal__n_after_start_req_35,
+ s_n_llhttp__internal__n_after_start_req_36,
+ s_n_llhttp__internal__n_after_start_req_34,
+ s_n_llhttp__internal__n_after_start_req_37,
+ s_n_llhttp__internal__n_after_start_req_38,
+ s_n_llhttp__internal__n_after_start_req_42,
+ s_n_llhttp__internal__n_after_start_req_43,
+ s_n_llhttp__internal__n_after_start_req_41,
+ s_n_llhttp__internal__n_after_start_req_40,
+ s_n_llhttp__internal__n_after_start_req_39,
+ s_n_llhttp__internal__n_after_start_req_45,
+ s_n_llhttp__internal__n_after_start_req_44,
+ s_n_llhttp__internal__n_after_start_req_33,
+ s_n_llhttp__internal__n_after_start_req_48,
+ s_n_llhttp__internal__n_after_start_req_49,
+ s_n_llhttp__internal__n_after_start_req_50,
+ s_n_llhttp__internal__n_after_start_req_51,
+ s_n_llhttp__internal__n_after_start_req_47,
+ s_n_llhttp__internal__n_after_start_req_46,
+ s_n_llhttp__internal__n_after_start_req_54,
+ s_n_llhttp__internal__n_after_start_req_56,
+ s_n_llhttp__internal__n_after_start_req_57,
+ s_n_llhttp__internal__n_after_start_req_55,
+ s_n_llhttp__internal__n_after_start_req_53,
+ s_n_llhttp__internal__n_after_start_req_58,
+ s_n_llhttp__internal__n_after_start_req_59,
+ s_n_llhttp__internal__n_after_start_req_52,
+ s_n_llhttp__internal__n_after_start_req_61,
+ s_n_llhttp__internal__n_after_start_req_62,
+ s_n_llhttp__internal__n_after_start_req_60,
+ s_n_llhttp__internal__n_after_start_req_65,
+ s_n_llhttp__internal__n_after_start_req_67,
+ s_n_llhttp__internal__n_after_start_req_68,
+ s_n_llhttp__internal__n_after_start_req_66,
+ s_n_llhttp__internal__n_after_start_req_69,
+ s_n_llhttp__internal__n_after_start_req_64,
+ s_n_llhttp__internal__n_after_start_req_63,
+ s_n_llhttp__internal__n_after_start_req,
+ s_n_llhttp__internal__n_span_start_llhttp__on_method_1,
+ s_n_llhttp__internal__n_res_line_almost_done,
+ s_n_llhttp__internal__n_res_status,
+ s_n_llhttp__internal__n_span_start_llhttp__on_status,
+ s_n_llhttp__internal__n_res_status_start,
+ s_n_llhttp__internal__n_res_status_code_otherwise,
+ s_n_llhttp__internal__n_res_status_code_digit_3,
+ s_n_llhttp__internal__n_res_status_code_digit_2,
+ s_n_llhttp__internal__n_res_status_code_digit_1,
+ s_n_llhttp__internal__n_res_after_version,
+ s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1,
+ s_n_llhttp__internal__n_error_73,
+ s_n_llhttp__internal__n_error_85,
+ s_n_llhttp__internal__n_res_http_minor,
+ s_n_llhttp__internal__n_error_86,
+ s_n_llhttp__internal__n_res_http_dot,
+ s_n_llhttp__internal__n_error_87,
+ s_n_llhttp__internal__n_res_http_major,
+ s_n_llhttp__internal__n_span_start_llhttp__on_version_1,
+ s_n_llhttp__internal__n_start_res,
+ s_n_llhttp__internal__n_invoke_llhttp__on_method_complete,
+ s_n_llhttp__internal__n_req_or_res_method_2,
+ s_n_llhttp__internal__n_invoke_update_type_1,
+ s_n_llhttp__internal__n_req_or_res_method_3,
+ s_n_llhttp__internal__n_req_or_res_method_1,
+ s_n_llhttp__internal__n_req_or_res_method,
+ s_n_llhttp__internal__n_span_start_llhttp__on_method,
+ s_n_llhttp__internal__n_start_req_or_res,
+ s_n_llhttp__internal__n_invoke_load_type,
+ s_n_llhttp__internal__n_invoke_update_finish,
+ s_n_llhttp__internal__n_start,
+};
+typedef enum llparse_state_e llparse_state_t;
+
+int llhttp__on_method(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_url(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_version(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_header_field(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_header_value(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_body(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_chunk_extension_name(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_chunk_extension_value(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_status(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_load_initial_message_completed(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->initial_message_completed;
+}
+
+int llhttp__on_reset(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_update_finish(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->finish = 2;
+ return 0;
+}
+
+int llhttp__on_message_begin(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_load_type(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->type;
+}
+
+int llhttp__internal__c_store_method(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ state->method = match;
+ return 0;
+}
+
+int llhttp__on_method_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_is_equal_method(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->method == 5;
+}
+
+int llhttp__internal__c_update_http_major(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->http_major = 0;
+ return 0;
+}
+
+int llhttp__internal__c_update_http_minor(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->http_minor = 9;
+ return 0;
+}
+
+int llhttp__on_url_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_test_lenient_flags(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 1) == 1;
+}
+
+int llhttp__after_headers_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_message_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__after_message_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_update_content_length(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->content_length = 0;
+ return 0;
+}
+
+int llhttp__internal__c_update_initial_message_completed(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->initial_message_completed = 1;
+ return 0;
+}
+
+int llhttp__internal__c_update_finish_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->finish = 0;
+ return 0;
+}
+
+int llhttp__internal__c_test_lenient_flags_2(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 4) == 4;
+}
+
+int llhttp__internal__c_test_lenient_flags_3(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 32) == 32;
+}
+
+int llhttp__internal__c_mul_add_content_length(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ /* Multiplication overflow */
+ if (state->content_length > 0xffffffffffffffffULL / 16) {
+ return 1;
+ }
+
+ state->content_length *= 16;
+
+ /* Addition overflow */
+ if (match >= 0) {
+ if (state->content_length > 0xffffffffffffffffULL - match) {
+ return 1;
+ }
+ } else {
+ if (state->content_length < 0ULL - match) {
+ return 1;
+ }
+ }
+ state->content_length += match;
+ return 0;
+}
+
+int llhttp__on_chunk_header(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_is_equal_content_length(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->content_length == 0;
+}
+
+int llhttp__on_chunk_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_test_lenient_flags_4(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 128) == 128;
+}
+
+int llhttp__internal__c_or_flags(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 128;
+ return 0;
+}
+
+int llhttp__internal__c_test_lenient_flags_5(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 64) == 64;
+}
+
+int llhttp__on_chunk_extension_name_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_chunk_extension_value_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_is_equal_upgrade(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->upgrade == 1;
+}
+
+int llhttp__internal__c_update_finish_3(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->finish = 1;
+ return 0;
+}
+
+int llhttp__internal__c_test_flags(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->flags & 128) == 128;
+}
+
+int llhttp__internal__c_test_flags_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->flags & 544) == 544;
+}
+
+int llhttp__internal__c_test_lenient_flags_6(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 2) == 2;
+}
+
+int llhttp__before_headers_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__on_headers_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_or_flags_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 64;
+ return 0;
+}
+
+int llhttp__internal__c_update_upgrade(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->upgrade = 1;
+ return 0;
+}
+
+int llhttp__internal__c_store_header_state(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ state->header_state = match;
+ return 0;
+}
+
+int llhttp__on_header_field_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_load_header_state(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->header_state;
+}
+
+int llhttp__internal__c_or_flags_3(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 1;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 1;
+ return 0;
+}
+
+int llhttp__on_header_value_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_or_flags_4(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 2;
+ return 0;
+}
+
+int llhttp__internal__c_or_flags_5(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 4;
+ return 0;
+}
+
+int llhttp__internal__c_or_flags_6(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 8;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state_3(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 6;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 0;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state_6(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 5;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state_7(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 7;
+ return 0;
+}
+
+int llhttp__internal__c_test_flags_2(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->flags & 32) == 32;
+}
+
+int llhttp__internal__c_mul_add_content_length_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ /* Multiplication overflow */
+ if (state->content_length > 0xffffffffffffffffULL / 10) {
+ return 1;
+ }
+
+ state->content_length *= 10;
+
+ /* Addition overflow */
+ if (match >= 0) {
+ if (state->content_length > 0xffffffffffffffffULL - match) {
+ return 1;
+ }
+ } else {
+ if (state->content_length < 0ULL - match) {
+ return 1;
+ }
+ }
+ state->content_length += match;
+ return 0;
+}
+
+int llhttp__internal__c_or_flags_15(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 32;
+ return 0;
+}
+
+int llhttp__internal__c_test_flags_3(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->flags & 8) == 8;
+}
+
+int llhttp__internal__c_test_lenient_flags_13(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 8) == 8;
+}
+
+int llhttp__internal__c_or_flags_16(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 512;
+ return 0;
+}
+
+int llhttp__internal__c_and_flags(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags &= -9;
+ return 0;
+}
+
+int llhttp__internal__c_update_header_state_8(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->header_state = 8;
+ return 0;
+}
+
+int llhttp__internal__c_or_flags_18(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->flags |= 16;
+ return 0;
+}
+
+int llhttp__internal__c_load_method(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->method;
+}
+
+int llhttp__internal__c_store_http_major(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ state->http_major = match;
+ return 0;
+}
+
+int llhttp__internal__c_store_http_minor(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ state->http_minor = match;
+ return 0;
+}
+
+int llhttp__internal__c_test_lenient_flags_15(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return (state->lenient_flags & 16) == 16;
+}
+
+int llhttp__on_version_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_load_http_major(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->http_major;
+}
+
+int llhttp__internal__c_load_http_minor(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ return state->http_minor;
+}
+
+int llhttp__internal__c_update_status_code(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->status_code = 0;
+ return 0;
+}
+
+int llhttp__internal__c_mul_add_status_code(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp,
+ int match) {
+ /* Multiplication overflow */
+ if (state->status_code > 0xffff / 10) {
+ return 1;
+ }
+
+ state->status_code *= 10;
+
+ /* Addition overflow */
+ if (match >= 0) {
+ if (state->status_code > 0xffff - match) {
+ return 1;
+ }
+ } else {
+ if (state->status_code < 0 - match) {
+ return 1;
+ }
+ }
+ state->status_code += match;
+ return 0;
+}
+
+int llhttp__on_status_complete(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
+int llhttp__internal__c_update_type(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->type = 1;
+ return 0;
+}
+
+int llhttp__internal__c_update_type_1(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ state->type = 2;
+ return 0;
+}
+
+int llhttp__internal_init(llhttp__internal_t* state) {
+ memset(state, 0, sizeof(*state));
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start;
+ return 0;
+}
+
+static llparse_state_t llhttp__internal__run(
+ llhttp__internal_t* state,
+ const unsigned char* p,
+ const unsigned char* endp) {
+ int match;
+ switch ((llparse_state_t) (intptr_t) state->_current) {
+ case s_n_llhttp__internal__n_closed:
+ s_n_llhttp__internal__n_closed: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_closed;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_closed;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_closed;
+ }
+ default: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_3;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: {
+ switch (llhttp__after_message_complete(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_update_content_length;
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_finish_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_pause_1:
+ s_n_llhttp__internal__n_pause_1: {
+ state->error = 0x16;
+ state->reason = "Pause on CONNECT/Upgrade";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_data_almost_done:
+ s_n_llhttp__internal__n_chunk_data_almost_done: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_data_almost_done;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob0, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_chunk_data_almost_done;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_consume_content_length:
+ s_n_llhttp__internal__n_consume_content_length: {
+ size_t avail;
+ uint64_t need;
+
+ avail = endp - p;
+ need = state->content_length;
+ if (avail >= need) {
+ p += need;
+ state->content_length = 0;
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_body;
+ }
+
+ state->content_length -= avail;
+ return s_n_llhttp__internal__n_consume_content_length;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_body:
+ s_n_llhttp__internal__n_span_start_llhttp__on_body: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_body;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_body;
+ goto s_n_llhttp__internal__n_consume_content_length;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_is_equal_content_length:
+ s_n_llhttp__internal__n_invoke_is_equal_content_length: {
+ switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_body;
+ default:
+ goto s_n_llhttp__internal__n_invoke_or_flags;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_size_almost_done:
+ s_n_llhttp__internal__n_chunk_size_almost_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_size_almost_done;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_5;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: {
+ switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_5;
+ default:
+ goto s_n_llhttp__internal__n_error_15;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1:
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: {
+ switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_extensions;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_6;
+ default:
+ goto s_n_llhttp__internal__n_error_16;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: {
+ switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_7;
+ default:
+ goto s_n_llhttp__internal__n_error_18;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_extension_quoted_value_done:
+ s_n_llhttp__internal__n_chunk_extension_quoted_value_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_extension_quoted_value_done;
+ }
+ switch (*p) {
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ }
+ case ';': {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extensions;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_20;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1:
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: {
+ switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_extension_quoted_value_done;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_8;
+ default:
+ goto s_n_llhttp__internal__n_error_19;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_21:
+ s_n_llhttp__internal__n_error_21: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions quoted value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_extension_quoted_value:
+ s_n_llhttp__internal__n_chunk_extension_quoted_value: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 2, 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, 0, 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, 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
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_extension_quoted_value;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extension_quoted_value;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2:
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: {
+ switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_size_otherwise;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_9;
+ default:
+ goto s_n_llhttp__internal__n_error_22;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_23:
+ s_n_llhttp__internal__n_error_23: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_extension_value:
+ s_n_llhttp__internal__n_chunk_extension_value: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 3, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 4, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_extension_value;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extension_value;
+ }
+ case 3: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extension_quoted_value;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value:
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_chunk_extension_value;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_24:
+ s_n_llhttp__internal__n_error_24: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions name";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_extension_name:
+ s_n_llhttp__internal__n_chunk_extension_name: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 0, 4, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_extension_name;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extension_name;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name:
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_chunk_extension_name;
+ goto s_n_llhttp__internal__n_chunk_extension_name;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_extensions:
+ s_n_llhttp__internal__n_chunk_extensions: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_extensions;
+ }
+ switch (*p) {
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_error_13;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_error_14;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_size_otherwise:
+ s_n_llhttp__internal__n_chunk_size_otherwise: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_size_otherwise;
+ }
+ switch (*p) {
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ }
+ case ';': {
+ p++;
+ goto s_n_llhttp__internal__n_chunk_extensions;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_25;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_size:
+ s_n_llhttp__internal__n_chunk_size: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_size;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'A': {
+ p++;
+ match = 10;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'B': {
+ p++;
+ match = 11;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'C': {
+ p++;
+ match = 12;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'D': {
+ p++;
+ match = 13;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'E': {
+ p++;
+ match = 14;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'F': {
+ p++;
+ match = 15;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'a': {
+ p++;
+ match = 10;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'b': {
+ p++;
+ match = 11;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'c': {
+ p++;
+ match = 12;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'd': {
+ p++;
+ match = 13;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'e': {
+ p++;
+ match = 14;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'f': {
+ p++;
+ match = 15;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_chunk_size_otherwise;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_chunk_size_digit:
+ s_n_llhttp__internal__n_chunk_size_digit: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_chunk_size_digit;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'A': {
+ p++;
+ match = 10;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'B': {
+ p++;
+ match = 11;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'C': {
+ p++;
+ match = 12;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'D': {
+ p++;
+ match = 13;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'E': {
+ p++;
+ match = 14;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'F': {
+ p++;
+ match = 15;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'a': {
+ p++;
+ match = 10;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'b': {
+ p++;
+ match = 11;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'c': {
+ p++;
+ match = 12;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'd': {
+ p++;
+ match = 13;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'e': {
+ p++;
+ match = 14;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ case 'f': {
+ p++;
+ match = 15;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_27;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_update_content_length_1:
+ s_n_llhttp__internal__n_invoke_update_content_length_1: {
+ switch (llhttp__internal__c_update_content_length(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_chunk_size_digit;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_is_equal_upgrade:
+ s_n_llhttp__internal__n_invoke_is_equal_upgrade: {
+ switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete;
+ default:
+ goto s_n_llhttp__internal__n_pause_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2:
+ s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: {
+ switch (llhttp__on_message_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_is_equal_upgrade;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_11;
+ default:
+ goto s_n_llhttp__internal__n_error_28;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_consume_content_length_1:
+ s_n_llhttp__internal__n_consume_content_length_1: {
+ size_t avail;
+ uint64_t need;
+
+ avail = endp - p;
+ need = state->content_length;
+ if (avail >= need) {
+ p += need;
+ state->content_length = 0;
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1;
+ }
+
+ state->content_length -= avail;
+ return s_n_llhttp__internal__n_consume_content_length_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_body_1:
+ s_n_llhttp__internal__n_span_start_llhttp__on_body_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_body_1;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_body;
+ goto s_n_llhttp__internal__n_consume_content_length_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_eof:
+ s_n_llhttp__internal__n_eof: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_eof;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_eof;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_body_2:
+ s_n_llhttp__internal__n_span_start_llhttp__on_body_2: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_body_2;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_body;
+ goto s_n_llhttp__internal__n_eof;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: {
+ switch (llhttp__after_headers_complete(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1;
+ case 2:
+ goto s_n_llhttp__internal__n_invoke_update_content_length_1;
+ case 3:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1;
+ case 4:
+ goto s_n_llhttp__internal__n_invoke_update_finish_3;
+ case 5:
+ goto s_n_llhttp__internal__n_error_29;
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_5:
+ s_n_llhttp__internal__n_error_5: {
+ state->error = 0xa;
+ state->reason = "Invalid header field char";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_headers_almost_done:
+ s_n_llhttp__internal__n_headers_almost_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_headers_almost_done;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_flags;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_colon_discard_ws:
+ s_n_llhttp__internal__n_header_field_colon_discard_ws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_colon_discard_ws;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_colon_discard_ws;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_field_colon;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: {
+ switch (llhttp__on_header_value_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_header_field_start;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_14;
+ default:
+ goto s_n_llhttp__internal__n_error_37;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_header_value:
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_value: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_header_value;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_header_value;
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_discard_lws:
+ s_n_llhttp__internal__n_header_value_discard_lws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_discard_lws;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_load_header_state;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_discard_ws_almost_done:
+ s_n_llhttp__internal__n_header_value_discard_ws_almost_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_discard_ws_almost_done;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_discard_lws;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_lws:
+ s_n_llhttp__internal__n_header_value_lws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_lws;
+ }
+ switch (*p) {
+ case 9: {
+ goto s_n_llhttp__internal__n_invoke_load_header_state_3;
+ }
+ case ' ': {
+ goto s_n_llhttp__internal__n_invoke_load_header_state_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_load_header_state_4;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_almost_done:
+ s_n_llhttp__internal__n_header_value_almost_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_almost_done;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_lws;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_40;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_lenient:
+ s_n_llhttp__internal__n_header_value_lenient: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_lenient;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4;
+ }
+ default: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_lenient;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_41:
+ s_n_llhttp__internal__n_error_41: {
+ state->error = 0xa;
+ state->reason = "Invalid header value char";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_otherwise:
+ s_n_llhttp__internal__n_header_value_otherwise: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ switch (*p) {
+ case 13: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_12;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection_token:
+ s_n_llhttp__internal__n_header_value_connection_token: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 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, 0,
+ 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
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection_ws:
+ s_n_llhttp__internal__n_header_value_connection_ws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection_ws;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection_ws;
+ }
+ case ',': {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_load_header_state_5;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_5;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection_1:
+ s_n_llhttp__internal__n_header_value_connection_1: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection_1;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob3, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_update_header_state_3;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_value_connection_1;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection_2:
+ s_n_llhttp__internal__n_header_value_connection_2: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection_2;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob4, 9);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_update_header_state_6;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_value_connection_2;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection_3:
+ s_n_llhttp__internal__n_header_value_connection_3: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection_3;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob5, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_update_header_state_7;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_value_connection_3;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_connection:
+ s_n_llhttp__internal__n_header_value_connection: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_connection;
+ }
+ switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ case 'c': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection_1;
+ }
+ case 'k': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection_2;
+ }
+ case 'u': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_connection_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_43:
+ s_n_llhttp__internal__n_error_43: {
+ state->error = 0xb;
+ state->reason = "Content-Length overflow";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_44:
+ s_n_llhttp__internal__n_error_44: {
+ state->error = 0xb;
+ state->reason = "Invalid character in Content-Length";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_content_length_ws:
+ s_n_llhttp__internal__n_header_value_content_length_ws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_content_length_ws;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_invoke_or_flags_15;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_invoke_or_flags_15;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_content_length_ws;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_content_length:
+ s_n_llhttp__internal__n_header_value_content_length: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_content_length;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_content_length_ws;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_46:
+ s_n_llhttp__internal__n_error_46: {
+ state->error = 0xf;
+ state->reason = "Invalid `Transfer-Encoding` header value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_45:
+ s_n_llhttp__internal__n_error_45: {
+ state->error = 0xf;
+ state->reason = "Invalid `Transfer-Encoding` header value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_te_token_ows:
+ s_n_llhttp__internal__n_header_value_te_token_ows: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_te_token_ows;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_token_ows;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_token_ows;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value:
+ s_n_llhttp__internal__n_header_value: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 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, 0,
+ 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
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value;
+ }
+ #ifdef __SSE4_2__
+ if (endp - p >= 16) {
+ __m128i ranges;
+ __m128i input;
+ int avail;
+ int match_len;
+
+ /* Load input */
+ input = _mm_loadu_si128((__m128i const*) p);
+ ranges = _mm_loadu_si128((__m128i const*) llparse_blob7);
+
+ /* Find first character that does not match `ranges` */
+ match_len = _mm_cmpestri(ranges, 6,
+ input, 16,
+ _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES |
+ _SIDD_NEGATIVE_POLARITY);
+
+ if (match_len != 0) {
+ p += match_len;
+ goto s_n_llhttp__internal__n_header_value;
+ }
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ #endif /* __SSE4_2__ */
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_te_token:
+ s_n_llhttp__internal__n_header_value_te_token: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 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, 0,
+ 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
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_te_token;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_token;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_token_ows;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_9;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_te_chunked_last:
+ s_n_llhttp__internal__n_header_value_te_chunked_last: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_te_chunked_last;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_8;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_8;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_chunked_last;
+ }
+ case ',': {
+ goto s_n_llhttp__internal__n_invoke_load_type_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_value_te_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_te_chunked:
+ s_n_llhttp__internal__n_header_value_te_chunked: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob6, 7);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_te_chunked_last;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_header_value_te_token;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1:
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_header_value;
+ goto s_n_llhttp__internal__n_invoke_load_header_state_2;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_value_discard_ws:
+ s_n_llhttp__internal__n_header_value_discard_ws: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_value_discard_ws;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_discard_ws;
+ }
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_9;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_header_value_discard_ws;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: {
+ switch (llhttp__on_header_field_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_header_value_discard_ws;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_15;
+ default:
+ goto s_n_llhttp__internal__n_error_34;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_general_otherwise:
+ s_n_llhttp__internal__n_header_field_general_otherwise: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_general_otherwise;
+ }
+ switch (*p) {
+ case ':': {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_47;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_general:
+ s_n_llhttp__internal__n_header_field_general: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 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, 0, 0, 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, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_general;
+ }
+ #ifdef __SSE4_2__
+ if (endp - p >= 16) {
+ __m128i ranges;
+ __m128i input;
+ int avail;
+ int match_len;
+
+ /* Load input */
+ input = _mm_loadu_si128((__m128i const*) p);
+ ranges = _mm_loadu_si128((__m128i const*) llparse_blob8);
+
+ /* Find first character that does not match `ranges` */
+ match_len = _mm_cmpestri(ranges, 16,
+ input, 16,
+ _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES |
+ _SIDD_NEGATIVE_POLARITY);
+
+ if (match_len != 0) {
+ p += match_len;
+ goto s_n_llhttp__internal__n_header_field_general;
+ }
+ ranges = _mm_loadu_si128((__m128i const*) llparse_blob9);
+
+ /* Find first character that does not match `ranges` */
+ match_len = _mm_cmpestri(ranges, 2,
+ input, 16,
+ _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES |
+ _SIDD_NEGATIVE_POLARITY);
+
+ if (match_len != 0) {
+ p += match_len;
+ goto s_n_llhttp__internal__n_header_field_general;
+ }
+ goto s_n_llhttp__internal__n_header_field_general_otherwise;
+ }
+ #endif /* __SSE4_2__ */
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_general;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_field_general_otherwise;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_colon:
+ s_n_llhttp__internal__n_header_field_colon: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_colon;
+ }
+ switch (*p) {
+ case ' ': {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_8;
+ }
+ case ':': {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_10;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_3:
+ s_n_llhttp__internal__n_header_field_3: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_3;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob2, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_header_state;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_3;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_4:
+ s_n_llhttp__internal__n_header_field_4: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_4;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob10, 10);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_header_state;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_4;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_2:
+ s_n_llhttp__internal__n_header_field_2: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_2;
+ }
+ switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) {
+ case 'n': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_3;
+ }
+ case 't': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_4;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_1:
+ s_n_llhttp__internal__n_header_field_1: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_1;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob1, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_2;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_1;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_5:
+ s_n_llhttp__internal__n_header_field_5: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_5;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob11, 15);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_header_state;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_5;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_6:
+ s_n_llhttp__internal__n_header_field_6: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_6;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob12, 16);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_header_state;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_6;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_7:
+ s_n_llhttp__internal__n_header_field_7: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_7;
+ }
+ match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob13, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_header_state;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_header_field_7;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field:
+ s_n_llhttp__internal__n_header_field: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field;
+ }
+ switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) {
+ case 'c': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_1;
+ }
+ case 'p': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_5;
+ }
+ case 't': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_6;
+ }
+ case 'u': {
+ p++;
+ goto s_n_llhttp__internal__n_header_field_7;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_header_state_11;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_header_field:
+ s_n_llhttp__internal__n_span_start_llhttp__on_header_field: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_header_field;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_header_field;
+ goto s_n_llhttp__internal__n_header_field;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_header_field_start:
+ s_n_llhttp__internal__n_header_field_start: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_header_field_start;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_1;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_headers_almost_done;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_headers_start:
+ s_n_llhttp__internal__n_headers_start: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_headers_start;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_header_field_start;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_to_http_09:
+ s_n_llhttp__internal__n_url_to_http_09: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_to_http_09;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_http_major;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_skip_to_http09:
+ s_n_llhttp__internal__n_url_skip_to_http09: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_skip_to_http09;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ p++;
+ goto s_n_llhttp__internal__n_url_to_http_09;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_skip_lf_to_http09_1:
+ s_n_llhttp__internal__n_url_skip_lf_to_http09_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_skip_lf_to_http09_1;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_url_to_http_09;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_48;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_skip_lf_to_http09:
+ s_n_llhttp__internal__n_url_skip_lf_to_http09: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_48;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_pri_upgrade:
+ s_n_llhttp__internal__n_req_pri_upgrade: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_pri_upgrade;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 10);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_error_55;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_pri_upgrade;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_56;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_complete_crlf:
+ s_n_llhttp__internal__n_req_http_complete_crlf: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_complete_crlf;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_headers_start;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_16;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_complete:
+ s_n_llhttp__internal__n_req_http_complete: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_complete;
+ }
+ switch (*p) {
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_complete_crlf;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_54;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_load_method_1:
+ s_n_llhttp__internal__n_invoke_load_method_1: {
+ switch (llhttp__internal__c_load_method(state, p, endp)) {
+ case 34:
+ goto s_n_llhttp__internal__n_req_pri_upgrade;
+ default:
+ goto s_n_llhttp__internal__n_req_http_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: {
+ switch (llhttp__on_version_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_load_method_1;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_17;
+ default:
+ goto s_n_llhttp__internal__n_error_52;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_51:
+ s_n_llhttp__internal__n_error_51: {
+ state->error = 0x9;
+ state->reason = "Invalid HTTP version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_57:
+ s_n_llhttp__internal__n_error_57: {
+ state->error = 0x9;
+ state->reason = "Invalid minor version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_minor:
+ s_n_llhttp__internal__n_req_http_minor: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_minor;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_2;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_58:
+ s_n_llhttp__internal__n_error_58: {
+ state->error = 0x9;
+ state->reason = "Expected dot";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_dot:
+ s_n_llhttp__internal__n_req_http_dot: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_dot;
+ }
+ switch (*p) {
+ case '.': {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_minor;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_3;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_59:
+ s_n_llhttp__internal__n_error_59: {
+ state->error = 0x9;
+ state->reason = "Invalid major version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_major:
+ s_n_llhttp__internal__n_req_http_major: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_major;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_store_http_major;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_4;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_version:
+ s_n_llhttp__internal__n_span_start_llhttp__on_version: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_version;
+ goto s_n_llhttp__internal__n_req_http_major;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_start_1:
+ s_n_llhttp__internal__n_req_http_start_1: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_start_1;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob14, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_load_method;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_http_start_1;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_62;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_start_2:
+ s_n_llhttp__internal__n_req_http_start_2: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_start_2;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_load_method_2;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_http_start_2;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_62;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_start_3:
+ s_n_llhttp__internal__n_req_http_start_3: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_start_3;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_load_method_3;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_http_start_3;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_62;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_http_start:
+ s_n_llhttp__internal__n_req_http_start: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_http_start;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_start;
+ }
+ case 'H': {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_start_1;
+ }
+ case 'I': {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_start_2;
+ }
+ case 'R': {
+ p++;
+ goto s_n_llhttp__internal__n_req_http_start_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_62;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_to_http:
+ s_n_llhttp__internal__n_url_to_http: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_to_http;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_skip_to_http:
+ s_n_llhttp__internal__n_url_skip_to_http: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_skip_to_http;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ p++;
+ goto s_n_llhttp__internal__n_url_to_http;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_fragment:
+ s_n_llhttp__internal__n_url_fragment: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_fragment;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8;
+ }
+ case 5: {
+ p++;
+ goto s_n_llhttp__internal__n_url_fragment;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_63;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_end_stub_query_3:
+ s_n_llhttp__internal__n_span_end_stub_query_3: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_end_stub_query_3;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_url_fragment;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_query:
+ s_n_llhttp__internal__n_url_query: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_query;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11;
+ }
+ case 5: {
+ p++;
+ goto s_n_llhttp__internal__n_url_query;
+ }
+ case 6: {
+ goto s_n_llhttp__internal__n_span_end_stub_query_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_64;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_query_or_fragment:
+ s_n_llhttp__internal__n_url_query_or_fragment: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_query_or_fragment;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 10: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4;
+ }
+ case ' ': {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5;
+ }
+ case '#': {
+ p++;
+ goto s_n_llhttp__internal__n_url_fragment;
+ }
+ case '?': {
+ p++;
+ goto s_n_llhttp__internal__n_url_query;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_65;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_path:
+ s_n_llhttp__internal__n_url_path: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_path;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ p++;
+ goto s_n_llhttp__internal__n_url_path;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_url_query_or_fragment;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_stub_path_2:
+ s_n_llhttp__internal__n_span_start_stub_path_2: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_stub_path_2;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_url_path;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_stub_path:
+ s_n_llhttp__internal__n_span_start_stub_path: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_stub_path;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_url_path;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_stub_path_1:
+ s_n_llhttp__internal__n_span_start_stub_path_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_stub_path_1;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_url_path;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_server_with_at:
+ s_n_llhttp__internal__n_url_server_with_at: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7,
+ 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5,
+ 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_server_with_at;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14;
+ }
+ case 5: {
+ p++;
+ goto s_n_llhttp__internal__n_url_server;
+ }
+ case 6: {
+ goto s_n_llhttp__internal__n_span_start_stub_path_1;
+ }
+ case 7: {
+ p++;
+ goto s_n_llhttp__internal__n_url_query;
+ }
+ case 8: {
+ p++;
+ goto s_n_llhttp__internal__n_error_66;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_67;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_server:
+ s_n_llhttp__internal__n_url_server: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7,
+ 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5,
+ 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_server;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1;
+ }
+ case 4: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2;
+ }
+ case 5: {
+ p++;
+ goto s_n_llhttp__internal__n_url_server;
+ }
+ case 6: {
+ goto s_n_llhttp__internal__n_span_start_stub_path;
+ }
+ case 7: {
+ p++;
+ goto s_n_llhttp__internal__n_url_query;
+ }
+ case 8: {
+ p++;
+ goto s_n_llhttp__internal__n_url_server_with_at;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_68;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_schema_delim_1:
+ s_n_llhttp__internal__n_url_schema_delim_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_schema_delim_1;
+ }
+ switch (*p) {
+ case '/': {
+ p++;
+ goto s_n_llhttp__internal__n_url_server;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_69;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_schema_delim:
+ s_n_llhttp__internal__n_url_schema_delim: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_schema_delim;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case '/': {
+ p++;
+ goto s_n_llhttp__internal__n_url_schema_delim_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_69;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_end_stub_schema:
+ s_n_llhttp__internal__n_span_end_stub_schema: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_end_stub_schema;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_url_schema_delim;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_schema:
+ s_n_llhttp__internal__n_url_schema: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0,
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_schema;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_end_stub_schema;
+ }
+ case 3: {
+ p++;
+ goto s_n_llhttp__internal__n_url_schema;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_70;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_start:
+ s_n_llhttp__internal__n_url_start: {
+ static uint8_t lookup_table[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0,
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_start;
+ }
+ switch (lookup_table[(uint8_t) *p]) {
+ case 1: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 2: {
+ goto s_n_llhttp__internal__n_span_start_stub_path_2;
+ }
+ case 3: {
+ goto s_n_llhttp__internal__n_url_schema;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_71;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_url_1:
+ s_n_llhttp__internal__n_span_start_llhttp__on_url_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_url_1;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_url;
+ goto s_n_llhttp__internal__n_url_start;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_entry_normal:
+ s_n_llhttp__internal__n_url_entry_normal: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_entry_normal;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_url:
+ s_n_llhttp__internal__n_span_start_llhttp__on_url: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_url;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_url;
+ goto s_n_llhttp__internal__n_url_server;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_url_entry_connect:
+ s_n_llhttp__internal__n_url_entry_connect: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_url_entry_connect;
+ }
+ switch (*p) {
+ case 9: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ case 12: {
+ p++;
+ goto s_n_llhttp__internal__n_error_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_url;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_spaces_before_url:
+ s_n_llhttp__internal__n_req_spaces_before_url: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_spaces_before_url;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_req_spaces_before_url;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_is_equal_method;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_first_space_before_url:
+ s_n_llhttp__internal__n_req_first_space_before_url: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_first_space_before_url;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_req_spaces_before_url;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_72;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1:
+ s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: {
+ switch (llhttp__on_method_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_req_first_space_before_url;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_22;
+ default:
+ goto s_n_llhttp__internal__n_error_89;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_2:
+ s_n_llhttp__internal__n_after_start_req_2: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_2;
+ }
+ switch (*p) {
+ case 'L': {
+ p++;
+ match = 19;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_3:
+ s_n_llhttp__internal__n_after_start_req_3: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_3;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 36;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_3;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_1:
+ s_n_llhttp__internal__n_after_start_req_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_1;
+ }
+ switch (*p) {
+ case 'C': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_2;
+ }
+ case 'N': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_4:
+ s_n_llhttp__internal__n_after_start_req_4: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_4;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 16;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_4;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_6:
+ s_n_llhttp__internal__n_after_start_req_6: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_6;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 22;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_6;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_8:
+ s_n_llhttp__internal__n_after_start_req_8: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_8;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_8;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_9:
+ s_n_llhttp__internal__n_after_start_req_9: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_9;
+ }
+ switch (*p) {
+ case 'Y': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_7:
+ s_n_llhttp__internal__n_after_start_req_7: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_7;
+ }
+ switch (*p) {
+ case 'N': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_8;
+ }
+ case 'P': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_9;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_5:
+ s_n_llhttp__internal__n_after_start_req_5: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_5;
+ }
+ switch (*p) {
+ case 'H': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_6;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_7;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_12:
+ s_n_llhttp__internal__n_after_start_req_12: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_12;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_12;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_13:
+ s_n_llhttp__internal__n_after_start_req_13: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_13;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 5);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 35;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_13;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_11:
+ s_n_llhttp__internal__n_after_start_req_11: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_11;
+ }
+ switch (*p) {
+ case 'L': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_12;
+ }
+ case 'S': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_13;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_10:
+ s_n_llhttp__internal__n_after_start_req_10: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_10;
+ }
+ switch (*p) {
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_11;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_14:
+ s_n_llhttp__internal__n_after_start_req_14: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_14;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 45;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_14;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_17:
+ s_n_llhttp__internal__n_after_start_req_17: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_17;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 9);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 41;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_17;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_16:
+ s_n_llhttp__internal__n_after_start_req_16: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_16;
+ }
+ switch (*p) {
+ case '_': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_17;
+ }
+ default: {
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_15:
+ s_n_llhttp__internal__n_after_start_req_15: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_15;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_16;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_15;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_18:
+ s_n_llhttp__internal__n_after_start_req_18: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_18;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_18;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_20:
+ s_n_llhttp__internal__n_after_start_req_20: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_20;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 31;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_20;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_21:
+ s_n_llhttp__internal__n_after_start_req_21: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_21;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_21;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_19:
+ s_n_llhttp__internal__n_after_start_req_19: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_19;
+ }
+ switch (*p) {
+ case 'I': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_20;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_21;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_23:
+ s_n_llhttp__internal__n_after_start_req_23: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_23;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 24;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_23;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_24:
+ s_n_llhttp__internal__n_after_start_req_24: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_24;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 23;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_24;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_26:
+ s_n_llhttp__internal__n_after_start_req_26: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_26;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 7);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 21;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_26;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_28:
+ s_n_llhttp__internal__n_after_start_req_28: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_28;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 30;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_28;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_29:
+ s_n_llhttp__internal__n_after_start_req_29: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_29;
+ }
+ switch (*p) {
+ case 'L': {
+ p++;
+ match = 10;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_27:
+ s_n_llhttp__internal__n_after_start_req_27: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_27;
+ }
+ switch (*p) {
+ case 'A': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_28;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_29;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_25:
+ s_n_llhttp__internal__n_after_start_req_25: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_25;
+ }
+ switch (*p) {
+ case 'A': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_26;
+ }
+ case 'C': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_27;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_30:
+ s_n_llhttp__internal__n_after_start_req_30: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_30;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 11;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_30;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_22:
+ s_n_llhttp__internal__n_after_start_req_22: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_22;
+ }
+ switch (*p) {
+ case '-': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_23;
+ }
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_24;
+ }
+ case 'K': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_25;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_30;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_31:
+ s_n_llhttp__internal__n_after_start_req_31: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_31;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 5);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 25;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_31;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_32:
+ s_n_llhttp__internal__n_after_start_req_32: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_32;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_32;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_35:
+ s_n_llhttp__internal__n_after_start_req_35: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_35;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 28;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_35;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_36:
+ s_n_llhttp__internal__n_after_start_req_36: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_36;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 39;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_36;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_34:
+ s_n_llhttp__internal__n_after_start_req_34: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_34;
+ }
+ switch (*p) {
+ case 'T': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_35;
+ }
+ case 'U': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_36;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_37:
+ s_n_llhttp__internal__n_after_start_req_37: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_37;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 38;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_37;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_38:
+ s_n_llhttp__internal__n_after_start_req_38: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_38;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_38;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_42:
+ s_n_llhttp__internal__n_after_start_req_42: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_42;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 12;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_42;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_43:
+ s_n_llhttp__internal__n_after_start_req_43: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_43;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 13;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_43;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_41:
+ s_n_llhttp__internal__n_after_start_req_41: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_41;
+ }
+ switch (*p) {
+ case 'F': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_42;
+ }
+ case 'P': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_43;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_40:
+ s_n_llhttp__internal__n_after_start_req_40: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_40;
+ }
+ switch (*p) {
+ case 'P': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_41;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_39:
+ s_n_llhttp__internal__n_after_start_req_39: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_39;
+ }
+ switch (*p) {
+ case 'I': {
+ p++;
+ match = 34;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_40;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_45:
+ s_n_llhttp__internal__n_after_start_req_45: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_45;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 29;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_45;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_44:
+ s_n_llhttp__internal__n_after_start_req_44: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_44;
+ }
+ switch (*p) {
+ case 'R': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_45;
+ }
+ case 'T': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_33:
+ s_n_llhttp__internal__n_after_start_req_33: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_33;
+ }
+ switch (*p) {
+ case 'A': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_34;
+ }
+ case 'L': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_37;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_38;
+ }
+ case 'R': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_39;
+ }
+ case 'U': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_44;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_48:
+ s_n_llhttp__internal__n_after_start_req_48: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_48;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 17;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_48;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_49:
+ s_n_llhttp__internal__n_after_start_req_49: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_49;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 44;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_49;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_50:
+ s_n_llhttp__internal__n_after_start_req_50: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_50;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 5);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 43;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_50;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_51:
+ s_n_llhttp__internal__n_after_start_req_51: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_51;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 20;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_51;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_47:
+ s_n_llhttp__internal__n_after_start_req_47: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_47;
+ }
+ switch (*p) {
+ case 'B': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_48;
+ }
+ case 'C': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_49;
+ }
+ case 'D': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_50;
+ }
+ case 'P': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_51;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_46:
+ s_n_llhttp__internal__n_after_start_req_46: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_46;
+ }
+ switch (*p) {
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_47;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_54:
+ s_n_llhttp__internal__n_after_start_req_54: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_54;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 14;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_54;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_56:
+ s_n_llhttp__internal__n_after_start_req_56: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_56;
+ }
+ switch (*p) {
+ case 'P': {
+ p++;
+ match = 37;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_57:
+ s_n_llhttp__internal__n_after_start_req_57: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_57;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 9);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 42;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_57;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_55:
+ s_n_llhttp__internal__n_after_start_req_55: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_55;
+ }
+ switch (*p) {
+ case 'U': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_56;
+ }
+ case '_': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_57;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_53:
+ s_n_llhttp__internal__n_after_start_req_53: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_53;
+ }
+ switch (*p) {
+ case 'A': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_54;
+ }
+ case 'T': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_55;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_58:
+ s_n_llhttp__internal__n_after_start_req_58: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_58;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 4);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 33;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_58;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_59:
+ s_n_llhttp__internal__n_after_start_req_59: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_59;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob51, 7);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 26;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_59;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_52:
+ s_n_llhttp__internal__n_after_start_req_52: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_52;
+ }
+ switch (*p) {
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_53;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_58;
+ }
+ case 'U': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_59;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_61:
+ s_n_llhttp__internal__n_after_start_req_61: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_61;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob52, 6);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 40;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_61;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_62:
+ s_n_llhttp__internal__n_after_start_req_62: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_62;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob53, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_62;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_60:
+ s_n_llhttp__internal__n_after_start_req_60: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_60;
+ }
+ switch (*p) {
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_61;
+ }
+ case 'R': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_62;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_65:
+ s_n_llhttp__internal__n_after_start_req_65: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_65;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob54, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 18;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_65;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_67:
+ s_n_llhttp__internal__n_after_start_req_67: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_67;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob55, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 32;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_67;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_68:
+ s_n_llhttp__internal__n_after_start_req_68: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_68;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob56, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 15;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_68;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_66:
+ s_n_llhttp__internal__n_after_start_req_66: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_66;
+ }
+ switch (*p) {
+ case 'I': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_67;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_68;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_69:
+ s_n_llhttp__internal__n_after_start_req_69: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_69;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob57, 8);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 27;
+ goto s_n_llhttp__internal__n_invoke_store_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_after_start_req_69;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_64:
+ s_n_llhttp__internal__n_after_start_req_64: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_64;
+ }
+ switch (*p) {
+ case 'B': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_65;
+ }
+ case 'L': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_66;
+ }
+ case 'S': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_69;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req_63:
+ s_n_llhttp__internal__n_after_start_req_63: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req_63;
+ }
+ switch (*p) {
+ case 'N': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_64;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_after_start_req:
+ s_n_llhttp__internal__n_after_start_req: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_after_start_req;
+ }
+ switch (*p) {
+ case 'A': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_1;
+ }
+ case 'B': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_4;
+ }
+ case 'C': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_5;
+ }
+ case 'D': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_10;
+ }
+ case 'F': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_14;
+ }
+ case 'G': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_15;
+ }
+ case 'H': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_18;
+ }
+ case 'L': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_19;
+ }
+ case 'M': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_22;
+ }
+ case 'N': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_31;
+ }
+ case 'O': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_32;
+ }
+ case 'P': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_33;
+ }
+ case 'R': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_46;
+ }
+ case 'S': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_52;
+ }
+ case 'T': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_60;
+ }
+ case 'U': {
+ p++;
+ goto s_n_llhttp__internal__n_after_start_req_63;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_90;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_method_1:
+ s_n_llhttp__internal__n_span_start_llhttp__on_method_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_method_1;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_method;
+ goto s_n_llhttp__internal__n_after_start_req;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_line_almost_done:
+ s_n_llhttp__internal__n_res_line_almost_done: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_line_almost_done;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status:
+ s_n_llhttp__internal__n_res_status: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_status;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1;
+ }
+ default: {
+ p++;
+ goto s_n_llhttp__internal__n_res_status;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_status:
+ s_n_llhttp__internal__n_span_start_llhttp__on_status: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_status;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_status;
+ goto s_n_llhttp__internal__n_res_status;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status_start:
+ s_n_llhttp__internal__n_res_status_start: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status_start;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_res_line_almost_done;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_status;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status_code_otherwise:
+ s_n_llhttp__internal__n_res_status_code_otherwise: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status_code_otherwise;
+ }
+ switch (*p) {
+ case 10: {
+ goto s_n_llhttp__internal__n_res_status_start;
+ }
+ case 13: {
+ goto s_n_llhttp__internal__n_res_status_start;
+ }
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_res_status_start;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_77;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status_code_digit_3:
+ s_n_llhttp__internal__n_res_status_code_digit_3: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status_code_digit_3;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_79;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status_code_digit_2:
+ s_n_llhttp__internal__n_res_status_code_digit_2: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status_code_digit_2;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_81;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_status_code_digit_1:
+ s_n_llhttp__internal__n_res_status_code_digit_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_status_code_digit_1;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_mul_add_status_code;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_83;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_after_version:
+ s_n_llhttp__internal__n_res_after_version: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_after_version;
+ }
+ switch (*p) {
+ case ' ': {
+ p++;
+ goto s_n_llhttp__internal__n_invoke_update_status_code;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_84;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1:
+ s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: {
+ switch (llhttp__on_version_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_res_after_version;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_21;
+ default:
+ goto s_n_llhttp__internal__n_error_74;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_73:
+ s_n_llhttp__internal__n_error_73: {
+ state->error = 0x9;
+ state->reason = "Invalid HTTP version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_85:
+ s_n_llhttp__internal__n_error_85: {
+ state->error = 0x9;
+ state->reason = "Invalid minor version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_http_minor:
+ s_n_llhttp__internal__n_res_http_minor: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_http_minor;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_store_http_minor_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_7;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_86:
+ s_n_llhttp__internal__n_error_86: {
+ state->error = 0x9;
+ state->reason = "Expected dot";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_http_dot:
+ s_n_llhttp__internal__n_res_http_dot: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_http_dot;
+ }
+ switch (*p) {
+ case '.': {
+ p++;
+ goto s_n_llhttp__internal__n_res_http_minor;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_8;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_error_87:
+ s_n_llhttp__internal__n_error_87: {
+ state->error = 0x9;
+ state->reason = "Invalid major version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_res_http_major:
+ s_n_llhttp__internal__n_res_http_major: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_res_http_major;
+ }
+ switch (*p) {
+ case '0': {
+ p++;
+ match = 0;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '1': {
+ p++;
+ match = 1;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '2': {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '3': {
+ p++;
+ match = 3;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '4': {
+ p++;
+ match = 4;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '5': {
+ p++;
+ match = 5;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '6': {
+ p++;
+ match = 6;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '7': {
+ p++;
+ match = 7;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '8': {
+ p++;
+ match = 8;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ case '9': {
+ p++;
+ match = 9;
+ goto s_n_llhttp__internal__n_invoke_store_http_major_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_9;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_version_1:
+ s_n_llhttp__internal__n_span_start_llhttp__on_version_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_version_1;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_version;
+ goto s_n_llhttp__internal__n_res_http_major;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_start_res:
+ s_n_llhttp__internal__n_start_res: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_start_res;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob58, 5);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_start_res;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_91;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete:
+ s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: {
+ switch (llhttp__on_method_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_req_first_space_before_url;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_19;
+ default:
+ goto s_n_llhttp__internal__n_error_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_or_res_method_2:
+ s_n_llhttp__internal__n_req_or_res_method_2: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_or_res_method_2;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob59, 2);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ match = 2;
+ goto s_n_llhttp__internal__n_invoke_store_method;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_or_res_method_2;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_88;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_update_type_1:
+ s_n_llhttp__internal__n_invoke_update_type_1: {
+ switch (llhttp__internal__c_update_type_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_or_res_method_3:
+ s_n_llhttp__internal__n_req_or_res_method_3: {
+ llparse_match_t match_seq;
+
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_or_res_method_3;
+ }
+ match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob60, 3);
+ p = match_seq.current;
+ switch (match_seq.status) {
+ case kMatchComplete: {
+ p++;
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_method_1;
+ }
+ case kMatchPause: {
+ return s_n_llhttp__internal__n_req_or_res_method_3;
+ }
+ case kMatchMismatch: {
+ goto s_n_llhttp__internal__n_error_88;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_or_res_method_1:
+ s_n_llhttp__internal__n_req_or_res_method_1: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_or_res_method_1;
+ }
+ switch (*p) {
+ case 'E': {
+ p++;
+ goto s_n_llhttp__internal__n_req_or_res_method_2;
+ }
+ case 'T': {
+ p++;
+ goto s_n_llhttp__internal__n_req_or_res_method_3;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_88;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_req_or_res_method:
+ s_n_llhttp__internal__n_req_or_res_method: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_req_or_res_method;
+ }
+ switch (*p) {
+ case 'H': {
+ p++;
+ goto s_n_llhttp__internal__n_req_or_res_method_1;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_88;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_span_start_llhttp__on_method:
+ s_n_llhttp__internal__n_span_start_llhttp__on_method: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_span_start_llhttp__on_method;
+ }
+ state->_span_pos0 = (void*) p;
+ state->_span_cb0 = llhttp__on_method;
+ goto s_n_llhttp__internal__n_req_or_res_method;
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_start_req_or_res:
+ s_n_llhttp__internal__n_start_req_or_res: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_start_req_or_res;
+ }
+ switch (*p) {
+ case 'H': {
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_method;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_update_type_2;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_load_type:
+ s_n_llhttp__internal__n_invoke_load_type: {
+ switch (llhttp__internal__c_load_type(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1;
+ case 2:
+ goto s_n_llhttp__internal__n_start_res;
+ default:
+ goto s_n_llhttp__internal__n_start_req_or_res;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_invoke_update_finish:
+ s_n_llhttp__internal__n_invoke_update_finish: {
+ switch (llhttp__internal__c_update_finish(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ case s_n_llhttp__internal__n_start:
+ s_n_llhttp__internal__n_start: {
+ if (p == endp) {
+ return s_n_llhttp__internal__n_start;
+ }
+ switch (*p) {
+ case 10: {
+ p++;
+ goto s_n_llhttp__internal__n_start;
+ }
+ case 13: {
+ p++;
+ goto s_n_llhttp__internal__n_start;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_invoke_load_initial_message_completed;
+ }
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ default:
+ /* UNREACHABLE */
+ abort();
+ }
+ s_n_llhttp__internal__n_error_2: {
+ state->error = 0x7;
+ state->reason = "Invalid characters in url";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_finish_2: {
+ switch (llhttp__internal__c_update_finish_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_start;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_initial_message_completed: {
+ switch (llhttp__internal__c_update_initial_message_completed(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_finish_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_content_length: {
+ switch (llhttp__internal__c_update_content_length(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_initial_message_completed;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_7: {
+ state->error = 0x5;
+ state->reason = "Data after `Connection: close`";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_3: {
+ switch (llhttp__internal__c_test_lenient_flags_3(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_closed;
+ default:
+ goto s_n_llhttp__internal__n_error_7;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_2: {
+ switch (llhttp__internal__c_test_lenient_flags_2(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_update_initial_message_completed;
+ default:
+ goto s_n_llhttp__internal__n_closed;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_finish_1: {
+ switch (llhttp__internal__c_update_finish_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_2: {
+ state->error = 0x15;
+ state->reason = "on_message_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_8: {
+ state->error = 0x12;
+ state->reason = "`on_message_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: {
+ switch (llhttp__on_message_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_pause_1;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_2;
+ default:
+ goto s_n_llhttp__internal__n_error_8;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_26: {
+ state->error = 0xc;
+ state->reason = "Chunk size overflow";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_3: {
+ state->error = 0x15;
+ state->reason = "on_chunk_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length_1;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_10: {
+ state->error = 0x14;
+ state->reason = "`on_chunk_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: {
+ switch (llhttp__on_chunk_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_update_content_length_1;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_3;
+ default:
+ goto s_n_llhttp__internal__n_error_10;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_11: {
+ state->error = 0x2;
+ state->reason = "Expected LF after chunk data";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_4: {
+ switch (llhttp__internal__c_test_lenient_flags_4(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete;
+ default:
+ goto s_n_llhttp__internal__n_error_11;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_body: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_body(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_chunk_data_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags: {
+ switch (llhttp__internal__c_or_flags(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_field_start;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_4: {
+ state->error = 0x15;
+ state->reason = "on_chunk_header pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_9: {
+ state->error = 0x13;
+ state->reason = "`on_chunk_header` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: {
+ switch (llhttp__on_chunk_header(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_is_equal_content_length;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_4;
+ default:
+ goto s_n_llhttp__internal__n_error_9;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_12: {
+ state->error = 0x2;
+ state->reason = "Expected LF after chunk size";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_5: {
+ switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header;
+ default:
+ goto s_n_llhttp__internal__n_error_12;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_13: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_14: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_5: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_name pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_15: {
+ state->error = 0x22;
+ state->reason = "`on_chunk_extension_name` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_name(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_6: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_name pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_16: {
+ state->error = 0x22;
+ state->reason = "`on_chunk_extension_name` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_name(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_7: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_value pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_18: {
+ state->error = 0x23;
+ state->reason = "`on_chunk_extension_value` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_20: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk extensions quote value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_8: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_value pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_quoted_value_done;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_19: {
+ state->error = 0x23;
+ state->reason = "`on_chunk_extension_value` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_21;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_21;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_9: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_value pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_otherwise;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_22: {
+ state->error = 0x23;
+ state->reason = "`on_chunk_extension_value` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_23;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_23;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_10: {
+ state->error = 0x15;
+ state->reason = "on_chunk_extension_name pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_value;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_17: {
+ state->error = 0x22;
+ state->reason = "`on_chunk_extension_name` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: {
+ switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_chunk_extension_value;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_10;
+ default:
+ goto s_n_llhttp__internal__n_error_17;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_name(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_chunk_extension_name(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_24;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_24;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_25: {
+ state->error = 0xc;
+ state->reason = "Invalid character in chunk size";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_mul_add_content_length: {
+ switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) {
+ case 1:
+ goto s_n_llhttp__internal__n_error_26;
+ default:
+ goto s_n_llhttp__internal__n_chunk_size;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_27: {
+ state->error = 0xc;
+ state->reason = "Invalid character in chunk size";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_11: {
+ state->error = 0x15;
+ state->reason = "on_message_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_28: {
+ state->error = 0x12;
+ state->reason = "`on_message_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_body_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_body(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_finish_3: {
+ switch (llhttp__internal__c_update_finish_3(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_29: {
+ state->error = 0xf;
+ state->reason = "Request has invalid `Transfer-Encoding`";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause: {
+ state->error = 0x15;
+ state->reason = "on_message_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_6: {
+ state->error = 0x12;
+ state->reason = "`on_message_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: {
+ switch (llhttp__on_message_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete;
+ case 21:
+ goto s_n_llhttp__internal__n_pause;
+ default:
+ goto s_n_llhttp__internal__n_error_6;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_1: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete;
+ default:
+ goto s_n_llhttp__internal__n_error_5;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_13: {
+ state->error = 0x15;
+ state->reason = "on_chunk_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_32: {
+ state->error = 0x14;
+ state->reason = "`on_chunk_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: {
+ switch (llhttp__on_chunk_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_13;
+ default:
+ goto s_n_llhttp__internal__n_error_32;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_31: {
+ state->error = 0x4;
+ state->reason = "Content-Length can't be present with Transfer-Encoding";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_1: {
+ switch (llhttp__internal__c_or_flags_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_2: {
+ switch (llhttp__internal__c_or_flags_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_upgrade: {
+ switch (llhttp__internal__c_update_upgrade(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_or_flags_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_12: {
+ state->error = 0x15;
+ state->reason = "Paused by on_headers_complete";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_30: {
+ state->error = 0x11;
+ state->reason = "User callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: {
+ switch (llhttp__on_headers_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete;
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_or_flags_1;
+ case 2:
+ goto s_n_llhttp__internal__n_invoke_update_upgrade;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_12;
+ default:
+ goto s_n_llhttp__internal__n_error_30;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: {
+ switch (llhttp__before_headers_complete(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_6: {
+ switch (llhttp__internal__c_test_lenient_flags_6(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_error_31;
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_flags_1: {
+ switch (llhttp__internal__c_test_flags_1(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_6;
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_flags: {
+ switch (llhttp__internal__c_test_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1;
+ default:
+ goto s_n_llhttp__internal__n_invoke_test_flags_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_33: {
+ state->error = 0x2;
+ state->reason = "Expected LF after headers";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_7: {
+ switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_test_flags;
+ default:
+ goto s_n_llhttp__internal__n_error_33;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_field: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_field(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_5;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_5;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_8: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_field_colon_discard_ws;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_36: {
+ state->error = 0xa;
+ state->reason = "Invalid header value char";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_10: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_value_discard_ws;
+ default:
+ goto s_n_llhttp__internal__n_error_36;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_38: {
+ state->error = 0xb;
+ state->reason = "Empty Content-Length";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_14: {
+ state->error = 0x15;
+ state->reason = "on_header_value_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_37: {
+ state->error = 0x1d;
+ state->reason = "`on_header_value_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state: {
+ switch (llhttp__internal__c_update_header_state(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_3: {
+ switch (llhttp__internal__c_or_flags_3(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_4: {
+ switch (llhttp__internal__c_or_flags_4(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_5: {
+ switch (llhttp__internal__c_or_flags_5(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_6: {
+ switch (llhttp__internal__c_or_flags_6(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state_1: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 5:
+ goto s_n_llhttp__internal__n_invoke_or_flags_3;
+ case 6:
+ goto s_n_llhttp__internal__n_invoke_or_flags_4;
+ case 7:
+ goto s_n_llhttp__internal__n_invoke_or_flags_5;
+ case 8:
+ goto s_n_llhttp__internal__n_invoke_or_flags_6;
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 2:
+ goto s_n_llhttp__internal__n_error_38;
+ default:
+ goto s_n_llhttp__internal__n_invoke_load_header_state_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_35: {
+ state->error = 0xa;
+ state->reason = "Invalid header value char";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_9: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_value_discard_lws;
+ default:
+ goto s_n_llhttp__internal__n_error_35;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_39: {
+ state->error = 0x2;
+ state->reason = "Expected LF after CR";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_11: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_value_discard_lws;
+ default:
+ goto s_n_llhttp__internal__n_error_39;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_1: {
+ switch (llhttp__internal__c_update_header_state_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state_3: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 8:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_1;
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_2: {
+ switch (llhttp__internal__c_update_header_state(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_7: {
+ switch (llhttp__internal__c_or_flags_3(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_8: {
+ switch (llhttp__internal__c_or_flags_4(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_9: {
+ switch (llhttp__internal__c_or_flags_5(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_10: {
+ switch (llhttp__internal__c_or_flags_6(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state_4: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 5:
+ goto s_n_llhttp__internal__n_invoke_or_flags_7;
+ case 6:
+ goto s_n_llhttp__internal__n_invoke_or_flags_8;
+ case 7:
+ goto s_n_llhttp__internal__n_invoke_or_flags_9;
+ case 8:
+ goto s_n_llhttp__internal__n_invoke_or_flags_10;
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_40: {
+ state->error = 0x3;
+ state->reason = "Missing expected LF after header value";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_header_value_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_header_value_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_header_value_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_41;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_41;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_12: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_value_lenient;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_4: {
+ switch (llhttp__internal__c_update_header_state(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_11: {
+ switch (llhttp__internal__c_or_flags_3(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_4;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_12: {
+ switch (llhttp__internal__c_or_flags_4(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_4;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_13: {
+ switch (llhttp__internal__c_or_flags_5(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_4;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_14: {
+ switch (llhttp__internal__c_or_flags_6(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state_5: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 5:
+ goto s_n_llhttp__internal__n_invoke_or_flags_11;
+ case 6:
+ goto s_n_llhttp__internal__n_invoke_or_flags_12;
+ case 7:
+ goto s_n_llhttp__internal__n_invoke_or_flags_13;
+ case 8:
+ goto s_n_llhttp__internal__n_invoke_or_flags_14;
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_5: {
+ switch (llhttp__internal__c_update_header_state_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection_token;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_3: {
+ switch (llhttp__internal__c_update_header_state_3(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection_ws;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_6: {
+ switch (llhttp__internal__c_update_header_state_6(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection_ws;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_7: {
+ switch (llhttp__internal__c_update_header_state_7(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_connection_ws;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_43;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_43;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_mul_add_content_length_1: {
+ switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) {
+ case 1:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5;
+ default:
+ goto s_n_llhttp__internal__n_header_value_content_length;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_15: {
+ switch (llhttp__internal__c_or_flags_15(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_44;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_44;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_42: {
+ state->error = 0x4;
+ state->reason = "Duplicate Content-Length";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_flags_2: {
+ switch (llhttp__internal__c_test_flags_2(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_header_value_content_length;
+ default:
+ goto s_n_llhttp__internal__n_error_42;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_46;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_46;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_8: {
+ switch (llhttp__internal__c_update_header_state_8(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_otherwise;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_value(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_45;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_error_45;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_13: {
+ switch (llhttp__internal__c_test_lenient_flags_13(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7;
+ default:
+ goto s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_type_1: {
+ switch (llhttp__internal__c_load_type(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_13;
+ default:
+ goto s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_9: {
+ switch (llhttp__internal__c_update_header_state_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_and_flags: {
+ switch (llhttp__internal__c_and_flags(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_value_te_chunked;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_17: {
+ switch (llhttp__internal__c_or_flags_16(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_and_flags;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_14: {
+ switch (llhttp__internal__c_test_lenient_flags_13(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8;
+ default:
+ goto s_n_llhttp__internal__n_invoke_or_flags_17;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_type_2: {
+ switch (llhttp__internal__c_load_type(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_14;
+ default:
+ goto s_n_llhttp__internal__n_invoke_or_flags_17;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_16: {
+ switch (llhttp__internal__c_or_flags_16(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_and_flags;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_flags_3: {
+ switch (llhttp__internal__c_test_flags_3(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_load_type_2;
+ default:
+ goto s_n_llhttp__internal__n_invoke_or_flags_16;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_or_flags_18: {
+ switch (llhttp__internal__c_or_flags_18(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_header_state_9;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_header_state_2: {
+ switch (llhttp__internal__c_load_header_state(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_value_connection;
+ case 2:
+ goto s_n_llhttp__internal__n_invoke_test_flags_2;
+ case 3:
+ goto s_n_llhttp__internal__n_invoke_test_flags_3;
+ case 4:
+ goto s_n_llhttp__internal__n_invoke_or_flags_18;
+ default:
+ goto s_n_llhttp__internal__n_header_value;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_15: {
+ state->error = 0x15;
+ state->reason = "on_header_field_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_discard_ws;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_34: {
+ state->error = 0x1c;
+ state->reason = "`on_header_field_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_field(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_header_field(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_47: {
+ state->error = 0xa;
+ state->reason = "Invalid header token";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_10: {
+ switch (llhttp__internal__c_update_header_state_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_field_general;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_header_state: {
+ switch (llhttp__internal__c_store_header_state(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_header_field_colon;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_header_state_11: {
+ switch (llhttp__internal__c_update_header_state_1(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_header_field_general;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_4: {
+ state->error = 0x1e;
+ state->reason = "Unexpected space after start line";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags: {
+ switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_header_field_start;
+ default:
+ goto s_n_llhttp__internal__n_error_4;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_16: {
+ state->error = 0x15;
+ state->reason = "on_url_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_3: {
+ state->error = 0x1a;
+ state->reason = "`on_url_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_url_complete: {
+ switch (llhttp__on_url_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_headers_start;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_16;
+ default:
+ goto s_n_llhttp__internal__n_error_3;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_http_minor: {
+ switch (llhttp__internal__c_update_http_minor(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_http_major: {
+ switch (llhttp__internal__c_update_http_major(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_http_minor;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_3: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_48: {
+ state->error = 0x7;
+ state->reason = "Expected CRLF";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_4: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_55: {
+ state->error = 0x17;
+ state->reason = "Pause on PRI/Upgrade";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_56: {
+ state->error = 0x9;
+ state->reason = "Expected HTTP/2 Connection Preface";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_53: {
+ state->error = 0x2;
+ state->reason = "Expected CRLF after version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_16: {
+ switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_headers_start;
+ default:
+ goto s_n_llhttp__internal__n_error_53;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_54: {
+ state->error = 0x9;
+ state->reason = "Expected CRLF after version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_17: {
+ state->error = 0x15;
+ state->reason = "on_version_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_1;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_52: {
+ state->error = 0x21;
+ state->reason = "`on_version_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_51;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_51;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 9:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor_1: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1;
+ case 1:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor_2: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_major: {
+ switch (llhttp__internal__c_load_http_major(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor;
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor_1;
+ case 2:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor_2;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_15: {
+ switch (llhttp__internal__c_test_lenient_flags_15(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1;
+ default:
+ goto s_n_llhttp__internal__n_invoke_load_http_major;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_http_minor: {
+ switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_57;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_57;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_3: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_58;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_58;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_http_major: {
+ switch (llhttp__internal__c_store_http_major(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_req_http_dot;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_4: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_59;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_59;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_50: {
+ state->error = 0x8;
+ state->reason = "Invalid method for HTTP/x.x request";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_method: {
+ switch (llhttp__internal__c_load_method(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 1:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 2:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 3:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 4:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 5:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 6:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 7:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 8:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 9:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 10:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 11:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 12:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 13:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 14:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 15:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 16:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 17:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 18:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 19:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 20:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 21:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 22:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 23:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 24:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 25:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 26:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 27:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 28:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 29:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 30:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 31:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 32:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 33:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 34:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ default:
+ goto s_n_llhttp__internal__n_error_50;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_62: {
+ state->error = 0x8;
+ state->reason = "Expected HTTP/";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_60: {
+ state->error = 0x8;
+ state->reason = "Expected SOURCE method for ICE/x.x request";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_method_2: {
+ switch (llhttp__internal__c_load_method(state, p, endp)) {
+ case 33:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ default:
+ goto s_n_llhttp__internal__n_error_60;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_61: {
+ state->error = 0x8;
+ state->reason = "Invalid method for RTSP/x.x request";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_method_3: {
+ switch (llhttp__internal__c_load_method(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 3:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 6:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 35:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 36:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 37:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 38:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 39:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 40:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 41:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 42:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 43:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 44:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ case 45:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_version;
+ default:
+ goto s_n_llhttp__internal__n_error_61;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_18: {
+ state->error = 0x15;
+ state->reason = "on_url_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_http_start;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_49: {
+ state->error = 0x1a;
+ state->reason = "`on_url_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1: {
+ switch (llhttp__on_url_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_req_http_start;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_18;
+ default:
+ goto s_n_llhttp__internal__n_error_49;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_5: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_6: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_7: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_8: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_63: {
+ state->error = 0x7;
+ state->reason = "Invalid char in url fragment start";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_9: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_10: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_11: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_64: {
+ state->error = 0x7;
+ state->reason = "Invalid char in url query";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_65: {
+ state->error = 0x7;
+ state->reason = "Invalid char in url path";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_12: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_13: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_lf_to_http09;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_url_14: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_url(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_url_skip_to_http;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_66: {
+ state->error = 0x7;
+ state->reason = "Double @ in url";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_67: {
+ state->error = 0x7;
+ state->reason = "Unexpected char in url server";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_68: {
+ state->error = 0x7;
+ state->reason = "Unexpected char in url server";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_69: {
+ state->error = 0x7;
+ state->reason = "Unexpected char in url schema";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_70: {
+ state->error = 0x7;
+ state->reason = "Unexpected char in url schema";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_71: {
+ state->error = 0x7;
+ state->reason = "Unexpected start char in url";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_is_equal_method: {
+ switch (llhttp__internal__c_is_equal_method(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_url_entry_normal;
+ default:
+ goto s_n_llhttp__internal__n_url_entry_connect;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_72: {
+ state->error = 0x6;
+ state->reason = "Expected space after method";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_22: {
+ state->error = 0x15;
+ state->reason = "on_method_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_89: {
+ state->error = 0x20;
+ state->reason = "`on_method_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_method_2: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_method(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_method_1: {
+ switch (llhttp__internal__c_store_method(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_method_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_90: {
+ state->error = 0x6;
+ state->reason = "Invalid method encountered";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_82: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_80: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_78: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_20: {
+ state->error = 0x15;
+ state->reason = "on_status_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_75: {
+ state->error = 0x1b;
+ state->reason = "`on_status_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_status_complete: {
+ switch (llhttp__on_status_complete(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_headers_start;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_20;
+ default:
+ goto s_n_llhttp__internal__n_error_75;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_76: {
+ state->error = 0x2;
+ state->reason = "Expected LF after CR";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_18: {
+ switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete;
+ default:
+ goto s_n_llhttp__internal__n_error_76;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_status: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_status(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_res_line_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_status_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_status(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) (p + 1);
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done;
+ return s_error;
+ }
+ p++;
+ goto s_n_llhttp__internal__n_res_line_almost_done;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_77: {
+ state->error = 0xd;
+ state->reason = "Invalid response status";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_mul_add_status_code_2: {
+ switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) {
+ case 1:
+ goto s_n_llhttp__internal__n_error_78;
+ default:
+ goto s_n_llhttp__internal__n_res_status_code_otherwise;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_79: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_mul_add_status_code_1: {
+ switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) {
+ case 1:
+ goto s_n_llhttp__internal__n_error_80;
+ default:
+ goto s_n_llhttp__internal__n_res_status_code_digit_3;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_81: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_mul_add_status_code: {
+ switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) {
+ case 1:
+ goto s_n_llhttp__internal__n_error_82;
+ default:
+ goto s_n_llhttp__internal__n_res_status_code_digit_2;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_83: {
+ state->error = 0xd;
+ state->reason = "Invalid status code";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_status_code: {
+ switch (llhttp__internal__c_update_status_code(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_res_status_code_digit_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_84: {
+ state->error = 0x9;
+ state->reason = "Expected space after version";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_21: {
+ state->error = 0x15;
+ state->reason = "on_version_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_version;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_74: {
+ state->error = 0x21;
+ state->reason = "`on_version_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_6: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_5: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_73;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_73;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor_3: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 9:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor_4: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6;
+ case 1:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_minor_5: {
+ switch (llhttp__internal__c_load_http_minor(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_http_major_1: {
+ switch (llhttp__internal__c_load_http_major(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor_3;
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor_4;
+ case 2:
+ goto s_n_llhttp__internal__n_invoke_load_http_minor_5;
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_test_lenient_flags_17: {
+ switch (llhttp__internal__c_test_lenient_flags_15(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6;
+ default:
+ goto s_n_llhttp__internal__n_invoke_load_http_major_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_http_minor_1: {
+ switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_test_lenient_flags_17;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_7: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_85;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_85;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_8: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_86;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_86;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_http_major_1: {
+ switch (llhttp__internal__c_store_http_major(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_res_http_dot;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_version_9: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_version(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_87;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_error_87;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_91: {
+ state->error = 0x8;
+ state->reason = "Expected HTTP/";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_19: {
+ state->error = 0x15;
+ state->reason = "on_method_complete pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_1: {
+ state->error = 0x20;
+ state->reason = "`on_method_complete` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_method: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_method(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_type: {
+ switch (llhttp__internal__c_update_type(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_method;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_store_method: {
+ switch (llhttp__internal__c_store_method(state, p, endp, match)) {
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_type;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_88: {
+ state->error = 0x8;
+ state->reason = "Invalid word encountered";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_span_end_llhttp__on_method_1: {
+ const unsigned char* start;
+ int err;
+
+ start = state->_span_pos0;
+ state->_span_pos0 = NULL;
+ err = llhttp__on_method(state, start, p);
+ if (err != 0) {
+ state->error = err;
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_type_1;
+ return s_error;
+ }
+ goto s_n_llhttp__internal__n_invoke_update_type_1;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_update_type_2: {
+ switch (llhttp__internal__c_update_type(state, p, endp)) {
+ default:
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_23: {
+ state->error = 0x15;
+ state->reason = "on_message_begin pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error: {
+ state->error = 0x10;
+ state->reason = "`on_message_begin` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: {
+ switch (llhttp__on_message_begin(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_load_type;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_23;
+ default:
+ goto s_n_llhttp__internal__n_error;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_pause_24: {
+ state->error = 0x15;
+ state->reason = "on_reset pause";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_finish;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_error_92: {
+ state->error = 0x1f;
+ state->reason = "`on_reset` callback error";
+ state->error_pos = (const char*) p;
+ state->_current = (void*) (intptr_t) s_error;
+ return s_error;
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_llhttp__on_reset: {
+ switch (llhttp__on_reset(state, p, endp)) {
+ case 0:
+ goto s_n_llhttp__internal__n_invoke_update_finish;
+ case 21:
+ goto s_n_llhttp__internal__n_pause_24;
+ default:
+ goto s_n_llhttp__internal__n_error_92;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+ s_n_llhttp__internal__n_invoke_load_initial_message_completed: {
+ switch (llhttp__internal__c_load_initial_message_completed(state, p, endp)) {
+ case 1:
+ goto s_n_llhttp__internal__n_invoke_llhttp__on_reset;
+ default:
+ goto s_n_llhttp__internal__n_invoke_update_finish;
+ }
+ /* UNREACHABLE */;
+ abort();
+ }
+}
+
+int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) {
+ llparse_state_t next;
+
+ /* check lingering errors */
+ if (state->error != 0) {
+ return state->error;
+ }
+
+ /* restart spans */
+ if (state->_span_pos0 != NULL) {
+ state->_span_pos0 = (void*) p;
+ }
+
+ next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp);
+ if (next == s_error) {
+ return state->error;
+ }
+ state->_current = (void*) (intptr_t) next;
+
+ /* execute spans */
+ if (state->_span_pos0 != NULL) {
+ int error;
+
+ error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp);
+ if (error != 0) {
+ state->error = error;
+ state->error_pos = endp;
+ return error;
+ }
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/third-party/url-parser/.gitignore b/third-party/url-parser/.gitignore
new file mode 100644
index 0000000..c83c013
--- /dev/null
+++ b/third-party/url-parser/.gitignore
@@ -0,0 +1 @@
+/.dirstamp
diff --git a/third-party/url-parser/AUTHORS b/third-party/url-parser/AUTHORS
new file mode 100644
index 0000000..5323b68
--- /dev/null
+++ b/third-party/url-parser/AUTHORS
@@ -0,0 +1,68 @@
+# 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>
+Cam Swords <cam.swords@gmail.com>
+Chris Dickinson <christopher.s.dickinson@gmail.com>
+Uli Köhler <ukoehler@btronik.de>
+Charlie Somerville <charlie@charliesomerville.com>
+Patrik Stutz <patrik.stutz@gmail.com>
+Fedor Indutny <fedor.indutny@gmail.com>
+runner <runner.mei@gmail.com>
+Alexis Campailla <alexis@janeasystems.com>
+David Wragg <david@wragg.org>
+Vinnie Falco <vinnie.falco@gmail.com>
+Alex Butum <alexbutum@linux.com>
+Rex Feng <rexfeng@gmail.com>
+Alex Kocharin <alex@kocharin.ru>
+Mark Koopman <markmontymark@yahoo.com>
+Helge Heß <me@helgehess.eu>
+Alexis La Goutte <alexis.lagoutte@gmail.com>
+George Miroshnykov <george.miroshnykov@gmail.com>
+Maciej Małecki <me@mmalecki.com>
+Marc O'Morain <github.com@marcomorain.com>
+Jeff Pinner <jpinner@twitter.com>
+Timothy J Fontaine <tjfontaine@gmail.com>
+Akagi201 <akagi201@gmail.com>
+Romain Giraud <giraud.romain@gmail.com>
+Jay Satiro <raysatiro@yahoo.com>
+Arne Steen <Arne.Steen@gmx.de>
+Kjell Schubert <kjell.schubert@gmail.com>
+Olivier Mengué <dolmen@cpan.org>
diff --git a/third-party/url-parser/LICENSE-MIT b/third-party/url-parser/LICENSE-MIT
new file mode 100644
index 0000000..1ec0ab4
--- /dev/null
+++ b/third-party/url-parser/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright Joyent, Inc. and other Node contributors.
+
+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/url-parser/url_parser.c b/third-party/url-parser/url_parser.c
new file mode 100644
index 0000000..4912ee2
--- /dev/null
+++ b/third-party/url-parser/url_parser.c
@@ -0,0 +1,652 @@
+/* Copyright Joyent, Inc. and other Node contributors.
+ *
+ * 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 "url_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef BIT_AT
+# define BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#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_http_major
+ , s_res_http_dot
+ , s_res_http_minor
+ , s_res_http_end
+ , 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_http_I
+ , s_req_http_IC
+ , s_req_http_major
+ , s_req_http_dot
+ , s_req_http_minor
+ , s_req_http_end
+ , 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
+ };
+
+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_v6_zone_start
+ , s_http_host_v6_zone
+ , 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) ((c == ' ') ? 0 : tokens[(unsigned char)c])
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c) STRICT_TOKEN(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) 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
+
+/* 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;
+ }
+
+ /* fall through */
+ 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;
+}
+
+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;
+ }
+
+ /* fall through */
+ 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;
+ }
+
+ /* fall through */
+ case s_http_host_v6_start:
+ if (IS_HEX(ch) || ch == ':' || ch == '.') {
+ return s_http_host_v6;
+ }
+
+ if (s == s_http_host_v6 && ch == '%') {
+ return s_http_host_v6_zone_start;
+ }
+ break;
+
+ case s_http_host_v6_zone:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* fall through */
+ case s_http_host_v6_zone_start:
+ /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
+ if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
+ ch == '~') {
+ return s_http_host_v6_zone;
+ }
+ 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;
+
+ assert(u->field_set & (1 << UF_HOST));
+
+ 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 = (uint16_t)(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 = (uint16_t)(p - buf);
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6_zone_start:
+ case s_http_host_v6_zone:
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ u->field_data[UF_PORT].off = (uint16_t)(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 = (uint16_t)(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_v6_zone_start:
+ case s_http_host_v6_zone:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void
+http_parser_url_init(struct http_parser_url *u) {
+ memset(u, 0, sizeof(*u));
+}
+
+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;
+
+ if (buflen == 0) {
+ return 1;
+ }
+
+ 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;
+
+ /* fall through */
+ 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 = (uint16_t)(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)) &&
+ (u->field_set & (1 << UF_HOST)) == 0) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_HOST)) {
+ 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)) {
+ uint16_t off;
+ uint16_t len;
+ const char* p;
+ const char* end;
+ unsigned long v;
+
+ off = u->field_data[UF_PORT].off;
+ len = u->field_data[UF_PORT].len;
+ end = buf + off + len;
+
+ /* NOTE: The characters are already validated and are in the [0-9] range */
+ assert(off + len <= buflen && "Port number overflow");
+ v = 0;
+ for (p = buf + off; p < end; p++) {
+ v *= 10;
+ v += *p - '0';
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+ }
+
+ u->port = (uint16_t) v;
+ }
+
+ return 0;
+}
diff --git a/third-party/url-parser/url_parser.h b/third-party/url-parser/url_parser.h
new file mode 100644
index 0000000..78b3096
--- /dev/null
+++ b/third-party/url-parser/url_parser.h
@@ -0,0 +1,94 @@
+/* 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 url_parser_h
+#define url_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 9
+#define HTTP_PARSER_VERSION_PATCH 1
+
+#include <stddef.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
+#include <BaseTsd.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
+
+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];
+};
+
+/* Initialize all http_parser_url members to 0 */
+void http_parser_url_init(struct http_parser_url *u);
+
+/* 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);
+#ifdef __cplusplus
+}
+#endif
+#endif