From dcf05622f182fb676b8b8f566ba31beca832da28 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 17:47:43 +0100 Subject: [PATCH 01/24] lib: nghttp2: new library introduction Signed-off-by: Leonardo Alminana --- CMakeLists.txt | 6 + cmake/headers.cmake | 4 + cmake/libraries.cmake | 1 + lib/nghttp2/AUTHORS | 155 + lib/nghttp2/CMakeLists.txt | 493 + lib/nghttp2/CMakeOptions.txt | 28 + lib/nghttp2/CONTRIBUTION | 18 + lib/nghttp2/COPYING | 23 + lib/nghttp2/ChangeLog | 0 lib/nghttp2/Dockerfile.android | 121 + lib/nghttp2/LICENSE | 1 + lib/nghttp2/Makefile.am | 61 + lib/nghttp2/NEWS | 0 lib/nghttp2/README | 1 + lib/nghttp2/README.rst | 1473 ++ lib/nghttp2/android-config | 37 + lib/nghttp2/android-env | 40 + lib/nghttp2/author.py | 52 + lib/nghttp2/bpf/CMakeLists.txt | 13 + lib/nghttp2/bpf/Makefile.am | 40 + lib/nghttp2/bpf/reuseport_kern.c | 663 + lib/nghttp2/cmake/ExtractValidFlags.cmake | 31 + lib/nghttp2/cmake/FindCUnit.cmake | 40 + lib/nghttp2/cmake/FindJansson.cmake | 40 + lib/nghttp2/cmake/FindJemalloc.cmake | 40 + lib/nghttp2/cmake/FindLibbpf.cmake | 32 + lib/nghttp2/cmake/FindLibcares.cmake | 40 + lib/nghttp2/cmake/FindLibev.cmake | 38 + lib/nghttp2/cmake/FindLibevent.cmake | 97 + lib/nghttp2/cmake/FindLibnghttp3.cmake | 41 + lib/nghttp2/cmake/FindLibngtcp2.cmake | 41 + .../cmake/FindLibngtcp2_crypto_quictls.cmake | 43 + lib/nghttp2/cmake/FindSystemd.cmake | 19 + lib/nghttp2/cmake/PickyWarningsC.cmake | 163 + lib/nghttp2/cmake/PickyWarningsCXX.cmake | 117 + lib/nghttp2/cmake/Version.cmake | 11 + lib/nghttp2/cmakeconfig.h.in | 110 + lib/nghttp2/configure.ac | 1192 ++ lib/nghttp2/contrib/.gitignore | 3 + lib/nghttp2/contrib/CMakeLists.txt | 12 + lib/nghttp2/contrib/Makefile.am | 51 + lib/nghttp2/contrib/nghttpx-init.in | 164 + lib/nghttp2/contrib/nghttpx-logrotate | 11 + lib/nghttp2/contrib/nghttpx-upstart.conf.in | 8 + lib/nghttp2/contrib/nghttpx.service.in | 17 + lib/nghttp2/contrib/tlsticketupdate.go | 112 + lib/nghttp2/contrib/usr.sbin.nghttpx | 16 + lib/nghttp2/doc/.gitignore | 19 + lib/nghttp2/doc/CMakeLists.txt | 352 + lib/nghttp2/doc/Makefile.am | 365 + lib/nghttp2/doc/README.rst | 160 + .../doc/_exts/rubydomain/LICENSE.rubydomain | 28 + lib/nghttp2/doc/_exts/rubydomain/__init__.py | 14 + .../doc/_exts/rubydomain/rubydomain.py | 703 + lib/nghttp2/doc/bash_completion/h2load | 19 + .../bash_completion/make_bash_completion.py | 75 + lib/nghttp2/doc/bash_completion/nghttp | 19 + lib/nghttp2/doc/bash_completion/nghttpd | 19 + lib/nghttp2/doc/bash_completion/nghttpx | 19 + .../doc/building-android-binary.rst.in | 1 + lib/nghttp2/doc/conf.py.in | 252 + lib/nghttp2/doc/contribute.rst.in | 1 + lib/nghttp2/doc/docutils.conf | 2 + lib/nghttp2/doc/h2load-howto.rst.in | 1 + lib/nghttp2/doc/h2load.1 | 531 + lib/nghttp2/doc/h2load.1.rst | 435 + lib/nghttp2/doc/h2load.h2r | 120 + lib/nghttp2/doc/index.rst.in | 1 + lib/nghttp2/doc/make.bat | 170 + lib/nghttp2/doc/mkapiref.py | 343 + lib/nghttp2/doc/nghttp.1 | 336 + lib/nghttp2/doc/nghttp.1.rst | 276 + lib/nghttp2/doc/nghttp.h2r | 57 + lib/nghttp2/doc/nghttp2.h.rst.in | 4 + lib/nghttp2/doc/nghttp2ver.h.rst.in | 4 + lib/nghttp2/doc/nghttpd.1 | 236 + lib/nghttp2/doc/nghttpd.1.rst | 186 + lib/nghttp2/doc/nghttpd.h2r | 4 + lib/nghttp2/doc/nghttpx-howto.rst.in | 1 + lib/nghttp2/doc/nghttpx.1 | 2771 ++++ lib/nghttp2/doc/nghttpx.1.rst | 2527 +++ lib/nghttp2/doc/nghttpx.h2r | 719 + lib/nghttp2/doc/package_README.rst.in | 1 + lib/nghttp2/doc/programmers-guide.rst | 526 + lib/nghttp2/doc/security.rst | 1 + lib/nghttp2/doc/sources/security.rst | 33 + lib/nghttp2/doc/tutorial-client.rst.in | 6 + lib/nghttp2/doc/tutorial-hpack.rst.in | 6 + lib/nghttp2/doc/tutorial-server.rst.in | 6 + lib/nghttp2/docker/Dockerfile | 78 + lib/nghttp2/docker/README.rst | 25 + lib/nghttp2/examples/.gitignore | 5 + lib/nghttp2/examples/CMakeLists.txt | 37 + lib/nghttp2/examples/Makefile.am | 54 + lib/nghttp2/examples/client.c | 741 + lib/nghttp2/examples/deflate.c | 206 + lib/nghttp2/examples/libevent-client.c | 635 + lib/nghttp2/examples/libevent-server.c | 835 + lib/nghttp2/fedora/spdylay.spec | 75 + lib/nghttp2/fuzz/README.rst | 33 + ...4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 | Bin 0 -> 61 bytes ...3c23a338b4c827bf6164640ff20a2d64d45a6b3f5a | Bin 0 -> 85 bytes ...8fcfff43b378a92c7da44268b9dda2bf32a1178c66 | Bin 0 -> 16466 bytes ...2bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a | Bin 0 -> 86 bytes ...2f5d07b1dd87de1f651f80ef82c2815b0248b7dccd | Bin 0 -> 82 bytes ...0a41547272ad42377149edcf130b2bf0b76804c61f | 2 + ...06ca725770a731e73c2144c7b81953dcc4b4f73c32 | Bin 0 -> 86 bytes ...6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 | Bin 0 -> 76 bytes ...c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 | Bin 0 -> 96 bytes ...e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 | Bin 0 -> 61 bytes ...84e8c2f917946ec7ef3f4720535478b41e097a798a | Bin 0 -> 73 bytes ...158af429421570e7363a3b75441edc5d740513b0dc | Bin 0 -> 3603 bytes ...9edfac603133e0144dba08836f90b1ae164b328800 | Bin 0 -> 88 bytes ...847b76c09921e984796f6dc482859b119cf4879300 | Bin 0 -> 77 bytes ...fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 | Bin 0 -> 86 bytes ...5432789b45aff588c606536e93824b89739a6d07ab | Bin 0 -> 85 bytes ...54d935ebc543f4d1305e318ccd2ff407517636bed8 | Bin 0 -> 78 bytes ...7f853954e6f11c1f4754ccd83b1603b808878cfa76 | Bin 0 -> 84 bytes ...ea37c900f13f429b750c87e6175b234b881bda6248 | Bin 0 -> 98 bytes ...983d11256d2432fcdeb55bfba9634aa88e3794adc6 | Bin 0 -> 98 bytes ...8ddd0722f69830ac04975ddb5a9d83cdc406cbb678 | Bin 0 -> 2487 bytes ...7c55330e80d8121a0cff19633a56eba8f2182a59df | Bin 0 -> 71 bytes ...01339096b80a382afa1083a19c4deab11be847502f | Bin 0 -> 77 bytes ...042038692eaede4d2c1f9e05a27f2410a6e0230132 | Bin 0 -> 61 bytes ...48ae962bb543dd5ca188dabc30897726f87403fbce | Bin 0 -> 93 bytes ...f68a89c9750332ae5063e36401eae150ce63188fe0 | Bin 0 -> 86 bytes ...4f5d37e4111fe6203bac35b220d50362d5e986aa91 | Bin 0 -> 65 bytes ...6401fee50c647552c99c0550ebfd7a3b736e8db9e5 | Bin 0 -> 3603 bytes ...8a4336f1386633bac75dea2c4b64c02541e7320933 | Bin 0 -> 114 bytes ...aab9a74dd392cbdae104307e8512e5e4113739e93a | Bin 0 -> 86 bytes ...b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c | Bin 0 -> 85 bytes ...a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d | Bin 0 -> 75 bytes ...0881328f80b5d3c942de3b1304a0382923ce896f8f | Bin 0 -> 89 bytes ...4a34c3574da63554ff06f52377b73a9cfc24eb02ca | Bin 0 -> 61 bytes ...5e7e4a102ba74721a04dfa1811e0968e9a4966d92c | Bin 0 -> 90 bytes ...4e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf | Bin 0 -> 17632 bytes ...99f21e9793f6ffc82ae0ef6917a8611e8879e05941 | Bin 0 -> 86 bytes ...ea13b0740f76898ccbb1da25f2281da76e50c1d04a | Bin 0 -> 93 bytes ...a505d84e76374b064aa5c71aab33bd9650c9a9d801 | Bin 0 -> 83 bytes ...f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 | Bin 0 -> 82 bytes ...391023a0981789c2351817996e0c253bfed708ad82 | Bin 0 -> 58 bytes ...fcf964f1897063e81da79c971e8af8c1fefa3e3cab | Bin 0 -> 63 bytes ...153c480754054a57777f22a00d377d745d78e9d193 | Bin 0 -> 73 bytes ...0da94f77bf4a44e4e741420291491343f7ae4ecc16 | Bin 0 -> 84 bytes ...bb7ac85f76a91229d9ba675fc9e09fe12f4a497937 | Bin 0 -> 86 bytes ...61a9f9da021f0fe52ebdbb148ee776ced87bac9b13 | Bin 0 -> 91 bytes ...c9f51f211183795660ec81a6bdb5614031d39ebe3a | Bin 0 -> 3593 bytes ...cb33958a806a1debf3d9ccf7b09c2d31256498cda6 | Bin 0 -> 85 bytes ...f8b4a0e8f7756b9846f2e2add8dd0df825296d993e | Bin 0 -> 72 bytes ...bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa | Bin 0 -> 85 bytes ...0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a | Bin 0 -> 63 bytes ...1e14519f10d8c669a5a2602fc948bc9a80e6114b63 | Bin 0 -> 82 bytes ...625307fe48ef29bc66641c4f80ed4593bf8b773f88 | Bin 0 -> 85 bytes ...90554c93278034ebacc24792509a32aeba466df4e8 | Bin 0 -> 96 bytes ...89d60c1995b7fab68ded6ab052814008d990862c23 | Bin 0 -> 85 bytes ...8cbcfe1a511670ae1a4a434f3d483f942738933a3e | Bin 0 -> 95 bytes ...61d387692063ce2ae73b3e5401b716326967b4ce0c | Bin 0 -> 73 bytes ...9cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d | Bin 0 -> 84 bytes ...fa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 | Bin 0 -> 71 bytes ...74d4fab435b7738e1a14d0754fb79229c4bda9f604 | Bin 0 -> 63 bytes ...ee59618a2ebd483812410e9f8ae5a92fb72ef70885 | Bin 0 -> 87 bytes ...f62659d3b26bcbb8f2055f1add504f599f9051f61e | Bin 0 -> 65 bytes ...d33933fae10c67e501d6cea8e73ce76f4363d0bbea | Bin 0 -> 89 bytes ...8849f277f914a889a54d44c1f2566b6ddd5bc83b4f | Bin 0 -> 86 bytes ...23da1173666aaeae9788b144fa2c723204d55cc0a2 | Bin 0 -> 61 bytes ...9e19835f34e56c7927fda22859e960f5f13bc847a0 | Bin 0 -> 87 bytes ...4677a6c1a118994d7534d1fb08d631898d67372f5a | Bin 0 -> 92 bytes ...03e3e553623d4fc382324d8b8ba53ebf83f0457707 | Bin 0 -> 74 bytes ...d0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 | Bin 0 -> 77 bytes ...5328f893972df210ab75cdb67f620b370ee5cddf45 | Bin 0 -> 60 bytes ...2d0a01173ea80ccc584b659947b64ffefddab7fada | Bin 0 -> 73 bytes ...fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 | Bin 0 -> 80 bytes ...0f7d85086319d4177524fad58dc01743434765902a | Bin 0 -> 101 bytes ...3a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 | Bin 0 -> 61 bytes ...5e9cc4e407fc62ce5688e1c6636f482ea02314c357 | Bin 0 -> 103 bytes ...556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 | Bin 0 -> 73 bytes ...48550f07fa8818d1ee8edae39ca50f516a57a12edb | Bin 0 -> 73 bytes ...9caf527d5f10667e0a38790f28f32af61efa930eef | Bin 0 -> 58 bytes ...d187c8acb61d3a638bc30568bdcc6be30fd9defd43 | Bin 0 -> 65 bytes ...88a40d0b144f11ee98624e3686c0f43684e34e6838 | Bin 0 -> 100 bytes ...b07b2fd54985ef27c99670bed582ce904569b95702 | Bin 0 -> 81 bytes ...c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 | Bin 0 -> 65 bytes ...d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 | Bin 0 -> 73 bytes ...73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 | Bin 0 -> 83 bytes ...c3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 | Bin 0 -> 85 bytes ...4a0969b588dc9281ea98fd744acd9b8bd1daf72225 | Bin 0 -> 3606 bytes ...c304c9c9ba6b43e13849235339710d6b5f941e80a1 | Bin 0 -> 84 bytes ...1b48d08b52752a41633279ff2e9e474eebf508250f | Bin 0 -> 91 bytes ...e44db5b90a4bb23e0558873f159bf09140782989d8 | Bin 0 -> 102 bytes ...ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 | Bin 0 -> 75 bytes ...eb105290328add76123b4a99ad4e78189e1337ae1b | Bin 0 -> 65 bytes ...ec483a4b493668ca1448948c62f641d176838306d5 | Bin 0 -> 73 bytes ...211893e7681fef6da4b623392d402fb40736dc1beb | Bin 0 -> 71 bytes ...3016c49cc2a868a1bfc007528138a28ea1c0abfda7 | Bin 0 -> 63 bytes ...d9897e41ceb2add1ebdec0937a64321c536eef71f7 | Bin 0 -> 104 bytes ...4bb9e6c9011855bbf954c273f45eb3ea97bb491c9a | Bin 0 -> 39 bytes ...8c177382ab3278a019935fa50b3e0d7971c28c40d9 | Bin 0 -> 63 bytes ...c5a009696cd5f659f85fc10ef76dc140851ffcc423 | Bin 0 -> 87 bytes ...e5de18d06d885b50be9136778b4937437f0d70738d | Bin 0 -> 101 bytes ...77470859ccb4ec9fa5e8c30de7b40521d620b87a1e | Bin 0 -> 117 bytes ...fcf3f3e41e75bae978dcfc8886981479d723fc44e9 | Bin 0 -> 98 bytes ...2a88c9537bee642b8a7a8a388cb4952f3bf60e64cc | Bin 0 -> 70 bytes ...b9a30f8faa658ee49f6ce90f3e34df70560a0477ad | Bin 0 -> 85 bytes ...4b1a0d0e22054f76bf704db8e19d73cb9bf792a89b | Bin 0 -> 86 bytes ...63f7c212d2465936090c06ba4db92071a3c247ca11 | Bin 0 -> 72 bytes ...bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 | Bin 0 -> 58 bytes ...db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 | Bin 0 -> 102 bytes ...ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 | Bin 0 -> 63 bytes ...5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 | Bin 0 -> 62 bytes ...b53931aa6bfd4ce95771c748372626414d5c37e105 | Bin 0 -> 3593 bytes ...1cef9c3f753d440c75efa489a952fdcd314d27ee1d | Bin 0 -> 81 bytes ...ff4970fb339f440867ebedf02eaab75fb555e293cf | Bin 0 -> 76 bytes ...ac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a | Bin 0 -> 98 bytes ...a945b7fd0035f6dba48d886160fdf1974aae8dee65 | Bin 0 -> 84 bytes ...c711940e4917d5dae3dc2723a034f44d2b53a34a11 | Bin 0 -> 16465 bytes ...e717a9e6aa8bb2842953e4528230a5bcfc3a59c120 | Bin 0 -> 62 bytes ...290f938c5bc247c440a2e572ab18021c8223c55bc7 | Bin 0 -> 65 bytes ...3d6e269f8b4bc785089040be666f480464cb13b4df | Bin 0 -> 102 bytes ...a68efea1d8d304ae595a094ebc955bceb6d06ed629 | Bin 0 -> 81 bytes ...5556875ab6df561f1ca718f1fc716a929d3c706f14 | Bin 0 -> 60 bytes ...1c1ef87680a96a1aca613180110df26259eb36c433 | Bin 0 -> 79 bytes ...51637a357cc1c84d30e3d48bccc9b97564c8a60b73 | Bin 0 -> 73 bytes ...1089ad6af12beea18f895be6f18d42962721d6e3ee | Bin 0 -> 102 bytes ...cac77778fe630b278f167321a46d861ac8ad56fd76 | Bin 0 -> 85 bytes ...98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a | Bin 0 -> 63 bytes ...157dbfa122f6de9b6f4e5a3a036c17f32da3030877 | Bin 0 -> 72 bytes ...12a9b8b0419fbae7c0934dda22e61f11556918f1cc | Bin 0 -> 115 bytes ...bd936130b0d06332ab062a48f41b206ce696428e03 | Bin 0 -> 65 bytes ...4af58d8bba7df12c1cd15c404d95680df6fc1cb89e | Bin 0 -> 72 bytes ...e7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 | Bin 0 -> 90 bytes ...57fde916a7d8db293427159f3b31bbc23b6b285116 | Bin 0 -> 85 bytes ...057b557ab044d24130bd360fe087e9f55bef2cadc6 | Bin 0 -> 91 bytes ...ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 | Bin 0 -> 3606 bytes ...4ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb | Bin 0 -> 191 bytes ...a704db43cdfec99fc1b9de83c195227161f4bdb911 | Bin 0 -> 5070 bytes ...084e3946d556086c9991cce7962e9e69a3eed406aa | Bin 0 -> 18740 bytes lib/nghttp2/fuzz/fuzz_frames.cc | 160 + lib/nghttp2/fuzz/fuzz_target.cc | 79 + lib/nghttp2/fuzz/fuzz_target_fdp.cc | 99 + lib/nghttp2/genauthoritychartbl.py | 32 + lib/nghttp2/gendowncasetbl.py | 30 + lib/nghttp2/genheaderfunc.py | 48 + lib/nghttp2/genlibtokenlookup.py | 143 + lib/nghttp2/genmethodchartbl.py | 29 + lib/nghttp2/genmethodfunc.py | 52 + lib/nghttp2/gennghttpxfun.py | 241 + lib/nghttp2/gennmchartbl.py | 28 + lib/nghttp2/genpathchartbl.py | 23 + lib/nghttp2/gentokenlookup.py | 69 + lib/nghttp2/genvchartbl.py | 26 + lib/nghttp2/git-clang-format | 484 + lib/nghttp2/go.mod | 26 + lib/nghttp2/go.sum | 78 + lib/nghttp2/help2rst.py | 192 + lib/nghttp2/integration-tests/.gitignore | 3 + lib/nghttp2/integration-tests/CMakeLists.txt | 45 + lib/nghttp2/integration-tests/Makefile.am | 52 + lib/nghttp2/integration-tests/alt-server.crt | 21 + lib/nghttp2/integration-tests/config.go.in | 6 + .../integration-tests/nghttpx_http1_test.go | 1631 ++ .../integration-tests/nghttpx_http2_test.go | 3740 +++++ .../integration-tests/nghttpx_http3_test.go | 393 + lib/nghttp2/integration-tests/req-return.rb | 12 + .../integration-tests/req-set-header.rb | 7 + lib/nghttp2/integration-tests/resp-return.rb | 12 + .../integration-tests/resp-set-header.rb | 7 + lib/nghttp2/integration-tests/server.crt | 21 + .../integration-tests/server_tester.go | 819 + .../integration-tests/server_tester_http3.go | 90 + lib/nghttp2/integration-tests/setenv.in | 13 + lib/nghttp2/lib/.gitignore | 3 + lib/nghttp2/lib/CMakeLists.txt | 80 + lib/nghttp2/lib/Makefile.am | 81 + lib/nghttp2/lib/Makefile.msvc | 254 + lib/nghttp2/lib/includes/CMakeLists.txt | 4 + lib/nghttp2/lib/includes/Makefile.am | 26 + lib/nghttp2/lib/includes/nghttp2/nghttp2.h | 5881 +++++++ .../lib/includes/nghttp2/nghttp2ver.h.in | 42 + lib/nghttp2/lib/libnghttp2.pc.in | 33 + lib/nghttp2/lib/nghttp2_buf.c | 527 + lib/nghttp2/lib/nghttp2_buf.h | 412 + lib/nghttp2/lib/nghttp2_callbacks.c | 175 + lib/nghttp2/lib/nghttp2_callbacks.h | 125 + lib/nghttp2/lib/nghttp2_debug.c | 60 + lib/nghttp2/lib/nghttp2_debug.h | 43 + lib/nghttp2/lib/nghttp2_extpri.c | 41 + lib/nghttp2/lib/nghttp2_extpri.h | 65 + lib/nghttp2/lib/nghttp2_frame.c | 1214 ++ lib/nghttp2/lib/nghttp2_frame.h | 637 + lib/nghttp2/lib/nghttp2_hd.c | 2357 +++ lib/nghttp2/lib/nghttp2_hd.h | 440 + lib/nghttp2/lib/nghttp2_hd_huffman.c | 144 + lib/nghttp2/lib/nghttp2_hd_huffman.h | 72 + lib/nghttp2/lib/nghttp2_hd_huffman_data.c | 4980 ++++++ lib/nghttp2/lib/nghttp2_helper.c | 803 + lib/nghttp2/lib/nghttp2_helper.h | 122 + lib/nghttp2/lib/nghttp2_http.c | 631 + lib/nghttp2/lib/nghttp2_http.h | 100 + lib/nghttp2/lib/nghttp2_int.h | 58 + lib/nghttp2/lib/nghttp2_map.c | 338 + lib/nghttp2/lib/nghttp2_map.h | 138 + lib/nghttp2/lib/nghttp2_mem.c | 74 + lib/nghttp2/lib/nghttp2_mem.h | 45 + lib/nghttp2/lib/nghttp2_net.h | 91 + lib/nghttp2/lib/nghttp2_npn.c | 57 + lib/nghttp2/lib/nghttp2_npn.h | 34 + lib/nghttp2/lib/nghttp2_option.c | 152 + lib/nghttp2/lib/nghttp2_option.h | 152 + lib/nghttp2/lib/nghttp2_outbound_item.c | 130 + lib/nghttp2/lib/nghttp2_outbound_item.h | 166 + lib/nghttp2/lib/nghttp2_pq.c | 183 + lib/nghttp2/lib/nghttp2_pq.h | 124 + lib/nghttp2/lib/nghttp2_priority_spec.c | 52 + lib/nghttp2/lib/nghttp2_priority_spec.h | 42 + lib/nghttp2/lib/nghttp2_queue.c | 85 + lib/nghttp2/lib/nghttp2_queue.h | 51 + lib/nghttp2/lib/nghttp2_ratelim.c | 75 + lib/nghttp2/lib/nghttp2_ratelim.h | 57 + lib/nghttp2/lib/nghttp2_rcbuf.c | 102 + lib/nghttp2/lib/nghttp2_rcbuf.h | 80 + lib/nghttp2/lib/nghttp2_session.c | 8395 ++++++++++ lib/nghttp2/lib/nghttp2_session.h | 974 ++ lib/nghttp2/lib/nghttp2_stream.c | 1016 ++ lib/nghttp2/lib/nghttp2_stream.h | 441 + lib/nghttp2/lib/nghttp2_submit.c | 900 ++ lib/nghttp2/lib/nghttp2_submit.h | 34 + lib/nghttp2/lib/nghttp2_time.c | 65 + lib/nghttp2/lib/nghttp2_time.h | 38 + lib/nghttp2/lib/nghttp2_version.c | 38 + lib/nghttp2/lib/sfparse.c | 1146 ++ lib/nghttp2/lib/sfparse.h | 409 + lib/nghttp2/lib/version.rc.in | 40 + lib/nghttp2/m4/ax_check_compile_flag.m4 | 74 + lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 | 1018 ++ lib/nghttp2/m4/libxml2.m4 | 188 + lib/nghttp2/makebashcompletion | 7 + lib/nghttp2/makemanpages | 12 + lib/nghttp2/makerelease.sh | 23 + lib/nghttp2/mkcipherlist.py | 325 + lib/nghttp2/mkhufftbl.py | 468 + lib/nghttp2/mkstatichdtbl.py | 36 + lib/nghttp2/nghttp2-master.zip | Bin 0 -> 1445803 bytes .../nghttp2-master/.github/dependabot.yml | 6 + .../.github/workflows/build.yml | 482 + .../nghttp2-master/.github/workflows/fuzz.yml | 24 + lib/nghttp2/nghttp2-master/.gitignore | 56 + lib/nghttp2/nghttp2-master/.gitmodules | 7 + lib/nghttp2/nghttpx.conf.sample | 29 + lib/nghttp2/pre-commit | 27 + lib/nghttp2/proxy.pac.sample | 6 + lib/nghttp2/releasechk | 6 + lib/nghttp2/script/CMakeLists.txt | 5 + lib/nghttp2/script/Makefile.am | 25 + lib/nghttp2/script/README.rst | 10 + lib/nghttp2/script/fetch-ocsp-response | 253 + lib/nghttp2/src/.gitignore | 13 + lib/nghttp2/src/CMakeLists.txt | 243 + lib/nghttp2/src/HtmlParser.cc | 217 + lib/nghttp2/src/HtmlParser.h | 94 + lib/nghttp2/src/HttpServer.cc | 2286 +++ lib/nghttp2/src/HttpServer.h | 253 + lib/nghttp2/src/Makefile.am | 257 + lib/nghttp2/src/allocator.h | 273 + lib/nghttp2/src/app_helper.cc | 518 + lib/nghttp2/src/app_helper.h | 98 + lib/nghttp2/src/base64.h | 225 + lib/nghttp2/src/base64_test.cc | 121 + lib/nghttp2/src/base64_test.h | 39 + lib/nghttp2/src/buffer.h | 78 + lib/nghttp2/src/buffer_test.cc | 78 + lib/nghttp2/src/buffer_test.h | 38 + lib/nghttp2/src/ca-config.json | 17 + lib/nghttp2/src/ca.nghttp2.org-key.pem | 27 + lib/nghttp2/src/ca.nghttp2.org.csr | 17 + lib/nghttp2/src/ca.nghttp2.org.csr.json | 17 + lib/nghttp2/src/ca.nghttp2.org.pem | 22 + lib/nghttp2/src/comp_helper.c | 133 + lib/nghttp2/src/comp_helper.h | 57 + lib/nghttp2/src/deflatehd.cc | 450 + lib/nghttp2/src/h2load.cc | 3343 ++++ lib/nghttp2/src/h2load.h | 510 + lib/nghttp2/src/h2load_http1_session.cc | 306 + lib/nghttp2/src/h2load_http1_session.h | 60 + lib/nghttp2/src/h2load_http2_session.cc | 313 + lib/nghttp2/src/h2load_http2_session.h | 54 + lib/nghttp2/src/h2load_http3_session.cc | 457 + lib/nghttp2/src/h2load_http3_session.h | 83 + lib/nghttp2/src/h2load_quic.cc | 844 + lib/nghttp2/src/h2load_quic.h | 38 + lib/nghttp2/src/h2load_session.h | 59 + lib/nghttp2/src/http-parser.patch | 28 + lib/nghttp2/src/http2.cc | 2096 +++ lib/nghttp2/src/http2.h | 457 + lib/nghttp2/src/http2_test.cc | 1249 ++ lib/nghttp2/src/http2_test.h | 54 + lib/nghttp2/src/http3.cc | 206 + lib/nghttp2/src/http3.h | 123 + lib/nghttp2/src/inflatehd.cc | 289 + lib/nghttp2/src/libevent_util.cc | 162 + lib/nghttp2/src/libevent_util.h | 75 + lib/nghttp2/src/memchunk.h | 664 + lib/nghttp2/src/memchunk_test.cc | 340 + lib/nghttp2/src/memchunk_test.h | 47 + lib/nghttp2/src/network.h | 67 + lib/nghttp2/src/nghttp.cc | 3164 ++++ lib/nghttp2/src/nghttp.h | 310 + lib/nghttp2/src/nghttp2_config.h | 32 + lib/nghttp2/src/nghttp2_gzip.c | 87 + lib/nghttp2/src/nghttp2_gzip.h | 122 + lib/nghttp2/src/nghttp2_gzip_test.c | 111 + lib/nghttp2/src/nghttp2_gzip_test.h | 42 + lib/nghttp2/src/nghttpd.cc | 508 + lib/nghttp2/src/quic.cc | 60 + lib/nghttp2/src/quic.h | 56 + lib/nghttp2/src/shrpx-unittest.cc | 248 + lib/nghttp2/src/shrpx.cc | 5335 ++++++ lib/nghttp2/src/shrpx.h | 58 + lib/nghttp2/src/shrpx_accept_handler.cc | 111 + lib/nghttp2/src/shrpx_accept_handler.h | 54 + .../src/shrpx_api_downstream_connection.cc | 478 + .../src/shrpx_api_downstream_connection.h | 114 + lib/nghttp2/src/shrpx_client_handler.cc | 1710 ++ lib/nghttp2/src/shrpx_client_handler.h | 236 + lib/nghttp2/src/shrpx_config.cc | 4687 ++++++ lib/nghttp2/src/shrpx_config.h | 1448 ++ lib/nghttp2/src/shrpx_config_test.cc | 249 + lib/nghttp2/src/shrpx_config_test.h | 42 + lib/nghttp2/src/shrpx_connect_blocker.cc | 143 + lib/nghttp2/src/shrpx_connect_blocker.h | 86 + lib/nghttp2/src/shrpx_connection.cc | 1318 ++ lib/nghttp2/src/shrpx_connection.h | 203 + lib/nghttp2/src/shrpx_connection_handler.cc | 1320 ++ lib/nghttp2/src/shrpx_connection_handler.h | 322 + lib/nghttp2/src/shrpx_dns_resolver.cc | 353 + lib/nghttp2/src/shrpx_dns_resolver.h | 118 + lib/nghttp2/src/shrpx_dns_tracker.cc | 328 + lib/nghttp2/src/shrpx_dns_tracker.h | 121 + lib/nghttp2/src/shrpx_downstream.cc | 1189 ++ lib/nghttp2/src/shrpx_downstream.h | 628 + .../src/shrpx_downstream_connection.cc | 48 + lib/nghttp2/src/shrpx_downstream_connection.h | 81 + .../src/shrpx_downstream_connection_pool.cc | 66 + .../src/shrpx_downstream_connection_pool.h | 53 + lib/nghttp2/src/shrpx_downstream_queue.cc | 175 + lib/nghttp2/src/shrpx_downstream_queue.h | 116 + lib/nghttp2/src/shrpx_downstream_test.cc | 231 + lib/nghttp2/src/shrpx_downstream_test.h | 44 + lib/nghttp2/src/shrpx_dual_dns_resolver.cc | 93 + lib/nghttp2/src/shrpx_dual_dns_resolver.h | 69 + lib/nghttp2/src/shrpx_error.h | 47 + lib/nghttp2/src/shrpx_exec.cc | 138 + lib/nghttp2/src/shrpx_exec.h | 47 + ...px_health_monitor_downstream_connection.cc | 116 + ...rpx_health_monitor_downstream_connection.h | 64 + lib/nghttp2/src/shrpx_http.cc | 280 + lib/nghttp2/src/shrpx_http.h | 96 + .../src/shrpx_http2_downstream_connection.cc | 621 + .../src/shrpx_http2_downstream_connection.h | 88 + lib/nghttp2/src/shrpx_http2_session.cc | 2433 +++ lib/nghttp2/src/shrpx_http2_session.h | 296 + lib/nghttp2/src/shrpx_http2_upstream.cc | 2404 +++ lib/nghttp2/src/shrpx_http2_upstream.h | 150 + lib/nghttp2/src/shrpx_http3_upstream.cc | 2924 ++++ lib/nghttp2/src/shrpx_http3_upstream.h | 194 + .../src/shrpx_http_downstream_connection.cc | 1617 ++ .../src/shrpx_http_downstream_connection.h | 124 + lib/nghttp2/src/shrpx_http_test.cc | 168 + lib/nghttp2/src/shrpx_http_test.h | 42 + lib/nghttp2/src/shrpx_https_upstream.cc | 1582 ++ lib/nghttp2/src/shrpx_https_upstream.h | 113 + lib/nghttp2/src/shrpx_io_control.cc | 66 + lib/nghttp2/src/shrpx_io_control.h | 58 + lib/nghttp2/src/shrpx_live_check.cc | 799 + lib/nghttp2/src/shrpx_live_check.h | 125 + lib/nghttp2/src/shrpx_log.cc | 1008 ++ lib/nghttp2/src/shrpx_log.h | 318 + lib/nghttp2/src/shrpx_log_config.cc | 127 + lib/nghttp2/src/shrpx_log_config.h | 79 + lib/nghttp2/src/shrpx_memcached_connection.cc | 777 + lib/nghttp2/src/shrpx_memcached_connection.h | 155 + lib/nghttp2/src/shrpx_memcached_dispatcher.cc | 53 + lib/nghttp2/src/shrpx_memcached_dispatcher.h | 63 + lib/nghttp2/src/shrpx_memcached_request.h | 59 + lib/nghttp2/src/shrpx_memcached_result.h | 50 + lib/nghttp2/src/shrpx_mruby.cc | 238 + lib/nghttp2/src/shrpx_mruby.h | 89 + lib/nghttp2/src/shrpx_mruby_module.cc | 113 + lib/nghttp2/src/shrpx_mruby_module.h | 52 + lib/nghttp2/src/shrpx_mruby_module_env.cc | 500 + lib/nghttp2/src/shrpx_mruby_module_env.h | 42 + lib/nghttp2/src/shrpx_mruby_module_request.cc | 367 + lib/nghttp2/src/shrpx_mruby_module_request.h | 42 + .../src/shrpx_mruby_module_response.cc | 398 + lib/nghttp2/src/shrpx_mruby_module_response.h | 42 + .../src/shrpx_null_downstream_connection.cc | 88 + .../src/shrpx_null_downstream_connection.h | 68 + lib/nghttp2/src/shrpx_process.h | 37 + lib/nghttp2/src/shrpx_quic.cc | 393 + lib/nghttp2/src/shrpx_quic.h | 138 + .../src/shrpx_quic_connection_handler.cc | 761 + .../src/shrpx_quic_connection_handler.h | 142 + lib/nghttp2/src/shrpx_quic_listener.cc | 132 + lib/nghttp2/src/shrpx_quic_listener.h | 51 + lib/nghttp2/src/shrpx_rate_limit.cc | 123 + lib/nghttp2/src/shrpx_rate_limit.h | 68 + lib/nghttp2/src/shrpx_router.cc | 420 + lib/nghttp2/src/shrpx_router.h | 110 + lib/nghttp2/src/shrpx_router_test.cc | 184 + lib/nghttp2/src/shrpx_router_test.h | 40 + lib/nghttp2/src/shrpx_signal.cc | 138 + lib/nghttp2/src/shrpx_signal.h | 60 + lib/nghttp2/src/shrpx_tls.cc | 2691 ++++ lib/nghttp2/src/shrpx_tls.h | 337 + lib/nghttp2/src/shrpx_tls_test.cc | 339 + lib/nghttp2/src/shrpx_tls_test.h | 42 + lib/nghttp2/src/shrpx_upstream.h | 112 + lib/nghttp2/src/shrpx_worker.cc | 1347 ++ lib/nghttp2/src/shrpx_worker.h | 480 + lib/nghttp2/src/shrpx_worker_process.cc | 701 + lib/nghttp2/src/shrpx_worker_process.h | 67 + lib/nghttp2/src/shrpx_worker_test.cc | 247 + lib/nghttp2/src/shrpx_worker_test.h | 38 + lib/nghttp2/src/ssl_compat.h | 51 + lib/nghttp2/src/template.h | 548 + lib/nghttp2/src/template_test.cc | 204 + lib/nghttp2/src/template_test.h | 39 + lib/nghttp2/src/test.example.com-key.pem | 27 + lib/nghttp2/src/test.example.com.csr | 17 + lib/nghttp2/src/test.example.com.csr.json | 14 + lib/nghttp2/src/test.example.com.pem | 23 + lib/nghttp2/src/test.nghttp2.org-key.pem | 27 + lib/nghttp2/src/test.nghttp2.org.csr | 19 + lib/nghttp2/src/test.nghttp2.org.csr.json | 19 + lib/nghttp2/src/test.nghttp2.org.pem | 24 + lib/nghttp2/src/testdata/Makefile.am | 27 + lib/nghttp2/src/testdata/ipaddr.crt | 10 + lib/nghttp2/src/testdata/nosan.crt | 9 + lib/nghttp2/src/testdata/nosan_ip.crt | 9 + lib/nghttp2/src/testdata/verify_hostname.crt | 10 + lib/nghttp2/src/timegm.c | 88 + lib/nghttp2/src/timegm.h | 51 + lib/nghttp2/src/tls.cc | 201 + lib/nghttp2/src/tls.h | 116 + lib/nghttp2/src/util.cc | 1808 +++ lib/nghttp2/src/util.h | 971 ++ lib/nghttp2/src/util_test.cc | 707 + lib/nghttp2/src/util_test.h | 75 + lib/nghttp2/src/xsi_strerror.c | 50 + lib/nghttp2/src/xsi_strerror.h | 55 + lib/nghttp2/tests/.gitignore | 3 + lib/nghttp2/tests/CMakeLists.txt | 60 + lib/nghttp2/tests/Makefile.am | 100 + lib/nghttp2/tests/end_to_end.py | 104 + lib/nghttp2/tests/failmalloc.c | 79 + lib/nghttp2/tests/failmalloc_test.c | 576 + lib/nghttp2/tests/failmalloc_test.h | 38 + lib/nghttp2/tests/main.c | 473 + lib/nghttp2/tests/malloc_wrapper.c | 85 + lib/nghttp2/tests/malloc_wrapper.h | 65 + lib/nghttp2/tests/nghttp2_buf_test.c | 344 + lib/nghttp2/tests/nghttp2_buf_test.h | 42 + lib/nghttp2/tests/nghttp2_extpri_test.c | 52 + lib/nghttp2/tests/nghttp2_extpri_test.h | 35 + lib/nghttp2/tests/nghttp2_frame_test.c | 735 + lib/nghttp2/tests/nghttp2_frame_test.h | 47 + lib/nghttp2/tests/nghttp2_hd_test.c | 1577 ++ lib/nghttp2/tests/nghttp2_hd_test.h | 55 + lib/nghttp2/tests/nghttp2_helper_test.c | 195 + lib/nghttp2/tests/nghttp2_helper_test.h | 37 + lib/nghttp2/tests/nghttp2_http_test.c | 206 + lib/nghttp2/tests/nghttp2_http_test.h | 35 + lib/nghttp2/tests/nghttp2_map_test.c | 208 + lib/nghttp2/tests/nghttp2_map_test.h | 38 + lib/nghttp2/tests/nghttp2_npn_test.c | 73 + lib/nghttp2/tests/nghttp2_npn_test.h | 34 + lib/nghttp2/tests/nghttp2_pq_test.c | 228 + lib/nghttp2/tests/nghttp2_pq_test.h | 36 + lib/nghttp2/tests/nghttp2_queue_test.c | 50 + lib/nghttp2/tests/nghttp2_queue_test.h | 34 + lib/nghttp2/tests/nghttp2_ratelim_test.c | 101 + lib/nghttp2/tests/nghttp2_ratelim_test.h | 35 + lib/nghttp2/tests/nghttp2_session_test.c | 13438 ++++++++++++++++ lib/nghttp2/tests/nghttp2_session_test.h | 184 + lib/nghttp2/tests/nghttp2_stream_test.c | 31 + lib/nghttp2/tests/nghttp2_stream_test.h | 32 + lib/nghttp2/tests/nghttp2_test_helper.c | 435 + lib/nghttp2/tests/nghttp2_test_helper.h | 158 + lib/nghttp2/tests/testdata/Makefile.am | 23 + lib/nghttp2/tests/testdata/cacert.pem | 14 + lib/nghttp2/tests/testdata/index.html | 1 + lib/nghttp2/tests/testdata/privkey.pem | 9 + lib/nghttp2/third-party/CMakeLists.txt | 81 + lib/nghttp2/third-party/Makefile.am | 593 + lib/nghttp2/third-party/build_config.rb | 34 + lib/nghttp2/third-party/llhttp/LICENSE-MIT | 22 + lib/nghttp2/third-party/llhttp/README.md | 482 + lib/nghttp2/third-party/llhttp/common.gypi | 46 + .../third-party/llhttp/include/llhttp.h | 871 + lib/nghttp2/third-party/llhttp/llhttp.gyp | 22 + lib/nghttp2/third-party/llhttp/src/api.c | 494 + lib/nghttp2/third-party/llhttp/src/http.c | 150 + lib/nghttp2/third-party/llhttp/src/llhttp.c | 9537 +++++++++++ lib/nghttp2/third-party/url-parser/.gitignore | 1 + lib/nghttp2/third-party/url-parser/AUTHORS | 68 + .../third-party/url-parser/LICENSE-MIT | 19 + .../third-party/url-parser/url_parser.c | 652 + .../third-party/url-parser/url_parser.h | 94 + 607 files changed, 177576 insertions(+) mode change 100644 => 100755 cmake/headers.cmake create mode 100644 lib/nghttp2/AUTHORS create mode 100644 lib/nghttp2/CMakeLists.txt create mode 100644 lib/nghttp2/CMakeOptions.txt create mode 100644 lib/nghttp2/CONTRIBUTION create mode 100644 lib/nghttp2/COPYING create mode 100644 lib/nghttp2/ChangeLog create mode 100644 lib/nghttp2/Dockerfile.android create mode 100644 lib/nghttp2/LICENSE create mode 100644 lib/nghttp2/Makefile.am create mode 100644 lib/nghttp2/NEWS create mode 100644 lib/nghttp2/README create mode 100644 lib/nghttp2/README.rst create mode 100755 lib/nghttp2/android-config create mode 100755 lib/nghttp2/android-env create mode 100755 lib/nghttp2/author.py create mode 100644 lib/nghttp2/bpf/CMakeLists.txt create mode 100644 lib/nghttp2/bpf/Makefile.am create mode 100644 lib/nghttp2/bpf/reuseport_kern.c create mode 100644 lib/nghttp2/cmake/ExtractValidFlags.cmake create mode 100644 lib/nghttp2/cmake/FindCUnit.cmake create mode 100644 lib/nghttp2/cmake/FindJansson.cmake create mode 100644 lib/nghttp2/cmake/FindJemalloc.cmake create mode 100644 lib/nghttp2/cmake/FindLibbpf.cmake create mode 100644 lib/nghttp2/cmake/FindLibcares.cmake create mode 100644 lib/nghttp2/cmake/FindLibev.cmake create mode 100644 lib/nghttp2/cmake/FindLibevent.cmake create mode 100644 lib/nghttp2/cmake/FindLibnghttp3.cmake create mode 100644 lib/nghttp2/cmake/FindLibngtcp2.cmake create mode 100644 lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake create mode 100644 lib/nghttp2/cmake/FindSystemd.cmake create mode 100644 lib/nghttp2/cmake/PickyWarningsC.cmake create mode 100644 lib/nghttp2/cmake/PickyWarningsCXX.cmake create mode 100644 lib/nghttp2/cmake/Version.cmake create mode 100644 lib/nghttp2/cmakeconfig.h.in create mode 100644 lib/nghttp2/configure.ac create mode 100644 lib/nghttp2/contrib/.gitignore create mode 100644 lib/nghttp2/contrib/CMakeLists.txt create mode 100644 lib/nghttp2/contrib/Makefile.am create mode 100755 lib/nghttp2/contrib/nghttpx-init.in create mode 100644 lib/nghttp2/contrib/nghttpx-logrotate create mode 100644 lib/nghttp2/contrib/nghttpx-upstart.conf.in create mode 100644 lib/nghttp2/contrib/nghttpx.service.in create mode 100644 lib/nghttp2/contrib/tlsticketupdate.go create mode 100644 lib/nghttp2/contrib/usr.sbin.nghttpx create mode 100644 lib/nghttp2/doc/.gitignore create mode 100644 lib/nghttp2/doc/CMakeLists.txt create mode 100644 lib/nghttp2/doc/Makefile.am create mode 100644 lib/nghttp2/doc/README.rst create mode 100644 lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain create mode 100644 lib/nghttp2/doc/_exts/rubydomain/__init__.py create mode 100644 lib/nghttp2/doc/_exts/rubydomain/rubydomain.py create mode 100644 lib/nghttp2/doc/bash_completion/h2load create mode 100755 lib/nghttp2/doc/bash_completion/make_bash_completion.py create mode 100644 lib/nghttp2/doc/bash_completion/nghttp create mode 100644 lib/nghttp2/doc/bash_completion/nghttpd create mode 100644 lib/nghttp2/doc/bash_completion/nghttpx create mode 100644 lib/nghttp2/doc/building-android-binary.rst.in create mode 100644 lib/nghttp2/doc/conf.py.in create mode 100644 lib/nghttp2/doc/contribute.rst.in create mode 100644 lib/nghttp2/doc/docutils.conf create mode 100644 lib/nghttp2/doc/h2load-howto.rst.in create mode 100644 lib/nghttp2/doc/h2load.1 create mode 100644 lib/nghttp2/doc/h2load.1.rst create mode 100644 lib/nghttp2/doc/h2load.h2r create mode 100644 lib/nghttp2/doc/index.rst.in create mode 100644 lib/nghttp2/doc/make.bat create mode 100755 lib/nghttp2/doc/mkapiref.py create mode 100644 lib/nghttp2/doc/nghttp.1 create mode 100644 lib/nghttp2/doc/nghttp.1.rst create mode 100644 lib/nghttp2/doc/nghttp.h2r create mode 100644 lib/nghttp2/doc/nghttp2.h.rst.in create mode 100644 lib/nghttp2/doc/nghttp2ver.h.rst.in create mode 100644 lib/nghttp2/doc/nghttpd.1 create mode 100644 lib/nghttp2/doc/nghttpd.1.rst create mode 100644 lib/nghttp2/doc/nghttpd.h2r create mode 100644 lib/nghttp2/doc/nghttpx-howto.rst.in create mode 100644 lib/nghttp2/doc/nghttpx.1 create mode 100644 lib/nghttp2/doc/nghttpx.1.rst create mode 100644 lib/nghttp2/doc/nghttpx.h2r create mode 100644 lib/nghttp2/doc/package_README.rst.in create mode 100644 lib/nghttp2/doc/programmers-guide.rst create mode 100644 lib/nghttp2/doc/security.rst create mode 100644 lib/nghttp2/doc/sources/security.rst create mode 100644 lib/nghttp2/doc/tutorial-client.rst.in create mode 100644 lib/nghttp2/doc/tutorial-hpack.rst.in create mode 100644 lib/nghttp2/doc/tutorial-server.rst.in create mode 100644 lib/nghttp2/docker/Dockerfile create mode 100644 lib/nghttp2/docker/README.rst create mode 100644 lib/nghttp2/examples/.gitignore create mode 100644 lib/nghttp2/examples/CMakeLists.txt create mode 100644 lib/nghttp2/examples/Makefile.am create mode 100644 lib/nghttp2/examples/client.c create mode 100644 lib/nghttp2/examples/deflate.c create mode 100644 lib/nghttp2/examples/libevent-client.c create mode 100644 lib/nghttp2/examples/libevent-server.c create mode 100644 lib/nghttp2/fedora/spdylay.spec create mode 100644 lib/nghttp2/fuzz/README.rst create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 create mode 100644 lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 create mode 100644 lib/nghttp2/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb create mode 100644 lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 create mode 100644 lib/nghttp2/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa create mode 100644 lib/nghttp2/fuzz/fuzz_frames.cc create mode 100644 lib/nghttp2/fuzz/fuzz_target.cc create mode 100644 lib/nghttp2/fuzz/fuzz_target_fdp.cc create mode 100755 lib/nghttp2/genauthoritychartbl.py create mode 100755 lib/nghttp2/gendowncasetbl.py create mode 100755 lib/nghttp2/genheaderfunc.py create mode 100755 lib/nghttp2/genlibtokenlookup.py create mode 100755 lib/nghttp2/genmethodchartbl.py create mode 100755 lib/nghttp2/genmethodfunc.py create mode 100755 lib/nghttp2/gennghttpxfun.py create mode 100755 lib/nghttp2/gennmchartbl.py create mode 100755 lib/nghttp2/genpathchartbl.py create mode 100644 lib/nghttp2/gentokenlookup.py create mode 100755 lib/nghttp2/genvchartbl.py create mode 100755 lib/nghttp2/git-clang-format create mode 100644 lib/nghttp2/go.mod create mode 100644 lib/nghttp2/go.sum create mode 100755 lib/nghttp2/help2rst.py create mode 100644 lib/nghttp2/integration-tests/.gitignore create mode 100644 lib/nghttp2/integration-tests/CMakeLists.txt create mode 100644 lib/nghttp2/integration-tests/Makefile.am create mode 100644 lib/nghttp2/integration-tests/alt-server.crt create mode 100644 lib/nghttp2/integration-tests/config.go.in create mode 100644 lib/nghttp2/integration-tests/nghttpx_http1_test.go create mode 100644 lib/nghttp2/integration-tests/nghttpx_http2_test.go create mode 100644 lib/nghttp2/integration-tests/nghttpx_http3_test.go create mode 100644 lib/nghttp2/integration-tests/req-return.rb create mode 100644 lib/nghttp2/integration-tests/req-set-header.rb create mode 100644 lib/nghttp2/integration-tests/resp-return.rb create mode 100644 lib/nghttp2/integration-tests/resp-set-header.rb create mode 100644 lib/nghttp2/integration-tests/server.crt create mode 100644 lib/nghttp2/integration-tests/server_tester.go create mode 100644 lib/nghttp2/integration-tests/server_tester_http3.go create mode 100644 lib/nghttp2/integration-tests/setenv.in create mode 100644 lib/nghttp2/lib/.gitignore create mode 100644 lib/nghttp2/lib/CMakeLists.txt create mode 100644 lib/nghttp2/lib/Makefile.am create mode 100644 lib/nghttp2/lib/Makefile.msvc create mode 100644 lib/nghttp2/lib/includes/CMakeLists.txt create mode 100644 lib/nghttp2/lib/includes/Makefile.am create mode 100644 lib/nghttp2/lib/includes/nghttp2/nghttp2.h create mode 100644 lib/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in create mode 100644 lib/nghttp2/lib/libnghttp2.pc.in create mode 100644 lib/nghttp2/lib/nghttp2_buf.c create mode 100644 lib/nghttp2/lib/nghttp2_buf.h create mode 100644 lib/nghttp2/lib/nghttp2_callbacks.c create mode 100644 lib/nghttp2/lib/nghttp2_callbacks.h create mode 100644 lib/nghttp2/lib/nghttp2_debug.c create mode 100644 lib/nghttp2/lib/nghttp2_debug.h create mode 100644 lib/nghttp2/lib/nghttp2_extpri.c create mode 100644 lib/nghttp2/lib/nghttp2_extpri.h create mode 100644 lib/nghttp2/lib/nghttp2_frame.c create mode 100644 lib/nghttp2/lib/nghttp2_frame.h create mode 100644 lib/nghttp2/lib/nghttp2_hd.c create mode 100644 lib/nghttp2/lib/nghttp2_hd.h create mode 100644 lib/nghttp2/lib/nghttp2_hd_huffman.c create mode 100644 lib/nghttp2/lib/nghttp2_hd_huffman.h create mode 100644 lib/nghttp2/lib/nghttp2_hd_huffman_data.c create mode 100644 lib/nghttp2/lib/nghttp2_helper.c create mode 100644 lib/nghttp2/lib/nghttp2_helper.h create mode 100644 lib/nghttp2/lib/nghttp2_http.c create mode 100644 lib/nghttp2/lib/nghttp2_http.h create mode 100644 lib/nghttp2/lib/nghttp2_int.h create mode 100644 lib/nghttp2/lib/nghttp2_map.c create mode 100644 lib/nghttp2/lib/nghttp2_map.h create mode 100644 lib/nghttp2/lib/nghttp2_mem.c create mode 100644 lib/nghttp2/lib/nghttp2_mem.h create mode 100644 lib/nghttp2/lib/nghttp2_net.h create mode 100644 lib/nghttp2/lib/nghttp2_npn.c create mode 100644 lib/nghttp2/lib/nghttp2_npn.h create mode 100644 lib/nghttp2/lib/nghttp2_option.c create mode 100644 lib/nghttp2/lib/nghttp2_option.h create mode 100644 lib/nghttp2/lib/nghttp2_outbound_item.c create mode 100644 lib/nghttp2/lib/nghttp2_outbound_item.h create mode 100644 lib/nghttp2/lib/nghttp2_pq.c create mode 100644 lib/nghttp2/lib/nghttp2_pq.h create mode 100644 lib/nghttp2/lib/nghttp2_priority_spec.c create mode 100644 lib/nghttp2/lib/nghttp2_priority_spec.h create mode 100644 lib/nghttp2/lib/nghttp2_queue.c create mode 100644 lib/nghttp2/lib/nghttp2_queue.h create mode 100644 lib/nghttp2/lib/nghttp2_ratelim.c create mode 100644 lib/nghttp2/lib/nghttp2_ratelim.h create mode 100644 lib/nghttp2/lib/nghttp2_rcbuf.c create mode 100644 lib/nghttp2/lib/nghttp2_rcbuf.h create mode 100644 lib/nghttp2/lib/nghttp2_session.c create mode 100644 lib/nghttp2/lib/nghttp2_session.h create mode 100644 lib/nghttp2/lib/nghttp2_stream.c create mode 100644 lib/nghttp2/lib/nghttp2_stream.h create mode 100644 lib/nghttp2/lib/nghttp2_submit.c create mode 100644 lib/nghttp2/lib/nghttp2_submit.h create mode 100644 lib/nghttp2/lib/nghttp2_time.c create mode 100644 lib/nghttp2/lib/nghttp2_time.h create mode 100644 lib/nghttp2/lib/nghttp2_version.c create mode 100644 lib/nghttp2/lib/sfparse.c create mode 100644 lib/nghttp2/lib/sfparse.h create mode 100644 lib/nghttp2/lib/version.rc.in create mode 100644 lib/nghttp2/m4/ax_check_compile_flag.m4 create mode 100644 lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 create mode 100644 lib/nghttp2/m4/libxml2.m4 create mode 100755 lib/nghttp2/makebashcompletion create mode 100755 lib/nghttp2/makemanpages create mode 100755 lib/nghttp2/makerelease.sh create mode 100755 lib/nghttp2/mkcipherlist.py create mode 100755 lib/nghttp2/mkhufftbl.py create mode 100755 lib/nghttp2/mkstatichdtbl.py create mode 100644 lib/nghttp2/nghttp2-master.zip create mode 100644 lib/nghttp2/nghttp2-master/.github/dependabot.yml create mode 100644 lib/nghttp2/nghttp2-master/.github/workflows/build.yml create mode 100644 lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml create mode 100644 lib/nghttp2/nghttp2-master/.gitignore create mode 100644 lib/nghttp2/nghttp2-master/.gitmodules create mode 100644 lib/nghttp2/nghttpx.conf.sample create mode 100755 lib/nghttp2/pre-commit create mode 100644 lib/nghttp2/proxy.pac.sample create mode 100755 lib/nghttp2/releasechk create mode 100644 lib/nghttp2/script/CMakeLists.txt create mode 100644 lib/nghttp2/script/Makefile.am create mode 100644 lib/nghttp2/script/README.rst create mode 100755 lib/nghttp2/script/fetch-ocsp-response create mode 100644 lib/nghttp2/src/.gitignore create mode 100644 lib/nghttp2/src/CMakeLists.txt create mode 100644 lib/nghttp2/src/HtmlParser.cc create mode 100644 lib/nghttp2/src/HtmlParser.h create mode 100644 lib/nghttp2/src/HttpServer.cc create mode 100644 lib/nghttp2/src/HttpServer.h create mode 100644 lib/nghttp2/src/Makefile.am create mode 100644 lib/nghttp2/src/allocator.h create mode 100644 lib/nghttp2/src/app_helper.cc create mode 100644 lib/nghttp2/src/app_helper.h create mode 100644 lib/nghttp2/src/base64.h create mode 100644 lib/nghttp2/src/base64_test.cc create mode 100644 lib/nghttp2/src/base64_test.h create mode 100644 lib/nghttp2/src/buffer.h create mode 100644 lib/nghttp2/src/buffer_test.cc create mode 100644 lib/nghttp2/src/buffer_test.h create mode 100644 lib/nghttp2/src/ca-config.json create mode 100644 lib/nghttp2/src/ca.nghttp2.org-key.pem create mode 100644 lib/nghttp2/src/ca.nghttp2.org.csr create mode 100644 lib/nghttp2/src/ca.nghttp2.org.csr.json create mode 100644 lib/nghttp2/src/ca.nghttp2.org.pem create mode 100644 lib/nghttp2/src/comp_helper.c create mode 100644 lib/nghttp2/src/comp_helper.h create mode 100644 lib/nghttp2/src/deflatehd.cc create mode 100644 lib/nghttp2/src/h2load.cc create mode 100644 lib/nghttp2/src/h2load.h create mode 100644 lib/nghttp2/src/h2load_http1_session.cc create mode 100644 lib/nghttp2/src/h2load_http1_session.h create mode 100644 lib/nghttp2/src/h2load_http2_session.cc create mode 100644 lib/nghttp2/src/h2load_http2_session.h create mode 100644 lib/nghttp2/src/h2load_http3_session.cc create mode 100644 lib/nghttp2/src/h2load_http3_session.h create mode 100644 lib/nghttp2/src/h2load_quic.cc create mode 100644 lib/nghttp2/src/h2load_quic.h create mode 100644 lib/nghttp2/src/h2load_session.h create mode 100644 lib/nghttp2/src/http-parser.patch create mode 100644 lib/nghttp2/src/http2.cc create mode 100644 lib/nghttp2/src/http2.h create mode 100644 lib/nghttp2/src/http2_test.cc create mode 100644 lib/nghttp2/src/http2_test.h create mode 100644 lib/nghttp2/src/http3.cc create mode 100644 lib/nghttp2/src/http3.h create mode 100644 lib/nghttp2/src/inflatehd.cc create mode 100644 lib/nghttp2/src/libevent_util.cc create mode 100644 lib/nghttp2/src/libevent_util.h create mode 100644 lib/nghttp2/src/memchunk.h create mode 100644 lib/nghttp2/src/memchunk_test.cc create mode 100644 lib/nghttp2/src/memchunk_test.h create mode 100644 lib/nghttp2/src/network.h create mode 100644 lib/nghttp2/src/nghttp.cc create mode 100644 lib/nghttp2/src/nghttp.h create mode 100644 lib/nghttp2/src/nghttp2_config.h create mode 100644 lib/nghttp2/src/nghttp2_gzip.c create mode 100644 lib/nghttp2/src/nghttp2_gzip.h create mode 100644 lib/nghttp2/src/nghttp2_gzip_test.c create mode 100644 lib/nghttp2/src/nghttp2_gzip_test.h create mode 100644 lib/nghttp2/src/nghttpd.cc create mode 100644 lib/nghttp2/src/quic.cc create mode 100644 lib/nghttp2/src/quic.h create mode 100644 lib/nghttp2/src/shrpx-unittest.cc create mode 100644 lib/nghttp2/src/shrpx.cc create mode 100644 lib/nghttp2/src/shrpx.h create mode 100644 lib/nghttp2/src/shrpx_accept_handler.cc create mode 100644 lib/nghttp2/src/shrpx_accept_handler.h create mode 100644 lib/nghttp2/src/shrpx_api_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_api_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_client_handler.cc create mode 100644 lib/nghttp2/src/shrpx_client_handler.h create mode 100644 lib/nghttp2/src/shrpx_config.cc create mode 100644 lib/nghttp2/src/shrpx_config.h create mode 100644 lib/nghttp2/src/shrpx_config_test.cc create mode 100644 lib/nghttp2/src/shrpx_config_test.h create mode 100644 lib/nghttp2/src/shrpx_connect_blocker.cc create mode 100644 lib/nghttp2/src/shrpx_connect_blocker.h create mode 100644 lib/nghttp2/src/shrpx_connection.cc create mode 100644 lib/nghttp2/src/shrpx_connection.h create mode 100644 lib/nghttp2/src/shrpx_connection_handler.cc create mode 100644 lib/nghttp2/src/shrpx_connection_handler.h create mode 100644 lib/nghttp2/src/shrpx_dns_resolver.cc create mode 100644 lib/nghttp2/src/shrpx_dns_resolver.h create mode 100644 lib/nghttp2/src/shrpx_dns_tracker.cc create mode 100644 lib/nghttp2/src/shrpx_dns_tracker.h create mode 100644 lib/nghttp2/src/shrpx_downstream.cc create mode 100644 lib/nghttp2/src/shrpx_downstream.h create mode 100644 lib/nghttp2/src/shrpx_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_downstream_connection_pool.cc create mode 100644 lib/nghttp2/src/shrpx_downstream_connection_pool.h create mode 100644 lib/nghttp2/src/shrpx_downstream_queue.cc create mode 100644 lib/nghttp2/src/shrpx_downstream_queue.h create mode 100644 lib/nghttp2/src/shrpx_downstream_test.cc create mode 100644 lib/nghttp2/src/shrpx_downstream_test.h create mode 100644 lib/nghttp2/src/shrpx_dual_dns_resolver.cc create mode 100644 lib/nghttp2/src/shrpx_dual_dns_resolver.h create mode 100644 lib/nghttp2/src/shrpx_error.h create mode 100644 lib/nghttp2/src/shrpx_exec.cc create mode 100644 lib/nghttp2/src/shrpx_exec.h create mode 100644 lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_http.cc create mode 100644 lib/nghttp2/src/shrpx_http.h create mode 100644 lib/nghttp2/src/shrpx_http2_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_http2_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_http2_session.cc create mode 100644 lib/nghttp2/src/shrpx_http2_session.h create mode 100644 lib/nghttp2/src/shrpx_http2_upstream.cc create mode 100644 lib/nghttp2/src/shrpx_http2_upstream.h create mode 100644 lib/nghttp2/src/shrpx_http3_upstream.cc create mode 100644 lib/nghttp2/src/shrpx_http3_upstream.h create mode 100644 lib/nghttp2/src/shrpx_http_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_http_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_http_test.cc create mode 100644 lib/nghttp2/src/shrpx_http_test.h create mode 100644 lib/nghttp2/src/shrpx_https_upstream.cc create mode 100644 lib/nghttp2/src/shrpx_https_upstream.h create mode 100644 lib/nghttp2/src/shrpx_io_control.cc create mode 100644 lib/nghttp2/src/shrpx_io_control.h create mode 100644 lib/nghttp2/src/shrpx_live_check.cc create mode 100644 lib/nghttp2/src/shrpx_live_check.h create mode 100644 lib/nghttp2/src/shrpx_log.cc create mode 100644 lib/nghttp2/src/shrpx_log.h create mode 100644 lib/nghttp2/src/shrpx_log_config.cc create mode 100644 lib/nghttp2/src/shrpx_log_config.h create mode 100644 lib/nghttp2/src/shrpx_memcached_connection.cc create mode 100644 lib/nghttp2/src/shrpx_memcached_connection.h create mode 100644 lib/nghttp2/src/shrpx_memcached_dispatcher.cc create mode 100644 lib/nghttp2/src/shrpx_memcached_dispatcher.h create mode 100644 lib/nghttp2/src/shrpx_memcached_request.h create mode 100644 lib/nghttp2/src/shrpx_memcached_result.h create mode 100644 lib/nghttp2/src/shrpx_mruby.cc create mode 100644 lib/nghttp2/src/shrpx_mruby.h create mode 100644 lib/nghttp2/src/shrpx_mruby_module.cc create mode 100644 lib/nghttp2/src/shrpx_mruby_module.h create mode 100644 lib/nghttp2/src/shrpx_mruby_module_env.cc create mode 100644 lib/nghttp2/src/shrpx_mruby_module_env.h create mode 100644 lib/nghttp2/src/shrpx_mruby_module_request.cc create mode 100644 lib/nghttp2/src/shrpx_mruby_module_request.h create mode 100644 lib/nghttp2/src/shrpx_mruby_module_response.cc create mode 100644 lib/nghttp2/src/shrpx_mruby_module_response.h create mode 100644 lib/nghttp2/src/shrpx_null_downstream_connection.cc create mode 100644 lib/nghttp2/src/shrpx_null_downstream_connection.h create mode 100644 lib/nghttp2/src/shrpx_process.h create mode 100644 lib/nghttp2/src/shrpx_quic.cc create mode 100644 lib/nghttp2/src/shrpx_quic.h create mode 100644 lib/nghttp2/src/shrpx_quic_connection_handler.cc create mode 100644 lib/nghttp2/src/shrpx_quic_connection_handler.h create mode 100644 lib/nghttp2/src/shrpx_quic_listener.cc create mode 100644 lib/nghttp2/src/shrpx_quic_listener.h create mode 100644 lib/nghttp2/src/shrpx_rate_limit.cc create mode 100644 lib/nghttp2/src/shrpx_rate_limit.h create mode 100644 lib/nghttp2/src/shrpx_router.cc create mode 100644 lib/nghttp2/src/shrpx_router.h create mode 100644 lib/nghttp2/src/shrpx_router_test.cc create mode 100644 lib/nghttp2/src/shrpx_router_test.h create mode 100644 lib/nghttp2/src/shrpx_signal.cc create mode 100644 lib/nghttp2/src/shrpx_signal.h create mode 100644 lib/nghttp2/src/shrpx_tls.cc create mode 100644 lib/nghttp2/src/shrpx_tls.h create mode 100644 lib/nghttp2/src/shrpx_tls_test.cc create mode 100644 lib/nghttp2/src/shrpx_tls_test.h create mode 100644 lib/nghttp2/src/shrpx_upstream.h create mode 100644 lib/nghttp2/src/shrpx_worker.cc create mode 100644 lib/nghttp2/src/shrpx_worker.h create mode 100644 lib/nghttp2/src/shrpx_worker_process.cc create mode 100644 lib/nghttp2/src/shrpx_worker_process.h create mode 100644 lib/nghttp2/src/shrpx_worker_test.cc create mode 100644 lib/nghttp2/src/shrpx_worker_test.h create mode 100644 lib/nghttp2/src/ssl_compat.h create mode 100644 lib/nghttp2/src/template.h create mode 100644 lib/nghttp2/src/template_test.cc create mode 100644 lib/nghttp2/src/template_test.h create mode 100644 lib/nghttp2/src/test.example.com-key.pem create mode 100644 lib/nghttp2/src/test.example.com.csr create mode 100644 lib/nghttp2/src/test.example.com.csr.json create mode 100644 lib/nghttp2/src/test.example.com.pem create mode 100644 lib/nghttp2/src/test.nghttp2.org-key.pem create mode 100644 lib/nghttp2/src/test.nghttp2.org.csr create mode 100644 lib/nghttp2/src/test.nghttp2.org.csr.json create mode 100644 lib/nghttp2/src/test.nghttp2.org.pem create mode 100644 lib/nghttp2/src/testdata/Makefile.am create mode 100644 lib/nghttp2/src/testdata/ipaddr.crt create mode 100644 lib/nghttp2/src/testdata/nosan.crt create mode 100644 lib/nghttp2/src/testdata/nosan_ip.crt create mode 100644 lib/nghttp2/src/testdata/verify_hostname.crt create mode 100644 lib/nghttp2/src/timegm.c create mode 100644 lib/nghttp2/src/timegm.h create mode 100644 lib/nghttp2/src/tls.cc create mode 100644 lib/nghttp2/src/tls.h create mode 100644 lib/nghttp2/src/util.cc create mode 100644 lib/nghttp2/src/util.h create mode 100644 lib/nghttp2/src/util_test.cc create mode 100644 lib/nghttp2/src/util_test.h create mode 100644 lib/nghttp2/src/xsi_strerror.c create mode 100644 lib/nghttp2/src/xsi_strerror.h create mode 100644 lib/nghttp2/tests/.gitignore create mode 100644 lib/nghttp2/tests/CMakeLists.txt create mode 100644 lib/nghttp2/tests/Makefile.am create mode 100755 lib/nghttp2/tests/end_to_end.py create mode 100644 lib/nghttp2/tests/failmalloc.c create mode 100644 lib/nghttp2/tests/failmalloc_test.c create mode 100644 lib/nghttp2/tests/failmalloc_test.h create mode 100644 lib/nghttp2/tests/main.c create mode 100644 lib/nghttp2/tests/malloc_wrapper.c create mode 100644 lib/nghttp2/tests/malloc_wrapper.h create mode 100644 lib/nghttp2/tests/nghttp2_buf_test.c create mode 100644 lib/nghttp2/tests/nghttp2_buf_test.h create mode 100644 lib/nghttp2/tests/nghttp2_extpri_test.c create mode 100644 lib/nghttp2/tests/nghttp2_extpri_test.h create mode 100644 lib/nghttp2/tests/nghttp2_frame_test.c create mode 100644 lib/nghttp2/tests/nghttp2_frame_test.h create mode 100644 lib/nghttp2/tests/nghttp2_hd_test.c create mode 100644 lib/nghttp2/tests/nghttp2_hd_test.h create mode 100644 lib/nghttp2/tests/nghttp2_helper_test.c create mode 100644 lib/nghttp2/tests/nghttp2_helper_test.h create mode 100644 lib/nghttp2/tests/nghttp2_http_test.c create mode 100644 lib/nghttp2/tests/nghttp2_http_test.h create mode 100644 lib/nghttp2/tests/nghttp2_map_test.c create mode 100644 lib/nghttp2/tests/nghttp2_map_test.h create mode 100644 lib/nghttp2/tests/nghttp2_npn_test.c create mode 100644 lib/nghttp2/tests/nghttp2_npn_test.h create mode 100644 lib/nghttp2/tests/nghttp2_pq_test.c create mode 100644 lib/nghttp2/tests/nghttp2_pq_test.h create mode 100644 lib/nghttp2/tests/nghttp2_queue_test.c create mode 100644 lib/nghttp2/tests/nghttp2_queue_test.h create mode 100644 lib/nghttp2/tests/nghttp2_ratelim_test.c create mode 100644 lib/nghttp2/tests/nghttp2_ratelim_test.h create mode 100644 lib/nghttp2/tests/nghttp2_session_test.c create mode 100644 lib/nghttp2/tests/nghttp2_session_test.h create mode 100644 lib/nghttp2/tests/nghttp2_stream_test.c create mode 100644 lib/nghttp2/tests/nghttp2_stream_test.h create mode 100644 lib/nghttp2/tests/nghttp2_test_helper.c create mode 100644 lib/nghttp2/tests/nghttp2_test_helper.h create mode 100644 lib/nghttp2/tests/testdata/Makefile.am create mode 100644 lib/nghttp2/tests/testdata/cacert.pem create mode 100644 lib/nghttp2/tests/testdata/index.html create mode 100644 lib/nghttp2/tests/testdata/privkey.pem create mode 100644 lib/nghttp2/third-party/CMakeLists.txt create mode 100644 lib/nghttp2/third-party/Makefile.am create mode 100644 lib/nghttp2/third-party/build_config.rb create mode 100644 lib/nghttp2/third-party/llhttp/LICENSE-MIT create mode 100644 lib/nghttp2/third-party/llhttp/README.md create mode 100644 lib/nghttp2/third-party/llhttp/common.gypi create mode 100644 lib/nghttp2/third-party/llhttp/include/llhttp.h create mode 100644 lib/nghttp2/third-party/llhttp/llhttp.gyp create mode 100644 lib/nghttp2/third-party/llhttp/src/api.c create mode 100644 lib/nghttp2/third-party/llhttp/src/http.c create mode 100644 lib/nghttp2/third-party/llhttp/src/llhttp.c create mode 100644 lib/nghttp2/third-party/url-parser/.gitignore create mode 100644 lib/nghttp2/third-party/url-parser/AUTHORS create mode 100644 lib/nghttp2/third-party/url-parser/LICENSE-MIT create mode 100644 lib/nghttp2/third-party/url-parser/url_parser.c create mode 100644 lib/nghttp2/third-party/url-parser/url_parser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 420ef82803c..5438fc5ffb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -563,6 +563,12 @@ add_subdirectory(${FLB_PATH_LIB_CMETRICS} EXCLUDE_FROM_ALL) # CTraces add_subdirectory(${FLB_PATH_LIB_CTRACES} EXCLUDE_FROM_ALL) +# Nghttp2 options +set(ENABLE_LIB_ONLY ON) +set(ENABLE_STATIC_LIB ON) + +add_subdirectory(${FLB_PATH_LIB_NGHTTP2}) + # C-Ares (DNS library) FLB_OPTION(CARES_STATIC ON) FLB_OPTION(CARES_SHARED OFF) diff --git a/cmake/headers.cmake b/cmake/headers.cmake old mode 100644 new mode 100755 index 3e814066c40..45a1394ca7f --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -46,6 +46,10 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}/lib/monkey/include/ ${CMAKE_CURRENT_BINARY_DIR}/lib/monkey/include/monkey/ + + ${CMAKE_CURRENT_BINARY_DIR}/lib/nghttp2 + ${CMAKE_CURRENT_BINARY_DIR}/lib/nghttp2/lib/includes/ + ${FLB_PATH_ROOT_SOURCE}/${FLB_PATH_LIB_NGHTTP2}/lib/includes/ ) if(FLB_IN_KAFKA OR FLB_OUT_KAFKA) diff --git a/cmake/libraries.cmake b/cmake/libraries.cmake index 8ac71e7964a..37bfe24c4fd 100644 --- a/cmake/libraries.cmake +++ b/cmake/libraries.cmake @@ -7,6 +7,7 @@ set(FLB_PATH_LIB_CTRACES "lib/ctraces") set(FLB_PATH_LIB_CO "lib/flb_libco") set(FLB_PATH_LIB_RBTREE "lib/rbtree") set(FLB_PATH_LIB_MSGPACK "lib/msgpack-c") +set(FLB_PATH_LIB_NGHTTP2 "lib/nghttp2") set(FLB_PATH_LIB_AVRO "lib/avro") set(FLB_PATH_LIB_CHUNKIO "lib/chunkio") set(FLB_PATH_LIB_LUAJIT "lib/luajit-3065c9") diff --git a/lib/nghttp2/AUTHORS b/lib/nghttp2/AUTHORS new file mode 100644 index 00000000000..bb1ae74f12c --- /dev/null +++ b/lib/nghttp2/AUTHORS @@ -0,0 +1,155 @@ +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 +Asra Ali +Benedikt Christoph Wolters +Benjamin Peterson +Bernard Spil +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 +jwchoi +kumagi +lhuang04 +lstefani +makovich +mod-h2-dev +moparisthebest +robaho +snnn +yuuki-kodama diff --git a/lib/nghttp2/CMakeLists.txt b/lib/nghttp2/CMakeLists.txt new file mode 100644 index 00000000000..99a2b77f3be --- /dev/null +++ b/lib/nghttp2/CMakeLists.txt @@ -0,0 +1,493 @@ +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa +# Copyright (c) 2016 Peter Wu +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# 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.0) +# XXX using 1.8.90 instead of 1.9.0-DEV +project(nghttp2 VERSION 1.58.90) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +set(LT_CURRENT 39) +set(LT_REVISION 1) +set(LT_AGE 25) + +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 +find_package(OpenSSL 1.0.1) +find_package(Libev 4.11) +find_package(Libcares 1.7.5) +find_package(ZLIB 1.2.3) +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 +#include +int main() { std::vector> v; }" HAVE_STD_FUTURE) +# Check that std::map::emplace is available for g++-4.7. +check_cxx_source_compiles(" +#include +int main() { std::map().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 +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("time.h" HAVE_TIME_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) + +# Check size of pointer to decide we need 8 bytes alignment adjustment. +check_type_size("int *" SIZEOF_INT_P) +check_type_size("time_t" SIZEOF_TIME_T) + +# 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( + BEFORE "${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/lib/nghttp2/CMakeOptions.txt b/lib/nghttp2/CMakeOptions.txt new file mode 100644 index 00000000000..238d5b1b2b3 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/CONTRIBUTION b/lib/nghttp2/CONTRIBUTION new file mode 100644 index 00000000000..8a2aae39e31 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/COPYING b/lib/nghttp2/COPYING new file mode 100644 index 00000000000..80201792ec7 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/ChangeLog b/lib/nghttp2/ChangeLog new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/nghttp2/Dockerfile.android b/lib/nghttp2/Dockerfile.android new file mode 100644 index 00000000000..96ce7c4670d --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/LICENSE b/lib/nghttp2/LICENSE new file mode 100644 index 00000000000..45d9408ffa5 --- /dev/null +++ b/lib/nghttp2/LICENSE @@ -0,0 +1 @@ +See COPYING diff --git a/lib/nghttp2/Makefile.am b/lib/nghttp2/Makefile.am new file mode 100644 index 00000000000..683b9896b09 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/NEWS b/lib/nghttp2/NEWS new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/nghttp2/README b/lib/nghttp2/README new file mode 100644 index 00000000000..5ccc0ea36b5 --- /dev/null +++ b/lib/nghttp2/README @@ -0,0 +1 @@ +See README.rst diff --git a/lib/nghttp2/README.rst b/lib/nghttp2/README.rst new file mode 100644 index 00000000000..596d5615753 --- /dev/null +++ b/lib/nghttp2/README.rst @@ -0,0 +1,1473 @@ +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 +`_ HTTP/2 and `RFC 7541 +`_ HPACK - Header Compression for +HTTP/2. Now we are updating our code to implement `RFC 9113 +`_. + +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/NPN and HTTP/3) + + This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and + ``http/1.1`` via ALPN/NPN 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.0.1 +* libev >= 4.11 +* zlib >= 1.2.3 +* libc-ares >= 1.7.5 + +ALPN support requires OpenSSL >= 1.0.2 (released 22 January 2015). +LibreSSL >= 2.2.0 can be used instead of OpenSSL, but OpenSSL has more +features than LibreSSL at the time of this writing. + +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 `_. + +To enable mruby support for nghttpx, `mruby +`_ is required. We need to build +mruby with C++ ABI explicitly turned on, and probably need other +mrgems, mruby is manged 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 `_, +privilege separation engine for OpenSSL / LibreSSL. 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 + `_; or + `BoringSSL `_ (commit + 6ca49385b168f47a50e7172d82a590b218f55e4d) +* `ngtcp2 `_ >= 1.0.0 +* `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 +`_. 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 +`_ 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 +`_. The free version of `Visual C++ 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.1.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 +`_. + +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 `_ 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 NPN/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 + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.212] send SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.212] send SETTINGS frame + ; ACK + (niv=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=3, weight=1, exclusive=0) + [ 0.212] send HEADERS frame + ; 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 + ; 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 + ; 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: ; 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 + ; END_HEADERS + (padlen=0) + ; First response header + [ 0.222] recv DATA frame + ; 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 + ; END_HEADERS + (padlen=0) + ; First push response header + [ 0.228] recv DATA frame + ; END_STREAM + [ 0.228] send GOAWAY frame + (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 + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.018] send SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.018] send SETTINGS frame + ; ACK + (niv=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=3, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (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 + ; 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: ; 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 + ; END_HEADERS + (padlen=0) + ; First response header + [ 0.019] recv DATA frame + ; 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 + ; END_HEADERS + (padlen=0) + ; First push response header + [ 0.026] recv DATA frame + [ 0.027] recv DATA frame + [ 0.027] send WINDOW_UPDATE frame + (window_size_increment=33343) + [ 0.032] send WINDOW_UPDATE frame + (window_size_increment=33707) + [ 0.032] recv DATA frame + ; END_STREAM + [ 0.032] recv SETTINGS frame + ; ACK + (niv=0) + [ 0.032] send GOAWAY frame + (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 NPN/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 + (niv=1) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [id=1] [ 1.521] recv SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [id=1] [ 1.521] recv SETTINGS frame + ; ACK + (niv=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (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 + ; 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 + ; ACK + (niv=0) + [id=1] [ 1.521] send HEADERS frame + ; 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 + ; END_STREAM + [id=1] [ 1.522] stream_id=13 closed + [id=1] [ 1.522] recv GOAWAY frame + (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 +`_ +to know how to migrate from earlier releases. + +``nghttpx`` implements `important performance-oriented features +`_ in TLS, such as +session IDs, session tickets (with automatic key rotation), OCSP +stapling, dynamic record sizing, ALPN/NPN, 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 ``--npn-list`` option +like so: + +.. code-block:: text + + $ h2load --npn-list h3 https://127.0.0.1:4433 + +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 +`_ 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 `_. + +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/lib/nghttp2/android-config b/lib/nghttp2/android-config new file mode 100755 index 00000000000..4a137dea748 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/android-env b/lib/nghttp2/android-env new file mode 100755 index 00000000000..4412fcf90c2 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/author.py b/lib/nghttp2/author.py new file mode 100755 index 00000000000..4bf97c4964c --- /dev/null +++ b/lib/nghttp2/author.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# script to extract commit author's name from standard input. The +# input should be :, 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/lib/nghttp2/bpf/CMakeLists.txt b/lib/nghttp2/bpf/CMakeLists.txt new file mode 100644 index 00000000000..904b82184c0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/bpf/Makefile.am b/lib/nghttp2/bpf/Makefile.am new file mode 100644 index 00000000000..9017fd95e16 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/bpf/reuseport_kern.c b/lib/nghttp2/bpf/reuseport_kern.c new file mode 100644 index 00000000000..74c08c5aae1 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +/* + * 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/lib/nghttp2/cmake/ExtractValidFlags.cmake b/lib/nghttp2/cmake/ExtractValidFlags.cmake new file mode 100644 index 00000000000..ccd57dc8133 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindCUnit.cmake b/lib/nghttp2/cmake/FindCUnit.cmake new file mode 100644 index 00000000000..ada87c16531 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindJansson.cmake b/lib/nghttp2/cmake/FindJansson.cmake new file mode 100644 index 00000000000..4c4bcb73d87 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindJemalloc.cmake b/lib/nghttp2/cmake/FindJemalloc.cmake new file mode 100644 index 00000000000..b7815fa0fa7 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibbpf.cmake b/lib/nghttp2/cmake/FindLibbpf.cmake new file mode 100644 index 00000000000..7f76255e51a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibcares.cmake b/lib/nghttp2/cmake/FindLibcares.cmake new file mode 100644 index 00000000000..1fe56ce7021 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibev.cmake b/lib/nghttp2/cmake/FindLibev.cmake new file mode 100644 index 00000000000..71e45082b3b --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibevent.cmake b/lib/nghttp2/cmake/FindLibevent.cmake new file mode 100644 index 00000000000..e8a3cef223f --- /dev/null +++ b/lib/nghttp2/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 ...]) # 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__FOUND - Component was found ( is uppercase) +# LIBEVENT__LIBRARY - Library to be linked for Libevent component . + +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/lib/nghttp2/cmake/FindLibnghttp3.cmake b/lib/nghttp2/cmake/FindLibnghttp3.cmake new file mode 100644 index 00000000000..ecd01f6c715 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibngtcp2.cmake b/lib/nghttp2/cmake/FindLibngtcp2.cmake new file mode 100644 index 00000000000..c670114928a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake b/lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake new file mode 100644 index 00000000000..3d55b63abf2 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/FindSystemd.cmake b/lib/nghttp2/cmake/FindSystemd.cmake new file mode 100644 index 00000000000..e7534e5fe67 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/PickyWarningsC.cmake b/lib/nghttp2/cmake/PickyWarningsC.cmake new file mode 100644 index 00000000000..50eb7891802 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/PickyWarningsCXX.cmake b/lib/nghttp2/cmake/PickyWarningsCXX.cmake new file mode 100644 index 00000000000..4699733d25a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmake/Version.cmake b/lib/nghttp2/cmake/Version.cmake new file mode 100644 index 00000000000..8ac4849c972 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/cmakeconfig.h.in b/lib/nghttp2/cmakeconfig.h.in new file mode 100644 index 00000000000..765a72a5c84 --- /dev/null +++ b/lib/nghttp2/cmakeconfig.h.in @@ -0,0 +1,110 @@ +/* Hint to the compiler that a function never returns */ +#define NGHTTP2_NORETURN @HINT_NORETURN@ + +/* Define to `int' if 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 + +/* sizeof(int *) */ +#cmakedefine SIZEOF_INT_P @SIZEOF_INT_P@ + +/* sizeof(time_t) */ +#cmakedefine SIZEOF_TIME_T @SIZEOF_TIME_T@ + +/* 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 header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_FCNTL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PWD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the 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/lib/nghttp2/configure.ac b/lib/nghttp2/configure.ac new file mode 100644 index 00000000000..0376e21fa92 --- /dev/null +++ b/lib/nghttp2/configure.ac @@ -0,0 +1,1192 @@ +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-DEV], [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, 39) +AC_SUBST(LT_REVISION, 1) +AC_SUBST(LT_AGE, 25) + +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 +#include +]], +[[ +std::vector> 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 +]], +[[ +std::map().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 +]], +[[ +auto a = std::make_shared(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.0.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 + ]], [[ + 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 + ]], [[ + 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 + ]], + [[ + 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 \ + time.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 ]]) + +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 +#include +#include +]]) + +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 +#include +#include +]]) + +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 + +# Check size of pointer to decide we need 8 bytes alignment +# adjustment. +AC_CHECK_SIZEOF([int *]) + +AC_CHECK_SIZEOF([time_t]) + +# 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 +]], +[[ +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 + #endif + #include +]]) + +AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[ +#include +]]) + +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/lib/nghttp2/contrib/.gitignore b/lib/nghttp2/contrib/.gitignore new file mode 100644 index 00000000000..85acde385e8 --- /dev/null +++ b/lib/nghttp2/contrib/.gitignore @@ -0,0 +1,3 @@ +nghttpx-init +nghttpx.service +nghttpx-upstart.conf diff --git a/lib/nghttp2/contrib/CMakeLists.txt b/lib/nghttp2/contrib/CMakeLists.txt new file mode 100644 index 00000000000..f598f7ba72b --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/Makefile.am b/lib/nghttp2/contrib/Makefile.am new file mode 100644 index 00000000000..5a02e2f0e3f --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/nghttpx-init.in b/lib/nghttp2/contrib/nghttpx-init.in new file mode 100755 index 00000000000..2b3c76e5b75 --- /dev/null +++ b/lib/nghttp2/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 +# +# 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/lib/nghttp2/contrib/nghttpx-logrotate b/lib/nghttp2/contrib/nghttpx-logrotate new file mode 100644 index 00000000000..c715cede2c1 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/nghttpx-upstart.conf.in b/lib/nghttp2/contrib/nghttpx-upstart.conf.in new file mode 100644 index 00000000000..0b7991601b7 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/nghttpx.service.in b/lib/nghttp2/contrib/nghttpx.service.in new file mode 100644 index 00000000000..06fb736c8f1 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/tlsticketupdate.go b/lib/nghttp2/contrib/tlsticketupdate.go new file mode 100644 index 00000000000..ff3d7596aff --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/contrib/usr.sbin.nghttpx b/lib/nghttp2/contrib/usr.sbin.nghttpx new file mode 100644 index 00000000000..891ff52cad9 --- /dev/null +++ b/lib/nghttp2/contrib/usr.sbin.nghttpx @@ -0,0 +1,16 @@ +#include + +/usr/sbin/nghttpx { + #include + #include + #include + + 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/lib/nghttp2/doc/.gitignore b/lib/nghttp2/doc/.gitignore new file mode 100644 index 00000000000..ed9e63493a7 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/CMakeLists.txt b/lib/nghttp2/doc/CMakeLists.txt new file mode 100644 index 00000000000..531791fc56c --- /dev/null +++ b/lib/nghttp2/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 ' where 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/lib/nghttp2/doc/Makefile.am b/lib/nghttp2/doc/Makefile.am new file mode 100644 index 00000000000..4e6f2779e25 --- /dev/null +++ b/lib/nghttp2/doc/Makefile.am @@ -0,0 +1,365 @@ +# 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_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 ' where 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/lib/nghttp2/doc/README.rst b/lib/nghttp2/doc/README.rst new file mode 100644 index 00000000000..549e5506ac4 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain b/lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain new file mode 100644 index 00000000000..a560d7597d3 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/_exts/rubydomain/__init__.py b/lib/nghttp2/doc/_exts/rubydomain/__init__.py new file mode 100644 index 00000000000..b5a7dc29ef0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/_exts/rubydomain/rubydomain.py b/lib/nghttp2/doc/_exts/rubydomain/rubydomain.py new file mode 100644 index 00000000000..db352334752 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/bash_completion/h2load b/lib/nghttp2/doc/bash_completion/h2load new file mode 100644 index 00000000000..80afe391882 --- /dev/null +++ b/lib/nghttp2/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 --npn-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/lib/nghttp2/doc/bash_completion/make_bash_completion.py b/lib/nghttp2/doc/bash_completion/make_bash_completion.py new file mode 100755 index 00000000000..e3a535ba90c --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/bash_completion/nghttp b/lib/nghttp2/doc/bash_completion/nghttp new file mode 100644 index 00000000000..dd48403972f --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/bash_completion/nghttpd b/lib/nghttp2/doc/bash_completion/nghttpd new file mode 100644 index 00000000000..7dd4d9b7973 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/bash_completion/nghttpx b/lib/nghttp2/doc/bash_completion/nghttpx new file mode 100644 index 00000000000..72dd590b318 --- /dev/null +++ b/lib/nghttp2/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 --npn-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/lib/nghttp2/doc/building-android-binary.rst.in b/lib/nghttp2/doc/building-android-binary.rst.in new file mode 100644 index 00000000000..2c77c98537b --- /dev/null +++ b/lib/nghttp2/doc/building-android-binary.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/building-android-binary.rst diff --git a/lib/nghttp2/doc/conf.py.in b/lib/nghttp2/doc/conf.py.in new file mode 100644 index 00000000000..228bdb195f6 --- /dev/null +++ b/lib/nghttp2/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 +# " v 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 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/lib/nghttp2/doc/contribute.rst.in b/lib/nghttp2/doc/contribute.rst.in new file mode 100644 index 00000000000..6a229d42805 --- /dev/null +++ b/lib/nghttp2/doc/contribute.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/contribute.rst diff --git a/lib/nghttp2/doc/docutils.conf b/lib/nghttp2/doc/docutils.conf new file mode 100644 index 00000000000..f5208a871bf --- /dev/null +++ b/lib/nghttp2/doc/docutils.conf @@ -0,0 +1,2 @@ +[parsers] +smart_quotes=no diff --git a/lib/nghttp2/doc/h2load-howto.rst.in b/lib/nghttp2/doc/h2load-howto.rst.in new file mode 100644 index 00000000000..252069d838f --- /dev/null +++ b/lib/nghttp2/doc/h2load-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/h2load-howto.rst diff --git a/lib/nghttp2/doc/h2load.1 b/lib/nghttp2/doc/h2load.1 new file mode 100644 index 00000000000..9f4cec51e22 --- /dev/null +++ b/lib/nghttp2/doc/h2load.1 @@ -0,0 +1,531 @@ +.\" 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" "Oct 27, 2023" "1.58.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 +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= +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= +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= +Number of native threads. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-i, \-\-input\-file= +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 , 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= +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= +Maximum frame size that the local endpoint is willing to +receive. +.sp +Default: \fB16K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-w, \-\-window\-bits= +Sets the stream level initial window size to (2**)\-1. +For QUIC, is capped to 26 (roughly 64MiB). +.sp +Default: \fB30\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +(2**)\-1. +.sp +Default: \fB30\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-H, \-\-header=
+Add/Override a header to the requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ciphers= +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= +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= +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= +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= +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= +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= +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= +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= +Specifies the maximum time that h2load is willing to +keep a connection open, regardless of the activity on +said connection. 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= +Specifies the amount of time that h2load is willing to +wait to see activity on a given connection. +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 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 , +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=(|unix:) +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 \-\-npn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. This is used in both ALPN and +NPN. The parameter must be delimited by a single comma +only and any white spaces are treated as a part of +protocol string. +.sp +Default: \fBh2,h2\-16,h2\-14,http/1.1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-h1 +Short hand for \fI\%\-\-npn\-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= +Specify decoder header table size. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-encoder\-header\-table\-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= +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= +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 and port to connect instead of using the authority +in . +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rps= +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= +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= +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 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 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/lib/nghttp2/doc/h2load.1.rst b/lib/nghttp2/doc/h2load.1.rst new file mode 100644 index 00000000000..0f65849d683 --- /dev/null +++ b/lib/nghttp2/doc/h2load.1.rst @@ -0,0 +1,435 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: h2load + +h2load(1) +========= + +SYNOPSIS +-------- + +**h2load** [OPTIONS]... [URI]... + +DESCRIPTION +----------- + +benchmarking tool for HTTP/2 server + +.. describe:: + + 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= + + 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= + + Number of concurrent clients. With :option:`-r` option, this + specifies the maximum number of connections to be made. + + Default: ``1`` + +.. option:: -t, --threads= + + Number of native threads. + + Default: ``1`` + +.. option:: -i, --input-file= + + Path of a file with multiple URIs are separated by EOLs. + This option will disable URIs getting from command-line. + If '-' is given as , 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= + + 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= + + Maximum frame size that the local endpoint is willing to + receive. + + Default: ``16K`` + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to (2\*\*)-1. + For QUIC, is capped to 26 (roughly 64MiB). + + Default: ``30`` + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + (2\*\*)-1. + + Default: ``30`` + +.. option:: -H, --header=
+ + Add/Override a header to the requests. + +.. option:: --ciphers= + + 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= + + 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= + + 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= + + 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= + + 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= + + 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= + + 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= + + 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= + + Specifies the maximum time that h2load is willing to + keep a connection open, regardless of the activity on + said connection. 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= + + Specifies the amount of time that h2load is willing to + wait to see activity on a given connection. + 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 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 , + 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=(|unix:) + + 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:: --npn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --h1 + + Short hand for :option:`--npn-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= + + Specify decoder header table size. + + Default: ``4K`` + +.. option:: --encoder-header-table-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= + + 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= + + 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 and port to connect instead of using the authority + in . + +.. option:: --rps= + + Specify request per second for each client. :option:`--rps` and + :option:`--timing-script-file` are mutually exclusive. + +.. option:: --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= + + 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 argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The 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/lib/nghttp2/doc/h2load.h2r b/lib/nghttp2/doc/h2load.h2r new file mode 100644 index 00000000000..50d9977de34 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/index.rst.in b/lib/nghttp2/doc/index.rst.in new file mode 100644 index 00000000000..2a49369c0db --- /dev/null +++ b/lib/nghttp2/doc/index.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/index.rst diff --git a/lib/nghttp2/doc/make.bat b/lib/nghttp2/doc/make.bat new file mode 100644 index 00000000000..2e0500de72f --- /dev/null +++ b/lib/nghttp2/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 ^` where ^ 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/lib/nghttp2/doc/mkapiref.py b/lib/nghttp2/doc/mkapiref.py new file mode 100755 index 00000000000..df59fbc7275 --- /dev/null +++ b/lib/nghttp2/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 * + +'''.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/lib/nghttp2/doc/nghttp.1 b/lib/nghttp2/doc/nghttp.1 new file mode 100644 index 00000000000..4bf274fb870 --- /dev/null +++ b/lib/nghttp2/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" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttp \- HTTP/2 client +.SH SYNOPSIS +.sp +\fBnghttp\fP [OPTIONS]... ... +.SH DESCRIPTION +.sp +HTTP/2 client +.INDENT 0.0 +.TP +.B +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= +Timeout each request after . Set 0 to disable +timeout. +.UNINDENT +.INDENT 0.0 +.TP +.B \-w, \-\-window\-bits= +Sets the stream level initial window size to 2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +2**\-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=
+Add a header to the requests. Example: \fI\%\-H\fP\(aq:method: PUT\(aq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-trailer=
+Add a trailer header to the requests.
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= +Use the specified client certificate file. The file +must be in PEM format. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-key= +Use the client private key file. The file must be in +PEM format. +.UNINDENT +.INDENT 0.0 +.TP +.B \-d, \-\-data= +Post FILE to server. If \(aq\-\(aq is given, data will be read +from stdin. +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-multiply= +Request each URI 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= +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= +Use 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= +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= +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= +Add at most bytes to a frame payload as padding. +Specify 0 to disable padding. +.UNINDENT +.INDENT 0.0 +.TP +.B \-r, \-\-har= +Output HTTP transactions 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= +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 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 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/lib/nghttp2/doc/nghttp.1.rst b/lib/nghttp2/doc/nghttp.1.rst new file mode 100644 index 00000000000..e10f3ee8a71 --- /dev/null +++ b/lib/nghttp2/doc/nghttp.1.rst @@ -0,0 +1,276 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttp + +nghttp(1) +========= + +SYNOPSIS +-------- + +**nghttp** [OPTIONS]... ... + +DESCRIPTION +----------- + +HTTP/2 client + +.. describe:: + + 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= + + Timeout each request after . Set 0 to disable + timeout. + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to 2\*\*-1. + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + 2\*\*-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=
+ + Add a header to the requests. Example: :option:`-H`\':method: PUT' + +.. option:: --trailer=
+ + Add a trailer header to the requests.
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= + + Use the specified client certificate file. The file + must be in PEM format. + +.. option:: --key= + + Use the client private key file. The file must be in + PEM format. + +.. option:: -d, --data= + + Post FILE to server. If '-' is given, data will be read + from stdin. + +.. option:: -m, --multiply= + + Request each URI 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= + + 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= + + Use as SETTINGS_MAX_CONCURRENT_STREAMS value of + remote endpoint as if it is received in SETTINGS frame. + + Default: ``100`` + +.. option:: -c, --header-table-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= + + 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= + + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + +.. option:: -r, --har= + + Output HTTP transactions 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= + + 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 argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The 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/lib/nghttp2/doc/nghttp.h2r b/lib/nghttp2/doc/nghttp.h2r new file mode 100644 index 00000000000..9d2a90eff7a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/nghttp2.h.rst.in b/lib/nghttp2/doc/nghttp2.h.rst.in new file mode 100644 index 00000000000..29e641da5ee --- /dev/null +++ b/lib/nghttp2/doc/nghttp2.h.rst.in @@ -0,0 +1,4 @@ +nghttp2.h +========= + +.. literalinclude:: @top_srcdir@/lib/includes/nghttp2/nghttp2.h diff --git a/lib/nghttp2/doc/nghttp2ver.h.rst.in b/lib/nghttp2/doc/nghttp2ver.h.rst.in new file mode 100644 index 00000000000..c6aa779da0d --- /dev/null +++ b/lib/nghttp2/doc/nghttp2ver.h.rst.in @@ -0,0 +1,4 @@ +nghttp2ver.h +============ + +.. literalinclude:: @top_builddir@/lib/includes/nghttp2/nghttp2ver.h diff --git a/lib/nghttp2/doc/nghttpd.1 b/lib/nghttp2/doc/nghttpd.1 new file mode 100644 index 00000000000..232757da1bb --- /dev/null +++ b/lib/nghttp2/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" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttpd \- HTTP/2 server +.SH SYNOPSIS +.sp +\fBnghttpd\fP [OPTION]... [ ] +.SH DESCRIPTION +.sp +HTTP/2 server +.INDENT 0.0 +.TP +.B +Specify listening port number. +.UNINDENT +.INDENT 0.0 +.TP +.B +Set path to server\(aqs private key. Required unless +\fI\%\-\-no\-tls\fP is specified. +.UNINDENT +.INDENT 0.0 +.TP +.B +Set path to server\(aqs certificate. Required unless +\fI\%\-\-no\-tls\fP is specified. +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-a, \-\-address= +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= +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= +Specify decoder header table size. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-encoder\-header\-table\-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== +Push resources s when is requested. +This option can be used repeatedly to specify multiple +push configurations. and 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= +Add at most bytes to a frame payload as padding. +Specify 0 to disable padding. +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-max\-concurrent\-streams= +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= +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= +Sets the stream level initial window size to 2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-dh\-param\-file= +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=
+Add a trailer header to a response.
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 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 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/lib/nghttp2/doc/nghttpd.1.rst b/lib/nghttp2/doc/nghttpd.1.rst new file mode 100644 index 00000000000..654a0253499 --- /dev/null +++ b/lib/nghttp2/doc/nghttpd.1.rst @@ -0,0 +1,186 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpd + +nghttpd(1) +========== + +SYNOPSIS +-------- + +**nghttpd** [OPTION]... [ ] + +DESCRIPTION +----------- + +HTTP/2 server + +.. describe:: + + Specify listening port number. + +.. describe:: + + + Set path to server's private key. Required unless + :option:`--no-tls` is specified. + +.. describe:: + + Set path to server's certificate. Required unless + :option:`--no-tls` is specified. + +OPTIONS +------- + +.. option:: -a, --address= + + 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= + + 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= + + Specify decoder header table size. + +.. option:: --encoder-header-table-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== + + Push resources s when is requested. + This option can be used repeatedly to specify multiple + push configurations. and s are + relative to document root. See :option:`--htdocs` option. + Example: :option:`-p`\/=/foo.png :option:`-p`\/doc=/bar.css + +.. option:: -b, --padding= + + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + +.. option:: -m, --max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + HTTP/2 session. + + Default: ``100`` + +.. option:: -n, --workers= + + Set the number of worker threads. + + Default: ``1`` + +.. option:: -e, --error-gzip + + Make error response gzipped. + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to 2\*\*-1. + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + 2\*\*-1. + +.. option:: --dh-param-file= + + 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=
+ + Add a trailer header to a response.
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 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 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/lib/nghttp2/doc/nghttpd.h2r b/lib/nghttp2/doc/nghttpd.h2r new file mode 100644 index 00000000000..e346cd1645d --- /dev/null +++ b/lib/nghttp2/doc/nghttpd.h2r @@ -0,0 +1,4 @@ +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttpx-howto.rst.in b/lib/nghttp2/doc/nghttpx-howto.rst.in new file mode 100644 index 00000000000..082ce510148 --- /dev/null +++ b/lib/nghttp2/doc/nghttpx-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/nghttpx-howto.rst diff --git a/lib/nghttp2/doc/nghttpx.1 b/lib/nghttp2/doc/nghttpx.1 new file mode 100644 index 00000000000..1aaa47e60dc --- /dev/null +++ b/lib/nghttp2/doc/nghttpx.1 @@ -0,0 +1,2771 @@ +.\" 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" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttpx \- HTTP/2 proxy +.SH SYNOPSIS +.sp +\fBnghttpx\fP [OPTIONS]... [ ] +.SH DESCRIPTION +.sp +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1. +.INDENT 0.0 +.TP +.B +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 +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=(,|unix:)[;[[:...]][[;]...] +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 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. +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 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 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 are grouped +together forming load balancing group. +.sp +Several parameters are accepted after . +The parameters are delimited by \(dq;\(dq. The available +parameters are: \(dqproto=\(dq, \(dqtls\(dq, +\(dqsni=\(dq, \(dqfall=\(dq, \(dqrise=\(dq, +\(dqaffinity=\(dq, \(dqdns\(dq, \(dqredirect\-if\-not\-tls\(dq, +\(dqupgrade\-scheme\(dq, \(dqmruby=\(dq, +\(dqread\-timeout=\(dq, \(dqwrite\-timeout=\(dq, +\(dqgroup=\(dq, \(dqgroup\-weight=\(dq, \(dqweight=\(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=\(dq. should be one of the following +list without quotes: \(dqh2\(dq, \(dqhttp/1.1\(dq. The default +value of 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=\(dq parameter, it can override the TLS +SNI field value with given . This will +default to the backend 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=\(dq parameter, if nghttpx +cannot connect to a this backend times in a row, +this backend is assumed to be offline, and it is +excluded from load balancing. If 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=\(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 times in a +row, the backend is assumed to be online, and it is now +eligible for load balancing target. If 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=\(dq parameter. If \(dqip\(dq is given in +, client IP based session affinity is enabled. +If \(dqcookie\(dq is given in , cookie based session +affinity is enabled. If \(dqnone\(dq is given in , +session affinity is disabled, and this is the default. +The session affinity is enabled per . If at +least one backend has \(dqaffinity\(dq parameter, and its + is not \(dqnone\(dq, session affinity is enabled for +all backend servers sharing the same . 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=\(dq must be used to specify a +name of cookie to use. Optionally, +\(dqaffinity\-cookie\-path=\(dq can be used to specify a +path which cookie is applied. The optional +\(dqaffinity\-cookie\-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 is \(dqyes\(dq, +the Secure attribute is always set. If is +\(dqno\(dq, the Secure attribute is always omitted. +\(dqaffinity\-cookie\-stickiness=\(dq controls +stickiness of this affinity. If 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 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. 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 . 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=\(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=\(dq and \(dqwrite\-timeout=\(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=\(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=\(dq parameter +specifies the weight of the group. The higher weight +gets more frequently selected by the load balancing +algorithm. must be [1, 256] inclusive. The weight +8 has 4 times more weight than 2. must be the same +for all addresses which share the same . 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=\(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. 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=\(dq parameter +above). \(dqdnf\(dq is an abbreviation of \(dqdo not forward\(dq. +.sp +Since \(dq;\(dq and \(dq:\(dq are used as delimiter, must +not contain these characters. In order to include \(dq:\(dq +in , 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=(,|unix:)[[;]...] +Set frontend host and port. If 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= +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= +Specify proxy URI in the form +\fI\%http:/\fP/[:@]:. If a proxy +requires authentication, specify and . +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= +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= +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= +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= +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= +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= +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= +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= +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= +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= +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= +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= +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= +Set maximum number of open files (RLIMIT_NOFILE) to . +If 0 is given, nghttpx does not set the limit. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rlimit\-memlock= +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= +Set buffer size used to store backend request. +.sp +Default: \fB16K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-response\-buffer= +Set buffer size used to store backend response. +.sp +Default: \fB128K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-fastopen= +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= +Specify read timeout for HTTP/2 frontend connection. +.sp +Default: \fB3m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-read\-timeout= +Specify read timeout for HTTP/3 frontend connection. +.sp +Default: \fB3m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-read\-timeout= +Specify read timeout for HTTP/1.1 frontend connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-write\-timeout= +Specify write timeout for all frontend connections. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-keep\-alive\-timeout= +Specify keep\-alive timeout for frontend HTTP/1 +connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-stream\-read\-timeout= +Specify read timeout for HTTP/2 streams. 0 means no +timeout. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-stream\-write\-timeout= +Specify write timeout for HTTP/2 streams. 0 means no +timeout. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-read\-timeout= +Specify read timeout for backend connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-write\-timeout= +Specify write timeout for backend connection. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-connect\-timeout= +Specify timeout before establishing TCP connection to +backend. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-keep\-alive\-timeout= +Specify keep\-alive timeout for backend HTTP/1 +connection. +.sp +Default: \fB2s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-listener\-disable\-timeout= +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= +Specify timeout before SETTINGS ACK is received from +client. +.sp +Default: \fB10s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-settings\-timeout= +Specify timeout before SETTINGS ACK is received from +backend server. +.sp +Default: \fB10s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-max\-backoff= +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= +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= +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= +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= +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= +Set supported curve list for frontend connections. + 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= +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 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=:[[;]...] +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, + must be absolute path. +.sp +Additional parameter can be specified in . The +available is \(dqsct\-dir=\(dq. +.sp +\(dqsct\-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 to file that contains DH parameters in PEM format. +Without this option, DHE cipher suites are not +available. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-npn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. This is used in both ALPN and +NPN. The parameter must be delimited by a single comma +only and any white spaces are treated as a part of +protocol string. +.sp +Default: \fBh2,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 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 to file that contains client private key used in +backend client authentication. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-cert\-file= +Path to file that contains client certificate used in +backend client authentication. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-min\-proto\-version= +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= +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 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=,[;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= +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= +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= +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= +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 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 to client private key for memcached connections to +get TLS ticket keys. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-fetch\-ocsp\-response\-file= +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= +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=,[;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 to client certificate for memcached connections to +store session cache. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-session\-cache\-memcached\-private\-key\-file= +Path to client private key for memcached connections to +store session cache. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-dyn\-rec\-warmup\-threshold= +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= +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= +Specifies the directory where *.sct files exist. All +*.sct files in 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 , 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= +Read list of PSK identity and secrets from . This +is used for frontend connection. The each line of input +file is formatted as :, where + is PSK identity, and 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= +Read PSK identity and secrets from . This is used +for backend connection. The each line of input file is +formatted as :, where +is PSK identity, and 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= +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= +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= +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= +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= +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= +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= +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= +Add at most 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= +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= +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= +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= +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= +Set the severity level of log output. must be +one of INFO, NOTICE, WARN, ERROR and FATAL. +.sp +Default: \fBNOTICE\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-accesslog\-file= +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= +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_: value of HTTP request header where +\(aq_\(aq in 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= +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= +Set syslog facility to . +.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= +Append RFC 7239 Forwarded header field with parameters +specified in comma delimited 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|) +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= +Specify protocol ID, port, host and origin of +alternative service. , and are +optional. Empty and 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= +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=
+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=
+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= +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= +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= +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= +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=(|*)= +Set file path to custom error page served when nghttpx +originally generates HTTP error status code . + must be greater than or equal to 400, and at most +599. If \(dq*\(dq is used instead of , 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= +Change server response header field value to . +.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= +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= +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= +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= +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= +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= +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= +Dumps request headers received by HTTP/2 frontend to the +file denoted in . 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, where >= 2. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-dump\-response\-header= +Dumps response headers sent from HTTP/2 frontend to the +file denoted in . 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, where >= 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= +Set path to save PID of this program. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-user= +Run this program as . 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= +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= +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= +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= +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= +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= +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= +Specify a congestion controller algorithm for a frontend +QUIC connection. should be either \(dqcubic\(dq or +\(dqbbr\(dq. +.sp +Default: \fBcubic\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-secret\-file= +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= +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= +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= +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= +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= +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 bytes. +.sp +Default: \fB6M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-max\-connection\-window\-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 bytes. +.sp +Default: \fB8M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-max\-concurrent\-streams= +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= +Load configuration from . 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= +Load additional configurations from . File +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 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 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 + (:) +.INDENT 7.0 +.TP +.B +It is a combination of date and time when the log is written. It +is in ISO 8601 format. +.TP +.B +It is a main process ID. +.TP +.B +It is a process ID which writes this log. +.TP +.B +It is a thread ID which writes this log. It would be unique +within . +.TP +.B and +They are source file name, and line number which produce this log. +.TP +.B +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: ; rel=preload +Link: ; 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/lib/nghttp2/doc/nghttpx.1.rst b/lib/nghttp2/doc/nghttpx.1.rst new file mode 100644 index 00000000000..b70b163233e --- /dev/null +++ b/lib/nghttp2/doc/nghttpx.1.rst @@ -0,0 +1,2527 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpx + +nghttpx(1) +========== + +SYNOPSIS +-------- + +**nghttpx** [OPTIONS]... [ ] + +DESCRIPTION +----------- + +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1. + +.. describe:: + + + Set path to server's private key. Required unless + "no-tls" parameter is used in :option:`--frontend` option. + +.. describe:: + + 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=(,|unix:)[;[[:...]][[;]...] + + + 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 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. + 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 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 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 are grouped + together forming load balancing group. + + Several parameters are accepted after . + The parameters are delimited by ";". The available + parameters are: "proto=", "tls", + "sni=", "fall=", "rise=", + "affinity=", "dns", "redirect-if-not-tls", + "upgrade-scheme", "mruby=", + "read-timeout=", "write-timeout=", + "group=", "group-weight=", "weight=", 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=". should be one of the following + list without quotes: "h2", "http/1.1". The default + value of 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=" parameter, it can override the TLS + SNI field value with given . This will + default to the backend name + + The feature to detect whether backend is online or + offline can be enabled using optional "fall" and "rise" + parameters. Using "fall=" parameter, if nghttpx + cannot connect to a this backend times in a row, + this backend is assumed to be offline, and it is + excluded from load balancing. If 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=" 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 times in a + row, the backend is assumed to be online, and it is now + eligible for load balancing target. If 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=" parameter. If "ip" is given in + , client IP based session affinity is enabled. + If "cookie" is given in , cookie based session + affinity is enabled. If "none" is given in , + session affinity is disabled, and this is the default. + The session affinity is enabled per . If at + least one backend has "affinity" parameter, and its + is not "none", session affinity is enabled for + all backend servers sharing the same . 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=" must be used to specify a + name of cookie to use. Optionally, + "affinity-cookie-path=" can be used to specify a + path which cookie is applied. The optional + "affinity-cookie-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 is "yes", + the Secure attribute is always set. If is + "no", the Secure attribute is always omitted. + "affinity-cookie-stickiness=" controls + stickiness of this affinity. If is + "loose", removing or adding a backend server might break + the affinity and the request might be forwarded to a + different backend server. If 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. 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 . 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=" 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=" and "write-timeout=" + 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=" 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=" parameter + specifies the weight of the group. The higher weight + gets more frequently selected by the load balancing + algorithm. must be [1, 256] inclusive. The weight + 8 has 4 times more weight than 2. must be the same + for all addresses which share the same . 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=" 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. 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=" parameter + above). "dnf" is an abbreviation of "do not forward". + + Since ";" and ":" are used as delimiter, must + not contain these characters. In order to include ":" + in , 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=(,|unix:)[[;]...] + + Set frontend host and port. If 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= + + 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= + + Specify proxy URI in the form + http://[:@]:. If a proxy + requires authentication, specify and . + 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= + + 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= + + Set maximum average read rate on frontend connection. + Setting 0 to this option means read rate is unlimited. + + Default: ``0`` + +.. option:: --read-burst= + + Set maximum read burst size on frontend connection. + Setting 0 to this option means read burst size is + unlimited. + + Default: ``0`` + +.. option:: --write-rate= + + Set maximum average write rate on frontend connection. + Setting 0 to this option means write rate is unlimited. + + Default: ``0`` + +.. option:: --write-burst= + + 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= + + 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= + + 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= + + 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= + + 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= + + Set maximum number of simultaneous connections frontend + accepts. Setting 0 means unlimited. + + Default: ``0`` + +.. option:: --backend-connections-per-host= + + 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= + + 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= + + Set maximum number of open files (RLIMIT_NOFILE) to . + If 0 is given, nghttpx does not set the limit. + + Default: ``0`` + +.. option:: --rlimit-memlock= + + 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= + + Set buffer size used to store backend request. + + Default: ``16K`` + +.. option:: --backend-response-buffer= + + Set buffer size used to store backend response. + + Default: ``128K`` + +.. option:: --fastopen= + + 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= + + Specify read timeout for HTTP/2 frontend connection. + + Default: ``3m`` + +.. option:: --frontend-http3-read-timeout= + + Specify read timeout for HTTP/3 frontend connection. + + Default: ``3m`` + +.. option:: --frontend-read-timeout= + + Specify read timeout for HTTP/1.1 frontend connection. + + Default: ``1m`` + +.. option:: --frontend-write-timeout= + + Specify write timeout for all frontend connections. + + Default: ``30s`` + +.. option:: --frontend-keep-alive-timeout= + + Specify keep-alive timeout for frontend HTTP/1 + connection. + + Default: ``1m`` + +.. option:: --stream-read-timeout= + + Specify read timeout for HTTP/2 streams. 0 means no + timeout. + + Default: ``0`` + +.. option:: --stream-write-timeout= + + Specify write timeout for HTTP/2 streams. 0 means no + timeout. + + Default: ``1m`` + +.. option:: --backend-read-timeout= + + Specify read timeout for backend connection. + + Default: ``1m`` + +.. option:: --backend-write-timeout= + + Specify write timeout for backend connection. + + Default: ``30s`` + +.. option:: --backend-connect-timeout= + + Specify timeout before establishing TCP connection to + backend. + + Default: ``30s`` + +.. option:: --backend-keep-alive-timeout= + + Specify keep-alive timeout for backend HTTP/1 + connection. + + Default: ``2s`` + +.. option:: --listener-disable-timeout= + + 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= + + Specify timeout before SETTINGS ACK is received from + client. + + Default: ``10s`` + +.. option:: --backend-http2-settings-timeout= + + Specify timeout before SETTINGS ACK is received from + backend server. + + Default: ``10s`` + +.. option:: --backend-max-backoff= + + 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= + + 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= + + 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= + + 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= + + 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= + + Set supported curve list for frontend connections. + 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= + + 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 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=:[[;]...] + + 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, + must be absolute path. + + Additional parameter can be specified in . The + available is "sct-dir=". + + "sct-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 to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + +.. option:: --npn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --verify-client + + Require and verify client certificate. + +.. option:: --verify-client-cacert= + + 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 to file that contains client private key used in + backend client authentication. + +.. option:: --client-cert-file= + + Path to file that contains client certificate used in + backend client authentication. + +.. option:: --tls-min-proto-version= + + 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= + + 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 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=,[;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= + + Set interval to get TLS ticket keys from memcached. + + Default: ``10m`` + +.. option:: --tls-ticket-key-memcached-max-retry= + + 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= + + Set maximum number of consecutive failure before + disabling TLS ticket until next scheduled key retrieval. + + Default: ``2`` + +.. option:: --tls-ticket-key-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 to client certificate for memcached connections to + get TLS ticket keys. + +.. option:: --tls-ticket-key-memcached-private-key-file= + + Path to client private key for memcached connections to + get TLS ticket keys. + +.. option:: --fetch-ocsp-response-file= + + Path to fetch-ocsp-response script file. It should be + absolute path. + + Default: ``/usr/local/share/nghttp2/fetch-ocsp-response`` + +.. option:: --ocsp-update-interval= + + 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=,[;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 to client certificate for memcached connections to + store session cache. + +.. option:: --tls-session-cache-memcached-private-key-file= + + Path to client private key for memcached connections to + store session cache. + +.. option:: --tls-dyn-rec-warmup-threshold= + + 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= + + 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= + + Specifies the directory where \*.sct files exist. All + \*.sct files in 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 , or certificate option in configuration + file. For additional certificates, use :option:`--subcert` + option. This option requires OpenSSL >= 1.0.2. + +.. option:: --psk-secrets= + + Read list of PSK identity and secrets from . This + is used for frontend connection. The each line of input + file is formatted as :, where + is PSK identity, and 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= + + Read PSK identity and secrets from . This is used + for backend connection. The each line of input file is + formatted as :, where + is PSK identity, and 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= + + 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= + + Set the maximum number of the concurrent streams in one + frontend HTTP/2 session. + + Default: ``100`` + +.. option:: --backend-http2-max-concurrent-streams= + + 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= + + Sets the per-stream initial window size of HTTP/2 + frontend connection. + + Default: ``65535`` + +.. option:: --frontend-http2-connection-window-size= + + Sets the per-connection window size of HTTP/2 frontend + connection. + + Default: ``65535`` + +.. option:: --backend-http2-window-size= + + Sets the initial window size of HTTP/2 backend + connection. + + Default: ``65535`` + +.. option:: --backend-http2-connection-window-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= + + Add at most 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= + + 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= + + Specify the maximum dynamic table size of HPACK decoder + in the frontend HTTP/2 connection. + + Default: ``4K`` + +.. option:: --backend-http2-encoder-dynamic-table-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= + + 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= + + Set the severity level of log output. must be + one of INFO, NOTICE, WARN, ERROR and FATAL. + + Default: ``NOTICE`` + +.. option:: --accesslog-file= + + 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= + + 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_: value of HTTP request header where + '_' in 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= + + 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= + + Set syslog facility to . + + 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= + + Append RFC 7239 Forwarded header field with parameters + specified in comma delimited 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|) + + 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= + + Specify protocol ID, port, host and origin of + alternative service. , and are + optional. Empty and 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= + + Just like :option:`--altsvc` option, but this altsvc is only sent + in HTTP/2 frontend. + +.. option:: --add-request-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=
+ + 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= + + 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= + + 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= + + 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= + + Set maximum number of incoming HTTP response header + fields. If trailer fields exist, they are counted + towards this number. + + Default: ``500`` + +.. option:: --error-page=(|*)= + + Set file path to custom error page served when nghttpx + originally generates HTTP error status code . + must be greater than or equal to 400, and at most + 599. If "\*" is used instead of , it matches all + HTTP status code. If error status code comes from + backend server, the custom error pages are not used. + +.. option:: --server-name= + + Change server response header field value to . + + 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= + + 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= + + Set the maximum size of request body for API request. + + Default: ``32M`` + + +DNS +~~~ + +.. option:: --dns-cache-timeout= + + Set duration that cached DNS results remain valid. Note + that nghttpx caches the unsuccessful results as well. + + Default: ``10s`` + +.. option:: --dns-lookup-timeout= + + 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= + + Set the number of DNS query before nghttpx gives up name + lookup. + + Default: ``2`` + +.. option:: --frontend-max-requests= + + 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= + + Dumps request headers received by HTTP/2 frontend to the + file denoted in . 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`\, where >= 2. + +.. option:: --frontend-http2-dump-response-header= + + Dumps response headers sent from HTTP/2 frontend to the + file denoted in . 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`\, where >= 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= + + Set path to save PID of this program. + +.. option:: --user= + + Run this program as . 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= + + 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= + + 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= + + 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= + + 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= + + 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= + + 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= + + Specify a congestion controller algorithm for a frontend + QUIC connection. should be either "cubic" or + "bbr". + + Default: ``cubic`` + +.. option:: --frontend-quic-secret-file= + + 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= + + 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= + + Specify the initial RTT of the frontend QUIC connection. + + Default: ``333ms`` + +.. option:: --no-quic-bpf + + Disable eBPF. + +.. option:: --frontend-http3-window-size= + + Sets the per-stream initial window size of HTTP/3 + frontend connection. + + Default: ``256K`` + +.. option:: --frontend-http3-connection-window-size= + + Sets the per-connection window size of HTTP/3 frontend + connection. + + Default: ``1M`` + +.. option:: --frontend-http3-max-window-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 bytes. + + Default: ``6M`` + +.. option:: --frontend-http3-max-connection-window-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 bytes. + + Default: ``8M`` + +.. option:: --frontend-http3-max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + frontend HTTP/3 connection. + + Default: ``100`` + + +Misc +~~~~ + +.. option:: --conf= + + Load configuration from . 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= + + Load additional configurations from . File + 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 argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The 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: + + (:) + + + It is a combination of date and time when the log is written. It + is in ISO 8601 format. + + + It is a main process ID. + + + It is a process ID which writes this log. + + + It is a thread ID which writes this log. It would be unique + within . + + and + They are source file name, and line number which produce this log. + + + 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 +`_) in response headers from +backend server and extracts URI-reference with parameter +``rel=preload`` (see `preload +`_) +and pushes those URIs to the frontend client. Here is a sample Link +header field to initiate server push: + +.. code-block:: text + + Link: ; rel=preload + Link: ; 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 `_). 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 +`_. This format is +the same one used by `nginx-ct +`_ and `mod_ssl_ct +`_. +`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/lib/nghttp2/doc/nghttpx.h2r b/lib/nghttp2/doc/nghttpx.h2r new file mode 100644 index 00000000000..12ac718e159 --- /dev/null +++ b/lib/nghttp2/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: + + (:) + + + It is a combination of date and time when the log is written. It + is in ISO 8601 format. + + + It is a main process ID. + + + It is a process ID which writes this log. + + + It is a thread ID which writes this log. It would be unique + within . + + and + They are source file name, and line number which produce this log. + + + 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 +`_) in response headers from +backend server and extracts URI-reference with parameter +``rel=preload`` (see `preload +`_) +and pushes those URIs to the frontend client. Here is a sample Link +header field to initiate server push: + +.. code-block:: text + + Link: ; rel=preload + Link: ; 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 `_). 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 +`_. This format is +the same one used by `nginx-ct +`_ and `mod_ssl_ct +`_. +`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/lib/nghttp2/doc/package_README.rst.in b/lib/nghttp2/doc/package_README.rst.in new file mode 100644 index 00000000000..dfa6b2d4152 --- /dev/null +++ b/lib/nghttp2/doc/package_README.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/README.rst diff --git a/lib/nghttp2/doc/programmers-guide.rst b/lib/nghttp2/doc/programmers-guide.rst new file mode 100644 index 00000000000..820cd204951 --- /dev/null +++ b/lib/nghttp2/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 + +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 +`_. 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. `_. +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 +`_. + +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 +`_ 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/lib/nghttp2/doc/security.rst b/lib/nghttp2/doc/security.rst new file mode 100644 index 00000000000..00b0c9cb054 --- /dev/null +++ b/lib/nghttp2/doc/security.rst @@ -0,0 +1 @@ +.. include:: ../doc/sources/security.rst diff --git a/lib/nghttp2/doc/sources/security.rst b/lib/nghttp2/doc/sources/security.rst new file mode 100644 index 00000000000..5a8fcd07966 --- /dev/null +++ b/lib/nghttp2/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 +`_ 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 +`_ 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/lib/nghttp2/doc/tutorial-client.rst.in b/lib/nghttp2/doc/tutorial-client.rst.in new file mode 100644 index 00000000000..4f7fcfc9ebc --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/tutorial-hpack.rst.in b/lib/nghttp2/doc/tutorial-hpack.rst.in new file mode 100644 index 00000000000..832dedf7d61 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/doc/tutorial-server.rst.in b/lib/nghttp2/doc/tutorial-server.rst.in new file mode 100644 index 00000000000..197226610c5 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/docker/Dockerfile b/lib/nghttp2/docker/Dockerfile new file mode 100644 index 00000000000..28839479415 --- /dev/null +++ b/lib/nghttp2/docker/Dockerfile @@ -0,0 +1,78 @@ +FROM debian:12 as build + +RUN apt-get update && \ + 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.1.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/lib/nghttp2/docker/README.rst b/lib/nghttp2/docker/README.rst new file mode 100644 index 00000000000..e08af23c5df --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/examples/.gitignore b/lib/nghttp2/examples/.gitignore new file mode 100644 index 00000000000..16335a5031a --- /dev/null +++ b/lib/nghttp2/examples/.gitignore @@ -0,0 +1,5 @@ +client +libevent-client +libevent-server +deflate +tiny-nghttpd diff --git a/lib/nghttp2/examples/CMakeLists.txt b/lib/nghttp2/examples/CMakeLists.txt new file mode 100644 index 00000000000..7a57bbf4108 --- /dev/null +++ b/lib/nghttp2/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 $ + $ + ) + add_executable(libevent-client libevent-client.c $ + $ + ) + add_executable(libevent-server libevent-server.c $ + $ + ) + add_executable(deflate deflate.c $ + $ + ) +endif() diff --git a/lib/nghttp2/examples/Makefile.am b/lib/nghttp2/examples/Makefile.am new file mode 100644 index 00000000000..7b682ed4981 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/examples/client.c b/lib/nghttp2/examples/client.c new file mode 100644 index 00000000000..6012d27cfbe --- /dev/null +++ b/lib/nghttp2/examples/client.c @@ -0,0 +1,741 @@ +/* + * 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 +#endif /* HAVE_CONFIG_H */ + +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_FCNTL_H +# include +#endif /* HAVE_FCNTL_H */ +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETDB_H +# include +#endif /* HAVE_NETDB_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +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); +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +/* + * Callback function for TLS NPN. Since this program only supports + * HTTP/2 protocol, if server does not offer HTTP/2 the nghttp2 + * library supports, we terminate program. + */ +static int select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + int rv; + (void)ssl; + (void)arg; + + /* nghttp2_select_next_protocol() selects HTTP/2 protocol the + nghttp2 library supports. */ + rv = nghttp2_select_next_protocol(out, outlen, in, inlen); + if (rv <= 0) { + die("Server did not advertise HTTP/2 protocol"); + } + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +/* + * Setup SSL/TLS context. + */ +static void init_ssl_ctx(SSL_CTX *ssl_ctx) { + /* Disable SSLv2 and enable all workarounds for buggy servers */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + /* Set NPN callback */ +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ +} + +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); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + rv = parse_uri(&uri, argv[1]); + if (rv != 0) { + die("parse_uri failed"); + } + fetch_uri(&uri); + return EXIT_SUCCESS; +} diff --git a/lib/nghttp2/examples/deflate.c b/lib/nghttp2/examples/deflate.c new file mode 100644 index 00000000000..df1cb92025d --- /dev/null +++ b/lib/nghttp2/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 +#endif /* !HAVE_CONFIG_H */ + +#include +#include + +#include + +#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/lib/nghttp2/examples/libevent-client.c b/lib/nghttp2/examples/libevent-client.c new file mode 100644 index 00000000000..be117a2eaf7 --- /dev/null +++ b/lib/nghttp2/examples/libevent-client.c @@ -0,0 +1,635 @@ +/* + * 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 +# 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 +#endif /* HAVE_CONFIG_H */ + +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#ifndef __sgi +# include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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; +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +/* NPN TLS extension client callback. We check that server advertised + the HTTP/2 protocol the nghttp2 library supports. If not, exit + the program. */ +static int select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + (void)ssl; + (void)arg; + + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID); + } + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +/* 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); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + 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); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + 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); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + run(argv[1]); + return 0; +} diff --git a/lib/nghttp2/examples/libevent-server.c b/lib/nghttp2/examples/libevent-server.c new file mode 100644 index 00000000000..bca8b645153 --- /dev/null +++ b/lib/nghttp2/examples/libevent-server.c @@ -0,0 +1,835 @@ +/* + * 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 +#endif /* HAVE_CONFIG_H */ + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETDB_H +# include +#endif /* HAVE_NETDB_H */ +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#include +#ifdef HAVE_FCNTL_H +# include +#endif /* HAVE_FCNTL_H */ +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#ifndef __sgi +# include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16) + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +struct app_context; +typedef struct app_context app_context; + +typedef struct http2_stream_data { + struct http2_stream_data *prev, *next; + char *request_path; + int32_t stream_id; + int fd; +} http2_stream_data; + +typedef struct http2_session_data { + struct http2_stream_data root; + struct bufferevent *bev; + app_context *app_ctx; + nghttp2_session *session; + char *client_addr; +} http2_session_data; + +struct app_context { + SSL_CTX *ssl_ctx; + struct event_base *evbase; +}; + +static unsigned char next_proto_list[256]; +static size_t next_proto_list_len; + +#ifndef OPENSSL_NO_NEXTPROTONEG +static int next_proto_cb(SSL *ssl, const unsigned char **data, + unsigned int *len, void *arg) { + (void)ssl; + (void)arg; + + *data = next_proto_list; + *len = (unsigned int)next_proto_list_len; + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +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_next_protocol((unsigned char **)out, outlen, in, inlen); + + if (rv != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + +/* 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); + } + + next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN; + memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID, + NGHTTP2_PROTO_VERSION_ID_LEN); + next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + 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 = data_source_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[] = "404" + "

404 Not Found

"; + +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); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + 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); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + run(argv[1], argv[2], argv[3]); + return 0; +} diff --git a/lib/nghttp2/fedora/spdylay.spec b/lib/nghttp2/fedora/spdylay.spec new file mode 100644 index 00000000000..967d908fc76 --- /dev/null +++ b/lib/nghttp2/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 0.3.7-DEV +- Initial RPM release. diff --git a/lib/nghttp2/fuzz/README.rst b/lib/nghttp2/fuzz/README.rst new file mode 100644 index 00000000000..54ae8325b6c --- /dev/null +++ b/lib/nghttp2/fuzz/README.rst @@ -0,0 +1,33 @@ +Fuzzer +====== + +This directory contains fuzzer target mainly written to integrate +nghttp2 into `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 `_ 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/lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 b/lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 new file mode 100644 index 0000000000000000000000000000000000000000..f2bc466b0e4a322b824c7397fa7941c5d0ac9dc6 GIT binary patch literal 61 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#vW(du|zySae>IQKD literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a b/lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a new file mode 100644 index 0000000000000000000000000000000000000000..629ce58585972f10efee78781505b320c34359e6 GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH XZVY#0!JQNacAz{1NC9I>YHh!xOLNG43Fv;d`6h{7K1%wEPk7NI#`aes1eb(;OQ zp4OYTZRX3_qAIKMu*)@KP{cQV>$-j_(r=%nd`P#)ef=E0j^pe3eTreMQsjqp5ds7V z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk V1PBlyK!5-N0t5&UAVA>%0w4O)4v+u< literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a b/lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a new file mode 100644 index 0000000000000000000000000000000000000000..f8651f1c385e3528f9c80e9c6321154c7b599c93 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfY5*Z|@2GqQqs UO>HfXT^w^`xEl-Zq%g1p0Q3+Hn*aa+ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd b/lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd new file mode 100644 index 0000000000000000000000000000000000000000..15c71aad065e5b83ae10e8286bc13d7572ab9bcc GIT binary patch literal 82 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIENkgVCY&(HfXT^w^`xEl-Zq%g1p05)F{F8}}l literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f b/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f new file mode 100644 index 00000000000..31a5ddcadcd --- /dev/null +++ b/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f @@ -0,0 +1,2 @@ +INVALID CONNECTION PREFACE + diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 b/lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 new file mode 100644 index 0000000000000000000000000000000000000000..cf66978abea0ec2467682b9da5e1aad1ab524c14 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfY53?hKS{EVzX V24hoOi(?nZ+!*f0f;%Y;>;V3=4IBUf literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 b/lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 new file mode 100644 index 0000000000000000000000000000000000000000..981e66ad916281e12d3e0191d1b31bba902f3b3c GIT binary patch literal 76 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEVOEd?h{N2}*5cU3 OF*k<0vEWV$13Lhv;0zrA literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 b/lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 new file mode 100644 index 0000000000000000000000000000000000000000..fb65c24434c7f124bfbbf8608f63739117f1c91f GIT binary patch literal 96 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH RZVY#0!JQNab_NFQ;sD@55ySuh literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 b/lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 new file mode 100644 index 0000000000000000000000000000000000000000..62d58871c2573fcfef46cf70a50080415864382b GIT binary patch literal 61 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#v4iF9EqyPXCeFoD2 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a b/lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a new file mode 100644 index 0000000000000000000000000000000000000000..e5285080e90069fa91a17c9164391148049ea991 GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_G18;3=G-; literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc b/lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc new file mode 100644 index 0000000000000000000000000000000000000000..4cd627b01da10dc5e34043b4fba897a70b565f2d GIT binary patch literal 3603 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEent?3v8k=av5RAF z40mI}ofHOk2HqWI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEAx4k{V{==JV;9HV Z81BY`J1Gq8F^q;lB@7@nj3ueXB>-924r%}Z literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 b/lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 new file mode 100644 index 0000000000000000000000000000000000000000..e8ac9ee03d93cc3379c7c7311b3c59a836739271 GIT binary patch literal 77 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE5k^%Y1H@tc-_+LP Q*u^n7hP$!gP6`7%0KXg!H~;_u literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 b/lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 new file mode 100644 index 0000000000000000000000000000000000000000..da1612a7f2911551e900ad2e508e90c2b52f0f07 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfV)8FFt7sv<&X>; literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab b/lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab new file mode 100644 index 0000000000000000000000000000000000000000..ab72d093230a9ff01cc3de11fbd4542276eb362c GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH VZVY#0!JQNacAz{5NFJn!5dbKp3?Tpj literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 b/lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 new file mode 100644 index 0000000000000000000000000000000000000000..a9123c4eb7d518844a91a449eb9a49d4c40ff6f0 GIT binary patch literal 78 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#v4hYS_02JV0gK@w# E0DwRSeEI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEX+~BcgR!Zt#j%TH aZVY#0!JQNac8Atao4i{7x6JWavjhN1(i05; literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 b/lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 new file mode 100644 index 0000000000000000000000000000000000000000..9338c998551a1667fdc787059bc1fd04a7e8ab0f GIT binary patch literal 98 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV a81BY`J1Gq8KzU}6JV+4-NHHr^(LMmn+75RB literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 b/lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 new file mode 100644 index 0000000000000000000000000000000000000000..aa67cbc7e8553550714746e726110323196e82a4 GIT binary patch literal 98 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV d81BY`J1Gq8KzU}6JV+4-kOO8gmZTP!007FR4d?&> literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 b/lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 new file mode 100644 index 0000000000000000000000000000000000000000..ad145181e17266dc97e2277971439be8d6a05792 GIT binary patch literal 2487 zcmZ|D{YM-C90u?^ySmmnXJ(R_N+w&j-ei(Vk~+z*H$gcYB*^ zq2S;BkEbCP%t&Q$dVaQcDQo9rBsu)MHxRz}2k;Dd82%6*fk)vn_#^mZ_!D>zJQw~H z{tTW6FMt=qpTmpbFW|-SSMU;e8N32s34a5xf>*=e!fW8Q@H%)sya67EH^H0X@8B)) zHh2g8J-ieC0p11ghJS+h!295z;r;Ld_#k`;J^~+wkHN>`U*Hq)N%#~z37>(_!583* z@MZW4d=I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEK6VHVB{PhQ3sRE- DTG0r+ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f b/lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f new file mode 100644 index 0000000000000000000000000000000000000000..a0c5dc36af07ccd7f65bc2c0d99a1eef172e5238 GIT binary patch literal 77 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE5k^%Y1I+o~)YjtI P#W6RAyRqO-3IjU;zY7gF literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 b/lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 new file mode 100644 index 0000000000000000000000000000000000000000..9176566da8038cf36646ec2aa0e8571c1e435285 GIT binary patch literal 61 wcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#v4hRh-0TL7jZU6uP literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce b/lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce new file mode 100644 index 0000000000000000000000000000000000000000..1eb839a9807c7153eae2da88538003af5dc98216 GIT binary patch literal 93 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE6{t9nE5N|O1QcXs TUI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIESw>bMgR!Zt#j%TH cZVY#0!JQNac7gV-tQ!;0L``m) literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 b/lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 new file mode 100644 index 0000000000000000000000000000000000000000..e8e09a8cefac10375f9be1e0b42b3b184fc60a2a GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mJ=CI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEent?3v8k=av5RAF z40mI}ofHOk2HqWI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEByo@cKO-xUWNd0{ gaqQxl8^hgLa3_UUs2_y^F1JupJ0pl?O07HolHUIzs literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a b/lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a new file mode 100644 index 0000000000000000000000000000000000000000..d168ca6b7a4b61969c519a4db05ef24c32aff224 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIESw>bMgR!Zt#j%TH cZVY#0!JQNac8B(@tQ!;0L``m)(^b literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c b/lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c new file mode 100644 index 0000000000000000000000000000000000000000..cf176ebaf39a550caac5a4fb2d066519945062a6 GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV W81BY`J1Gq8KzRm`0>+Zm;t~KXunpM& literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d b/lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d new file mode 100644 index 0000000000000000000000000000000000000000..07a4f677c14a8dda34c0f1c2c15a99d19cbdc014 GIT binary patch literal 75 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEAx2gpgR!Zt#j%TH QZVY#0!JQNab|*%C0I-(~hyVZp literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f b/lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f new file mode 100644 index 0000000000000000000000000000000000000000..73f52d1ba7a0bfdb6e2fe1f8a3d5d11cffd5c909 GIT binary patch literal 89 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEAx4k{V{==JV;9HV b81BY`J1Gq8F^nb*46F>CASt$x)Z!8VW~mNb literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca b/lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca new file mode 100644 index 0000000000000000000000000000000000000000..a27ca46aa8b4d5fcecfe05dbb1b58e59e5675a1d GIT binary patch literal 61 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#vW)KbHZ~y=i?*?)J literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c b/lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c new file mode 100644 index 0000000000000000000000000000000000000000..f5a2f2f22fbf2c0c7a455848b824d9bae886f8a3 GIT binary patch literal 90 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEMMhR2gR!Zt#j%TH gZVY#0!JQNac87MwS1Xoy_J?=83c9x8g?Z6`0KfYdOaK4? literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf b/lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf new file mode 100644 index 0000000000000000000000000000000000000000..bdb70efdc149e9826cba66d308380006c80a8200 GIT binary patch literal 17632 zcmeI&Jx;=K90uSv0VE~808?iNF&@BR!XSgOK}JUpzyVB*#%RI;+>E&B#OBTs7+l@m zVPdMaDd`pd!_%Q3ThsLI^ZwfAlW}&KolmFv;N<8yP15A*;(r}QomNyW<)$dgy;fWu zjy~gdxf4G;J)X_Guh+f%+0RXMFnr#8y?kFT?uuo9`@4QSz1`Wk1nvSs@GS5+Bmxa- zNJARZkcI(ixS!h!bDLyJW0ESXompqrnaNCM8q$!4jfVTVZRI~nKmrnw XfCMBU8zALD9^^qDv_tJMpmz8NjD6PX literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 b/lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 new file mode 100644 index 0000000000000000000000000000000000000000..b5a05b670763b89b9596357c4b0d74e0f41eddc4 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfY548$M-enwUh UtEsKUv5RAF40mI}ofHOk0OjTk9smFU literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a b/lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a new file mode 100644 index 0000000000000000000000000000000000000000..fa18efff7a69358a93b564fe6dddc8e1e153e3dc GIT binary patch literal 93 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE9)vhU3Im7(l;CG% W1u>f1S{%DL=EiV07Tif;UI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEDMnTxgR!Zt#j%TH YZVY#0!JQNab_ccy-4NH{5T^WW03izx1poj5 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 b/lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 new file mode 100644 index 0000000000000000000000000000000000000000..2be33f54ad9e284ec0b67024402bdc336e578e6c GIT binary patch literal 82 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mL0&Gp{T$Co=^o R#=!=b$S^7{NKIy7008EI3#$MC literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 b/lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 new file mode 100644 index 0000000000000000000000000000000000000000..a7d696a598d3f3c44b7ffded04b4c47f24fe3df1 GIT binary patch literal 58 vcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEMkoUS{*4A` literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab b/lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab new file mode 100644 index 0000000000000000000000000000000000000000..2f00755ed85a02647e1e791c7d03d1d9b9cffcf3 GIT binary patch literal 63 xcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEm^dql4*(wb266xZ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 b/lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 new file mode 100644 index 0000000000000000000000000000000000000000..d212c23388534053f6ce282d01c7eeae25ab9a18 GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_9g(A$PAVM literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 b/lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 new file mode 100644 index 0000000000000000000000000000000000000000..43a84230834b774529cbb2909d0224d5a39910e3 GIT binary patch literal 84 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH VZVY#0!JQNab_ND!W{^Bc5da)a3>p9c literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 b/lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 new file mode 100644 index 0000000000000000000000000000000000000000..43cf97250939738ef8445213163f2a5a99b1bca0 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfV)<^YBH8CgNR TrnVNxE{?e|+>HfyQW)3){&x); literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 b/lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 new file mode 100644 index 0000000000000000000000000000000000000000..e41d6379601900a5120bfca3a3cbf5f87c3a82a1 GIT binary patch literal 91 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEB}P^tgR!Zt#j%TH gZVY#0!JQNab_cG~;?yGD#Prm>61EJZ;)2v<0J6Rkwg3PC literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a b/lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a new file mode 100644 index 0000000000000000000000000000000000000000..cc92edc5c3cacf9ea9b711a232d0edf6fdc9b4c4 GIT binary patch literal 3593 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEent?3v8k=av5RAF z40mI}ofHOk2HqWI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH VZVY#0!JQNacAz{HNS?6)hyf~S44VJ| literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e b/lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e new file mode 100644 index 0000000000000000000000000000000000000000..11175d83dbce4bff1106b0b6b0db4c372b4f3279 GIT binary patch literal 72 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH NZVY#0!JQNab^wbU3=;qV literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa b/lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa new file mode 100644 index 0000000000000000000000000000000000000000..4d233a0f4e7983fb3d5138ab2957ce3de44db96b GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV T81BY`J1Gq8KzR<3JXjF`D1rI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEm^do~gZ+O1AaDn- literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 b/lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 new file mode 100644 index 0000000000000000000000000000000000000000..979e96aa7ae13e1fd2b2d639f92286946d54b774 GIT binary patch literal 82 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4lxJ~Cpp;QOb`PA DoEQd< literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 b/lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 new file mode 100644 index 0000000000000000000000000000000000000000..5b47f4f3d124078d3dffc4d7ee7f5dcaa6d130ba GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH SZVY#0!JQNac91+wd>;TU>JDfC literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 b/lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 new file mode 100644 index 0000000000000000000000000000000000000000..1ebf432cb985464fbc01bd3875c17de519abb91b GIT binary patch literal 96 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH VZVY#0!JQNab_NE1PLKw4aRA`e5z7Do literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 b/lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 new file mode 100644 index 0000000000000000000000000000000000000000..f2e3ff2b59b690385b6e012cf7da20678ec867ba GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE8AethgR!Zt#j%TH bZVY#0!JQNac81nZo4nfp|NsA2)u|o;U!@cJ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e b/lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e new file mode 100644 index 0000000000000000000000000000000000000000..22137b3fd4e3a8bdd2e8fc77bbdd2753d6ab5314 GIT binary patch literal 95 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV Z81BY`J1Gq8KzXnI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNac0~Y}XACs} literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d b/lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d new file mode 100644 index 0000000000000000000000000000000000000000..c0f50b7a96add18ab9a81618f48561cba90a89c0 GIT binary patch literal 84 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEX+~BcgR!Zt#j%TH aZVY#0!JQNac81nZo4i{7x6JWavjhM}(h|)8 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 b/lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 new file mode 100644 index 0000000000000000000000000000000000000000..5a4c52ad50761a39c9777cda36c2b13bcd6c57fb GIT binary patch literal 71 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEK1Nm`gR!Z_v5RAF M40mI}ofHOk0DQR%i~s-t literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 b/lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 new file mode 100644 index 0000000000000000000000000000000000000000..a7fa65ddd3c7201832395883159911d6e610ad4f GIT binary patch literal 63 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEHZ~9qI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEIYw3>gR!Zt#j%TH cZVY#0!JQNab_cc!-ICPe61EJZ;)2v<0B11~7ytkO literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e b/lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e new file mode 100644 index 0000000000000000000000000000000000000000..e42f5f421c1d8912032b02ee576af122533f61b9 GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4t59)A^!saEcpj; literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea b/lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea new file mode 100644 index 0000000000000000000000000000000000000000..32000271188022475aadfb760aa990e6f24ae2b9 GIT binary patch literal 89 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEent?3v8k=av5RAF U40mI}ofHOk1_ll>h&+@809l3%EdT%j literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f b/lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f new file mode 100644 index 0000000000000000000000000000000000000000..28709d4b00e024554d8a5cec0bd44d76c4a14d98 GIT binary patch literal 86 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEelX40)YjtI#W6RA UyRqO-3IjU>11l3q9xVAE05swa6#xJL literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 b/lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 new file mode 100644 index 0000000000000000000000000000000000000000..46316c1dfaa55e2d23cc62038966105009fba5a2 GIT binary patch literal 61 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQue^Fry^3xC8(eCI}}0 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 b/lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 new file mode 100644 index 0000000000000000000000000000000000000000..68a831a41639a3d6482c901f339a401e0ebe3f84 GIT binary patch literal 87 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEIYw3>gR!Zt#j%TH cZVY#0!JQNab_TWz-ICPe61EJZ;)2v<0Aq0w*Z=?k literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a b/lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a new file mode 100644 index 0000000000000000000000000000000000000000..582b979f4bd93c4192f9b75c465d50dfc6f832c3 GIT binary patch literal 92 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV e81BY`J1Gq83=G^1oFH+wlGNgog2a@R)D!@fju21) literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 b/lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 new file mode 100644 index 0000000000000000000000000000000000000000..ea39871a70836b25ee69a9b6a8d0f82fc249370e GIT binary patch literal 74 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQw#v4lrH+A0*0x-~a%l CUJY^p literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 b/lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 new file mode 100644 index 0000000000000000000000000000000000000000..38e577422c0168679830b42cb9d7ae115bd861f3 GIT binary patch literal 77 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE5k^)ZgVBk_!`0EH St;Mm6V{Qz0W5Jyi26h0uf(-5e literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 b/lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 new file mode 100644 index 0000000000000000000000000000000000000000..7a8ad5d1cb0a38ca6b6fa3db144a8fc0f16ab3ea GIT binary patch literal 60 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEW)29=zz6^fod#|I literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada b/lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada new file mode 100644 index 0000000000000000000000000000000000000000..063bdab6e8f1442b450ba1683449cd4af658eefb GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_BH^Q%nX_U literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 b/lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 new file mode 100644 index 0000000000000000000000000000000000000000..7eed61500003bf4a00e58950041f535397643695 GIT binary patch literal 80 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEm^erbD8#`A5r&cg DqRt0; literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a b/lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a new file mode 100644 index 0000000000000000000000000000000000000000..aa862db4abe6a0fbb5542275be2f4d430db7bde8 GIT binary patch literal 101 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEAx4k{V{==JV;9HV e81BY`J1Gq8F^q;lC14edC8@I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQue^5QDKKwYUTT7ApuS literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 b/lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 new file mode 100644 index 0000000000000000000000000000000000000000..4f6a2ebbc8f7392b9abd7deb3786e06825fb9071 GIT binary patch literal 103 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH dZVY#0!JQNab_NDkCXhT>5l}e?8$>mf1OOEw4JZHr literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 b/lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 new file mode 100644 index 0000000000000000000000000000000000000000..0c101941100b1d7f50b323338d1383c49540f851 GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_67i##tf7I literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb b/lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb new file mode 100644 index 0000000000000000000000000000000000000000..b92e520ac82ae3e760d9f98d2ee108a75cc8caca GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!a2g~73l OV{Qz0W5Jyi26h0Cw+qw& literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef b/lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef new file mode 100644 index 0000000000000000000000000000000000000000..cd87803f0e8dcc91eb193d53ef75af8ac868bb56 GIT binary patch literal 58 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEMn+a3gV6y1{?Z1j literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 b/lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 new file mode 100644 index 0000000000000000000000000000000000000000..63ca53a9c60149a68929bd296fd5b4b761b6a1b1 GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mPm{2n$RC05b{) A1^@s6 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 b/lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 new file mode 100644 index 0000000000000000000000000000000000000000..08dd2bdb3403ee989e21207f7db8d42b4350be71 GIT binary patch literal 100 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEByo@cKO-xUWNd0{ ZaqQxl8^hgLa3_U<9jKfGBn#5R2mp%u3@-ox literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 b/lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 new file mode 100644 index 0000000000000000000000000000000000000000..51c931c7cb41942009771c7bae0f1c4c85947228 GIT binary patch literal 81 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERz?tmv8k=av5SF$ TixZ@bkz;NQcVoew6b5zx+Bys! literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 b/lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 new file mode 100644 index 0000000000000000000000000000000000000000..35134714362f388e94c0bdcff62344f14ae1ddad GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4t5X?I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_6`7-&J3IY literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 b/lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 new file mode 100644 index 0000000000000000000000000000000000000000..a8b06a9be11705d7e75bd4b318544e35cd375955 GIT binary patch literal 83 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEDMnTxgR!Zt#j%TH ZZVY#0!JQNac81nZo4i{7H+u$5001)E5x@Wd literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 b/lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 new file mode 100644 index 0000000000000000000000000000000000000000..dfa976699ed24289f4632247504a5c77f1095ce3 GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEelX40+}7gQ#W6RA VyRqO-3IjV(o&hAmSdv;?0st&54b=bu literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 b/lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 new file mode 100644 index 0000000000000000000000000000000000000000..1b8b19e0f1f6281690b1db23344af4eb0c4cef18 GIT binary patch literal 3606 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEelX40+}7gQ#W6RA zyRqO-3IjU>?+#9oJflPVr!CVi`!A^fzgFt=^YioVYJQGVqd_p53Pv-+Xjw2?9FEoq kqqX5^Z8%yRj@E{wwc%)OIEZRPUI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEX+~BcgR!Zt#j%TH aZVY#0!JQNac81nZo4i{7w|EB3{|^8~-xAOO literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f b/lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f new file mode 100644 index 0000000000000000000000000000000000000000..1b4ae593007a1a0dc09a71942def4ebab3f7f2fa GIT binary patch literal 91 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEB}P^tgR!Zt#j%TH gZVY#0!JQNab^)%^;?yGD#Prm>61EJZ;)2v<0Ix0*h5!Hn literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 b/lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 new file mode 100644 index 0000000000000000000000000000000000000000..7d81f74a7fbe90a6f5df62458326ecdd2f07726f GIT binary patch literal 102 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH bZVY#0!JQNacAz{nNFJn!1Blo_95?^~_m~VZ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 b/lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 new file mode 100644 index 0000000000000000000000000000000000000000..eae40c48ee1eb343903a5e454520ca1a5e34c5b8 GIT binary patch literal 75 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEAx2gpgVFwxd{bMC QV;9HV81BY`J1Gq80JJX+RsaA1 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b b/lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b new file mode 100644 index 0000000000000000000000000000000000000000..875250c11259babd3c03bc84db767f8be00fbfbd GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mJ?Y4B;>^001lp B26F%a literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 b/lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 new file mode 100644 index 0000000000000000000000000000000000000000..4ca7ddb6c9adffbb9fb696ad953df3f4e8404405 GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgHfTWt;Mm6 OV{Qz0W5Jyi26h0Gs|+*% literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb b/lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb new file mode 100644 index 0000000000000000000000000000000000000000..8c90cf336469824fe9007a9b4a9c19ed90e00067 GIT binary patch literal 71 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEK1Nm`gR!a2v5RAF M40mI}ofHOk0DRL6jsO4v literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 b/lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 new file mode 100644 index 0000000000000000000000000000000000000000..3aff72385938034cc09cf491896336ae4c0fad48 GIT binary patch literal 63 ycmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEm^c%V%LD)(<_2&8 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 b/lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 new file mode 100644 index 0000000000000000000000000000000000000000..20ed145a26d98e37649a9edf48d494d9cc0d1f1e GIT binary patch literal 104 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV k81BY`J1Gq8KzXnI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{II6g)IhM literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 b/lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 new file mode 100644 index 0000000000000000000000000000000000000000..28b6df13b53396939601590fe43c617e6d69a678 GIT binary patch literal 63 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEh&UqyGmx7C03TEa A)Bpeg literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 b/lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 new file mode 100644 index 0000000000000000000000000000000000000000..6c1c4bc8b06eae3ff9625288e20bfe412bd09ceb GIT binary patch literal 87 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEByk3y5I-X;P@J); St;Mm6V{Qz0W5Jyi26h1K)eIs4 literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d b/lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d new file mode 100644 index 0000000000000000000000000000000000000000..e290f86fe3fa677acb7d2eee18c44c9eae43f1ef GIT binary patch literal 101 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE9Y$6lgR!Zt#j%TH mZVY#0!JQNac850KW>eOU^39$B^BtHI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE8Agx literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 b/lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 new file mode 100644 index 0000000000000000000000000000000000000000..5759f66944bc0450c06225ebe03c2e07330e5d53 GIT binary patch literal 98 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV b81BY`J1Gq8KzXnI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEUPe|RgR#Z2i(_sK LcVoew6b5zxZxRa$ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad b/lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad new file mode 100644 index 0000000000000000000000000000000000000000..e3ef3dccc63270304405e3f23404b495139cefb2 GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!}-#j%TH XZVY#0!JQNacAz{1NC9I>YHI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIESw>bMgR!Zt#j%TH cZVY#0!JQNac82z?tQ!;0L``m)I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgQ=;l#j%TH NZVY#0!JQNab^wbk3={wW literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 b/lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 new file mode 100644 index 0000000000000000000000000000000000000000..40659b535bbdbd9b41fd305e79c69b541468c214 GIT binary patch literal 58 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+XRgHaFw|4Rmv literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 b/lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 new file mode 100644 index 0000000000000000000000000000000000000000..98b11ed9edd911f81c87045083c15ab0126b9585 GIT binary patch literal 102 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV a81BY`J1Gq8KzU}6JV+4-5V3(cZ~y@JW(+U@ literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 b/lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 new file mode 100644 index 0000000000000000000000000000000000000000..a88435f17f0564766b95a450dea9ec06580e93d3 GIT binary patch literal 63 xcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEm^c%N3jiME25tZV literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 b/lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 new file mode 100644 index 0000000000000000000000000000000000000000..783b6a3b9edd02cb4740a44a998e18b2854afc77 GIT binary patch literal 62 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfV);xPUP02xyU AY5)KL literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 b/lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 new file mode 100644 index 0000000000000000000000000000000000000000..3ec3fbbb61adb02f9081c648a76f4adcbefa8120 GIT binary patch literal 3593 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgR!Zt#j%TH zZVY#0!JQNab_U)ZoFEO14(*?|OuOvAp#J|_sn5^P&$p}jIZBNN!DuQN%?P7q!Dw+f aS|g0shNHFNXl*!J8;;h7qqX56stp0(Y0k3% literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d b/lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d new file mode 100644 index 0000000000000000000000000000000000000000..348471d4a461dd080c99db26cd96de5b37cd0325 GIT binary patch literal 81 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE2}V{RgR!Zt#j%TH XZVY#0!JQNac88W7lNQ=DI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEVMbOUgHgf$p?p(Y Ri(?nZ+!*f0f;%Y;>;SzJ4R!zk literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a b/lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a new file mode 100644 index 0000000000000000000000000000000000000000..11e84b4c686c08214733e1d4a413aa9cb142d927 GIT binary patch literal 98 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV Y81BY`J1Gq8KzR<3JY)TTkZL3j00Z3I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEX+~BcgR!Zt#j%TH aZVY#0!JQNac7fJUo4i{7x6JWavjhM~k`mnj literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 b/lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 new file mode 100644 index 0000000000000000000000000000000000000000..d894be7e13aaa1e3a9defaec7d88a0aeb50af745 GIT binary patch literal 16465 zcmeIuJq`gu7zW^(WP_}1Tw!Yk;skW7kZjme=>STt5QRIqnY--Ha0t!w7T<68w1-_i zubZxGm+QrI}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwf7yB>w{d8A}If literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 b/lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 new file mode 100644 index 0000000000000000000000000000000000000000..b19f4f64201f09dc0e2e331028e5f368cf1d8b29 GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mL2IVN_g@n#{le E05~ZKwEzGB literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df b/lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df new file mode 100644 index 0000000000000000000000000000000000000000..e5c39ccd9f71c242214862fc7f05045ebcd63cd8 GIT binary patch literal 102 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEByo@cKO-xUWNd0{ VaqQxl8^hgLa3_U<9Ys3>BLJgK3@`uy literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 b/lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 new file mode 100644 index 0000000000000000000000000000000000000000..81e30d797f075da3fa8721060e487d12e0590132 GIT binary patch literal 81 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE2}WKZgOQ`Dt;Mm6 SV{Qz0W5Jyi26l)f0|Nl`>kKRa literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 b/lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 new file mode 100644 index 0000000000000000000000000000000000000000..0af0a7d69fb7a45a4431c6a877986a9f4e047985 GIT binary patch literal 60 xcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEW~ewb0{{zt25bNT literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 b/lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 new file mode 100644 index 0000000000000000000000000000000000000000..27dec296578227b65d51201d74c408d7c0939565 GIT binary patch literal 79 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERwfV)<^Y8`*dU@% F5&)PM2X_Df literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 b/lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 new file mode 100644 index 0000000000000000000000000000000000000000..d528c3f1a74a354e900558fff3b09048267a9699 GIT binary patch literal 73 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE0Y+9JgR!Zt#j%TH OZVY#0!JQNa_FVv&01VCm literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee b/lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee new file mode 100644 index 0000000000000000000000000000000000000000..ac58d53e2337408d7435570b0fd7efedd9d69cab GIT binary patch literal 102 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V{==JV;9HV a81BY`J1Gq8KzU}6JV+5xH3u7r0|x*J01YSr literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 b/lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 new file mode 100644 index 0000000000000000000000000000000000000000..317ead97b86f0b36e067a77e04d33b8dc001d69b GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUxgSDxx#j%TH VZVY#0!JQNac91+soEa#-4*)J`4sHMd literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a b/lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a new file mode 100644 index 0000000000000000000000000000000000000000..ff4af29f08dba0145e1769be7e4097416656857f GIT binary patch literal 63 xcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIExOfAQ4*($U2Jip? literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 b/lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 new file mode 100644 index 0000000000000000000000000000000000000000..2019289459d3b7b0820bb8ee078438cd40af49d8 GIT binary patch literal 72 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEeol}CV^dp;V;9HV M81BY`J1Gq80E@5;8UO$Q literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc b/lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc new file mode 100644 index 0000000000000000000000000000000000000000..1514db50c4f10572cf234f0b795f99c71a567e12 GIT binary patch literal 115 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEByk321_pjcR-iaz gQ(KE;7suQf?#6;UDGcl=+8LODx;a3)K}IkE07Co>LjV8( literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 b/lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 new file mode 100644 index 0000000000000000000000000000000000000000..45db5f02181716d12bdb91c9f0799c82edf51fd0 GIT binary patch literal 65 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIE4mL3e1404-EeZyP literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e b/lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e new file mode 100644 index 0000000000000000000000000000000000000000..76f3f051d2cffd01c51149dfc310180664285239 GIT binary patch literal 72 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenwUhqp7XMv5RAF M40mI}ofHOk0E-L^5&!@I literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 b/lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 new file mode 100644 index 0000000000000000000000000000000000000000..f7cf1d7a2845e12a870a305d4256f774ec016678 GIT binary patch literal 90 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIERz?tmv8k=av5SF$ Yl@lb&$T2sDyAi|#X<{t6lfu9b05{4ECjbBd literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 b/lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 new file mode 100644 index 0000000000000000000000000000000000000000..14891ed8b95c6e1d3603aab1767be6776aa3114e GIT binary patch literal 85 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEelX40)YjtI#W6RA UyRqO-3IjVxo)xHo87RIF04+}rWdHyG literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 b/lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 new file mode 100644 index 0000000000000000000000000000000000000000..95f5a2ebcd6de415984099f7675a5e2b96b5c79e GIT binary patch literal 91 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEB}P^tgR!Zt#j%TH gZVY#0!JQNab_TA};?yGD#Prm>61EJZ;)2v<0ImKKb^rhX literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 b/lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 new file mode 100644 index 0000000000000000000000000000000000000000..8b31bfd2fa20f4102df277446450eefd2c06de58 GIT binary patch literal 3606 zcmWFt@>I}L@CXSB&^OXE;N{}w3ibt&3=C{63}67H{{IKESQsIEenyZ4V^dp;V;9HV z81BY`J1Gq8KzU}6JV+4-1Mdz_up)={Pg|y4_Fqu{f34K#=jZ3!)%+Z#MuT896^v$t g(XwE)I2^4JMr*^-+HkZs9IXvUYs1mna1hmo080bT!TI}L@CXSB&^OXE;N{}w3ibt&3=BLh3}CNhq*s$s6-e>|F*koxOPgcYf+w=Vb2)dMVBimC*3#FS hP}RFC^U|y%2f_u!nXZ2*1S(`<1QI~N0W%-M1poln7&`y} literal 0 HcmV?d00001 diff --git a/lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 b/lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 new file mode 100644 index 0000000000000000000000000000000000000000..6f9f9ac215de171296eb461b94099685412236b5 GIT binary patch literal 5070 zcmaLO|0`W_9LMqFBExQXk|YV4gzWl~BuSD?5_ah(NrrTjBuOSoHCP|V> zb_pR#=7;?Q`vdkL*g0;~UC;LV)cKtE`}yco`>rx6Bk65@eXTS1O}0J49^tCCZo_yP zVmzH-^KLiHuqk(YxB07 zU!=nu;(hO0`B?ht%ddp!xsnjy5VVWisY6nSrH)7)mFkr`CUsougw#o?Q&Oj;&Ptt=IxlrW>Y~&o zsmoGVq^?R`le#W-L+Yl~Eveg5ccku0-IKa6^+4*O)FY|KQhicSrJhN>ka{WgTI!9| zTd8+a@1;KcM4r88wR zWidIKa+va%@|g;l3Ym(SikV87N}0--%9$#dDw(R7s+nq->X=+i4NPvPMy4jFW~LUV zR;D(l4yH~f4^tOYH&YK&FH;{=KhpryAkz@jFw+RrD3h0IjA@){f@zXzifNi@mT8V@ zo@s$;k!gu(nQ4V-m1&J>ooRzmuZh_pXq?(Sv}fra}X$>pAe4EMPV`3*_yEUhKA(@v+=INaM0D`BPGG`+}X zQI@TseNVK=G949j_G~F{j^>)oL~Kom`aN*N_uSO4# zPsh)nzdPF>!FVQ7yO1=Jm%HI;@cllWTx`YppT&&~>+^K^|=H-`Eg9 z009ILKmY**5J12a0XGrEBmz&?ooOO~00IagfB*srAYcXDL~uzW;K4uu0R#|0009IL zK)@>jHxXQs2)tT-CW`<92q1s}0tg_0fSU-eNd!C+2q1s}0tg_000IbjCEzB4TM~g+ ntIuTrRY1zGf%)4&$hZ6mAb +#include + +extern "C" { +#include +#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/lib/nghttp2/fuzz/fuzz_target.cc b/lib/nghttp2/fuzz/fuzz_target.cc new file mode 100644 index 00000000000..4adc5ed658f --- /dev/null +++ b/lib/nghttp2/fuzz/fuzz_target.cc @@ -0,0 +1,79 @@ +#include + +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/lib/nghttp2/fuzz/fuzz_target_fdp.cc b/lib/nghttp2/fuzz/fuzz_target_fdp.cc new file mode 100644 index 00000000000..f94b96433d2 --- /dev/null +++ b/lib/nghttp2/fuzz/fuzz_target_fdp.cc @@ -0,0 +1,99 @@ +#include +#include +#include + +#include + +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 d = data_provider.ConsumeRemainingBytes(); + nghttp2_session_mem_recv(session, d.data(), d.size()); + + send_pending(session); + + nghttp2_session_del(session); + + free(iv); + + return 0; +} diff --git a/lib/nghttp2/genauthoritychartbl.py b/lib/nghttp2/genauthoritychartbl.py new file mode 100755 index 00000000000..1b100283d36 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/gendowncasetbl.py b/lib/nghttp2/gendowncasetbl.py new file mode 100755 index 00000000000..04aeda75366 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genheaderfunc.py b/lib/nghttp2/genheaderfunc.py new file mode 100755 index 00000000000..2ac3c373415 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genlibtokenlookup.py b/lib/nghttp2/genlibtokenlookup.py new file mode 100755 index 00000000000..d15d0b500c4 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genmethodchartbl.py b/lib/nghttp2/genmethodchartbl.py new file mode 100755 index 00000000000..a0c3a378e38 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genmethodfunc.py b/lib/nghttp2/genmethodfunc.py new file mode 100755 index 00000000000..436a77a8409 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/gennghttpxfun.py b/lib/nghttp2/gennghttpxfun.py new file mode 100755 index 00000000000..ffd253f019f --- /dev/null +++ b/lib/nghttp2/gennghttpxfun.py @@ -0,0 +1,241 @@ +#!/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", +] + +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/lib/nghttp2/gennmchartbl.py b/lib/nghttp2/gennmchartbl.py new file mode 100755 index 00000000000..b75b9a6732a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genpathchartbl.py b/lib/nghttp2/genpathchartbl.py new file mode 100755 index 00000000000..cd8c7144932 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/gentokenlookup.py b/lib/nghttp2/gentokenlookup.py new file mode 100644 index 00000000000..82778580cc5 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/genvchartbl.py b/lib/nghttp2/genvchartbl.py new file mode 100755 index 00000000000..a4ff8d1feb0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/git-clang-format b/lib/nghttp2/git-clang-format new file mode 100755 index 00000000000..6a0db27fa9f --- /dev/null +++ b/lib/nghttp2/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] [] [--] [...]' + +desc = ''' +Run clang-format on all lines that differ between the working directory +and , 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 was present. + # However, to print pretty messages, we make use of metavar and help. + p.add_argument('args', nargs='*', metavar='', + help='revision from which to compute the diff') + p.add_argument('ignored', nargs='*', metavar='...', + 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 " ". 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/lib/nghttp2/go.mod b/lib/nghttp2/go.mod new file mode 100644 index 00000000000..a6a6a6a7721 --- /dev/null +++ b/lib/nghttp2/go.mod @@ -0,0 +1,26 @@ +module github.com/nghttp2/nghttp2 + +go 1.20 + +require ( + github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 + github.com/quic-go/quic-go v0.35.1 + github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 + golang.org/x/net v0.18.0 +) + +require ( + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.6.0 // indirect +) diff --git a/lib/nghttp2/go.sum b/lib/nghttp2/go.sum new file mode 100644 index 00000000000..a91f2789154 --- /dev/null +++ b/lib/nghttp2/go.sum @@ -0,0 +1,78 @@ +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 h1:wAIE/kN63Oig1DdOzN7O+k4AbFh2cCJoKMFXrwRJtzk= +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +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-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +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.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +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/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= +github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 h1:ccVm9C6f5YMcVv6t9MXahIDkqVvzD6vklkJTIE4D2nY= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90/go.mod h1:YZhsh86DfZgAShPKeg1eBLVrmuQxWcR9H4TdpgNvSnw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/lib/nghttp2/help2rst.py b/lib/nghttp2/help2rst.py new file mode 100755 index 00000000000..245f669a349 --- /dev/null +++ b/lib/nghttp2/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 >: + # + # + # + # 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 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/lib/nghttp2/integration-tests/.gitignore b/lib/nghttp2/integration-tests/.gitignore new file mode 100644 index 00000000000..f40c109af7d --- /dev/null +++ b/lib/nghttp2/integration-tests/.gitignore @@ -0,0 +1,3 @@ +# generated files +config.go +setenv diff --git a/lib/nghttp2/integration-tests/CMakeLists.txt b/lib/nghttp2/integration-tests/CMakeLists.txt new file mode 100644 index 00000000000..cc92e9fdc7d --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/Makefile.am b/lib/nghttp2/integration-tests/Makefile.am new file mode 100644 index 00000000000..21166eda498 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/alt-server.crt b/lib/nghttp2/integration-tests/alt-server.crt new file mode 100644 index 00000000000..f003eb1481a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/config.go.in b/lib/nghttp2/integration-tests/config.go.in new file mode 100644 index 00000000000..3d79297e144 --- /dev/null +++ b/lib/nghttp2/integration-tests/config.go.in @@ -0,0 +1,6 @@ +package nghttp2 + +const ( + buildDir = "@top_builddir@" + sourceDir = "@top_srcdir@" +) diff --git a/lib/nghttp2/integration-tests/nghttpx_http1_test.go b/lib/nghttp2/integration-tests/nghttpx_http1_test.go new file mode 100644 index 00000000000..740396d8f8e --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/nghttpx_http2_test.go b/lib/nghttp2/integration-tests/nghttpx_http2_test.go new file mode 100644 index 00000000000..5324a18c525 --- /dev/null +++ b/lib/nghttp2/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", "; rel=preload, , ; 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/lib/nghttp2/integration-tests/nghttpx_http3_test.go b/lib/nghttp2/integration-tests/nghttpx_http3_test.go new file mode 100644 index 00000000000..9ea85d7447c --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/req-return.rb b/lib/nghttp2/integration-tests/req-return.rb new file mode 100644 index 00000000000..51d315efe35 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/req-set-header.rb b/lib/nghttp2/integration-tests/req-set-header.rb new file mode 100644 index 00000000000..986f128a7f8 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/resp-return.rb b/lib/nghttp2/integration-tests/resp-return.rb new file mode 100644 index 00000000000..fbbd775c332 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/resp-set-header.rb b/lib/nghttp2/integration-tests/resp-set-header.rb new file mode 100644 index 00000000000..228837a0ec9 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/server.crt b/lib/nghttp2/integration-tests/server.crt new file mode 100644 index 00000000000..c50fdaa1341 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/server_tester.go b/lib/nghttp2/integration-tests/server_tester.go new file mode 100644 index 00000000000..6a4ce650d7c --- /dev/null +++ b/lib/nghttp2/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 sotres pushed request header + pushResponse []*serverResponse // pushed response +} + +type ByStreamID []*serverResponse + +func (b ByStreamID) Len() int { + return len(b) +} + +func (b ByStreamID) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b ByStreamID) Less(i, j int) bool { + return b[i].streamID < b[j].streamID +} + +func cloneHeader(h http.Header) http.Header { + h2 := make(http.Header, len(h)) + for k, vv := range h { + vv2 := make([]string, len(vv)) + copy(vv2, vv) + h2[k] = vv2 + } + return h2 +} + +func noopHandler(w http.ResponseWriter, r *http.Request) { + 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/lib/nghttp2/integration-tests/server_tester_http3.go b/lib/nghttp2/integration-tests/server_tester_http3.go new file mode 100644 index 00000000000..7bbc4e74c02 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/integration-tests/setenv.in b/lib/nghttp2/integration-tests/setenv.in new file mode 100644 index 00000000000..7177200e9e0 --- /dev/null +++ b/lib/nghttp2/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/nghttp2/lib/.gitignore b/lib/nghttp2/lib/.gitignore new file mode 100644 index 00000000000..f64f46bea3e --- /dev/null +++ b/lib/nghttp2/lib/.gitignore @@ -0,0 +1,3 @@ +# generated files +includes/nghttp2/nghttp2ver.h +libnghttp2.pc diff --git a/lib/nghttp2/lib/CMakeLists.txt b/lib/nghttp2/lib/CMakeLists.txt new file mode 100644 index 00000000000..7adba3a3ffa --- /dev/null +++ b/lib/nghttp2/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_npn.c + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c + nghttp2_version.c + nghttp2_priority_spec.c + nghttp2_option.c + nghttp2_callbacks.c + nghttp2_mem.c + 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/nghttp2/lib/Makefile.am b/lib/nghttp2/lib/Makefile.am new file mode 100644 index 00000000000..c3ace4029a6 --- /dev/null +++ b/lib/nghttp2/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_npn.c \ + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + 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_npn.h \ + nghttp2_submit.h nghttp2_outbound_item.h \ + nghttp2_net.h \ + nghttp2_hd.h nghttp2_hd_huffman.h \ + nghttp2_priority_spec.h \ + nghttp2_option.h \ + nghttp2_callbacks.h \ + nghttp2_mem.h \ + 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/nghttp2/lib/Makefile.msvc b/lib/nghttp2/lib/Makefile.msvc new file mode 100644 index 00000000000..611b39d0b1d --- /dev/null +++ b/lib/nghttp2/lib/Makefile.msvc @@ -0,0 +1,254 @@ +# +# GNU Makefile for nghttp2 / MSVC. +# +# By G. Vanem 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_npn.c \ + nghttp2_hd.c \ + nghttp2_hd_huffman.c \ + nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + 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 + + 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/nghttp2/lib/includes/CMakeLists.txt b/lib/nghttp2/lib/includes/CMakeLists.txt new file mode 100644 index 00000000000..17de2ec691e --- /dev/null +++ b/lib/nghttp2/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/nghttp2/lib/includes/Makefile.am b/lib/nghttp2/lib/includes/Makefile.am new file mode 100644 index 00000000000..c07cb4d2c02 --- /dev/null +++ b/lib/nghttp2/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/nghttp2/lib/includes/nghttp2/nghttp2.h b/lib/nghttp2/lib/includes/nghttp2/nghttp2.h new file mode 100644 index 00000000000..66ea3c63c1d --- /dev/null +++ b/lib/nghttp2/lib/includes/nghttp2/nghttp2.h @@ -0,0 +1,5881 @@ +/* + * 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 +#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 +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include +#include + +#include + +#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 `_. 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 + * `_. + */ + NGHTTP2_ALTSVC = 0x0a, + /** + * The ORIGIN frame, which is defined by `RFC 8336 + * `_. + */ + 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 `_) + */ + 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 ot 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 + * `_. 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 `_. + * + * 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 + * `_. + * + * 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 `_. + * + * 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 ` 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 + * + * A helper function for dealing with NPN in client side or ALPN in + * server side. The |in| contains peer's protocol list in preferable + * order. The format of |in| is length-prefixed and not + * null-terminated. For example, ``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 + * + * See http://technotes.googlecode.com/git/nextprotoneg.html for more + * details about NPN. + * + * For NPN, to use this method you should do something like:: + * + * static int select_next_proto_cb(SSL* ssl, + * unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_next_protocol(out, outlen, in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * Returns a pointer to a nghttp2_info struct with version information + * about the run-time library in use. The |least_version| argument + * can be set to a 24 bit numerical value for the least accepted + * version number and if the condition is not met, this function will + * return a ``NULL``. Pass in 0 to skip the version checking. + */ +NGHTTP2_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/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in b/lib/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in new file mode 100644 index 00000000000..7717a647f75 --- /dev/null +++ b/lib/nghttp2/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/nghttp2/lib/libnghttp2.pc.in b/lib/nghttp2/lib/libnghttp2.pc.in new file mode 100644 index 00000000000..da6938f2847 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_buf.c b/lib/nghttp2/lib/nghttp2_buf.c new file mode 100644 index 00000000000..a32844712e4 --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2_buf.h b/lib/nghttp2/lib/nghttp2_buf.h new file mode 100644 index 00000000000..45f62f16e27 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2_callbacks.c b/lib/nghttp2/lib/nghttp2_callbacks.c new file mode 100644 index 00000000000..3c38214859b --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2_callbacks.h b/lib/nghttp2/lib/nghttp2_callbacks.h new file mode 100644 index 00000000000..61e51fa5363 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * 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/lib/nghttp2_debug.c b/lib/nghttp2/lib/nghttp2_debug.c new file mode 100644 index 00000000000..cb2779700bd --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2_debug.h b/lib/nghttp2/lib/nghttp2_debug.h new file mode 100644 index 00000000000..cbb4dd57547 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2_extpri.c b/lib/nghttp2/lib/nghttp2_extpri.c new file mode 100644 index 00000000000..ba0263e7c8e --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_extpri.h b/lib/nghttp2/lib/nghttp2_extpri.h new file mode 100644 index 00000000000..23c6ddc0c05 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * 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/lib/nghttp2_frame.c b/lib/nghttp2/lib/nghttp2_frame.c new file mode 100644 index 00000000000..77cb463df54 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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/lib/nghttp2_frame.h b/lib/nghttp2/lib/nghttp2_frame.h new file mode 100644 index 00000000000..d58668806c4 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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/lib/nghttp2_hd.c b/lib/nghttp2/lib/nghttp2_hd.c new file mode 100644 index 00000000000..8a2bda64c1f --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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/lib/nghttp2_hd.h b/lib/nghttp2/lib/nghttp2_hd.h new file mode 100644 index 00000000000..6de0052aaea --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2_hd_huffman.c b/lib/nghttp2/lib/nghttp2_hd_huffman.c new file mode 100644 index 00000000000..ac90f49c44f --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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/lib/nghttp2_hd_huffman.h b/lib/nghttp2/lib/nghttp2_hd_huffman.h new file mode 100644 index 00000000000..2bfd5318165 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +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/lib/nghttp2_hd_huffman_data.c b/lib/nghttp2/lib/nghttp2_hd_huffman_data.c new file mode 100644 index 00000000000..2e2e13f7bee --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_helper.c b/lib/nghttp2/lib/nghttp2_helper.c new file mode 100644 index 00000000000..93dd4754b7f --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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/lib/nghttp2_helper.h b/lib/nghttp2/lib/nghttp2_helper.h new file mode 100644 index 00000000000..b1f18ce541a --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#include +#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/lib/nghttp2_http.c b/lib/nghttp2/lib/nghttp2_http.c new file mode 100644 index 00000000000..ecdeb21ddb6 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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/lib/nghttp2_http.h b/lib/nghttp2/lib/nghttp2_http.h new file mode 100644 index 00000000000..d9992fe6908 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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/lib/nghttp2_int.h b/lib/nghttp2/lib/nghttp2_int.h new file mode 100644 index 00000000000..b23585ccb27 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* 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/lib/nghttp2_map.c b/lib/nghttp2/lib/nghttp2_map.c new file mode 100644 index 00000000000..0aaaf29155c --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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 \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/lib/nghttp2_map.h b/lib/nghttp2/lib/nghttp2_map.h new file mode 100644 index 00000000000..236d28296e3 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2_mem.c b/lib/nghttp2/lib/nghttp2_mem.c new file mode 100644 index 00000000000..6a449cffd70 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_mem.h b/lib/nghttp2/lib/nghttp2_mem.h new file mode 100644 index 00000000000..f83dbcb8f9a --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* 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/lib/nghttp2_net.h b/lib/nghttp2/lib/nghttp2_net.h new file mode 100644 index 00000000000..521f98143e4 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_ARPA_INET_H +# include +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ + +#include + +#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/lib/nghttp2_npn.c b/lib/nghttp2/lib/nghttp2_npn.c new file mode 100644 index 00000000000..d1384c80758 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_npn.c @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_npn.h" + +#include + +static int select_next_protocol(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const char *key, unsigned int keylen) { + unsigned int i; + for (i = 0; i + keylen <= inlen; i += (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_next_protocol(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN, + NGHTTP2_PROTO_ALPN_LEN) == 0) { + return 1; + } + if (select_next_protocol(out, outlen, in, inlen, NGHTTP2_HTTP_1_1_ALPN, + NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) { + return 0; + } + return -1; +} diff --git a/lib/nghttp2/lib/nghttp2_npn.h b/lib/nghttp2/lib/nghttp2_npn.h new file mode 100644 index 00000000000..c6f1c04b683 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_npn.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_NPN_H +#define NGHTTP2_NPN_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_NPN_H */ diff --git a/lib/nghttp2/lib/nghttp2_option.c b/lib/nghttp2/lib/nghttp2_option.c new file mode 100644 index 00000000000..43d4e952291 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_option.h b/lib/nghttp2/lib/nghttp2_option.h new file mode 100644 index 00000000000..2259e1849d8 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/** + * 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/lib/nghttp2_outbound_item.c b/lib/nghttp2/lib/nghttp2_outbound_item.c new file mode 100644 index 00000000000..2a3041db195 --- /dev/null +++ b/lib/nghttp2/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 +#include + +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/lib/nghttp2_outbound_item.h b/lib/nghttp2/lib/nghttp2_outbound_item.h new file mode 100644 index 00000000000..bd4611b551b --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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/lib/nghttp2_pq.c b/lib/nghttp2/lib/nghttp2_pq.c new file mode 100644 index 00000000000..64353acc961 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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/lib/nghttp2_pq.h b/lib/nghttp2/lib/nghttp2_pq.h new file mode 100644 index 00000000000..c8d90ef2cc8 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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/lib/nghttp2_priority_spec.c b/lib/nghttp2/lib/nghttp2_priority_spec.c new file mode 100644 index 00000000000..c2196e30630 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_priority_spec.h b/lib/nghttp2/lib/nghttp2_priority_spec.h new file mode 100644 index 00000000000..92ece822a8f --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * 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/lib/nghttp2_queue.c b/lib/nghttp2/lib/nghttp2_queue.c new file mode 100644 index 00000000000..055eb69c7e5 --- /dev/null +++ b/lib/nghttp2/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 +#include + +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/lib/nghttp2_queue.h b/lib/nghttp2/lib/nghttp2_queue.h new file mode 100644 index 00000000000..a06fa6c7a46 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2_ratelim.c b/lib/nghttp2/lib/nghttp2_ratelim.c new file mode 100644 index 00000000000..7011655b006 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2_ratelim.h b/lib/nghttp2/lib/nghttp2_ratelim.h new file mode 100644 index 00000000000..866ed3f00ae --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +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/lib/nghttp2_rcbuf.c b/lib/nghttp2/lib/nghttp2_rcbuf.c new file mode 100644 index 00000000000..7e7814d2d3c --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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/lib/nghttp2_rcbuf.h b/lib/nghttp2/lib/nghttp2_rcbuf.h new file mode 100644 index 00000000000..6814e709fb4 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +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/lib/nghttp2_session.c b/lib/nghttp2/lib/nghttp2_session.c new file mode 100644 index 00000000000..c0d86026ae2 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include + +#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 terminiate + connection. */ + rv = nghttp2_session_terminate_session(session, + NGHTTP2_INTERNAL_ERROR); + } + if (nghttp2_is_fatal(rv)) { + return rv; + } + break; + } + + aob->item = item; + + nghttp2_bufs_rewind(framebufs); + + if (item->frame.hd.type != NGHTTP2_DATA) { + nghttp2_frame *frame; + + frame = &item->frame; + + DEBUGF("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/lib/nghttp2_session.h b/lib/nghttp2/lib/nghttp2_session.h new file mode 100644 index 00000000000..b119329a04d --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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 /* */ 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/lib/nghttp2_stream.c b/lib/nghttp2/lib/nghttp2_stream.c new file mode 100644 index 00000000000..f1951f879d7 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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/lib/nghttp2_stream.h b/lib/nghttp2/lib/nghttp2_stream.h new file mode 100644 index 00000000000..71b9fb1140c --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#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/lib/nghttp2_submit.c b/lib/nghttp2/lib/nghttp2_submit.c new file mode 100644 index 00000000000..f5554eb5649 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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(©_pri_spec); + } else { + nghttp2_priority_spec_default_init(©_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, ©_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(©_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, ©_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/lib/nghttp2_submit.h b/lib/nghttp2/lib/nghttp2_submit.h new file mode 100644 index 00000000000..74d702fbcf0 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_SUBMIT_H */ diff --git a/lib/nghttp2/lib/nghttp2_time.c b/lib/nghttp2/lib/nghttp2_time.c new file mode 100644 index 00000000000..bdc83d2ec7f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_time.c @@ -0,0 +1,65 @@ +/* + * 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_TIME_H +# include +#endif /* HAVE_TIME_H */ + +#ifdef HAVE_WINDOWS_H +# include +#endif /* HAVE_WINDOWS_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/lib/nghttp2_time.h b/lib/nghttp2/lib/nghttp2_time.h new file mode 100644 index 00000000000..03c0bbe944e --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +/* 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/lib/nghttp2_version.c b/lib/nghttp2/lib/nghttp2_version.c new file mode 100644 index 00000000000..4211f2cf8f6 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +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/nghttp2/lib/sfparse.c b/lib/nghttp2/lib/sfparse.c new file mode 100644 index 00000000000..efa2850c9d6 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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/nghttp2/lib/sfparse.h b/lib/nghttp2/lib/sfparse.h new file mode 100644 index 00000000000..1474db1429a --- /dev/null +++ b/lib/nghttp2/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 +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include +#include + +/** + * @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_* `. + */ + 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 ` == 0, :member:`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 ` 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 + * ` == 0. + * + * This function sets the length of unescaped string to + * :member:`dest->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 ` must point to the buffer that + * has sufficient space to store the decoded byte string. + * + * If :member:`src->len ` == 0, |*src| is assigned to + * |*dest|. + * + * This function sets the length of decoded byte string to + * :member:`dest->len `. + */ +void sf_base64decode(sf_vec *dest, const sf_vec *src); + +#ifdef __cplusplus +} +#endif + +#endif /* SFPARSE_H */ diff --git a/lib/nghttp2/lib/version.rc.in b/lib/nghttp2/lib/version.rc.in new file mode 100644 index 00000000000..4edfa7a49f9 --- /dev/null +++ b/lib/nghttp2/lib/version.rc.in @@ -0,0 +1,40 @@ +#include + +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/lib/nghttp2/m4/ax_check_compile_flag.m4 b/lib/nghttp2/m4/ax_check_compile_flag.m4 new file mode 100644 index 00000000000..ca3639715e7 --- /dev/null +++ b/lib/nghttp2/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 +# Copyright (c) 2011 Maarten Bosmans +# +# 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 . +# +# 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/lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 b/lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 00000000000..8edf5152ec7 --- /dev/null +++ b/lib/nghttp2/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 +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp +# +# 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 + 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 single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> 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 + { + 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::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::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 + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::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 + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(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 + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::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 +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + 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, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template 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 + 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 + 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 + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + 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 + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template 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 + +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/lib/nghttp2/m4/libxml2.m4 b/lib/nghttp2/m4/libxml2.m4 new file mode 100644 index 00000000000..68cd8242fbc --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +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, µ) != 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 +#include +], [ 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/lib/nghttp2/makebashcompletion b/lib/nghttp2/makebashcompletion new file mode 100755 index 00000000000..9e88d9d4b6d --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/makemanpages b/lib/nghttp2/makemanpages new file mode 100755 index 00000000000..0b7c54ea4dd --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/makerelease.sh b/lib/nghttp2/makerelease.sh new file mode 100755 index 00000000000..b65e08f57f4 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/mkcipherlist.py b/lib/nghttp2/mkcipherlist.py new file mode 100755 index 00000000000..e366f5e5bf8 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/mkhufftbl.py b/lib/nghttp2/mkhufftbl.py new file mode 100755 index 00000000000..a9f10d751e3 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/mkstatichdtbl.py b/lib/nghttp2/mkstatichdtbl.py new file mode 100755 index 00000000000..20f0c153955 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/nghttp2-master.zip b/lib/nghttp2/nghttp2-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..4b9bea3090354e2b3f1092dc3588292d96a9a0fe GIT binary patch literal 1445803 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

-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 xN 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ð_1Y};YMzYIdx>(1&5nO+ig?cyTp;ofGp0-8*b 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%0D~ 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 z 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

OmF}D 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⋘oK1v<*zn8Xv;K41;)do8lOYN5rDT((qF31` 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 $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 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 + 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 + 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/../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 + + 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] + 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 + + 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: 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: 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 + 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 + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3' + 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/lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml b/lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml new file mode 100644 index 00000000000..720b25fff01 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.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/lib/nghttp2/nghttp2-master/.gitignore b/lib/nghttp2/nghttp2-master/.gitignore new file mode 100644 index 00000000000..8c089da4031 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.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/lib/nghttp2/nghttp2-master/.gitmodules b/lib/nghttp2/nghttp2-master/.gitmodules new file mode 100644 index 00000000000..393c80881d4 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.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/lib/nghttp2/nghttpx.conf.sample b/lib/nghttp2/nghttpx.conf.sample new file mode 100644 index 00000000000..97d79543910 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/pre-commit b/lib/nghttp2/pre-commit new file mode 100755 index 00000000000..1dfe1f548be --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/proxy.pac.sample b/lib/nghttp2/proxy.pac.sample new file mode 100644 index 00000000000..9283920b7f0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/releasechk b/lib/nghttp2/releasechk new file mode 100755 index 00000000000..0c05cca6895 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/script/CMakeLists.txt b/lib/nghttp2/script/CMakeLists.txt new file mode 100644 index 00000000000..f89885857fe --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/script/Makefile.am b/lib/nghttp2/script/Makefile.am new file mode 100644 index 00000000000..387f33c5e96 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/script/README.rst b/lib/nghttp2/script/README.rst new file mode 100644 index 00000000000..a9f773771ec --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/script/fetch-ocsp-response b/lib/nghttp2/script/fetch-ocsp-response new file mode 100755 index 00000000000..0ff7461ee2e --- /dev/null +++ b/lib/nghttp2/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 exitted 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/lib/nghttp2/src/.gitignore b/lib/nghttp2/src/.gitignore new file mode 100644 index 00000000000..27631a77d6a --- /dev/null +++ b/lib/nghttp2/src/.gitignore @@ -0,0 +1,13 @@ +# programs +deflatehd +h2load +inflatehd +nghttp +nghttpd +nghttpx + +# build +libnghttpx.a + +# tests +nghttpx-unittest diff --git a/lib/nghttp2/src/CMakeLists.txt b/lib/nghttp2/src/CMakeLists.txt new file mode 100644 index 00000000000..201c5a2d511 --- /dev/null +++ b/lib/nghttp2/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_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} $ + $ + ) + add_executable(nghttpd ${NGHTTPD_SOURCES} $ + $ + ) + add_executable(nghttpx ${NGHTTPX-bin_SOURCES} $ + $ + ) + target_compile_definitions(nghttpx PRIVATE + "-DPKGDATADIR=\"${PKGDATADIR}\"" + "-DPKGLIBDIR=\"${PKGLIBDIR}\"" + ) + target_link_libraries(nghttpx nghttpx_static) + add_executable(h2load ${H2LOAD_SOURCES} $ + $ + ) + + 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/lib/nghttp2/src/HtmlParser.cc b/lib/nghttp2/src/HtmlParser.cc new file mode 100644 index 00000000000..591c4c7e020 --- /dev/null +++ b/lib/nghttp2/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 + +#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( + attrs[0]))}, + name)) { + return StringRef{attrs[1], + strlen(reinterpret_cast(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(uri.c_str()), + reinterpret_cast(parser_data->base_uri.c_str())); + if (u) { + parser_data->links.push_back( + std::make_pair(reinterpret_cast(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(user_data); + auto name = + StringRef{src_name, strlen(reinterpret_cast(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(user_data); + if (util::strieq_l( + "head", + StringRef{name, strlen(reinterpret_cast(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> & +HtmlParser::get_links() const { + return parser_data_.links; +} + +void HtmlParser::clear_links() { parser_data_.links.clear(); } + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/HtmlParser.h b/lib/nghttp2/src/HtmlParser.h new file mode 100644 index 00000000000..1e846882cd0 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#ifdef HAVE_LIBXML2 + +# include + +#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> 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> &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> &get_links() const { + return links_; + } + void clear_links() {} + +private: + std::vector> links_; +}; + +#endif // !HAVE_LIBXML2 + +} // namespace nghttp2 + +#endif // HTML_PARSER_H diff --git a/lib/nghttp2/src/HttpServer.cc b/lib/nghttp2/src/HttpServer.cc new file mode 100644 index 00000000000..23cba25d804 --- /dev/null +++ b/lib/nghttp2/src/HttpServer.cc @@ -0,0 +1,2286 @@ +/* + * 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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H + +#include +#include +#include +#include +#include +#include + +#include "ssl_compat.h" + +#include +#include +#if OPENSSL_3_0_0_API +# include +#endif // OPENSSL_3_0_0_API + +#include + +#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(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::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(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(ent)); +#else // !HAVE_STD_MAP_EMPLACE + // for gcc-4.7 + auto rv = fd_cache_.insert( + std::make_pair(path, std::make_unique(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 handlers_; + // cache for file descriptors to read file. + std::multimap> fd_cache_; + DList 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(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(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(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(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(datalen)) { + data_pending_ = data + n; + data_pendinglen_ = datalen - n; + break; + } + } + return 0; +} + +int Http2Handler::read_clear() { + int rv; + std::array 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_npn_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 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 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_npn_result() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + // Check the negotiated protocol in NPN or ALPN +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG + for (int i = 0; i < 2; ++i) { + 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; + } + break; + } else { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len); +#else // OPENSSL_VERSION_NUMBER < 0x10002000L + break; +#endif // OPENSSL_VERSION_NUMBER < 0x10002000L + } + } + if (sessions_->get_config()->verbose) { + std::cerr << "Client did not advertise HTTP/2 protocol." + << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" + << std::endl; + } + return -1; +} + +int Http2Handler::submit_file_response(const 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(); + 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(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) { + 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(user_data); + auto stream = hd->get_stream(stream_id); + + auto nread = std::min(stream->body_length - stream->body_offset, + static_cast(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 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(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(user_data); + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto stream = std::make_unique(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(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(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(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(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(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(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; + ev_async w; + // protects q + std::mutex m; + std::deque q; +}; + +namespace { +void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) { + auto worker = static_cast(w->data); + auto &sessions = worker->sessions; + + std::deque q; + { + std::lock_guard 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(); + auto loop = ev_loop_new(get_ev_loop_flags()); + worker->sessions = std::make_unique(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 lock(worker->m); + worker->q.push_back({fd}); + } + ev_async_send(worker->sessions->get_loop(), &worker->w); + } + +private: + std::vector> 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 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 acceptor_; + Sessions *sessions_; + int fd_; +}; + +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revents) { + auto handler = static_cast(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 = ""; + body += status_string; + body += ' '; + body += reason_pharase; + body += "

"; + body += status_string; + body += ' '; + body += reason_pharase; + body += "


"; + body += NGHTTPD_SERVER; + body += " at port "; + body += util::utos(port); + body += "
"; + body += ""; + + 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{ + {"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)}, + }; +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) { + auto next_proto = static_cast *>(arg); + *data = next_proto->data(); + *len = next_proto->size(); + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +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 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(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(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(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 + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto config = static_cast(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(&in[i + 1]), in[i]); + std::cout << std::endl; + } + } + if (!util::select_h2(out, outlen, in, inlen)) { + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +int HttpServer::run() { + SSL_CTX *ssl_ctx = nullptr; + std::vector 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 !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + 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; + } +# else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + std::cerr << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) +#endif // OPENSSL_NO_EC + + if (!config_->dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(config_->dh_param_file.c_str(), "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(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } + + auto loop = EV_DEFAULT; + + Sessions sessions(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); + 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/lib/nghttp2/src/HttpServer.h b/lib/nghttp2/src/HttpServer.h new file mode 100644 index 00000000000..f825b83e338 --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "http2.h" +#include "buffer.h" +#include "template.h" +#include "allocator.h" + +namespace nghttp2 { + +struct Config { + std::map> push; + std::map 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>::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_npn_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); + 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> id2stream_; + WriteBuf wb_; + std::function 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 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/lib/nghttp2/src/Makefile.am b/lib/nghttp2/src/Makefile.am new file mode 100644 index 00000000000..f112ac2cbcb --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/allocator.h b/lib/nghttp2/src/allocator.h new file mode 100644 index 00000000000..97b9a418182 --- /dev/null +++ b/lib/nghttp2/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 +#endif // !_WIN32 + +#include +#include + +#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(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(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(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(mb->begin); + *sp = len; + mb->last = mb->end; + return mb->begin + sizeof(size_t); + } + + if (!head || + head->end - head->last < static_cast(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(head->last); + *sp = size; + + head->last = reinterpret_cast( + (reinterpret_cast(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(static_cast(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(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(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 +StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) { + auto dst = static_cast(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 +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)...); +} + +// 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 +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)...); +} + +// Returns the string which is the concatenation of |args| in the +// given order. The resulting string will be NULL-terminated. +template +StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) { + size_t len = concat_string_ref_count(0, std::forward(args)...); + auto dst = static_cast(alloc.alloc(len + 1)); + auto p = dst; + p = concat_string_ref_copy(p, std::forward(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 +StringRef realloc_concat_string_ref(BlockAllocator &alloc, + const StringRef &value, Args &&...args) { + if (value.empty()) { + return concat_string_ref(alloc, std::forward(args)...); + } + + auto len = + value.size() + concat_string_ref_count(0, std::forward(args)...); + auto dst = static_cast( + alloc.realloc(const_cast(value.byte()), len + 1)); + auto p = dst + value.size(); + p = concat_string_ref_copy(p, std::forward(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 +ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) { + auto dst = static_cast(alloc.alloc(size)); + return {dst, size}; +} + +} // namespace nghttp2 + +#endif // ALLOCATOR_H diff --git a/lib/nghttp2/src/app_helper.cc b/lib/nghttp2/src/app_helper.cc new file mode 100644 index 00000000000..ef9276285a7 --- /dev/null +++ b/lib/nghttp2/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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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, "\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(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(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(frame->ext.payload); + print_frame_attr_indent(); + fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n", + static_cast(altsvc->origin_len), altsvc->origin, + static_cast(altsvc->field_value_len), altsvc->field_value); + break; + } + case NGHTTP2_ORIGIN: { + auto origin = static_cast(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(frame->ext.payload); + print_frame_attr_indent(); + fprintf(outfile, + "(prioritized_stream_id=%d, priority_field_value=[%.*s])\n", + priority_update->stream_id, + static_cast(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(name), const_cast(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/lib/nghttp2/src/app_helper.h b/lib/nghttp2/src/app_helper.h new file mode 100644 index 00000000000..5424054ffa0 --- /dev/null +++ b/lib/nghttp2/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 +#include +#ifdef HAVE_SYS_TIME_H +# include +#endif // HAVE_SYS_TIME_H +#include + +#include +#include + +#include + +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 +std::chrono::milliseconds time_delta(const TimePoint &a, const TimePoint &b) { + return std::chrono::duration_cast(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/lib/nghttp2/src/base64.h b/lib/nghttp2/src/base64.h new file mode 100644 index 00000000000..1bd51afa1dd --- /dev/null +++ b/lib/nghttp2/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 + +#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 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(*first++) << 16; + n += static_cast(*first++) << 8; + n += static_cast(*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(*first++) << 16; + n += static_cast(*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(*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 +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(*first++) << 16; + n += static_cast(*first++) << 8; + n += static_cast(*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(*first++) << 16; + n += static_cast(*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(*first++) << 16; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = '='; + *p++ = '='; + break; + } + } + return p; +} + +template +InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) { + for (; first != last; ++first) { + if (tbl[static_cast(*first)] != -1 || *first == '=') { + break; + } + } + return first; +} + +template +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(*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 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 +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/lib/nghttp2/src/base64_test.cc b/lib/nghttp2/src/base64_test.cc new file mode 100644 index 00000000000..4324bd744ca --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include + +#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/lib/nghttp2/src/base64_test.h b/lib/nghttp2/src/base64_test.h new file mode 100644 index 00000000000..8bdb84f8ec2 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/buffer.h b/lib/nghttp2/src/buffer.h new file mode 100644 index 00000000000..1921edf19a2 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +namespace nghttp2 { + +template 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(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 buf; + uint8_t *pos, *last; +}; + +} // namespace nghttp2 + +#endif // BUFFER_H diff --git a/lib/nghttp2/src/buffer_test.cc b/lib/nghttp2/src/buffer_test.cc new file mode 100644 index 00000000000..38688edc7ad --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include + +#include + +#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/lib/nghttp2/src/buffer_test.h b/lib/nghttp2/src/buffer_test.h new file mode 100644 index 00000000000..6789aa39bc5 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_buffer_write(void); + +} // namespace nghttp2 + +#endif // BUFFER_TEST_H diff --git a/lib/nghttp2/src/ca-config.json b/lib/nghttp2/src/ca-config.json new file mode 100644 index 00000000000..5788d0739b0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/ca.nghttp2.org-key.pem b/lib/nghttp2/src/ca.nghttp2.org-key.pem new file mode 100644 index 00000000000..6ce8707f4dd --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/ca.nghttp2.org.csr b/lib/nghttp2/src/ca.nghttp2.org.csr new file mode 100644 index 00000000000..37ee5600000 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/ca.nghttp2.org.csr.json b/lib/nghttp2/src/ca.nghttp2.org.csr.json new file mode 100644 index 00000000000..69d9c6a4d5e --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/ca.nghttp2.org.pem b/lib/nghttp2/src/ca.nghttp2.org.pem new file mode 100644 index 00000000000..e50acfc9a4f --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/comp_helper.c b/lib/nghttp2/src/comp_helper.c new file mode 100644 index 00000000000..98db08a487a --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/comp_helper.h b/lib/nghttp2/src/comp_helper.h new file mode 100644 index 00000000000..131ed21b637 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#include + +#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/lib/nghttp2/src/deflatehd.cc b/lib/nghttp2/src/deflatehd.cc new file mode 100644 index 00000000000..7dcfccffcf7 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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 &nva, int seq) { + auto hex = std::vector(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 &nva, size_t inputlen, + int seq) { + ssize_t rv; + std::array 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(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 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= + Set dynamic table size. In the HPACK + specification, this value is denoted by + SETTINGS_HEADER_TABLE_SIZE. + Default: 4096 + -S, --deflate-table-size= + 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/lib/nghttp2/src/h2load.cc b/lib/nghttp2/src/h2load.cc new file mode 100644 index 00000000000..1c0fbc3b7db --- /dev/null +++ b/lib/nghttp2/src/h2load.cc @@ -0,0 +1,3343 @@ +/* + * 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 +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +# 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 + +#if OPENSSL_1_1_1_API +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 +#endif // OPENSSL_1_1_1_API + +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 !npn_list.empty() && + (npn_list[0] == NGHTTP3_ALPN_H3 || npn_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(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(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(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(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(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(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(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( + round(std::chrono::duration(d).count() * config.rps)); + client->rps_req_pending += n; + client->rps_duration_started += + util::duration_from(static_cast(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(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(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(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(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) { +// libressl does not have SSL_get_server_tmp_key +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && defined(SSL_get_server_tmp_key) + 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 curve_name; + const char *cname; + if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(), + curve_name.size(), nullptr)) { + cname = ""; + } 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; + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} +} // namespace + +void Client::report_tls_info() { + if (worker->id == 0 && !worker->tls_info_report_done) { + worker->tls_info_report_done = true; + auto cipher = SSL_get_current_cipher(ssl); + std::cout << "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; + } + } + + 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( + req_stat->request_wall_time.time_since_epoch()); + auto delta = std::chrono::duration_cast( + req_stat->stream_close_time - req_stat->request_time); + + std::array 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(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; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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(this); + } else if (util::streq(NGHTTP2_H1_1, proto)) { + session = std::make_unique(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.npn_list) { + if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) { + std::cout + << "Server does not support NPN/ALPN. Falling back to HTTP/1.1." + << std::endl; + session = std::make_unique(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.npn_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(this); + selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID; + break; + case Config::PROTO_HTTP1_1: + session = std::make_unique(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(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(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 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(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; + msg.msg_name = const_cast(addr); + msg.msg_namelen = addrlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +# ifdef UDP_SEGMENT + std::array 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(1), req_todo / 10); + } else { + progress_interval = std::max(static_cast(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(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 +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(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 &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(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 &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::max(), + std::numeric_limits::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> &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 request_times; + request_times.reserve(nrequest_times); + + std::vector 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>( + 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>( + 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>( + cstat.connect_time - cstat.connect_start_time) + .count()); + + if (!recorded(cstat.ttfb)) { + continue; + } + + ttfb_times.push_back( + std::chrono::duration_cast>( + 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(); + 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(static_cast(&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 + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int client_select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (util::select_protocol(const_cast(out), outlen, in, + inlen, config.npn_list)) { + return SSL_TLSEXT_ERR_OK; + } + + // OpenSSL will terminate handshake with fatal alert if we return + // NOACK. So there is no way to fallback. + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +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::iterator explicitly, without that, +// http_parser_url u{} fails with clang-3.4. +std::vector parse_uris(std::vector::iterator first, + std::vector::iterator last) { + std::vector 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 read_uri_from_file(std::istream &infile) { + std::vector 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 &timings, + std::vector &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::duration(v))); + uris.push_back(script_line.substr(pos + 1, script_line.size())); + } +} +} // namespace + +namespace { +std::unique_ptr 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(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(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::max()) { + std::cerr << "--" << opt + << ": Value too large. It should be less than or equal to " + << std::numeric_limits::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_NPN_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"( + 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= + 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= + Number of concurrent clients. With -r option, this + specifies the maximum number of connections to be made. + Default: )" + << config.nclients << R"( + -t, --threads= + Number of native threads. + Default: )" + << config.nthreads << R"( + -i, --input-file= + Path of a file with multiple URIs are separated by EOLs. + This option will disable URIs getting from command-line. + If '-' is given as , 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= + 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= + Maximum frame size that the local endpoint is willing to + receive. + Default: )" + << util::utos_unit(config.max_frame_size) << R"( + -w, --window-bits= + Sets the stream level initial window size to (2**)-1. + For QUIC, is capped to 26 (roughly 64MiB). + Default: )" + << config.window_bits << R"( + -W, --connection-window-bits= + Sets the connection level initial window size to + (2**)-1. + Default: )" + << config.connection_window_bits << R"( + -H, --header=
+ Add/Override a header to the requests. + --ciphers= + 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= + 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= + 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= + 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= + 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= + 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= + Specifies the main duration for the measurements in case + of timing-based benchmarking. -D and -r are mutually + exclusive. + --warm-up-time= + 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= + Specifies the maximum time that h2load is willing to + keep a connection open, regardless of the activity on + said connection. 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= + Specifies the amount of time that h2load is willing to + wait to see activity on a given connection. + 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 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 , + 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=(|unix:) + 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. + --npn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + Default: )" + << DEFAULT_NPN_LIST << R"( + --h1 Short hand for --npn-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= + Specify decoder header table size. + Default: )" + << util::utos_unit(config.header_table_size) << R"( + --encoder-header-table-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= + 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= + 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 and port to connect instead of using the authority + in . + --rps= Specify request per second for each client. --rps and + --timing-script-file are mutually exclusive. + --groups= + Specify the supported groups. + Default: )" + << config.groups << R"( + --no-udp-gso + Disable UDP GSO. + --max-udp-payload-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 argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The 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) { + tls::libssl_init(); + +#ifndef NOTHREADS + tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + std::string datafile; + std::string logfile; + std::string qlog_base; + 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}, + {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(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(n) < 16_k) { + std::cerr << "--max-frame-size: minimum 16384" << std::endl; + exit(EXIT_FAILURE); + } + if (static_cast(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 4: + // npn-list option + config.npn_list = util::parse_config_str_list(StringRef{optarg}); + 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.npn_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 + qlog_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(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; + } + 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.npn_list.empty()) { + config.npn_list = + util::parse_config_str_list(StringRef::from_lit(DEFAULT_NPN_LIST)); + } + + // serialize the APLN tokens + for (auto &proto : config.npn_list) { + proto.insert(proto.begin(), static_cast(proto.size())); + } + + std::vector reqlines; + + if (config.ifile.empty()) { + std::vector uris; + std::copy(&argv[optind], &argv[argc], std::back_inserter(uris)); + reqlines = parse_uris(std::begin(uris), std::end(uris)); + } else { + std::vector 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(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 (!qlog_base.empty()) { + if (!config.is_quic()) { + std::cerr + << "Warning: --qlog-file-base: only effective in quic, ignoring." + << std::endl; + } else { +#ifdef ENABLE_HTTP3 + config.qlog_file_base = qlog_base; +#endif // ENABLE_HTTP3 + } + } + + 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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#if OPENSSL_1_1_1_API + 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); + } +#else // !OPENSSL_1_1_1_API + if (SSL_CTX_set1_curves_list(ssl_ctx, config.groups.c_str()) != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl; + exit(EXIT_FAILURE); + } +#endif // !OPENSSL_1_1_1_API + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, + nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + std::vector proto_list; + for (const auto &proto : config.npn_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()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +#if OPENSSL_1_1_1_API + 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); + } + } +#endif // OPENSSL_1_1_1_API + + 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(":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 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> 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(256), MAX_SAMPLES / config.nthreads); + + std::mutex mu; + std::condition_variable cv; + auto ready = false; + + std::vector> 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 ulk(mu); + cv.wait(ulk, [&ready] { return ready; }); + } + worker->run(); + })); + } + + { + std::lock_guard 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(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>( + 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(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/lib/nghttp2/src/h2load.h b/lib/nghttp2/src/h2load.h new file mode 100644 index 00000000000..d848fdf2ee2 --- /dev/null +++ b/lib/nghttp2/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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +#endif // ENABLE_HTTP3 + +#include + +#include + +#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> nva; + std::vector h1reqs; + std::vector 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 NPN/ALPN protocol strings in the order of + // preference. + std::vector npn_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 status; + // The statistics per request + std::vector req_stats; + // The statistics per client + std::vector 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 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 streams; + ClientStat cstat; + std::unique_ptr session; + ev_io wev; + ev_io rev; + std::function 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 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/lib/nghttp2/src/h2load_http1_session.cc b/lib/nghttp2/src/h2load_http1_session.cc new file mode 100644 index 00000000000..6bdcd0471e6 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include "h2load.h" +#include "util.h" +#include "template.h" + +#include +#include + +using namespace nghttp2; + +namespace h2load { + +namespace { +// HTTP response message begin +int htp_msg_begincb(llhttp_t *htp) { + auto session = static_cast(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(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(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(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(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(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(data), len); + auto nread = htperr == HPE_OK + ? len + : static_cast(reinterpret_cast( + llhttp_get_error_pos(&htp_)) - + data); + + if (client_->worker->config->verbose) { + std::cout.write(reinterpret_cast(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 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/lib/nghttp2/src/h2load_http1_session.h b/lib/nghttp2/src/h2load_http1_session.h new file mode 100644 index 00000000000..cc10f50fb1f --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/h2load_http2_session.cc b/lib/nghttp2/src/h2load_http2_session.cc new file mode 100644 index 00000000000..9cafa0e384a --- /dev/null +++ b/lib/nghttp2/src/h2load_http2_session.cc @@ -0,0 +1,313 @@ +/* + * 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 +#include +#include + +#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(user_data); + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + return 0; + } + client->on_header(frame->hd.stream_id, name, namelen, value, valuelen); + 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(name), namelen); + std::cout << ": "; + std::cout.write(reinterpret_cast(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(user_data); + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + return 0; + } + client->worker->stats.bytes_head += + frame->hd.length - frame->headers.padlen - + ((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0); + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + client->record_ttfb(); + } + 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(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(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(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(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(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 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(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/lib/nghttp2/src/h2load_http2_session.h b/lib/nghttp2/src/h2load_http2_session.h new file mode 100644 index 00000000000..1b9b9f773a9 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/h2load_http3_session.cc b/lib/nghttp2/src/h2load_http3_session.cc new file mode 100644 index 00000000000..f4779beb5f3 --- /dev/null +++ b/lib/nghttp2/src/h2load_http3_session.cc @@ -0,0 +1,457 @@ +/* + * 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 + +#include + +#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(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(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(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 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(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(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(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(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(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(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, + nullptr, // 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/lib/nghttp2/src/h2load_http3_session.h b/lib/nghttp2/src/h2load_http3_session.h new file mode 100644 index 00000000000..89c7ca00f5e --- /dev/null +++ b/lib/nghttp2/src/h2load_http3_session.h @@ -0,0 +1,83 @@ +/* + * 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 + +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); + 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/lib/nghttp2/src/h2load_quic.cc b/lib/nghttp2/src/h2load_quic.cc new file mode 100644 index 00000000000..65fbc109cb1 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +#endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + +#include +#include + +#include "h2load_http3_session.h" + +namespace h2load { + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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::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(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(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(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(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(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(¶ms); + 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(local_addr), + local_addrlen, + }, + { + const_cast(remote_addr), + remote_addrlen, + }, + }; + + assert(config->npn_list.size()); + + uint32_t quic_version; + + if (config->npn_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, ¶ms, 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 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(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(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(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 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(nread); + } + + assert(quic.conn); + + ++worker->stats.udp_dgram_recv; + + auto path = ngtcp2_path{ + { + &local_addr.su.sa, + static_cast(local_addr.len), + }, + { + &su.sa, + msg.msg_namelen, + }, + }; + + auto data = buf.data(); + + for (;;) { + auto datalen = std::min(static_cast(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 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(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(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(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(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast(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(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/lib/nghttp2/src/h2load_quic.h b/lib/nghttp2/src/h2load_quic.h new file mode 100644 index 00000000000..225f00fb297 --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/h2load_session.h b/lib/nghttp2/src/h2load_session.h new file mode 100644 index 00000000000..ab3b8ec80a7 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/src/http-parser.patch b/lib/nghttp2/src/http-parser.patch new file mode 100644 index 00000000000..ef809409da8 --- /dev/null +++ b/lib/nghttp2/src/http-parser.patch @@ -0,0 +1,28 @@ +commit a143133d43420ef89e4ba0d84c73998863cf9f81 +Author: Tatsuhiro Tsujikawa +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/lib/nghttp2/src/http2.cc b/lib/nghttp2/src/http2.cc new file mode 100644 index 00000000000..acbb4e4a525 --- /dev/null +++ b/lib/nghttp2/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(name), namelen), + std::string(reinterpret_cast(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 &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 &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 &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 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 +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 +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 followd 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 +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 parse_link_header(const StringRef &src) { + std::vector 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(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 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 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(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(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 s; + auto p = std::copy(std::begin(key), std::end(key), std::begin(s)); + std::copy_n(magic, str_size(magic), p); + + std::array 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/lib/nghttp2/src/http2.h b/lib/nghttp2/src/http2.h new file mode 100644 index 00000000000..7cfe46193f7 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include + +#include + +#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
; +using HeaderRefs = std::vector; + +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 +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 +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 +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 +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 +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 +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 &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 &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; + +// 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 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/lib/nghttp2/src/http2_test.cc b/lib/nghttp2/src/http2_test.cc new file mode 100644 index 00000000000..f8be9f45446 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include + +#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{0, 1, 4, 5, 6, 7, 12}; + std::vector 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{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("")); + CU_ASSERT(0 == res.size()); + } + { + // URI url should be extracted + auto res = + http2::parse_link_header(StringRef::from_lit("; 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("; 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("; 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"(; 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"(; 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"(; title="foo,bar", ; 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("; rel=preload, ")); + 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("; rel=preload, ; 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(", ;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(";rel=preload;")); + CU_ASSERT(0 == res.size()); + } + { + // Error if link header ends with ';' + auto res = http2::parse_link_header( + StringRef::from_lit(";rel=preload;, ")); + CU_ASSERT(0 == res.size()); + } + { + // OK if input ends with ',' + auto res = + http2::parse_link_header(StringRef::from_lit(";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(",,,;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(";rel=preload; as=")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter value is not allowed + auto res = + http2::parse_link_header(StringRef::from_lit(";as=;rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter value is not allowed + auto res = http2::parse_link_header( + StringRef::from_lit(";as=, ;rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter name is not allowed + auto res = http2::parse_link_header( + StringRef::from_lit("; =file; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Without whitespaces + auto res = http2::parse_link_header( + StringRef::from_lit(";as=file;rel=preload,;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("; 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("; 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("; *=bar; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // '*' is not allowed expect for trailing one + auto res = http2::parse_link_header( + StringRef::from_lit("; 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("; foo*; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // '>' is not followed by ';' + auto res = + http2::parse_link_header(StringRef::from_lit(" rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Starting with whitespace is no problem. + auto res = + http2::parse_link_header(StringRef::from_lit(" ; 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("; rel=preloadx")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; 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"(; 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"(; 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"(; 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"(; 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"(; 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"(; 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"(; rel="preload", )")); + 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"(, ; 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"(; 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"(; 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"(; 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"(; 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"(; 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"(; rel="preload ")")); + CU_ASSERT(0 == res.size()); + } + { + // backslash escaped characters in quoted-string + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; 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"(; 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"(; 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("; 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"(; rel=preload; anchor="#foo", ; 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"(; 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"(; 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"(; rel=preload; ANCHOR="#foo", ; )" + R"(REL=PRELOAD, ; 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("; rel=preload; nopush")); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ';' + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; nopush; foo")); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ',' + auto res = http2::parse_link_header( + StringRef::from_lit("; nopush; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // string whose prefix is nopush + auto res = http2::parse_link_header( + StringRef::from_lit("; 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("; 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/lib/nghttp2/src/http2_test.h b/lib/nghttp2/src/http2_test.h new file mode 100644 index 00000000000..382470d471f --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/http3.cc b/lib/nghttp2/src/http3.cc new file mode 100644 index 00000000000..61134ad74dc --- /dev/null +++ b/lib/nghttp2/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 &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 &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 &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/lib/nghttp2/src/http3.h b/lib/nghttp2/src/http3.h new file mode 100644 index 00000000000..81ee0d732d9 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include + +#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 +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 +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 +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 +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 +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 +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 &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 &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/lib/nghttp2/src/inflatehd.cc b/lib/nghttp2/src/inflatehd.cc new file mode 100644 index 00000000000..f484042a3aa --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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(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/lib/nghttp2/src/libevent_util.cc b/lib/nghttp2/src/libevent_util.cc new file mode 100644 index 00000000000..3b60b6d9dfb --- /dev/null +++ b/lib/nghttp2/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 +#include + +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/lib/nghttp2/src/libevent_util.h b/lib/nghttp2/src/libevent_util.h new file mode 100644 index 00000000000..1d1ee91c422 --- /dev/null +++ b/lib/nghttp2/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 +#include + +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/lib/nghttp2/src/memchunk.h b/lib/nghttp2/src/memchunk.h new file mode 100644 index 00000000000..7a7f2e9b056 --- /dev/null +++ b/lib/nghttp2/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 +#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 +#endif // !_WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#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 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 buf; + uint8_t *pos, *last; + Memchunk *knext; + Memchunk *next; + static const size_t size = N; +}; + +template 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 struct Memchunks { + Memchunks(Pool *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(src); + auto last = first + count; + + if (!tail) { + head = tail = pool->get(); + } + + for (;;) { + auto n = std::min(static_cast(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 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(dest); + auto last = first + count; + + auto m = head; + + while (m) { + auto next = m->next; + auto n = std::min(static_cast(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(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 *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 struct PeekMemchunks { + PeekMemchunks(Pool *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(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(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 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; +using DefaultMemchunks = Memchunks; +using DefaultPeekMemchunks = PeekMemchunks; + +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 struct MemchunkBuffer { + MemchunkBuffer(Pool *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(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 *pool; + Memchunk *chunk; +}; + +using DefaultMemchunkBuffer = MemchunkBuffer; + +} // namespace nghttp2 + +#endif // MEMCHUNK_H diff --git a/lib/nghttp2/src/memchunk_test.cc b/lib/nghttp2/src/memchunk_test.cc new file mode 100644 index 00000000000..236d9ea4135 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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; +using Memchunks16 = Memchunks; +using PeekMemchunks16 = PeekMemchunks; + +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 buf{}; + + chunks.append(buf.data(), buf.size()); + + std::array 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 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 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 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 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 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 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/lib/nghttp2/src/memchunk_test.h b/lib/nghttp2/src/memchunk_test.h new file mode 100644 index 00000000000..7d677e787ee --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/network.h b/lib/nghttp2/src/network.h new file mode 100644 index 00000000000..45311d84fb4 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef _WIN32 +# include +#else // !_WIN32 +# include +#endif // !_WIN32 +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +# include +#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/lib/nghttp2/src/nghttp.cc b/lib/nghttp2/src/nghttp.cc new file mode 100644 index 00000000000..cb6a3b15358 --- /dev/null +++ b/lib/nghttp2/src/nghttp.cc @@ -0,0 +1,3164 @@ +/* + * 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 +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_JANSSON +# include +#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{{ + {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::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(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(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(ev_userdata(loop)); + auto req = static_cast(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(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(); + 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(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(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(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(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(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; + +#if LIBRESSL_2_7_API || \ + (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) || \ + defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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()); +#endif // LIBRESSL_2_7_API || (!LIBRESSL_IN_USE && + // OPENSSL_VERSION_NUMBER >= 0x10002000L) || + // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 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 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_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 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", 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(data), len); + auto nread = htperr == HPE_OK + ? len + : static_cast(reinterpret_cast( + llhttp_get_error_pos(htp.get())) - + data); + + if (config.verbose) { + std::cout.write(reinterpret_cast(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 NPN or ALPN result + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG + for (int i = 0; i < 2; ++i) { + 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; + } + break; + } +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); +#else // OPENSSL_VERSION_NUMBER < 0x10002000L + break; +#endif // OPENSSL_VERSION_NUMBER < 0x10002000L + } + if (!next_proto) { + print_protocol_nego_error(); + return -1; + } + } + + 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 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(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 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(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( + timing.domain_lookup_end_time - timing.start_time) + .count() / + 1000.0; + auto connect_delta = + std::chrono::duration_cast( + 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( + req_timing.response_start_time - req_timing.request_start_time) + .count() / + 1000.0; + auto receive_delta = + std::chrono::duration_cast( + req_timing.response_end_time - req_timing.response_start_time) + .count() / + 1000.0; + + auto time_sum = + std::chrono::duration_cast( + (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(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( + 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 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(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(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( + 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("", 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( + 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(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( + 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( + 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(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( + 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( + 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( + 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( + 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 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 <iming = 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( + req->timing.response_end_time - base); + auto request_start = std::chrono::duration_cast( + req->timing.request_start_time - base); + auto total = std::chrono::duration_cast( + 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 + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int client_select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (config.verbose) { + print_timer(); + std::cout << "[NPN] server offers:" << std::endl; + } + for (unsigned int i = 0; i < inlen; i += in[i] + 1) { + if (config.verbose) { + std::cout << " * "; + std::cout.write(reinterpret_cast(&in[i + 1]), in[i]); + std::cout << std::endl; + } + } + if (!util::select_h2(const_cast(out), outlen, in, + inlen)) { + print_protocol_nego_error(); + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +namespace { +int communicate( + const std::string &scheme, const std::string &host, uint16_t port, + std::vector< + std::tuple> + 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; + } + } +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, + nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + auto proto_list = util::get_default_alpn(); + + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } + { + HttpClient client{callbacks, loop, ssl_ctx}; + + 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( + 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 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 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> + 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]... ... +HTTP/2 client)" + << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + print_usage(out); + out << R"( + 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= + Timeout each request after . Set 0 to disable + timeout. + -w, --window-bits= + Sets the stream level initial window size to 2**-1. + -W, --connection-window-bits= + Sets the connection level initial window size to + 2**-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=
+ Add a header to the requests. Example: -H':method: PUT' + --trailer=
+ Add a trailer header to the requests.
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= + Use the specified client certificate file. The file + must be in PEM format. + --key= Use the client private key file. The file must be in + PEM format. + -d, --data= + Post FILE to server. If '-' is given, data will be read + from stdin. + -m, --multiply= + Request each URI 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= + 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= + Use as SETTINGS_MAX_CONCURRENT_STREAMS value of + remote endpoint as if it is received in SETTINGS frame. + Default: 100 + -c, --header-table-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= + 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= + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + -r, --har= + Output HTTP transactions 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= + 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 argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The 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) { + tls::libssl_init(); + + 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::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(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::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::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::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::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/lib/nghttp2/src/nghttp.h b/lib/nghttp2/src/nghttp.h new file mode 100644 index 00000000000..a8804142275 --- /dev/null +++ b/lib/nghttp2/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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#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 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 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 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> reqvec; + // Insert path already added in reqvec to prevent multiple request + // for 1 resource. + std::set path_cache; + std::string scheme; + std::string host; + std::string hostport; + // Used for parse the HTTP upgrade response from server + std::unique_ptr htp; + SessionTiming timing; + ev_io wev; + ev_io rev; + ev_timer wt; + ev_timer rt; + ev_timer settings_timer; + std::function readfn, writefn; + std::function on_readfn; + std::function 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 settings_payload; + + enum { ERR_CONNECT_FAIL = -100 }; +}; + +} // namespace nghttp2 + +#endif // NGHTTP_H diff --git a/lib/nghttp2/src/nghttp2_config.h b/lib/nghttp2/src/nghttp2_config.h new file mode 100644 index 00000000000..8e6cd05f1da --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +#endif // NGHTTP2_CONFIG_H diff --git a/lib/nghttp2/src/nghttp2_gzip.c b/lib/nghttp2/src/nghttp2_gzip.c new file mode 100644 index 00000000000..5f17ab2a0c3 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/nghttp2_gzip.h b/lib/nghttp2/src/nghttp2_gzip.h new file mode 100644 index 00000000000..a40352c3c2f --- /dev/null +++ b/lib/nghttp2/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 +# endif /* HAVE_CONFIG_H */ +# include + +# include + +# 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/lib/nghttp2/src/nghttp2_gzip_test.c b/lib/nghttp2/src/nghttp2_gzip_test.c new file mode 100644 index 00000000000..de19d5da338 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include + +#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/lib/nghttp2/src/nghttp2_gzip_test.h b/lib/nghttp2/src/nghttp2_gzip_test.h new file mode 100644 index 00000000000..8d554f7209a --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/nghttpd.cc b/lib/nghttp2/src/nghttpd.cc new file mode 100644 index 00000000000..3c5f5b4bec1 --- /dev/null +++ b/lib/nghttp2/src/nghttpd.cc @@ -0,0 +1,508 @@ +/* + * 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 +#endif // HAVE_UNISTD_H +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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]... [ ]\n" + << "HTTP/2 server" << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + Config config; + print_usage(out); + out << R"( + Specify listening port number. + + Set path to server's private key. Required unless + --no-tls is specified. + Set path to server's certificate. Required unless + --no-tls is specified. +Options: + -a, --address= + 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= + 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= + Specify decoder header table size. + --encoder-header-table-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== + Push resources s when is requested. + This option can be used repeatedly to specify multiple + push configurations. and s are + relative to document root. See --htdocs option. + Example: -p/=/foo.png -p/doc=/bar.css + -b, --padding= + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + -m, --max-concurrent-streams= + Set the maximum number of the concurrent streams in one + HTTP/2 session. + Default: )" + << config.max_concurrent_streams << R"( + -n, --workers= + Set the number of worker threads. + Default: 1 + -e, --error-gzip + Make error response gzipped. + -w, --window-bits= + Sets the stream level initial window size to 2**-1. + -W, --connection-window-bits= + Sets the connection level initial window size to + 2**-1. + --dh-param-file= + 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=
+ Add a trailer header to a response.
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 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 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) { + tls::libssl_init(); + +#ifndef NOTHREADS + tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + 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::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::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(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::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::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::max()) { + std::cerr << ": 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/lib/nghttp2/src/quic.cc b/lib/nghttp2/src/quic.cc new file mode 100644 index 00000000000..1c68a5bed46 --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include + +#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/lib/nghttp2/src/quic.h b/lib/nghttp2/src/quic.h new file mode 100644 index 00000000000..d72ae1feb5e --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx-unittest.cc b/lib/nghttp2/src/shrpx-unittest.cc new file mode 100644 index 00000000000..07373d57087 --- /dev/null +++ b/lib/nghttp2/src/shrpx-unittest.cc @@ -0,0 +1,248 @@ +/* + * 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 +#endif // HAVE_CONFIG_H + +#include +#include +#include +// 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; + + nghttp2::tls::libssl_init(); + + shrpx::create_config(); + + // initialize the CUnit test registry + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + // add a suite to the registry + pSuite = CU_add_suite("shrpx_TestSuite", init_suite1, clean_suite1); + if (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/lib/nghttp2/src/shrpx.cc b/lib/nghttp2/src/shrpx.cc new file mode 100644 index 00000000000..ccd9e0642cb --- /dev/null +++ b/lib/nghttp2/src/shrpx.cc @@ -0,0 +1,5335 @@ +/* + * 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 +#include +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#include +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#ifdef HAVE_SYSLOG_H +# include +#endif // HAVE_SYSLOG_H +#ifdef HAVE_LIMITS_H +# include +#endif // HAVE_LIMITS_H +#ifdef HAVE_SYS_TIME_H +# include +#endif // HAVE_SYS_TIME_H +#include +#ifdef HAVE_LIBSYSTEMD +# include +#endif // HAVE_LIBSYSTEMD +#ifdef HAVE_LIBBPF +# include +#endif // HAVE_LIBBPF + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +#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,. +// is file descriptor. For UNIX domain socket, the value must be +// comma separated 3 parameters: unix,,. is file +// descriptor. 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: +// ,,,... is the file +// descriptor. 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> 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> + &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> cid_prefixes; +#endif // ENABLE_HTTP3 +}; + +namespace { +void reload_config(); +} // namespace + +namespace { +std::deque> 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 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>().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 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(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 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(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(envlen + listenerconf.addrs.size() + + worker_processes.size() + 2); + size_t envidx = 0; + + std::vector 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(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(ipc_fd_str.c_str()); + +#ifdef ENABLE_HTTP3 + std::vector 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(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 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(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 &iaddrs) { + std::array 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(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 &iaddrs) { + std::array 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 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(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(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(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(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 +get_inherited_addr_from_config(BlockAllocator &balloc, Config *config) { + std::array errbuf; + int rv; + + auto &listenerconf = config->conn.listener; + + std::vector 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 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 get_inherited_addr_from_env(Config *config) { + std::array errbuf; + int rv; + std::vector 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(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(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 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(port); + addr.fd = static_cast(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 &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 + inherited_quic_lingering_worker_processes; +} // namespace + +namespace { +std::vector +get_inherited_quic_lingering_worker_process_from_env() { + std::vector 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(env), end_fd - env); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " + << StringRef{env, static_cast(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> 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 &iaddrs) { + std::array 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 &ipc_fd) { + std::array 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 &ipc_fd) { + std::array 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 &quic_ipc_fd) { + std::array 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> &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 +collect_quic_lingering_worker_processes() { + std::vector 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 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 buf; + ssize_t nread; + + while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + + if (nread == -1) { + std::array 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 &iaddrs +#ifdef ENABLE_HTTP3 + , + const std::vector> + &cid_prefixes, + const std::vector &quic_lwps +#endif // ENABLE_HTTP3 +) { + std::array errbuf; + int rv; + sigset_t oldset; + + std::array ipc_fd; + + rv = create_ipc_socket(ipc_fd); + if (rv != 0) { + return -1; + } + +#ifdef ENABLE_HTTP3 + std::array 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 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> 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(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_NPN_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; +#if OPENSSL_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + tlsconf.ecdh_curves = StringRef::from_lit("P-256:P-384:P-521"); +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + 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::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(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(); + 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]... [ ] +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"( + + Set path to server's private key. Required unless + "no-tls" parameter is used in --frontend option. + 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=(,|unix:)[;[[:...]][[;]...] + + 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 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. + 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 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 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 are grouped + together forming load balancing group. + + Several parameters are accepted after . + The parameters are delimited by ";". The available + parameters are: "proto=", "tls", + "sni=", "fall=", "rise=", + "affinity=", "dns", "redirect-if-not-tls", + "upgrade-scheme", "mruby=", + "read-timeout=", "write-timeout=", + "group=", "group-weight=", "weight=", 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=". should be one of the following + list without quotes: "h2", "http/1.1". The default + value of 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=" parameter, it can override the TLS + SNI field value with given . This will + default to the backend name + + The feature to detect whether backend is online or + offline can be enabled using optional "fall" and "rise" + parameters. Using "fall=" parameter, if nghttpx + cannot connect to a this backend times in a row, + this backend is assumed to be offline, and it is + excluded from load balancing. If 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=" 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 times in a + row, the backend is assumed to be online, and it is now + eligible for load balancing target. If 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=" parameter. If "ip" is given in + , client IP based session affinity is enabled. + If "cookie" is given in , cookie based session + affinity is enabled. If "none" is given in , + session affinity is disabled, and this is the default. + The session affinity is enabled per . If at + least one backend has "affinity" parameter, and its + is not "none", session affinity is enabled for + all backend servers sharing the same . 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=" must be used to specify a + name of cookie to use. Optionally, + "affinity-cookie-path=" can be used to specify a + path which cookie is applied. The optional + "affinity-cookie-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 is "yes", + the Secure attribute is always set. If is + "no", the Secure attribute is always omitted. + "affinity-cookie-stickiness=" controls + stickiness of this affinity. If is + "loose", removing or adding a backend server might break + the affinity and the request might be forwarded to a + different backend server. If 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. 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 . 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=" 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=" and "write-timeout=" + 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=" 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=" parameter + specifies the weight of the group. The higher weight + gets more frequently selected by the load balancing + algorithm. must be [1, 256] inclusive. The weight + 8 has 4 times more weight than 2. must be the same + for all addresses which share the same . 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=" 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. 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=" parameter + above). "dnf" is an abbreviation of "do not forward". + + Since ";" and ":" are used as delimiter, must + not contain these characters. In order to include ":" + in , 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=(,|unix:)[[;]...] + Set frontend host and port. If 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= + 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= + Specify proxy URI in the form + http://[:@]:. If a proxy + requires authentication, specify and . + 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= + 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= + 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= + 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= + 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= + 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= + 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= + 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= + 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= + 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= + Set maximum number of simultaneous connections frontend + accepts. Setting 0 means unlimited. + Default: )" + << config->conn.upstream.worker_connections << R"( + --backend-connections-per-host= + 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= + 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= + Set maximum number of open files (RLIMIT_NOFILE) to . + If 0 is given, nghttpx does not set the limit. + Default: )" + << config->rlimit_nofile << R"( + --rlimit-memlock= + 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= + Set buffer size used to store backend request. + Default: )" + << util::utos_unit(config->conn.downstream->request_buffer_size) << R"( + --backend-response-buffer= + Set buffer size used to store backend response. + Default: )" + << util::utos_unit(config->conn.downstream->response_buffer_size) << R"( + --fastopen= + 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= + Specify read timeout for HTTP/2 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http2_read) << R"( + --frontend-http3-read-timeout= + Specify read timeout for HTTP/3 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http3_read) << R"( + --frontend-read-timeout= + Specify read timeout for HTTP/1.1 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.read) << R"( + --frontend-write-timeout= + Specify write timeout for all frontend connections. + Default: )" + << util::duration_str(config->conn.upstream.timeout.write) << R"( + --frontend-keep-alive-timeout= + Specify keep-alive timeout for frontend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.idle_read) << R"( + --stream-read-timeout= + Specify read timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_read) << R"( + --stream-write-timeout= + Specify write timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_write) << R"( + --backend-read-timeout= + Specify read timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.read) << R"( + --backend-write-timeout= + Specify write timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.write) << R"( + --backend-connect-timeout= + Specify timeout before establishing TCP connection to + backend. + Default: )" + << util::duration_str(config->conn.downstream->timeout.connect) << R"( + --backend-keep-alive-timeout= + Specify keep-alive timeout for backend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.idle_read) << R"( + --listener-disable-timeout= + 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= + Specify timeout before SETTINGS ACK is received from + client. + Default: )" + << util::duration_str(config->http2.upstream.timeout.settings) << R"( + --backend-http2-settings-timeout= + Specify timeout before SETTINGS ACK is received from + backend server. + Default: )" + << util::duration_str(config->http2.downstream.timeout.settings) << R"( + --backend-max-backoff= + 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= + 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= + 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= + 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= + 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= + Set supported curve list for frontend connections. + 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= + 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 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=:[[;]...] + 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, + must be absolute path. + + Additional parameter can be specified in . The + available is "sct-dir=". + + "sct-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 to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + --npn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + Default: )" + << DEFAULT_NPN_LIST + << R"( + --verify-client + Require and verify client certificate. + --verify-client-cacert= + Path 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 to file that contains client private key used in + backend client authentication. + --client-cert-file= + Path to file that contains client certificate used in + backend client authentication. + --tls-min-proto-version= + 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= + 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 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=,[;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= + 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= + 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= + 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= + 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 to client certificate for memcached connections to + get TLS ticket keys. + --tls-ticket-key-memcached-private-key-file= + Path to client private key for memcached connections to + get TLS ticket keys. + --fetch-ocsp-response-file= + Path to fetch-ocsp-response script file. It should be + absolute path. + Default: )" + << config->tls.ocsp.fetch_ocsp_response_file << R"( + --ocsp-update-interval= + 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=,[;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 to client certificate for memcached connections to + store session cache. + --tls-session-cache-memcached-private-key-file= + Path to client private key for memcached connections to + store session cache. + --tls-dyn-rec-warmup-threshold= + 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= + 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= + Specifies the directory where *.sct files exist. All + *.sct files in 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 , or certificate option in configuration + file. For additional certificates, use --subcert + option. This option requires OpenSSL >= 1.0.2. + --psk-secrets= + Read list of PSK identity and secrets from . This + is used for frontend connection. The each line of input + file is formatted as :, where + is PSK identity, and 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= + Read PSK identity and secrets from . This is used + for backend connection. The each line of input file is + formatted as :, where + is PSK identity, and 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= + 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= + 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= + 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= + Sets the per-stream initial window size of HTTP/2 + frontend connection. + Default: )" + << config->http2.upstream.window_size << R"( + --frontend-http2-connection-window-size= + Sets the per-connection window size of HTTP/2 frontend + connection. + Default: )" + << config->http2.upstream.connection_window_size << R"( + --backend-http2-window-size= + Sets the initial window size of HTTP/2 backend + connection. + Default: )" + << config->http2.downstream.window_size << R"( + --backend-http2-connection-window-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= + Add at most 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= + 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= + 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= + 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= + 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= + Set the severity level of log output. must be + one of INFO, NOTICE, WARN, ERROR and FATAL. + Default: NOTICE + --accesslog-file= + 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= + 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_: value of HTTP request header where + '_' in 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= + 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= + Set syslog facility to . + 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= + Append RFC 7239 Forwarded header field with parameters + specified in comma delimited 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|) + 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= + Specify protocol ID, port, host and origin of + alternative service. , and are + optional. Empty and 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= + Just like --altsvc option, but this altsvc is only sent + in HTTP/2 frontend. + --add-request-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=
+ 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= + 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= + 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= + 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= + 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=(|*)= + Set file path to custom error page served when nghttpx + originally generates HTTP error status code . + must be greater than or equal to 400, and at most + 599. If "*" is used instead of , it matches all + HTTP status code. If error status code comes from + backend server, the custom error pages are not used. + --server-name= + Change server response header field value to . + 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= + 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= + Set the maximum size of request body for API request. + Default: )" + << util::utos_unit(config->api.max_request_body) << R"( + +DNS: + --dns-cache-timeout= + 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= + 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= + Set the number of DNS query before nghttpx gives up name + lookup. + Default: )" + << config->dns.max_try << R"( + --frontend-max-requests= + 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= + Dumps request headers received by HTTP/2 frontend to the + file denoted in . 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, where >= 2. + --frontend-http2-dump-response-header= + Dumps response headers sent from HTTP/2 frontend to the + file denoted in . 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, where >= 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= + Set path to save PID of this program. + --user= + Run this program as . 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= + 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= + 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= + 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= + 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= + 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= + 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= + Specify a congestion controller algorithm for a frontend + QUIC connection. should be either "cubic" or + "bbr". + Default: )" + << (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC + ? "cubic" + : "bbr") + << R"( + --frontend-quic-secret-file= + 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= + 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= + 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= + 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= + 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= + 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 bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_window_size) << R"( + --frontend-http3-max-connection-window-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 bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_connection_window_size) + << R"( + --frontend-http3-max-concurrent-streams= + 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= + Load configuration from . Please note that + nghttpx always tries to read the default configuration + file if --conf is not given. + Default: )" + << config->conf_path << R"( + --include= + Load additional configurations from . File + 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 argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The 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> &cmdcfgs) { + std::array errbuf; + std::map pattern_addr_indexer; + if (conf_exists(config->conf_path.c_str())) { + LOG(NOTICE) << "Loading configuration from " << config->conf_path; + std::set 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 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.npn_list.empty()) { + tlsconf.npn_list = util::split_str(DEFAULT_NPN_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.npn_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::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 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(config->rlimit_nofile), + static_cast(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(config->rlimit_memlock), + static_cast(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 &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(); + + 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> 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(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 errbuf; + + nghttp2::tls::libssl_init(); + +#ifdef HAVE_LIBBPF + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); +#endif // HAVE_LIBBPF + +#ifndef NOTHREADS + nghttp2::tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + 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}, + {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; + 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/lib/nghttp2/src/shrpx.h b/lib/nghttp2/src/shrpx.h new file mode 100644 index 00000000000..d881ef5d9d4 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_CONFIG_H + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H + +#include + +#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/lib/nghttp2/src/shrpx_accept_handler.cc b/lib/nghttp2/src/shrpx_accept_handler.cc new file mode 100644 index 00000000000..01b641580c1 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H + +#include + +#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(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/lib/nghttp2/src/shrpx_accept_handler.h b/lib/nghttp2/src/shrpx_accept_handler.h new file mode 100644 index 00000000000..853e9a2b80f --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_api_downstream_connection.cc b/lib/nghttp2/src/shrpx_api_downstream_connection.cc new file mode 100644 index 00000000000..254ab59e2bb --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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 &apis() { + static const auto apis = new std::array{ + 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(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(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(-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(); + 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 include_set; + std::map pattern_addr_indexer; + + for (auto first = reinterpret_cast(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 & +APIDownstreamConnection::get_downstream_addr_group() const { + static std::shared_ptr s; + return s; +} + +DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_api_downstream_connection.h b/lib/nghttp2/src/shrpx_api_downstream_connection.h new file mode 100644 index 00000000000..5d4182f0306 --- /dev/null +++ b/lib/nghttp2/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 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 & + 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/lib/nghttp2/src/shrpx_client_handler.cc b/lib/nghttp2/src/shrpx_client_handler.cc new file mode 100644 index 00000000000..ac7a65ec94a --- /dev/null +++ b/lib/nghttp2/src/shrpx_client_handler.cc @@ -0,0 +1,1710 @@ +/* + * 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 +#endif // HAVE_UNISTD_H +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include + +#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(w->data); + auto handler = static_cast(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(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(w->data); + auto handler = static_cast(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(w->data); + auto handler = static_cast(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 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(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(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 &&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; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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.npn_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(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(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 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 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(buf[0]) << 24) | + (static_cast(buf[1]) << 16) | + (static_cast(buf[2]) << 8) | static_cast(buf[3]); +} +} // namespace + +Http2Session *ClientHandler::get_http2_session( + const std::shared_ptr &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(1); + auto rh = d(worker_->get_randgen()); + h = util::hash32(StringRef{reinterpret_cast(&rh), + reinterpret_cast(&rh) + sizeof(rh)}); + + downstream->renew_affinity_cookie(h); + + return h; +} + +namespace { +void reschedule_addr( + std::priority_queue, + 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, + 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(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 &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(1); + auto rh = d(worker_->get_randgen()); + h = util::hash32(StringRef{reinterpret_cast(&rh), + reinterpret_cast(&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(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 +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(worker_); + dconn->set_client_handler(this); + return dconn; + } + case UpstreamAltMode::HEALTHMON: { + auto dconn = std::make_unique(); + 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(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(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(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(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(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{'\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(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(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(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 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/lib/nghttp2/src/shrpx_client_handler.h b/lib/nghttp2/src/shrpx_client_handler.h new file mode 100644 index 00000000000..511dd9108e8 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include + +#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 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 + 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 &&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 &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 &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_; + // 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 read_, write_; + std::function 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/lib/nghttp2/src/shrpx_config.cc b/lib/nghttp2/src/shrpx_config.cc new file mode 100644 index 00000000000..bcb48ebccef --- /dev/null +++ b/lib/nghttp2/src/shrpx_config.cc @@ -0,0 +1,4687 @@ +/* + * 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 +#endif // HAVE_PWD_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_SYSLOG_H +# include +#endif // HAVE_SYSLOG_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include + +#include + +#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 replace_config(std::unique_ptr another) { + auto p = config; + config = another.release(); + return std::unique_ptr(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::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 +read_tls_ticket_key_file(const std::vector &files, + const EVP_CIPHER *cipher, const EVP_MD *hmac) { + auto ticket_keys = std::make_unique(); + 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(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(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 +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(); + 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 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(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 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 +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 +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(std::numeric_limits::max()) < + static_cast(n)) { + LOG(ERROR) << opt + << ": too large. The value should be less than or equal to " + << std::numeric_limits::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(std::numeric_limits::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 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(); + + 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::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::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 &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(-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(-1); + } + + return ForwardedNode::OBFUSCATED; +} +} // namespace + +namespace { +int parse_error_page(std::vector &error_pages, const StringRef &opt, + const StringRef &optarg) { + std::array 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(n); + } + + auto path = StringRef{eq + 1, std::end(optarg)}; + + std::vector 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 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 !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + 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 // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << "subcert: sct-dir requires OpenSSL >= 1.0.2"; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + } 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 &dst, const StringRef &opt, + const StringRef &dir_path) { + std::array 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 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 + +#if !LIBRESSL_LEGACY_API +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 // !LIBRESSL_LEGACY_API + +#if !LIBRESSL_LEGACY_API +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 // !LIBRESSL_LEGACY_API + +// 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; + } + 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 &included_set, + std::map &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 &included_set, + std::map &pattern_addr_indexer) { + std::array 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=,;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_CLIENT_PROXY: + LOG(ERROR) + << opt + << ": deprecated. Use http2-proxy, frontend=,;no-tls " + "and backend=,;;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 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=,;no-tls, " + "backend=,;;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_NPN_LIST: { + auto list = util::split_str(optarg, ','); + config->tls.npn_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.npn_list[i] = make_string_ref(config->balloc, list[i]); + } + + 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 ¶m : 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(-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: +#if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + config->tls.ecdh_curves = make_string_ref(config->balloc, optarg); +#else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2"; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + return 0; + case SHRPX_OPTID_TLS_SCT_DIR: +#if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg); +#else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2"; + return 0; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + 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: +#if !LIBRESSL_LEGACY_API + return parse_psk_secrets(config, optarg); +#else // LIBRESSL_LEGACY_API + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // LIBRESSL_LEGACY_API + case SHRPX_OPTID_CLIENT_PSK_SECRETS: +#if !LIBRESSL_LEGACY_API + return parse_client_psk_secrets(config, optarg); +#else // LIBRESSL_LEGACY_API + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // LIBRESSL_LEGACY_API + 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_CONF: + LOG(WARN) << "conf: ignored"; + + return 0; + } + + LOG(ERROR) << "Unknown option: " << opt; + + return -1; +} + +int load_config(Config *config, const char *filename, + std::set &include_set, + std::map &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 &res, size_t idx, + const StringRef &s) { + int rv; + std::array 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(buf[4 * i]) << 24) | + (static_cast(buf[4 * i + 1]) << 16) | + (static_cast(buf[4 * i + 2]) << 8) | + static_cast(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 hostport_buf; + + for (auto &g : addr_groups) { + std::unordered_map 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(&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/lib/nghttp2/src/shrpx_config.h b/lib/nghttp2/src/shrpx_config.h new file mode 100644 index 00000000000..0c43a3c267d --- /dev/null +++ b/lib/nghttp2/src/shrpx_config.h @@ -0,0 +1,1448 @@ +/* + * 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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#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 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 :. 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; + // :. 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 addrs; + // Bunch of session affinity hash. Only used if affinity == + // SessionAffinity::IP. + std::vector 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 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 name; + // encryption key for |cipher| + std::array enc_key; + // hmac key for |hmac| + std::array hmac_key; + } data; +}; + +struct TicketKeys { + ~TicketKeys(); + std::vector keys; +}; + +struct TLSCertificate { + TLSCertificate(StringRef private_key_file, StringRef cert_file, + std::vector 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 sct_data; +}; + +#ifdef ENABLE_HTTP3 +struct QUICKeyingMaterial { + std::array reserved; + std::array secret; + std::array salt; + std::array cid_encryption_key; + // Identifier of this keying material. Only the first 2 bits are + // used. + uint8_t id; +}; + +struct QUICKeyingMaterials { + std::vector 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 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 psk_secrets; + // The list of additional TLS certificate pair + std::vector subcerts; + std::vector alpn_prefs; + // list of supported NPN/ALPN protocol strings in the order of + // preference. + std::vector npn_list; + // list of supported SSL/TLS protocol strings. + std::vector tls_proto_list; + std::vector 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 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 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 altsvcs; + // altsvcs serialized in a wire format. + StringRef altsvc_header_value; + std::vector http2_altsvcs; + // http2_altsvcs serialized in a wire format. + StringRef http2_altsvc_header_value; + std::vector 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 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 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 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 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 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 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 replace_config(std::unique_ptr 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_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 &included_set, + std::map &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 &included_set, + std::map &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 &include_set, + std::map &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 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 +read_tls_ticket_key_file(const std::vector &files, + const EVP_CIPHER *cipher, const EVP_MD *hmac); + +#ifdef ENABLE_HTTP3 +std::shared_ptr +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/lib/nghttp2/src/shrpx_config_test.cc b/lib/nghttp2/src/shrpx_config_test.cc new file mode 100644 index 00000000000..a8f09628d71 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H + +#include + +#include + +#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/lib/nghttp2/src/shrpx_config_test.h b/lib/nghttp2/src/shrpx_config_test.h new file mode 100644 index 00000000000..a30de41aa5f --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/shrpx_connect_blocker.cc b/lib/nghttp2/src/shrpx_connect_blocker.cc new file mode 100644 index 00000000000..ff767b078ea --- /dev/null +++ b/lib/nghttp2/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(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 block_func, + std::function 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::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/lib/nghttp2/src/shrpx_connect_blocker.h b/lib/nghttp2/src/shrpx_connect_blocker.h new file mode 100644 index 00000000000..1ebe78903cc --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +namespace shrpx { + +class ConnectBlocker { +public: + ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop, + std::function block_func, + std::function 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 block_func_; + // Called when unblocked + std::function 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/lib/nghttp2/src/shrpx_connection.cc b/lib/nghttp2/src/shrpx_connection.cc new file mode 100644 index 00000000000..28c25b18218 --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection.cc @@ -0,0 +1,1318 @@ +/* + * 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 +#endif // HAVE_UNISTD_H +#include + +#include + +#include + +#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 { + +#if !LIBRESSL_3_5_API && !LIBRESSL_2_7_API && !OPENSSL_1_1_API + +void *BIO_get_data(BIO *bio) { return bio->ptr; } +void BIO_set_data(BIO *bio, void *ptr) { bio->ptr = ptr; } +void BIO_set_init(BIO *bio, int init) { bio->init = init; } + +#endif // !LIBRESSL_3_5_API && !LIBRESSL_2_7_API && !OPENSSL_1_1_API + +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(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(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) { +#if OPENSSL_1_1_API || LIBRESSL_3_5_API + BIO_set_init(b, 1); +#else // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + b->init = 1; + b->num = 0; + b->ptr = nullptr; + b->flags = 0; +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + return 1; +} +} // namespace + +namespace { +int shrpx_bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + +#if !OPENSSL_1_1_API && !LIBRESSL_3_5_API + b->ptr = nullptr; + b->init = 0; + b->flags = 0; +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + + return 1; +} +} // namespace + +#if OPENSSL_1_1_API || LIBRESSL_3_5_API + +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; +} + +#else // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + +BIO_METHOD *create_bio_method() { + static auto meth = new BIO_METHOD{ + BIO_TYPE_FD, "nghttpx-bio", shrpx_bio_write, + shrpx_bio_read, shrpx_bio_puts, shrpx_bio_gets, + shrpx_bio_ctrl, shrpx_bio_create, shrpx_bio_destroy, + }; + + return meth; +} + +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + +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 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(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + rv = SSL_do_handshake(tls.ssl); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + 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 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 OPENSSL_1_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto &tlsconf = get_config()->tls; + std::array buf; +#endif // OPENSSL_1_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + ERR_clear_error(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + rv = SSL_do_handshake(tls.ssl); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + 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 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; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + 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::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::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(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + auto rv = SSL_write(tls.ssl, data, len); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + 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 OPENSSL_1_1_1_API + if (tls.earlybuf.rleft()) { + return tls.earlybuf.remove(data, len); + } +#endif // OPENSSL_1_1_1_API + + // 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(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + 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(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/lib/nghttp2/src/shrpx_connection.h b/lib/nghttp2/src/shrpx_connection.h new file mode 100644 index 00000000000..3a458488bb5 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +#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 using EVCb = void (*)(struct ev_loop *, T *, int); + +using IOCb = EVCb; +using TimerCb = EVCb; + +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 chainging 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::value, + "Conneciton 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/lib/nghttp2/src/shrpx_connection_handler.cc b/lib/nghttp2/src/shrpx_connection_handler.cc new file mode 100644 index 00000000000..9d78e80e9e1 --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection_handler.cc @@ -0,0 +1,1320 @@ +/* + * 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 +#endif // HAVE_UNISTD_H +#include +#include + +#include +#include +#include + +#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(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(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(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(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(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(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(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 &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 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, nullptr); + 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( + 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, nullptr); + 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( + 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 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 ticket_keys) { + ticket_keys_ = std::move(ticket_keys); + if (single_worker_) { + single_worker_->set_ticket_keys(ticket_keys_); + } +} + +const std::shared_ptr &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( + get_config()->tls.ocsp.fetch_ocsp_response_file.c_str()), + const_cast(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 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(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( + 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>(ocsp_.resp), + std::memory_order_release); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(quic_tls_ctx_data->mu); + quic_tls_ctx_data->ocsp_data = + std::make_shared>(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::move(ocsp_.resp)), + std::memory_order_release); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(tls_ctx_data->mu); + tls_ctx_data->ocsp_data = + std::make_shared>(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(); +} + +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(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 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 &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, + nullptr); + + 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 q; + { + std::lock_guard 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 &downstreamconf) { + send_serial_event( + SerialEvent(SerialEventType::REPLACE_DOWNSTREAM, downstreamconf)); +} + +void ConnectionHandler::send_serial_event(SerialEvent ev) { + { + std::lock_guard 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 & +ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const { + return indexed_ssl_ctx_[idx]; +} + +#ifdef ENABLE_HTTP3 +const std::vector & +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(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 qkms) { + quic_keying_materials_ = std::move(qkms); +} + +const std::shared_ptr & +ConnectionHandler::get_quic_keying_materials() const { + return quic_keying_materials_; +} + +void ConnectionHandler::set_cid_prefixes( + const std::vector> + &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 &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 &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 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(QUICIPCType::DGRAM_FORWARD); + *p++ = static_cast(remote_addr.len - 1); + p = std::copy_n(reinterpret_cast(&remote_addr.su), + remote_addr.len, p); + *p++ = static_cast(local_addr.len - 1); + p = std::copy_n(reinterpret_cast(&local_addr.su), + local_addr.len, p); + *p++ = pi.ecn; + + iovec msg_iov[] = { + { + .iov_base = header.data(), + .iov_len = static_cast(p - header.data()), + }, + { + .iov_base = const_cast(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 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 buf; + + ssize_t nread; + + while ((nread = recv(quic_ipc_fd_, buf.data(), buf.size(), 0)) == -1 && + errno == EINTR) + ; + + if (nread == -1) { + std::array 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(nread) < len) { + return 0; + } + + auto p = buf.data(); + if (*p != static_cast(QUICIPCType::DGRAM_FORWARD)) { + LOG(ERROR) << "Unknown QUICIPCType: " << static_cast(*p); + + return -1; + } + + ++p; + + auto pkt = std::make_unique(); + + auto remote_addrlen = static_cast(*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(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(*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(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(-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 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/lib/nghttp2/src/shrpx_connection_handler.h b/lib/nghttp2/src/shrpx_connection_handler.h new file mode 100644 index 00000000000..f3748ab678e --- /dev/null +++ b/lib/nghttp2/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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H + +#include +#include +#include +#include +#ifndef NOTHREADS +# include +#endif // NOTHREADS + +#ifdef HAVE_LIBBPF +# include +#endif // HAVE_LIBBPF + +#include + +#include + +#ifdef HAVE_NEVERBLEED +# include +#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 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 &downstreamconf) + : type(type), downstreamconf(downstreamconf) {} + + SerialEventType type; + std::shared_ptr 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> cid_prefixes, + int quic_ipc_fd) + : cid_prefixes{std::move(cid_prefixes)}, quic_ipc_fd{quic_ipc_fd} {} + + std::vector> 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 &ticket_keys); + void worker_reopen_log_files(); + void set_ticket_keys(std::shared_ptr ticket_keys); + const std::shared_ptr &get_ticket_keys() const; + struct ev_loop *get_loop() const; + Worker *get_single_worker() const; + void add_acceptor(std::unique_ptr 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 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 &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 &get_indexed_ssl_ctx(size_t idx) const; +#ifdef ENABLE_HTTP3 + const std::vector &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 qkms); + const std::shared_ptr &get_quic_keying_materials() const; + + void set_cid_prefixes( + const std::vector> + &cid_prefixes); + + void set_quic_lingering_worker_processes( + const std::vector &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 &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 &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 downstreamconf); + + void set_enable_acceptor_on_ocsp_completion(bool f); + +private: + // Stores all SSL_CTX objects. + std::vector 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> indexed_ssl_ctx_; +#ifdef ENABLE_HTTP3 + std::vector> cid_prefixes_; + std::vector> + lingering_cid_prefixes_; + int quic_ipc_fd_; + std::vector quic_lingering_worker_processes_; +# ifdef HAVE_LIBBPF + std::vector quic_bpf_refs_; +# endif // HAVE_LIBBPF + std::shared_ptr quic_keying_materials_; + std::vector quic_all_ssl_ctx_; + std::vector> quic_indexed_ssl_ctx_; +#endif // ENABLE_HTTP3 + OCSPUpdateContext ocsp_; + std::mt19937 &gen_; + // ev_loop for each worker + std::vector 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> workers_; + // mutex for serial event resive buffer handling + std::mutex serial_event_mu_; + // SerialEvent receive buffer + std::vector serial_events_; + // Worker instance used when single threaded mode (-n1) is used. + // Otherwise, nullptr and workers_ has instances of Worker instead. + std::unique_ptr single_worker_; + std::unique_ptr cert_tree_; +#ifdef ENABLE_HTTP3 + std::unique_ptr quic_cert_tree_; +#endif // ENABLE_HTTP3 + std::unique_ptr 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 ticket_keys_; + struct ev_loop *loop_; + std::vector> 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 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/lib/nghttp2/src/shrpx_dns_resolver.cc b/lib/nghttp2/src/shrpx_dns_resolver.cc new file mode 100644 index 00000000000..f83ecb786a9 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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(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(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(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(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(w->data); + resolv->on_timeout(); + process_result(resolv); +} +} // namespace + +namespace { +void stop_ev(struct ev_loop *loop, + const std::vector> &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(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> &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_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> &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/lib/nghttp2/src/shrpx_dns_resolver.h b/lib/nghttp2/src/shrpx_dns_resolver.h new file mode 100644 index 00000000000..e622f99625a --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include +#include + +#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; + +// 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> 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/lib/nghttp2/src/shrpx_dns_tracker.cc b/lib/nghttp2/src/shrpx_dns_tracker.cc new file mode 100644 index 00000000000..57387ce72b2 --- /dev/null +++ b/lib/nghttp2/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(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 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 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(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(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/lib/nghttp2/src/shrpx_dns_tracker.h b/lib/nghttp2/src/shrpx_dns_tracker.h new file mode 100644 index 00000000000..c7caac0c2e1 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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 resolv; + // DNSQuery interested in this name lookup result. The result is + // notified to them all. + DList 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 resolv, + ImmutableString host, DNSResolverStatus status, + const Address *result); + + void update_entry(ResolverEntry &ent, std::unique_ptr resolv, + DNSResolverStatus status, const Address *result); + + void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq); + + std::map 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/lib/nghttp2/src/shrpx_downstream.cc b/lib/nghttp2/src/shrpx_downstream.cc new file mode 100644 index 00000000000..9ea52b4ac05 --- /dev/null +++ b/lib/nghttp2/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 + +#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(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(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 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(dconn_.release())); +} + +DownstreamConnection *Downstream::get_downstream_connection() { + return dconn_.get(); +} + +std::unique_ptr 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(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 &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(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 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 &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/lib/nghttp2/src/shrpx_downstream.h b/lib/nghttp2/src/shrpx_downstream.h new file mode 100644 index 00000000000..146cae583b7 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +#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 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>>(); + } + 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>> + 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 dconn); + void detach_downstream_connection(); + DownstreamConnection *get_downstream_connection(); + // Returns dconn_ and nullifies dconn_. + std::unique_ptr 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 &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 &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 rcbufs_; +#ifdef ENABLE_HTTP3 + std::vector 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 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 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/lib/nghttp2/src/shrpx_downstream_connection.cc b/lib/nghttp2/src/shrpx_downstream_connection.cc new file mode 100644 index 00000000000..16a2d6fd188 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_downstream_connection.h b/lib/nghttp2/src/shrpx_downstream_connection.h new file mode 100644 index 00000000000..8efdcbef5a2 --- /dev/null +++ b/lib/nghttp2/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 + +#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 & + 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/lib/nghttp2/src/shrpx_downstream_connection_pool.cc b/lib/nghttp2/src/shrpx_downstream_connection_pool.cc new file mode 100644 index 00000000000..0ee66b60f1e --- /dev/null +++ b/lib/nghttp2/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 dconn) { + pool_.insert(dconn.release()); +} + +std::unique_ptr +DownstreamConnectionPool::pop_downstream_connection() { + if (pool_.empty()) { + return nullptr; + } + + auto it = std::begin(pool_); + auto dconn = std::unique_ptr(*it); + pool_.erase(it); + + return dconn; +} + +void DownstreamConnectionPool::remove_downstream_connection( + DownstreamConnection *dconn) { + pool_.erase(dconn); + delete dconn; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream_connection_pool.h b/lib/nghttp2/src/shrpx_downstream_connection_pool.h new file mode 100644 index 00000000000..34dc30d8754 --- /dev/null +++ b/lib/nghttp2/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 +#include + +namespace shrpx { + +class DownstreamConnection; + +class DownstreamConnectionPool { +public: + DownstreamConnectionPool(); + ~DownstreamConnectionPool(); + + void add_downstream_connection(std::unique_ptr dconn); + std::unique_ptr pop_downstream_connection(); + void remove_downstream_connection(DownstreamConnection *dconn); + void remove_all(); + +private: + std::set pool_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_CONNECTION_POOL_H diff --git a/lib/nghttp2/src/shrpx_downstream_queue.cc b/lib/nghttp2/src/shrpx_downstream_queue.cc new file mode 100644 index 00000000000..f8906e8e2e5 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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::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->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); + + 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/lib/nghttp2/src/shrpx_downstream_queue.h b/lib/nghttp2/src/shrpx_downstream_queue.h new file mode 100644 index 00000000000..a5b980fbb45 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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 blocked; + // The number of connections currently made to this host. + size_t num_active; + }; + + using HostEntryMap = std::map; + + // 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); + // 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 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/lib/nghttp2/src/shrpx_downstream_test.cc b/lib/nghttp2/src/shrpx_downstream_test.cc new file mode 100644 index 00000000000..6100b18050e --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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 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/lib/nghttp2/src/shrpx_downstream_test.h b/lib/nghttp2/src/shrpx_downstream_test.h new file mode 100644 index 00000000000..ef06ea301a9 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/shrpx_dual_dns_resolver.cc b/lib/nghttp2/src/shrpx_dual_dns_resolver.cc new file mode 100644 index 00000000000..8c6c5c9b308 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_dual_dns_resolver.h b/lib/nghttp2/src/shrpx_dual_dns_resolver.h new file mode 100644 index 00000000000..98065dab160 --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/shrpx_error.h b/lib/nghttp2/src/shrpx_error.h new file mode 100644 index 00000000000..c89611f1ca7 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_exec.cc b/lib/nghttp2/src/shrpx_exec.cc new file mode 100644 index 00000000000..d5cd0916b2b --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/shrpx_exec.h b/lib/nghttp2/src/shrpx_exec.h new file mode 100644 index 00000000000..2d8a7701755 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc new file mode 100644 index 00000000000..89e539630ad --- /dev/null +++ b/lib/nghttp2/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 & +HealthMonitorDownstreamConnection::get_downstream_addr_group() const { + static std::shared_ptr s; + return s; +} + +DownstreamAddr *HealthMonitorDownstreamConnection::get_addr() const { + return nullptr; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h new file mode 100644 index 00000000000..c0eb633459d --- /dev/null +++ b/lib/nghttp2/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 & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; +}; + +} // namespace shrpx + +#endif // SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_http.cc b/lib/nghttp2/src/shrpx_http.cc new file mode 100644 index 00000000000..ad32dc9e191 --- /dev/null +++ b/lib/nghttp2/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"()"), + status_string, StringRef::from_lit(" "), reason_phrase, + StringRef::from_lit("

"), status_string, + StringRef::from_lit(" "), reason_phrase, + StringRef::from_lit("

"), httpconf.server_name, + StringRef::from_lit("
")); +} + +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(": "); + } else { + nhdrs.append(p); + } + break; + } + if (redact) { + nhdrs.append(": \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"); + // =[; 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(&affinity_cookie), + reinterpret_cast(&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 &altsvcs) { + // =":"; + 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(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/lib/nghttp2/src/shrpx_http.h b/lib/nghttp2/src/shrpx_http.h new file mode 100644 index 00000000000..18935d82796 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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 +OutputIt create_via_header_value(OutputIt dst, int major, int minor) { + *dst++ = static_cast(major + '0'); + if (major < 2) { + *dst++ = '.'; + *dst++ = static_cast(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, "; " 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 &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/lib/nghttp2/src/shrpx_http2_downstream_connection.cc b/lib/nghttp2/src/shrpx_http2_downstream_connection.cc new file mode 100644 index 00000000000..d27dcc114d5 --- /dev/null +++ b/lib/nghttp2/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 +#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( + 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 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(); + 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 OPENSSL_1_1_1_API + 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 // OPENSSL_1_1_1_API + + 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 + << ": \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 & +Http2DownstreamConnection::get_downstream_addr_group() const { + return http2session_->get_downstream_addr_group(); +} + +DownstreamAddr *Http2DownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http2_downstream_connection.h b/lib/nghttp2/src/shrpx_http2_downstream_connection.h new file mode 100644 index 00000000000..0fc7d91cf24 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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 & + 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/lib/nghttp2/src/shrpx_http2_session.cc b/lib/nghttp2/src/shrpx_http2_session.cc new file mode 100644 index 00000000000..18c934bed41 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_session.cc @@ -0,0 +1,2433 @@ +/* + * 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 +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include + +#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(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(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(w->data); + auto http2session = static_cast(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(w->data); + auto http2session = static_cast(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(w->data); + auto http2session = static_cast(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(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(w->data); + http2session->check_retire(); +} +} // namespace + +Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, + Worker *worker, + const std::shared_ptr &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( + 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
(); + 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_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(&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(&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(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(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(); + 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(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( + 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(user_data); + auto sd = static_cast( + 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( + 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(user_data); + auto sd = static_cast( + 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(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(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( + 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( + 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(user_data); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + auto sd = static_cast( + 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( + 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( + 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( + 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( + 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(user_data); + auto sd = static_cast( + 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(user_data); + + if (frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) { + auto sd = static_cast( + 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(user_data); + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Failed to send control frame type=" + << static_cast(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( + 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{}; +} // 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(user_data); + auto sd = static_cast( + 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(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; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (!next_proto) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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 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 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 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 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(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(); + + 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 & +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/lib/nghttp2/src/shrpx_http2_session.h b/lib/nghttp2/src/shrpx_http2_session.h new file mode 100644 index 00000000000..31b25458bf2 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include + +#include + +#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 &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 &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 dconns_; + DList streams_; + std::function read_, write_; + std::function on_read_; + std::function on_write_; + // Used to parse the response from HTTP proxy + std::unique_ptr proxy_htp_; + Worker *worker_; + // NULL if no TLS is configured + SSL_CTX *ssl_ctx_; + std::shared_ptr 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
resolved_addr_; + std::unique_ptr 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/lib/nghttp2/src/shrpx_http2_upstream.cc b/lib/nghttp2/src/shrpx_http2_upstream.cc new file mode 100644 index 00000000000..c9f8a8caca2 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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(user_data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Stream stream_id=" << stream_id + << " is being closed"; + } + + auto downstream = static_cast( + 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(user_data); + auto downstream = static_cast( + 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(user_data); + auto downstream = static_cast( + 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(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(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 << ": \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(user_data); + auto handler = upstream->get_client_handler(); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + auto downstream = static_cast( + 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( + 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(user_data); + auto downstream = static_cast( + 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(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( + 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( + 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(user_data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Failed to send control frame type=" + << static_cast(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( + 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{}; +} // 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(source->ptr); + auto upstream = static_cast(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(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(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(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(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 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::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(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(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(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(source->ptr); + auto body = downstream->get_response_buf(); + assert(body); + auto upstream = static_cast(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(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 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(); + // 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{ + {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_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(); + // 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 &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 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 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(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 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/lib/nghttp2/src/shrpx_http2_upstream.h b/lib/nghttp2/src/shrpx_http2_upstream.h new file mode 100644 index 00000000000..739c6ffa509 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include + +#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); + 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 &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 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/lib/nghttp2/src/shrpx_http3_upstream.cc b/lib/nghttp2/src/shrpx_http3_upstream.cc new file mode 100644 index 00000000000..46800de04f0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http3_upstream.cc @@ -0,0 +1,2924 @@ +/* + * 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 +#include +#include +#include + +#include + +#include + +#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(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(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(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(conn_ref->user_data); + auto handler = static_cast(conn->data); + auto upstream = static_cast(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(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 buf; + + va_start(ap, fmt); + auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap); + va_end(ap); + + if (static_cast(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(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(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(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(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(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_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(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(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(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(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(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(user_data); + + if (upstream->stream_reset(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::stream_reset(int64_t stream_id) { + if (http_shutdown_stream_read(stream_id) != 0) { + return -1; + } + + if (ngtcp2_is_bidi_stream(stream_id)) { + auto rv = ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, + NGHTTP3_H3_NO_ERROR); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_write: " + << ngtcp2_strerror(rv); + return -1; + } + } + + return 0; +} + +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(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(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(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 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(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( + 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( + 0, std::numeric_limits::max())(worker->get_randgen()); + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + 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( + 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 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(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(&local_addr.su.sa), + static_cast(local_addr.len), + }, + { + const_cast(&remote_addr.su.sa), + static_cast(remote_addr.len), + }, + const_cast(faddr), + }; + + rv = ngtcp2_conn_server_new(&conn_, &initial_hd.scid, &scid, &path, + initial_hd.version, &callbacks, &settings, + ¶ms, 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 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(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(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(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(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast(nwrite) != gso_size)) { + auto faddr = static_cast(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(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(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(nwrite) < gso_size) { + auto faddr = static_cast(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(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(conn_user_data); + auto downstream = static_cast(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(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(); + // 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 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 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(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(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(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 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(); + // 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(&local_addr.su.sa), + static_cast(local_addr.len), + }, + { + const_cast(&remote_addr.su.sa), + static_cast(remote_addr.len), + }, + const_cast(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(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(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(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(user_data); + auto downstream = static_cast(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(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(user_data); + auto downstream = static_cast(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(user_data); + auto downstream = static_cast(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(user_data); + auto handler = upstream->get_client_handler(); + auto downstream = static_cast(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 << ": \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(user_data); + auto downstream = static_cast(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(user_data); + auto downstream = static_cast(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(conn_user_data); + auto downstream = static_cast(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(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(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{ + {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 &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(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 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/lib/nghttp2/src/shrpx_http3_upstream.h b/lib/nghttp2/src/shrpx_http3_upstream.h new file mode 100644 index 00000000000..18076522ad0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http3_upstream.h @@ -0,0 +1,194 @@ +/* + * 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 +#include + +#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); + 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); + int stream_reset(int64_t stream_id); + void log_response_headers(Downstream *downstream, + const std::vector &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 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 data; + } tx_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTP3_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_http_downstream_connection.cc b/lib/nghttp2/src/shrpx_http_downstream_connection.cc new file mode 100644 index 00000000000..bdb42943e51 --- /dev/null +++ b/lib/nghttp2/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 + +#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(w->data); + auto dconn = static_cast(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(w->data); + auto dconn = static_cast(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(w->data); + auto dconn = static_cast(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(w->data); + auto dconn = static_cast(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(w->data); + auto dconn = static_cast(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 &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( + 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
(); + } + 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 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 OPENSSL_1_1_1_API + auto conn = handler->get_connection(); + + if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) { + buf->append("Early-Data: 1\r\n"); + } +#endif // OPENSSL_1_1_1_API + + 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 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(w->data); + auto dconn = static_cast(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(w->data); + auto dconn = static_cast(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(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(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(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(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(htp->data); + auto &resp = downstream->response(); + + resp.recv_body_length += len; + + return downstream->get_upstream()->on_downstream_body( + downstream, reinterpret_cast(data), len, true); +} +} // namespace + +namespace { +int htp_msg_completecb(llhttp_t *htp) { + auto downstream = static_cast(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 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 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 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(data), datalen); + auto nproc = + htperr == HPE_OK + ? datalen + : static_cast(reinterpret_cast( + 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 & +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/lib/nghttp2/src/shrpx_http_downstream_connection.h b/lib/nghttp2/src/shrpx_http_downstream_connection.h new file mode 100644 index 00000000000..a453f0dc5cf --- /dev/null +++ b/lib/nghttp2/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 &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 & + 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 on_read_, on_write_, + signal_write_; + Worker *worker_; + // nullptr if TLS is not used. + SSL_CTX *ssl_ctx_; + std::shared_ptr 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
resolved_addr_; + std::unique_ptr 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/lib/nghttp2/src/shrpx_http_test.cc b/lib/nghttp2/src/shrpx_http_test.cc new file mode 100644 index 00000000000..3ace8702b7b --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H + +#include + +#include + +#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 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 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 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/lib/nghttp2/src/shrpx_http_test.h b/lib/nghttp2/src/shrpx_http_test.h new file mode 100644 index 00000000000..d50ab533971 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/shrpx_https_upstream.cc b/lib/nghttp2/src/shrpx_https_upstream.cc new file mode 100644 index 00000000000..49d20883430 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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(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(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(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(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(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(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 << ": \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(htp->data); + auto downstream = upstream->get_downstream(); + rv = downstream->push_upload_data_chunk( + reinterpret_cast(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(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(rb->pos()), + rb->rleft()); + + if (htperr == HPE_PAUSED_UPGRADE && + rb->pos() == + reinterpret_cast(llhttp_get_error_pos(&htp_))) { + llhttp_resume_after_upgrade(&htp_); + + htperr = llhttp_execute(&htp_, reinterpret_cast(rb->pos()), + rb->rleft()); + } + + auto nread = + htperr == HPE_OK + ? rb->rleft() + : reinterpret_cast(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(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 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) { + 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 HttpsUpstream::pop_downstream() { + return std::unique_ptr(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 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 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 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/lib/nghttp2/src/shrpx_https_upstream.h b/lib/nghttp2/src/shrpx_https_upstream.h new file mode 100644 index 00000000000..d85d2dea4e5 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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); + void delete_downstream(); + Downstream *get_downstream() const; + std::unique_ptr 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_; + IOControl ioctrl_; + // The number of requests seen so far. + size_t num_requests_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTPS_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_io_control.cc b/lib/nghttp2/src/shrpx_io_control.cc new file mode 100644 index 00000000000..f43a25767ca --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/shrpx_io_control.h b/lib/nghttp2/src/shrpx_io_control.h new file mode 100644 index 00000000000..d427fcbc34b --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/src/shrpx_live_check.cc b/lib/nghttp2/src/shrpx_live_check.cc new file mode 100644 index 00000000000..5d1057c6e84 --- /dev/null +++ b/lib/nghttp2/src/shrpx_live_check.cc @@ -0,0 +1,799 @@ +/* + * 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(w->data); + auto live_check = static_cast(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(w->data); + auto live_check = static_cast(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(w->data); + auto live_check = static_cast(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(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(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( + 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
(); + } + + 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; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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 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 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(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(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/lib/nghttp2/src/shrpx_live_check.h b/lib/nghttp2/src/shrpx_live_check.h new file mode 100644 index 00000000000..b65ecdcda55 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include + +#include + +#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 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
resolved_addr_; + std::unique_ptr 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/lib/nghttp2/src/shrpx_log.cc b/lib/nghttp2/src/shrpx_log.cc new file mode 100644 index 00000000000..de5e09fa156 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_SYSLOG_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_INTTYPES_H +# include +#endif // HAVE_INTTYPES_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#include + +#include +#include +#include +#include +#include +#include + +#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(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(rleft()), + begin_); + } else { + syslog(severity_to_syslog_level(severity_), "[%s] %.*s (%s:%d)", + SEVERITY_STR[severity_].c_str(), static_cast(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: + // (:) + 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(rleft()), begin_); + + if (rv < 0) { + return; + } + + auto nwrite = std::min(static_cast(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(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(last_), left, "%.9f", n); + if (rv > static_cast(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(last_), left, "%.9Lf", n); + if (rv > static_cast(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(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 +std::pair copy(const char *src, size_t srclen, + OutputIterator d_first, + OutputIterator d_last) { + auto nwrite = + std::min(static_cast(std::distance(d_first, d_last)), srclen); + return std::make_pair(std::copy_n(src, nwrite, d_first), d_last); +} +} // namespace + +namespace { +template +std::pair +copy(const char *src, OutputIterator d_first, OutputIterator d_last) { + return copy(src, strlen(src), d_first, d_last); +} +} // namespace + +namespace { +template +std::pair +copy(const StringRef &src, OutputIterator d_first, OutputIterator d_last) { + return copy(src.c_str(), src.size(), d_first, d_last); +} +} // namespace + +namespace { +template +std::pair +copy_l(const char (&src)[N], OutputIterator d_first, OutputIterator d_last) { + return copy(src, N - 1, d_first, d_last); +} +} // namespace + +namespace { +template +std::pair 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 +std::pair +copy_hex_low(const uint8_t *src, size_t srclen, OutputIterator d_first, + OutputIterator d_last) { + auto nwrite = std::min(static_cast(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 +std::pair copy(T n, OutputIterator d_first, + OutputIterator d_last) { + if (static_cast(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 +std::pair +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 +std::pair 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 &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 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("") + : 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( + 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 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/lib/nghttp2/src/shrpx_log.h b/lib/nghttp2/src/shrpx_log.h new file mode 100644 index 00000000000..bc30097c67c --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include +#include + +#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; + +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(n); } + Log &operator<<(int n) { return *this << static_cast(n); } + Log &operator<<(long n) { return *this << static_cast(n); } + Log &operator<<(long long n); + Log &operator<<(unsigned short n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned int n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned long n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned long long n); + Log &operator<<(float n) { return *this << static_cast(n); } + Log &operator<<(double n); + Log &operator<<(long double n); + Log &operator<<(bool n); + Log &operator<<(const void *p); + template Log &operator<<(const std::shared_ptr &ptr) { + return *this << ptr.get(); + } + Log &operator<<(void (*func)(Log &log)) { + func(*this); + return *this; + } + template void write_seq(InputIt first, InputIt last) { + if (full_) { + return; + } + + auto d = std::distance(first, last); + auto n = std::min(wleft(), static_cast(d)); + last_ = std::copy(first, first + n, last_); + update_full(); + } + + template 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 &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/lib/nghttp2/src/shrpx_log_config.cc b/lib/nghttp2/src/shrpx_log_config.cc new file mode 100644 index 00000000000..92eb0559164 --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include + +#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(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(&tid), + reinterpret_cast(&tid) + sizeof(tid)}); + thread_id = util::format_hex(reinterpret_cast(&tid_hash), + sizeof(tid_hash)); +} + +#ifndef NOTHREADS +# ifdef HAVE_THREAD_LOCAL +namespace { +thread_local std::unique_ptr config = std::make_unique(); +} // 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 config = std::make_unique(); +} // 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( + now.time_since_epoch()) == + std::chrono::duration_cast( + time_str_updated.time_since_epoch())) { + return; + } + + time_str_updated = now; + + tstamp = std::make_shared(now); +} + +void LogConfig::update_tstamp( + const std::chrono::system_clock::time_point &now) { + if (std::chrono::duration_cast( + now.time_since_epoch()) == + std::chrono::duration_cast( + time_str_updated.time_since_epoch())) { + return; + } + + time_str_updated = now; + + tstamp = std::make_shared(now); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_log_config.h b/lib/nghttp2/src/shrpx_log_config.h new file mode 100644 index 00000000000..76fa78b6594 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +struct Timestamp { + Timestamp(const std::chrono::system_clock::time_point &tp); + + std::array time_local_buf; + std::array time_iso8601_buf; + std::array 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 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/lib/nghttp2/src/shrpx_memcached_connection.cc b/lib/nghttp2/src/shrpx_memcached_connection.cc new file mode 100644 index 00000000000..f72cb111c77 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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(w->data); + auto mconn = static_cast(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(w->data); + auto mconn = static_cast(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(w->data); + auto mconn = static_cast(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(w->data); + auto mconn = static_cast(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> &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 iov; + std::array 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(std::end(buf) - p), v.iov_len); + p = std::copy_n(static_cast(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 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(*in); + return -1; + } + ++in; + + parse_state_.op = static_cast(*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(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(req->op) << ", got " + << static_cast(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(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(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(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(nwrite), buf.headbuf.rleft()); + buf.headbuf.drain(n); + nwrite -= n; + n = std::min(static_cast(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(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 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> 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/lib/nghttp2/src/shrpx_memcached_connection.h b/lib/nghttp2/src/shrpx_memcached_connection.h new file mode 100644 index 00000000000..2e097e1f8f9 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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 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 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> recvq_; + std::deque> sendq_; + std::deque sendbufv_; + std::function 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/lib/nghttp2/src/shrpx_memcached_dispatcher.cc b/lib/nghttp2/src/shrpx_memcached_dispatcher.cc new file mode 100644 index 00000000000..024bd5ad5ce --- /dev/null +++ b/lib/nghttp2/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(addr, loop_, ssl_ctx, + sni_name, mcpool, gen)) {} + +MemcachedDispatcher::~MemcachedDispatcher() {} + +int MemcachedDispatcher::add_request(std::unique_ptr req) { + if (mconn_->add_request(std::move(req)) != 0) { + return -1; + } + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_memcached_dispatcher.h b/lib/nghttp2/src/shrpx_memcached_dispatcher.h new file mode 100644 index 00000000000..de1af80ba04 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#include + +#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 req); + +private: + struct ev_loop *loop_; + std::unique_ptr mconn_; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_DISPATCHER_H diff --git a/lib/nghttp2/src/shrpx_memcached_request.h b/lib/nghttp2/src/shrpx_memcached_request.h new file mode 100644 index 00000000000..36969831943 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include "shrpx_memcached_result.h" + +namespace shrpx { + +enum class MemcachedOp : uint8_t { + GET = 0x00, + ADD = 0x02, +}; + +struct MemcachedRequest; + +using MemcachedResultCallback = + std::function; + +struct MemcachedRequest { + std::string key; + std::vector value; + MemcachedResultCallback cb; + uint32_t expiry; + MemcachedOp op; + bool canceled; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_REQUEST_H diff --git a/lib/nghttp2/src/shrpx_memcached_result.h b/lib/nghttp2/src/shrpx_memcached_result.h new file mode 100644 index 00000000000..60a87afc698 --- /dev/null +++ b/lib/nghttp2/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 + +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 value) + : value(std::move(value)), status_code(status_code) {} + + std::vector value; + MemcachedStatusCode status_code; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_RESULT_H diff --git a/lib/nghttp2/src/shrpx_mruby.cc b/lib/nghttp2/src/shrpx_mruby.cc new file mode 100644 index 00000000000..b5c6ed3c85a --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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 create_mruby_context(const StringRef &filename) { + if (filename.empty()) { + return std::make_unique(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(mrb, std::move(app), std::move(env)); +} + +mrb_sym intern_ptr(mrb_state *mrb, void *ptr) { + auto p = reinterpret_cast(ptr); + + return mrb_intern(mrb, reinterpret_cast(&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/lib/nghttp2/src/shrpx_mruby.h b/lib/nghttp2/src/shrpx_mruby.h new file mode 100644 index 00000000000..518063d1348 --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include + +#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 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/lib/nghttp2/src/shrpx_mruby_module.cc b/lib/nghttp2/src/shrpx_mruby_module.cc new file mode 100644 index 00000000000..27b7769c6df --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include +#include +#include + +#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(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/lib/nghttp2/src/shrpx_mruby_module.h b/lib/nghttp2/src/shrpx_mruby_module.h new file mode 100644 index 00000000000..a426bead406 --- /dev/null +++ b/lib/nghttp2/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 + +#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/lib/nghttp2/src/shrpx_mruby_module_env.cc b/lib/nghttp2/src/shrpx_mruby_module_env.cc new file mode 100644 index 00000000000..5ebd9c053a0 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#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(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(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(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(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(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(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(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(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 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(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(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(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(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(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(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(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(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(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(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(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/lib/nghttp2/src/shrpx_mruby_module_env.h b/lib/nghttp2/src/shrpx_mruby_module_env.h new file mode 100644 index 00000000000..08846788758 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_mruby_module_request.cc b/lib/nghttp2/src/shrpx_mruby_module_request.cc new file mode 100644 index 00000000000..1ed3aa90fa0 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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(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(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(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(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(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(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(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(n)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) { + auto data = static_cast(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(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(n)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_path(mrb_state *mrb, mrb_value self) { + auto data = static_cast(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(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(pathlen)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_headers(mrb_state *mrb, mrb_value self) { + auto data = static_cast(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(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(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(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(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(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(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(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/lib/nghttp2/src/shrpx_mruby_module_request.h b/lib/nghttp2/src/shrpx_mruby_module_request.h new file mode 100644 index 00000000000..e74f335131b --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_mruby_module_response.cc b/lib/nghttp2/src/shrpx_mruby_module_response.cc new file mode 100644 index 00000000000..1de1d5f07fd --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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/lib/nghttp2/src/shrpx_mruby_module_response.h b/lib/nghttp2/src/shrpx_mruby_module_response.h new file mode 100644 index 00000000000..a35b42b1199 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_null_downstream_connection.cc b/lib/nghttp2/src/shrpx_null_downstream_connection.cc new file mode 100644 index 00000000000..cd81c8aa0e6 --- /dev/null +++ b/lib/nghttp2/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 &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 & +NullDownstreamConnection::get_downstream_addr_group() const { + return group_; +} + +DownstreamAddr *NullDownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_null_downstream_connection.h b/lib/nghttp2/src/shrpx_null_downstream_connection.h new file mode 100644 index 00000000000..7defcc33313 --- /dev/null +++ b/lib/nghttp2/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 &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 & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; + +private: + std::shared_ptr group_; +}; + +} // namespace shrpx + +#endif // SHRPX_NULL_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_process.h b/lib/nghttp2/src/shrpx_process.h new file mode 100644 index 00000000000..d35461b9aad --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_quic.cc b/lib/nghttp2/src/shrpx_quic.cc new file mode 100644 index 00000000000..2d4de593369 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#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::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(data), datalen}; + msghdr msg{}; + msg.msg_name = const_cast(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(const_cast(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(const_cast(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 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::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::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::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::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(const_cast(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/lib/nghttp2/src/shrpx_quic.h b/lib/nghttp2/src/shrpx_quic.h new file mode 100644 index 00000000000..b2f60879bc5 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include + +#include "network.h" + +using namespace nghttp2; + +namespace std { +template <> struct hash { + 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(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/lib/nghttp2/src/shrpx_quic_connection_handler.cc b/lib/nghttp2/src/shrpx_quic_connection_handler.cc new file mode 100644 index 00000000000..1a4e6a8bb32 --- /dev/null +++ b/lib/nghttp2/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 + +#include +#include + +#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(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 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 host; + std::array 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; + } + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + assert(SSL_is_quic(ssl)); +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + SSL_set_accept_state(ssl); + + auto config = get_config(); + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data) { +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + SSL_set_quic_early_data_enabled(ssl, 1); +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + SSL_set_early_data_enabled(ssl, 1); +#endif // !(OPENSSL_1_1_1_API && !defined(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( + worker_, faddr->fd, ssl, StringRef{host.data()}, + StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr); + + auto upstream = std::make_unique(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(&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 host; + std::array 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 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 buf; + buf.resize(std::min(max_pktlen, static_cast(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(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(worker_, std::vector{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 sv{ + generate_reserved_version(remote_addr, version), + NGTCP2_PROTO_VER_V1, + }; + + std::array 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 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 rand_bytes; + + if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) { + return -1; + } + + std::array 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 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(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 scids, + std::vector 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/lib/nghttp2/src/shrpx_quic_connection_handler.h b/lib/nghttp2/src/shrpx_quic_connection_handler.h new file mode 100644 index 00000000000..29e73a41c1d --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include + +#include + +#include + +#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 scids, + std::vector 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 scids; + // QUIC packet which is sent in response to the incoming packet. It + // might be empty. + std::vector 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 connections_; + std::unordered_map 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/lib/nghttp2/src/shrpx_quic_listener.cc b/lib/nghttp2/src/shrpx_quic_listener.cc new file mode 100644 index 00000000000..9b9f1203aa7 --- /dev/null +++ b/lib/nghttp2/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(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 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(nread); + } + + auto data = buf.data(); + + for (;;) { + auto datalen = std::min(static_cast(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/lib/nghttp2/src/shrpx_quic_listener.h b/lib/nghttp2/src/shrpx_quic_listener.h new file mode 100644 index 00000000000..3d709216a10 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_rate_limit.cc b/lib/nghttp2/src/shrpx_rate_limit.cc new file mode 100644 index 00000000000..0d4f92126b7 --- /dev/null +++ b/lib/nghttp2/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 + +#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(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::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/lib/nghttp2/src/shrpx_rate_limit.h b/lib/nghttp2/src/shrpx_rate_limit.h new file mode 100644 index 00000000000..7502a27a2b1 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +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/lib/nghttp2/src/shrpx_router.cc b/lib/nghttp2/src/shrpx_router.cc new file mode 100644 index 00000000000..d3565db8913 --- /dev/null +++ b/lib/nghttp2/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 + +#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 &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 new_node) { + auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), + new_node->s[0], + [](const std::unique_ptr &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(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( + &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(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(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(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(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/lib/nghttp2/src/shrpx_router.h b/lib/nghttp2/src/shrpx_router.h new file mode 100644 index 00000000000..295db7e669a --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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> 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/lib/nghttp2/src/shrpx_router_test.cc b/lib/nghttp2/src/shrpx_router_test.cc new file mode 100644 index 00000000000..21c2f51964d --- /dev/null +++ b/lib/nghttp2/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 + +#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{ + {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{{ + {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{ + {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/lib/nghttp2/src/shrpx_router_test.h b/lib/nghttp2/src/shrpx_router_test.h new file mode 100644 index 00000000000..d39cb87b4a6 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/shrpx_signal.cc b/lib/nghttp2/src/shrpx_signal.cc new file mode 100644 index 00000000000..63fcc077600 --- /dev/null +++ b/lib/nghttp2/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 + +#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 +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{SIGPIPE}; +} // namespace + +namespace { +constexpr auto worker_proc_ign_signals = + std::array{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/lib/nghttp2/src/shrpx_signal.h b/lib/nghttp2/src/shrpx_signal.h new file mode 100644 index 00000000000..152ca369744 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/shrpx_tls.cc b/lib/nghttp2/src/shrpx_tls.cc new file mode 100644 index 00000000000..5dd2d150614 --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls.cc @@ -0,0 +1,2691 @@ +/* + * 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 +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#include +#include + +#include +#include +#include + +#include + +#include "ssl_compat.h" + +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_OCSP +# include +#endif // OPENSSL_NO_OCSP +#if OPENSSL_3_0_0_API +# include +# include +# include +#endif // OPENSSL_3_0_0_API +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL +# include +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +# 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 { + +#if !OPENSSL_1_1_API +namespace { +const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *x) { + return ASN1_STRING_data(x); +} +} // namespace +#endif // !OPENSSL_1_1_API + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) { + auto &prefs = get_config()->tls.alpn_prefs; + *data = prefs.data(); + *len = prefs.size(); + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +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 &out, + const std::vector &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(user_data); + auto len = static_cast(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(SSL_get_app_data(ssl)); + auto handler = static_cast(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 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()); + +#if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L + 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); + +# if OPENSSL_1_1_API + auto pubkey = X509_get0_pubkey(cert); +# else // !OPENSSL_1_1_API + auto pubkey = X509_get_pubkey(cert); +# endif // !OPENSSL_1_1_API + + if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) { + continue; + } + +# if OPENSSL_3_0_0_API + std::array 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 +# if OPENSSL_1_1_API + auto eckey = EVP_PKEY_get0_EC_KEY(pubkey); +# else // !OPENSSL_1_1_API + auto eckey = EVP_PKEY_get1_EC_KEY(pubkey); +# endif // !OPENSSL_1_1_API + + if (eckey == nullptr) { + continue; + } + + auto ecgroup = EC_KEY_get0_group(eckey); + auto cert_curve = EC_GROUP_get_curve_name(ecgroup); + +# if !OPENSSL_1_1_API + EC_KEY_free(eckey); + EVP_PKEY_free(pubkey); +# endif // !OPENSSL_1_1_API + + if (shared_curve == cert_curve) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } +# endif // !OPENSSL_3_0_0_API + } + } +#endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && + // OPENSSL_VERSION_NUMBER >= 0x10002000L + + SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]); + + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL +namespace { +std::shared_ptr> +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 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(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( + 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(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(SSL_get_app_data(ssl)); + auto handler = static_cast(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(); + req->op = MemcachedOp::ADD; + req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str(); + req->key += + util::format_hex(balloc, StringRef{id, static_cast(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(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(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, +#if OPENSSL_1_1_API || LIBRESSL_2_7_API + const unsigned char *id, +#else // !(OPENSSL_1_1_API || LIBRESSL_2_7_API) + unsigned char *id, +#endif // !(OPENSSL_1_1_API || LIBRESSL_2_7_API) + int idlen, int *copy) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(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(); + req->op = MemcachedOp::GET; + req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str(); + req->key += + util::format_hex(balloc, StringRef{id, static_cast(idlen)}); + req->cb = [conn](MemcachedRequest *, MemcachedResult res) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: returned status code " + << static_cast(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(SSL_get_app_data(ssl)); + auto handler = static_cast(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 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(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 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(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(SSL_get_app_data(ssl)); + if (conn && conn->tls.initial_handshake_done) { + auto handler = static_cast(conn->data); + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "TLS renegotiation started"; + } + handler->start_immediate_shutdown(); + } + } +} +} // namespace + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + // We assume that get_config()->npn_list contains ALPN protocol + // identifier sorted by preference order. So we just break when we + // found the first overlap. + for (const auto &target_proto_id : get_config()->tls.npn_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(proto_id); + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +#ifdef ENABLE_HTTP3 +# if OPENSSL_VERSION_NUMBER >= 0x10002000L +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 // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // ENABLE_HTTP3 + +#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# ifndef TLSEXT_TYPE_signed_certificate_timestamp +# define TLSEXT_TYPE_signed_certificate_timestamp 18 +# endif // !TLSEXT_TYPE_signed_certificate_timestamp + +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(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(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(SSL_get_app_data(ssl)); + conn->tls.sct_requested = true; + + return 1; +} +} // namespace + +# if !OPENSSL_1_1_1_API + +namespace { +int legacy_sct_add_cb(SSL *ssl, unsigned int ext_type, + const unsigned char **out, size_t *outlen, int *al, + void *add_arg) { + return sct_add_cb(ssl, ext_type, 0, out, outlen, nullptr, 0, al, add_arg); +} +} // namespace + +namespace { +void legacy_sct_free_cb(SSL *ssl, unsigned int ext_type, + const unsigned char *out, void *add_arg) { + sct_free_cb(ssl, ext_type, 0, out, add_arg); +} +} // namespace + +namespace { +int legacy_sct_parse_cb(SSL *ssl, unsigned int ext_type, + const unsigned char *in, size_t inlen, int *al, + void *parse_arg) { + return sct_parse_cb(ssl, ext_type, 0, in, inlen, nullptr, 0, al, parse_arg); +} +} // namespace + +# endif // !OPENSSL_1_1_1_API +#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && + // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#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(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(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 &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 &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 +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + ; + + 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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + 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(); + } +# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API + // It looks like we need this function call for OpenSSL 1.0.2. This + // function was deprecated in OpenSSL 1.1.0 and BoringSSL. + SSL_CTX_set_ecdh_auto(ssl_ctx, 1); +# endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L + // Use P-256, which is sufficiently secure at the time of this + // writing. + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + LOG(FATAL) << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +#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 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 + + // NPN advertisement +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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); + +#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // 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()) { +# if OPENSSL_1_1_1_API + // 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(); + } +# else // !OPENSSL_1_1_1_API + if (SSL_CTX_add_server_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, + nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // !OPENSSL_1_1_1_API + } +#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 // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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(); + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#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 &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 +# if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + ; + + 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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + 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(); + } +# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API + // It looks like we need this function call for OpenSSL 1.0.2. This + // function was deprecated in OpenSSL 1.1.0 and BoringSSL. + SSL_CTX_set_ecdh_auto(ssl_ctx, 1); +# endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L + // Use P-256, which is sufficiently secure at the time of this + // writing. + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + LOG(FATAL) << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +# 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 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 + +# if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); +# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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); + +# if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // 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()) { +# if OPENSSL_1_1_1_API + // 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(); + } +# else // !OPENSSL_1_1_1_API + if (SSL_CTX_add_server_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, + nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // !OPENSSL_1_1_1_API + } +# 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 // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data && + SSL_CTX_set_max_early_data(ssl_ctx, + std::numeric_limits::max()) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# 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 + +namespace { +int select_h2_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (!util::select_h2(const_cast(out), outlen, in, + inlen)) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +namespace { +int select_h1_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto end = in + inlen; + for (; in < end;) { + if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{in, in + (in[0] + 1)})) { + *out = const_cast(in) + 1; + *outlen = in[0]; + return SSL_TLSEXT_ERR_OK; + } + in += in[0] + 1; + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace + +namespace { +int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) { + auto conn = static_cast(SSL_get_app_data(ssl)); + switch (conn->proto) { + case Proto::HTTP1: + return select_h1_next_proto_cb(ssl, out, outlen, in, inlen, arg); + case Proto::HTTP2: + return select_h2_next_proto_cb(ssl, out, outlen, in, inlen, arg); + default: + return SSL_TLSEXT_ERR_NOACK; + } +} +} // namespace + +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, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)) { + 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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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 // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + 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 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 + + // NPN selection callback. This is required to set SSL_CTX because + // OpenSSL does not offer SSL_set_next_proto_select_cb. +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_cb, nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + + 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 host; + std::array 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(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( + 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(cn.c_str())); + + if (rv) { + return 0; + } + + return -1; +} + +int verify_dns_hostname(X509 *cert, const StringRef &hostname) { + auto altnames = static_cast( + 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(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(cn.c_str())); + + return -1; + } + cn = StringRef{cn.c_str(), cn.size() - 1}; + } + + auto rv = tls_hostname_match(cn, hostname); + OPENSSL_free(const_cast(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 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 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(&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> &indexed_ssl_ctx, + SSL_CTX *ssl_ctx) { + std::array buf; + +#if LIBRESSL_2_7_API || \ + (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) + auto cert = SSL_CTX_get0_certificate(ssl_ctx); +#else // !LIBRESSL_2_7_API && OPENSSL_VERSION_NUMBER < 0x10002000L + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + auto cert = load_certificate(tls_ctx_data->cert_file); + auto cert_deleter = defer(X509_free, cert); +#endif // !LIBRESSL_2_7_API && OPENSSL_VERSION_NUMBER < 0x10002000L + + auto altnames = static_cast( + 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(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(idx) < indexed_ssl_ctx.size()) { + indexed_ssl_ctx[idx].push_back(ssl_ctx); + } else { + assert(static_cast(idx) == indexed_ssl_ctx.size()); + indexed_ssl_ctx.emplace_back(std::vector{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(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(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(idx) < indexed_ssl_ctx.size()) { + indexed_ssl_ctx[idx].push_back(ssl_ctx); + } else { + assert(static_cast(idx) == indexed_ssl_ctx.size()); + indexed_ssl_ctx.emplace_back(std::vector{ssl_ctx}); + } + + return 0; +} + +bool in_proto_list(const std::vector &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 &all_ssl_ctx, + std::vector> &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 &all_ssl_ctx, + std::vector> &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, + select_next_proto_cb); +} + +void setup_downstream_http2_alpn(SSL *ssl) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN advertisement + auto alpn = util::get_default_alpn(); + SSL_set_alpn_protos(ssl, alpn.data(), alpn.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +void setup_downstream_http1_alpn(SSL *ssl) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN advertisement + SSL_set_alpn_protos(ssl, NGHTTP2_H1_1_ALPN.byte(), NGHTTP2_H1_1_ALPN.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +std::unique_ptr create_cert_lookup_tree() { + auto config = get_config(); + if (!upstream_tls_enabled(config->conn)) { + return nullptr; + } + return std::make_unique(); +} + +namespace { +std::vector serialize_ssl_session(SSL_SESSION *session) { + auto len = i2d_SSL_SESSION(session, nullptr); + auto buf = std::vector(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) { + +#if !defined(OPENSSL_NO_OCSP) && !LIBRESSL_IN_USE && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L + 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; + } + +# if OPENSSL_1_1_API + auto certid = OCSP_SINGLERESP_get0_id(sresp); +# else // !OPENSSL_1_1_API + auto certid = sresp->certId; +# endif // !OPENSSL_1_1_API + assert(certid != nullptr); + + ASN1_INTEGER *serial; + rv = OCSP_id_get0_info(nullptr, nullptr, nullptr, &serial, + const_cast(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 // !defined(OPENSSL_NO_OCSP) && !LIBRESSL_IN_USE + // && OPENSSL_VERSION_NUMBER >= 0x10002000L + + 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(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 b; + auto n = BN_bn2bin(bn, b.data()); + assert(n <= 20); + + return util::format_hex(balloc, StringRef{b.data(), static_cast(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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + struct tm tm; + rv = ASN1_TIME_to_tm(at, &tm); + if (rv != 1) { + return -1; + } + + t = nghttp2_timegm(&tm); +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + 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(slen)}); + if (tt == 0) { + return -1; + } + + t = tt; +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + return 0; +} +} // namespace + +int get_x509_not_before(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notBefore(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notBefore(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +int get_x509_not_after(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notAfter(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notAfter(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +} // namespace tls + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_tls.h b/lib/nghttp2/src/shrpx_tls.h new file mode 100644 index 00000000000..00265d55d7f --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls.h @@ -0,0 +1,337 @@ +/* + * 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 +#include + +#include +#include + +#include + +#ifdef HAVE_NEVERBLEED +# include +#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 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 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> 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 &sct_data + +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +); + +// Create client side SSL_CTX. This does not configure ALPN settings. +// |next_proto_select_cb| is for NPN. +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, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)); + +#ifdef ENABLE_HTTP3 +SSL_CTX *create_quic_ssl_client_context( +# ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +# endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)); +#endif // ENABLE_HTTP3 + +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 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 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> &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 &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 &tls_proto_list); + +int set_alpn_prefs(std::vector &out, + const std::vector &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 &all_ssl_ctx, + std::vector> &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 &all_ssl_ctx, + std::vector> &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 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/lib/nghttp2/src/shrpx_tls_test.cc b/lib/nghttp2/src/shrpx_tls_test.cc new file mode 100644 index 00000000000..02fb1681fa7 --- /dev/null +++ b/lib/nghttp2/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 + +#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(); + + 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(); + 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(); + 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(); + 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> 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 +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/lib/nghttp2/src/shrpx_tls_test.h b/lib/nghttp2/src/shrpx_tls_test.h new file mode 100644 index 00000000000..7edc742b7f2 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/shrpx_upstream.h b/lib/nghttp2/src/shrpx_upstream.h new file mode 100644 index 00000000000..3da62c9e9dc --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/shrpx_worker.cc b/lib/nghttp2/src/shrpx_worker.cc new file mode 100644 index 00000000000..a2b19fbfa76 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H +#include + +#include +#include + +#include + +#ifdef HAVE_LIBBPF +# include +# include +#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(w->data); + worker->process_events(); +} +} // namespace + +namespace { +void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(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(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>, + bool, SessionAffinity, StringRef, StringRef, SessionAffinityCookieSecure, + SessionAffinityCookieStickiness, int64_t, int64_t, StringRef, bool>; + +namespace { +DownstreamKey +create_downstream_key(const std::shared_ptr &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 &ticket_keys, + ConnectionHandler *conn_handler, + std::shared_ptr 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(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( + &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, + 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 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>(groups.size()); + + std::map 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> 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(); + dst->pattern = + ImmutableString{std::begin(src.pattern), std::end(src.pattern)}; + + auto shared_addr = std::make_shared(); + + 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( + 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(loop_, cl_ssl_ctx_, this, + &addr, randgen_); + } + + size_t seq = 0; + for (auto &addr : shared_addr->addrs) { + addr.dconn_pool = std::make_unique(); + 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 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(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 = 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 g(m_); + + q_.emplace_back(std::move(event)); + } + + ev_async_send(loop_, &w_); +} + +void Worker::process_events() { + WorkerEvent wev; + { + std::lock_guard 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(-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(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 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 g(ticket_keys_m_); + return ticket_keys_; +#endif // !HAVE_ATOMIC_STD_SHARED_PTR +} + +void Worker::set_ticket_keys(std::shared_ptr 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 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> & +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(&addr, this)); + } + + return 0; +} + +int Worker::create_quic_server_socket(UpstreamAddr &faddr) { + std::array 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 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(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(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(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(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(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(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(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(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(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(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 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 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> &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> &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/lib/nghttp2/src/shrpx_worker.h b/lib/nghttp2/src/shrpx_worker.h new file mode 100644 index 00000000000..3cc7b576235 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include +#include +#include +#ifndef NOTHREADS +# include +#endif // NOTHREADS + +#include +#include + +#include + +#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 connect_blocker; + std::unique_ptr live_check; + // Connection pool for this particular address if session affinity + // is enabled + std::unique_ptr 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 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, + 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 addrs; + std::vector wgs; + std::priority_queue, + WeightGroupEntryGreater> + pq; + // Bunch of session affinity hash. Only used if affinity == + // SessionAffinity::IP. + std::vector 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 affinity_hash_map; +#ifdef HAVE_MRUBY + std::shared_ptr 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 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 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 ticket_keys; + std::shared_ptr downstreamconf; +#ifdef ENABLE_HTTP3 + std::unique_ptr 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 &ticket_keys, + ConnectionHandler *conn_handler, + std::shared_ptr 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 get_ticket_keys(); + void set_ticket_keys(std::shared_ptr 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> & + get_downstream_addr_groups(); + + ConnectBlocker *get_connect_blocker() const; + + const DownstreamConfig *get_downstream_config() const; + + void + replace_downstream_config(std::shared_ptr 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 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 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 cid_prefix_; + std::vector quic_upstream_addrs_; + std::vector> quic_listeners_; +#endif // ENABLE_HTTP3 + + std::shared_ptr downstreamconf_; + std::unique_ptr session_cache_memcached_dispatcher_; +#ifdef HAVE_MRUBY + std::unique_ptr 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 ticket_keys_; + std::vector> downstream_addr_groups_; + // Worker level blocker for downstream connection. For example, + // this is used when file descriptor is exhausted. + std::unique_ptr 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> &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/lib/nghttp2/src/shrpx_worker_process.cc b/lib/nghttp2/src/shrpx_worker_process.cc new file mode 100644 index 00000000000..33ef29c47ce --- /dev/null +++ b/lib/nghttp2/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 +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#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 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(w->data); + std::array 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(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(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(&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(w->data); + const auto &old_ticket_keys = conn_handler->get_ticket_keys(); + + auto ticket_keys = std::make_shared(); + 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(std::chrono::duration_cast( + 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(w->data); + auto dispatcher = conn_handler->get_tls_ticket_key_memcached_dispatcher(); + + auto req = std::make_unique(); + 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(); + + 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 exitted; aborting now"; + + nghttp2_Exit(EXIT_FAILURE); +} +} // namespace +#endif // HAVE_NEVERBLEED + +namespace { +int send_ready_event(int ready_ipc_fd) { + std::array 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 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 nb_errbuf; + auto nb = std::make_unique(); + 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(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(&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( + &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 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(); + 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/lib/nghttp2/src/shrpx_worker_process.h b/lib/nghttp2/src/shrpx_worker_process.h new file mode 100644 index 00000000000..f4325031b41 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#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> 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 quic_lingering_worker_processes; +#endif // ENABLE_HTTP3 +}; + +int worker_process_event_loop(WorkerProcessConfig *wpconf); + +} // namespace shrpx + +#endif // SHRPX_WORKER_PROCESS_H diff --git a/lib/nghttp2/src/shrpx_worker_test.cc b/lib/nghttp2/src/shrpx_worker_test.cc new file mode 100644 index 00000000000..7c5c3299ec5 --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H + +#include + +#include + +#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>(); + 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(); + 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(); + g1->pattern = ImmutableString::from_lit("git.nghttp2.org"); + groups.push_back(std::move(g1)); + + auto g2 = std::make_shared(); + g2->pattern = ImmutableString::from_lit(".nghttp2.org"); + groups.push_back(std::move(g2)); + + auto g3 = std::make_shared(); + 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/lib/nghttp2/src/shrpx_worker_test.h b/lib/nghttp2/src/shrpx_worker_test.h new file mode 100644 index 00000000000..8ffa2f12d03 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/ssl_compat.h b/lib/nghttp2/src/ssl_compat.h new file mode 100644 index 00000000000..ed3071973b8 --- /dev/null +++ b/lib/nghttp2/src/ssl_compat.h @@ -0,0 +1,51 @@ +/* + * 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 + +# ifdef LIBRESSL_VERSION_NUMBER +# define OPENSSL_1_1_API 0 +# define OPENSSL_1_1_1_API 0 +# define OPENSSL_3_0_0_API 0 +# define LIBRESSL_IN_USE 1 +# define LIBRESSL_LEGACY_API (LIBRESSL_VERSION_NUMBER < 0x20700000L) +# define LIBRESSL_2_7_API (LIBRESSL_VERSION_NUMBER >= 0x20700000L) +# define LIBRESSL_3_5_API (LIBRESSL_VERSION_NUMBER >= 0x30500000L) +# else // !LIBRESSL_VERSION_NUMBER +# define OPENSSL_1_1_API (OPENSSL_VERSION_NUMBER >= 0x1010000fL) +# define OPENSSL_1_1_1_API (OPENSSL_VERSION_NUMBER >= 0x10101000L) +# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L) +# define LIBRESSL_IN_USE 0 +# define LIBRESSL_LEGACY_API 0 +# define LIBRESSL_2_7_API 0 +# define LIBRESSL_3_5_API 0 +# 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 + +#endif // OPENSSL_COMPAT_H diff --git a/lib/nghttp2/src/template.h b/lib/nghttp2/src/template.h new file mode 100644 index 00000000000..530a1d13334 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nghttp2 { + +// std::forward is constexpr since C++14 +template +constexpr std::array< + typename std::decay::type>::type, + sizeof...(T)> +make_array(T &&...t) { + return std::array< + typename std::decay::type>::type, + sizeof...(T)>{{std::forward(t)...}}; +} + +template constexpr size_t array_size(T (&)[N]) { + return N; +} + +template constexpr size_t str_size(T (&)[N]) { + return N - 1; +} + +// inspired by , but our +// template can take functions returning other than void. +template struct Defer { + Defer(F &&f, T &&...t) + : f(std::bind(std::forward(f), std::forward(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = typename std::result_of::type( + typename std::decay::type...)>::type; + std::function f; +}; + +template Defer defer(F &&f, T &&...t) { + return Defer(std::forward(f), std::forward(t)...); +} + +template 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 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 void dlist_delete_all(DList &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 +std::unique_ptr strcopy(InputIt first, InputIt last) { + auto res = std::make_unique(last - first + 1); + *std::copy(first, last, res.get()) = '\0'; + return res; +} + +// Returns a copy of NULL-terminated string |val|. +inline std::unique_ptr strcopy(const char *val) { + return strcopy(val, val + strlen(val)); +} + +inline std::unique_ptr strcopy(const char *val, size_t n) { + return strcopy(val, val + n); +} + +// Returns a copy of val.c_str(). +inline std::unique_ptr strcopy(const std::string &val) { + return strcopy(std::begin(val), std::end(val)); +} + +inline std::unique_ptr strcopy(const std::unique_ptr &val) { + if (!val) { + return nullptr; + } + return strcopy(val.get()); +} + +inline std::unique_ptr strcopy(const std::unique_ptr &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; + using value_type = traits_type::char_type; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = + std::allocator_traits::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; + + 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 + 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 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 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; + using value_type = traits_type::char_type; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = + std::allocator_traits::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; + + 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 + constexpr StringRef(const CharT *s, size_t n) + : base(reinterpret_cast(s)), len(n) {} + template + StringRef(InputIt first, InputIt last) + : base(reinterpret_cast(&*first)), + len(std::distance(first, last)) {} + template + StringRef(InputIt *first, InputIt *last) + : base(reinterpret_cast(first)), + len(std::distance(first, last)) {} + template + 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(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 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 { + 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(c); + h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); + } + return h; + } +}; +} // namespace std + +#endif // TEMPLATE_H diff --git a/lib/nghttp2/src/template_test.cc b/lib/nghttp2/src/template_test.cc new file mode 100644 index 00000000000..4a773158b54 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include + +#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/lib/nghttp2/src/template_test.h b/lib/nghttp2/src/template_test.h new file mode 100644 index 00000000000..2c1448f34df --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/test.example.com-key.pem b/lib/nghttp2/src/test.example.com-key.pem new file mode 100644 index 00000000000..6d5515cfc98 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.example.com.csr b/lib/nghttp2/src/test.example.com.csr new file mode 100644 index 00000000000..816393545dd --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.example.com.csr.json b/lib/nghttp2/src/test.example.com.csr.json new file mode 100644 index 00000000000..5cd3e1efd66 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.example.com.pem b/lib/nghttp2/src/test.example.com.pem new file mode 100644 index 00000000000..1c7e71ef185 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.nghttp2.org-key.pem b/lib/nghttp2/src/test.nghttp2.org-key.pem new file mode 100644 index 00000000000..253289577d0 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.nghttp2.org.csr b/lib/nghttp2/src/test.nghttp2.org.csr new file mode 100644 index 00000000000..dc4bb105dac --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.nghttp2.org.csr.json b/lib/nghttp2/src/test.nghttp2.org.csr.json new file mode 100644 index 00000000000..5ee306955f3 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/test.nghttp2.org.pem b/lib/nghttp2/src/test.nghttp2.org.pem new file mode 100644 index 00000000000..0c386fc0eec --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/testdata/Makefile.am b/lib/nghttp2/src/testdata/Makefile.am new file mode 100644 index 00000000000..b1cb575c012 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/testdata/ipaddr.crt b/lib/nghttp2/src/testdata/ipaddr.crt new file mode 100644 index 00000000000..cdacdf03137 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/testdata/nosan.crt b/lib/nghttp2/src/testdata/nosan.crt new file mode 100644 index 00000000000..ebbb5c04de2 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/testdata/nosan_ip.crt b/lib/nghttp2/src/testdata/nosan_ip.crt new file mode 100644 index 00000000000..717a1bd847a --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/testdata/verify_hostname.crt b/lib/nghttp2/src/testdata/verify_hostname.crt new file mode 100644 index 00000000000..a327616fde1 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/src/timegm.c b/lib/nghttp2/src/timegm.c new file mode 100644 index 00000000000..fc6df82494a --- /dev/null +++ b/lib/nghttp2/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 + +/* 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; + } +#endif /* SIZEOF_TIME_T == 4 */ + + 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; + } +#endif /* SIZEOF_TIME_T == 4 */ + + return (time_t)t; +} diff --git a/lib/nghttp2/src/timegm.h b/lib/nghttp2/src/timegm.h new file mode 100644 index 00000000000..56f9cc6c964 --- /dev/null +++ b/lib/nghttp2/src/timegm.h @@ -0,0 +1,51 @@ +/* + * 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 +#endif /* HAVE_CONFIG_H */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef HAVE_TIME_H +# include +#endif // HAVE_TIME_H + +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/lib/nghttp2/src/tls.cc b/lib/nghttp2/src/tls.cc new file mode 100644 index 00000000000..b0cadfd62f4 --- /dev/null +++ b/lib/nghttp2/src/tls.cc @@ -0,0 +1,201 @@ +/* + * 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 +#include +#include +#include + +#include +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +#if OPENSSL_1_1_API + +// CRYPTO_LOCK is deprecated as of OpenSSL 1.1.0 +LibsslGlobalLock::LibsslGlobalLock() {} + +#else // !OPENSSL_1_1_API + +namespace { +std::mutex *ssl_global_locks; +} // namespace + +namespace { +void ssl_locking_cb(int mode, int type, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + ssl_global_locks[type].lock(); + } else { + ssl_global_locks[type].unlock(); + } +} +} // namespace + +LibsslGlobalLock::LibsslGlobalLock() { + if (ssl_global_locks) { + std::cerr << "OpenSSL global lock has been already set" << std::endl; + assert(0); + } + ssl_global_locks = new std::mutex[CRYPTO_num_locks()]; + // CRYPTO_set_id_callback(ssl_thread_id); OpenSSL manual says that + // if threadid_func is not specified using + // CRYPTO_THREADID_set_callback(), then default implementation is + // used. We use this default one. + CRYPTO_set_locking_callback(ssl_locking_cb); +} + +#endif // !OPENSSL_1_1_API + +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); +} + +void libssl_init() { +#if OPENSSL_1_1_API +// No explicit initialization is required. +#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + CRYPTO_library_init(); +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + OPENSSL_config(nullptr); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) +} + +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { +#if OPENSSL_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + 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; +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + long int opts = 0; + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (min > TLS1_VERSION) { + opts |= SSL_OP_NO_TLSv1; + } + if (min > TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + if (min > TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + + if (max < TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + if (max < TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + + SSL_CTX_set_options(ssl_ctx, opts); + + return 0; +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) +} + +} // namespace tls + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/tls.h b/lib/nghttp2/src/tls.h new file mode 100644 index 00000000000..332ccb65310 --- /dev/null +++ b/lib/nghttp2/src/tls.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 TLS_H +#define TLS_H + +#include "nghttp2_config.h" + +#include + +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +// Acquire OpenSSL global lock to share SSL_CTX across multiple +// threads. The constructor acquires lock and destructor unlocks. +class LibsslGlobalLock { +public: + LibsslGlobalLock(); + LibsslGlobalLock(const LibsslGlobalLock &) = delete; + LibsslGlobalLock &operator=(const LibsslGlobalLock &) = delete; +}; + +// 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 OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" +#else + "" +#endif + ; + +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); + +// Initializes OpenSSL library +void libssl_init(); + +// 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/lib/nghttp2/src/util.cc b/lib/nghttp2/src/util.cc new file mode 100644 index 00000000000..9d0c1bd7bd8 --- /dev/null +++ b/lib/nghttp2/src/util.cc @@ -0,0 +1,1808 @@ +/* + * 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" + +#ifdef HAVE_TIME_H +# include +#endif // HAVE_TIME_H +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_NETINET_IP_H +# include +#endif // HAVE_NETINET_IP_H +#include +#ifdef _WIN32 +# include +#else // !_WIN32 +# include +#endif // !_WIN32 +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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(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 +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>(3, std::vector(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>(); + 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(unkoptlen)) { + // Exact match, then we don't show any condidates. + return; + } + ++prefix_match; + cands.emplace_back(0, options[i].name); + continue; + } + // Use cost 0 for suffix match, but match at least 3 characters + if (unkoptlen >= 3 && + 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 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 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(sa)->sun_path; + } +#endif // !_WIN32 + + std::array host; + std::array 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(malloc(len + 1)); + if (path == nullptr) { + return nullptr; + } + memcpy(path, argv0, len + 1); + } else { + auto cwdlen = strlen(cwd); + path = static_cast(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 proto_list) { + for (const auto &proto : proto_list) { + if (select_proto(out, outlen, in, inlen, StringRef{proto})) { + return true; + } + } + + return false; +} + +std::vector get_default_alpn() { + auto res = std::vector(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 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(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 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(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 parse_config_str_list(const StringRef &s, char delim) { + auto sublist = split_str(s, delim); + auto res = std::vector(); + 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(&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 parse_uint_digits(const void *ss, size_t len) { + const uint8_t *s = static_cast(ss); + int64_t n = 0; + size_t i; + if (len == 0) { + return {-1, 0}; + } + constexpr int64_t max = std::numeric_limits::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(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::max(); + if (n > max / mul) { + return -1; + } + return n * mul; +} + +int64_t parse_uint(const char *s) { + return parse_uint(reinterpret_cast(s), strlen(s)); +} + +int64_t parse_uint(const std::string &s) { + return parse_uint(reinterpret_cast(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(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::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(n); + } + switch (s[i]) { + case 'S': + case 's': + // seconds + if (i + 1 != len) { + goto fail; + } + return static_cast(n); + case 'M': + case 'm': + if (i + 1 == len) { + // minutes + if (n > max / 60) { + goto fail; + } + return static_cast(n) * 60; + } + + if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) { + goto fail; + } + // milliseconds + return static_cast(n) / 1000.; + case 'H': + case 'h': + // hours + if (i + 1 != len) { + goto fail; + } + if (n > max / 3600) { + goto fail; + } + return static_cast(n) * 3600; + } +fail: + return std::numeric_limits::infinity(); +} + +std::string duration_str(double t) { + if (t == 0.) { + return "0"; + } + auto frac = static_cast(t * 1000) % 1000; + if (frac > 0) { + return utos(static_cast(t * 1000)) + "ms"; + } + auto v = static_cast(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(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(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 buf{}; + auto end = src + len; + auto i = src; + for (;;) { + auto nextlen = + std::min(static_cast(16), static_cast(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(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(data[0]) << 56; + n += static_cast(data[1]) << 48; + n += static_cast(data[2]) << 40; + n += static_cast(data[3]) << 32; + n += static_cast(data[4]) << 24; + n += data[5] << 16; + n += data[6] << 8; + n += data[7]; + return n; +} + +int read_mime_types(std::map &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; +} + +#if !OPENSSL_1_1_API +namespace { +EVP_MD_CTX *EVP_MD_CTX_new(void) { return EVP_MD_CTX_create(); } +} // namespace + +namespace { +void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } +} // namespace +#endif // !OPENSSL_1_1_API + +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 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(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/lib/nghttp2/src/util.h b/lib/nghttp2/src/util.h new file mode 100644 index 00000000000..d818bf2fd3b --- /dev/null +++ b/lib/nghttp2/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 +#endif // HAVE_UNISTD_H +#include +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBEV +# include +#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 +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 +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 +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 std::string format_hex(const unsigned char (&s)[N]) { + return format_hex(s, N); +} + +template std::string format_hex(const std::array &s) { + return format_hex(s.data(), s.size()); +} + +StringRef format_hex(BlockAllocator &balloc, const StringRef &s); + +static constexpr char LOWER_XDIGITS[] = "0123456789abcdef"; + +template +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 +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(c)]; +} + +template +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 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 +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 bool istarts_with(const S &a, const T &b) { + return istarts_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool istarts_with_l(const T &a, const CharT (&b)[N]) { + return istarts_with(a.begin(), a.end(), b, b + N - 1); +} + +template +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 bool ends_with(const T &a, const S &b) { + return ends_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool ends_with_l(const T &a, const CharT (&b)[N]) { + return ends_with(a.begin(), a.end(), b, b + N - 1); +} + +template +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 bool iends_with(const T &a, const S &b) { + return iends_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool iends_with_l(const T &a, const CharT (&b)[N]) { + return iends_with(a.begin(), a.end(), b, b + N - 1); +} + +template +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 bool strieq(const T &a, const S &b) { + return strieq(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool strieq_l(const CharT (&a)[N], InputIt b, size_t blen) { + return strieq(a, a + (N - 1), b, b + blen); +} + +template +bool strieq_l(const CharT (&a)[N], const T &b) { + return strieq(a, a + (N - 1), b.begin(), b.end()); +} + +template +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 bool streq(const T &a, const S &b) { + return streq(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool streq_l(const CharT (&a)[N], InputIt b, size_t blen) { + return streq(a, a + (N - 1), b, b + blen); +} + +template +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 bool strifind(const S &a, const T &b) { + return std::search(a.begin(), a.end(), b.begin(), b.end(), CaseCmp()) != + a.end(); +} + +template 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 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 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 +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 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 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(n) / (1 << b)) + u; +} + +template 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 :. 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 proto_list); + +// Returns default ALPN protocol list, which only contains supported +// HTTP/2 protocol identifier. +std::vector 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 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 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 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 StringRef format_common_log(char *out, const T &tp) { + auto t = + std::chrono::duration_cast(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 std::string format_iso8601(const T &tp) { + auto t = std::chrono::duration_cast( + 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 StringRef format_iso8601(char *out, const T &tp) { + auto t = std::chrono::duration_cast( + 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 StringRef format_iso8601_basic(char *out, const T &tp) { + auto t = std::chrono::duration_cast( + 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 StringRef format_http_date(char *out, const T &tp) { + auto t = + std::chrono::duration_cast(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 Rep clock_precision() { + std::chrono::duration duration = typename Clock::duration(1); + + return duration.count(); +} + +#ifdef HAVE_LIBEV +template +Duration duration_from(ev_tstamp d) { + return std::chrono::duration_cast(std::chrono::duration(d)); +} + +template ev_tstamp ev_tstamp_from(const Duration &d) { + return std::chrono::duration(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::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 +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 +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 &res, + const char *filename); + +// Fills random alpha and digit byte to the range [|first|, |last|). +// Returns the one beyond the |last|. +template +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 +void random_bytes(OutputIt first, OutputIt last, Generator &gen) { + std::uniform_int_distribution 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 +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(len - 1); ++i) { + auto dis = std::uniform_int_distribution(i, len - 1); + auto j = dis(gen); + if (i == j) { + continue; + } + fun(first + i, first + j); + } +} + +template +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 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/lib/nghttp2/src/util_test.cc b/lib/nghttp2/src/util_test.cc new file mode 100644 index 00000000000..0ac0f1de61d --- /dev/null +++ b/lib/nghttp2/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 +#include +#include + +#include + +#include + +#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(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 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::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 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 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{ + {0x01, 0x12, 0x34, 0x56, 0xff, 0x9a, 0xab, 0xbc}}; + + auto n = util::get_uint64(v.data()); + + CU_ASSERT(0x01123456ff9aabbcULL == n); + } + { + auto v = std::array{ + {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 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 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::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',')); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',')); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("")}) == + util::split_str(StringRef::from_lit("alpha,"), ',')); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo")}) == + util::split_str(StringRef::from_lit("alpha,bravo"), ',')); + CU_ASSERT((std::vector{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::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::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',', 1)); + CU_ASSERT(std::vector{StringRef::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',', 2)); + CU_ASSERT( + (std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo,charlie")}) == + util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2)); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',', 2)); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("")}) == + util::split_str(StringRef::from_lit("alpha,"), ',', 2)); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',', 0)); + CU_ASSERT( + std::vector{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/lib/nghttp2/src/util_test.h b/lib/nghttp2/src/util_test.h new file mode 100644 index 00000000000..48925ab2b1e --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/src/xsi_strerror.c b/lib/nghttp2/src/xsi_strerror.c new file mode 100644 index 00000000000..d008d4e26e4 --- /dev/null +++ b/lib/nghttp2/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 + +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/lib/nghttp2/src/xsi_strerror.h b/lib/nghttp2/src/xsi_strerror.h new file mode 100644 index 00000000000..32cadc34c44 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2/tests/.gitignore b/lib/nghttp2/tests/.gitignore new file mode 100644 index 00000000000..42dfe637767 --- /dev/null +++ b/lib/nghttp2/tests/.gitignore @@ -0,0 +1,3 @@ +# tests +failmalloc +main diff --git a/lib/nghttp2/tests/CMakeLists.txt b/lib/nghttp2/tests/CMakeLists.txt new file mode 100644 index 00000000000..0e05235e187 --- /dev/null +++ b/lib/nghttp2/tests/CMakeLists.txt @@ -0,0 +1,60 @@ +# 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_npn_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() + + if(ENABLE_APP) + # EXTRA_DIST = end_to_end.py + # TESTS += end_to_end.py + endif() +endif() diff --git a/lib/nghttp2/tests/Makefile.am b/lib/nghttp2/tests/Makefile.am new file mode 100644 index 00000000000..e6cdde111cb --- /dev/null +++ b/lib/nghttp2/tests/Makefile.am @@ -0,0 +1,100 @@ +# 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_npn_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_npn_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 + +if ENABLE_APP + +# EXTRA_DIST = end_to_end.py +# TESTS += end_to_end.py + +endif # ENABLE_APP + +endif # HAVE_CUNIT diff --git a/lib/nghttp2/tests/end_to_end.py b/lib/nghttp2/tests/end_to_end.py new file mode 100755 index 00000000000..cfb3cdd7dce --- /dev/null +++ b/lib/nghttp2/tests/end_to_end.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +"""End to end tests for the utility programs. + +This test assumes the utilities inside src directory have already been +built. + +At the moment top_buiddir is not in the environment, but top_builddir would be +more reliable than '..', so it's worth trying to pull it from the environment. +""" + +__author__ = 'Jim Morrison ' + + +import os +import subprocess +import time +import unittest + + +_PORT = 9893 + + +def _run_server(port, args): + srcdir = os.environ.get('srcdir', '.') + testdata = '%s/testdata' % srcdir + top_builddir = os.environ.get('top_builddir', '..') + base_args = ['%s/src/spdyd' % top_builddir, '-d', testdata] + if args: + base_args.extend(args) + base_args.extend([str(port), '%s/privkey.pem' % testdata, + '%s/cacert.pem' % testdata]) + return subprocess.Popen(base_args) + +def _check_server_up(port): + # Check this check for now. + time.sleep(1) + +def _kill_server(server): + while server.returncode is None: + server.terminate() + time.sleep(1) + server.poll() + + +class EndToEndSpdyTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.setUpServer([]) + + @classmethod + def setUpServer(cls, args): + cls.server = _run_server(_PORT, args) + _check_server_up(_PORT) + + @classmethod + def tearDownClass(cls): + _kill_server(cls.server) + + def setUp(self): + build_dir = os.environ.get('top_builddir', '..') + self.client = '%s/src/spdycat' % build_dir + self.stdout = 'No output' + + def call(self, path, args): + full_args = [self.client,'http://localhost:%d%s' % (_PORT, path)] + args + p = subprocess.Popen(full_args, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + self.stdout, self.stderr = p.communicate() + return p.returncode + + +class EndToEndSpdy2Tests(EndToEndSpdyTests): + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', [])) + + def testSimpleRequestSpdy3(self): + self.assertEquals(0, self.call('/', ['-v', '-3'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + def testFailedRequests(self): + self.assertEquals( + 2, self.call('/', ['https://localhost:25/', 'http://localhost:79'])) + + def testOneFailedRequest(self): + self.assertEquals(1, subprocess.call([self.client, 'http://localhost:2/'])) + + def testOneTimedOutRequest(self): + self.assertEquals(1, self.call('/?spdyd_do_not_respond_to_req=yes', + ['--timeout=2'])) + self.assertEquals(0, self.call('/', ['--timeout=20'])) + + +class EndToEndSpdy3Tests(EndToEndSpdyTests): + @classmethod + def setUpClass(cls): + cls.setUpServer(['-3']) + + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', ['-v'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + +if __name__ == '__main__': + unittest.main() diff --git a/lib/nghttp2/tests/failmalloc.c b/lib/nghttp2/tests/failmalloc.c new file mode 100644 index 00000000000..6294cffff88 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +/* 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/lib/nghttp2/tests/failmalloc_test.c b/lib/nghttp2/tests/failmalloc_test.c new file mode 100644 index 00000000000..594bc727e3f --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/failmalloc_test.h b/lib/nghttp2/tests/failmalloc_test.h new file mode 100644 index 00000000000..576932a7a93 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/main.c b/lib/nghttp2/tests/main.c new file mode 100644 index 00000000000..fc3c28d17a5 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +/* include test cases' include files here */ +#include "nghttp2_pq_test.h" +#include "nghttp2_map_test.h" +#include "nghttp2_queue_test.h" +#include "nghttp2_session_test.h" +#include "nghttp2_frame_test.h" +#include "nghttp2_stream_test.h" +#include "nghttp2_hd_test.h" +#include "nghttp2_npn_test.h" +#include "nghttp2_helper_test.h" +#include "nghttp2_buf_test.h" +#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, "npn", test_nghttp2_npn) || + !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || + !CU_add_test(pSuite, "session_recv_invalid_stream_id", + test_nghttp2_session_recv_invalid_stream_id) || + !CU_add_test(pSuite, "session_recv_invalid_frame", + test_nghttp2_session_recv_invalid_frame) || + !CU_add_test(pSuite, "session_recv_eof", test_nghttp2_session_recv_eof) || + !CU_add_test(pSuite, "session_recv_data", + test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_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/lib/nghttp2/tests/malloc_wrapper.c b/lib/nghttp2/tests/malloc_wrapper.c new file mode 100644 index 00000000000..f814c3dd9c1 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/tests/malloc_wrapper.h b/lib/nghttp2/tests/malloc_wrapper.h new file mode 100644 index 00000000000..a3a3dd79f73 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#include + +#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/lib/nghttp2/tests/nghttp2_buf_test.c b/lib/nghttp2/tests/nghttp2_buf_test.c new file mode 100644 index 00000000000..e3e8a14a5b6 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_buf_test.h b/lib/nghttp2/tests/nghttp2_buf_test.h new file mode 100644 index 00000000000..714b89fde6f --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_extpri_test.c b/lib/nghttp2/tests/nghttp2_extpri_test.c new file mode 100644 index 00000000000..0ef59b7428b --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_extpri_test.h b/lib/nghttp2/tests/nghttp2_extpri_test.h new file mode 100644 index 00000000000..a8a93b92d98 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_extpri_to_uint8(void); + +#endif /* NGHTTP2_EXTPRI_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_frame_test.c b/lib/nghttp2/tests/nghttp2_frame_test.c new file mode 100644 index 00000000000..7ce98dd045c --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/nghttp2_frame_test.h b/lib/nghttp2/tests/nghttp2_frame_test.h new file mode 100644 index 00000000000..dc0762573b2 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_hd_test.c b/lib/nghttp2/tests/nghttp2_hd_test.c new file mode 100644 index 00000000000..657d895faba --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/nghttp2_hd_test.h b/lib/nghttp2/tests/nghttp2_hd_test.h new file mode 100644 index 00000000000..ab0117cb9cc --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_helper_test.c b/lib/nghttp2/tests/nghttp2_helper_test.c new file mode 100644 index 00000000000..377f49d7666 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_helper_test.h b/lib/nghttp2/tests/nghttp2_helper_test.h new file mode 100644 index 00000000000..8790dcf6d5e --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_http_test.c b/lib/nghttp2/tests/nghttp2_http_test.c new file mode 100644 index 00000000000..19f345bae79 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/nghttp2_http_test.h b/lib/nghttp2/tests/nghttp2_http_test.h new file mode 100644 index 00000000000..e616cdcf492 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_http_parse_priority(void); + +#endif /* NGHTTP2_HTTP_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_map_test.c b/lib/nghttp2/tests/nghttp2_map_test.c new file mode 100644 index 00000000000..7ba9bd6b669 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_map_test.h b/lib/nghttp2/tests/nghttp2_map_test.h new file mode 100644 index 00000000000..235624de182 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_npn_test.c b/lib/nghttp2/tests/nghttp2_npn_test.c new file mode 100644 index 00000000000..99cb751a485 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_npn_test.c @@ -0,0 +1,73 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_npn_test.h" + +#include +#include + +#include +#include + +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; + unsigned char *out; + CU_ASSERT(1 == nghttp2_select_next_protocol(&out, &outlen, p, sizeof(p))); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0); +} + +static void http11(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + unsigned char outlen; + unsigned char *out; + CU_ASSERT(0 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(8 == outlen); + CU_ASSERT(memcmp("http/1.1", out, outlen) == 0); +} + +static void no_overlap(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '0', + }; + unsigned char outlen = 0; + unsigned char *out = NULL; + CU_ASSERT(-1 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(0 == outlen); + CU_ASSERT(NULL == out); +} + +void test_nghttp2_npn(void) { + http2(); + http11(); + no_overlap(); +} diff --git a/lib/nghttp2/tests/nghttp2_npn_test.h b/lib/nghttp2/tests/nghttp2_npn_test.h new file mode 100644 index 00000000000..f1c97631f1e --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_npn_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_NPN_TEST_H +#define NGHTTP2_NPN_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_npn(void); + +#endif /* NGHTTP2_NPN_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_pq_test.c b/lib/nghttp2/tests/nghttp2_pq_test.c new file mode 100644 index 00000000000..90db26de39d --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_pq_test.h b/lib/nghttp2/tests/nghttp2_pq_test.h new file mode 100644 index 00000000000..969662a954e --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_queue_test.c b/lib/nghttp2/tests/nghttp2_queue_test.c new file mode 100644 index 00000000000..cb993a81b03 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_queue_test.h b/lib/nghttp2/tests/nghttp2_queue_test.h new file mode 100644 index 00000000000..64f8ce85a74 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_queue(void); + +#endif /* NGHTTP2_QUEUE_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_ratelim_test.c b/lib/nghttp2/tests/nghttp2_ratelim_test.c new file mode 100644 index 00000000000..6abece95291 --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#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/lib/nghttp2/tests/nghttp2_ratelim_test.h b/lib/nghttp2/tests/nghttp2_ratelim_test.h new file mode 100644 index 00000000000..02b2f2b207c --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_ratelim_update(void); +void test_nghttp2_ratelim_drain(void); + +#endif /* NGHTTP2_RATELIM_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_session_test.c b/lib/nghttp2/tests/nghttp2_session_test.c new file mode 100644 index 00000000000..9f6a6673474 --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/nghttp2_session_test.h b/lib/nghttp2/tests/nghttp2_session_test.h new file mode 100644 index 00000000000..be4fdf89762 --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/nghttp2_stream_test.c b/lib/nghttp2/tests/nghttp2_stream_test.c new file mode 100644 index 00000000000..75b4d68043f --- /dev/null +++ b/lib/nghttp2/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 + +#include + +#include "nghttp2_stream.h" diff --git a/lib/nghttp2/tests/nghttp2_stream_test.h b/lib/nghttp2/tests/nghttp2_stream_test.h new file mode 100644 index 00000000000..ad7be640e63 --- /dev/null +++ b/lib/nghttp2/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 +#endif /* HAVE_CONFIG_H */ + +#endif /* NGHTTP2_STREAM_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_test_helper.c b/lib/nghttp2/tests/nghttp2_test_helper.c new file mode 100644 index 00000000000..1bd4a63fc8c --- /dev/null +++ b/lib/nghttp2/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 +#include + +#include + +#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/lib/nghttp2/tests/nghttp2_test_helper.h b/lib/nghttp2/tests/nghttp2_test_helper.h new file mode 100644 index 00000000000..c66298a00aa --- /dev/null +++ b/lib/nghttp2/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 +#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/lib/nghttp2/tests/testdata/Makefile.am b/lib/nghttp2/tests/testdata/Makefile.am new file mode 100644 index 00000000000..ee381133649 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/tests/testdata/cacert.pem b/lib/nghttp2/tests/testdata/cacert.pem new file mode 100644 index 00000000000..e462790dac9 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/tests/testdata/index.html b/lib/nghttp2/tests/testdata/index.html new file mode 100644 index 00000000000..cdd75fc5b9f --- /dev/null +++ b/lib/nghttp2/tests/testdata/index.html @@ -0,0 +1 @@ +small diff --git a/lib/nghttp2/tests/testdata/privkey.pem b/lib/nghttp2/tests/testdata/privkey.pem new file mode 100644 index 00000000000..0ab825b4e1d --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/CMakeLists.txt b/lib/nghttp2/third-party/CMakeLists.txt new file mode 100644 index 00000000000..b0ef0606279 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/Makefile.am b/lib/nghttp2/third-party/Makefile.am new file mode 100644 index 00000000000..8b47532e2bd --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/build_config.rb b/lib/nghttp2/third-party/build_config.rb new file mode 100644 index 00000000000..e06a4ccca4e --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/llhttp/LICENSE-MIT b/lib/nghttp2/third-party/llhttp/LICENSE-MIT new file mode 100644 index 00000000000..6c1512dd6bc --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/llhttp/README.md b/lib/nghttp2/third-party/llhttp/README.md new file mode 100644 index 00000000000..e982ed0ae71 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/llhttp/common.gypi b/lib/nghttp2/third-party/llhttp/common.gypi new file mode 100644 index 00000000000..ef7549f809d --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/llhttp/include/llhttp.h b/lib/nghttp2/third-party/llhttp/include/llhttp.h new file mode 100644 index 00000000000..6588ae5e368 --- /dev/null +++ b/lib/nghttp2/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 + +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 + +#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/lib/nghttp2/third-party/llhttp/llhttp.gyp b/lib/nghttp2/third-party/llhttp/llhttp.gyp new file mode 100644 index 00000000000..c7b8800a4f1 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/llhttp/src/api.c b/lib/nghttp2/third-party/llhttp/src/api.c new file mode 100644 index 00000000000..e0d03850514 --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/api.c @@ -0,0 +1,494 @@ +#include +#include +#include + +#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/lib/nghttp2/third-party/llhttp/src/http.c b/lib/nghttp2/third-party/llhttp/src/http.c new file mode 100644 index 00000000000..3a66044f5fb --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/http.c @@ -0,0 +1,150 @@ +#include +#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/lib/nghttp2/third-party/llhttp/src/llhttp.c b/lib/nghttp2/third-party/llhttp/src/llhttp.c new file mode 100644 index 00000000000..03f6d57c8cc --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/llhttp.c @@ -0,0 +1,9537 @@ +#include +#include +#include + +#ifdef __SSE4_2__ + #ifdef _MSC_VER + #include + #else /* !_MSC_VER */ + #include + #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/lib/nghttp2/third-party/url-parser/.gitignore b/lib/nghttp2/third-party/url-parser/.gitignore new file mode 100644 index 00000000000..c83c0130e87 --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/.gitignore @@ -0,0 +1 @@ +/.dirstamp diff --git a/lib/nghttp2/third-party/url-parser/AUTHORS b/lib/nghttp2/third-party/url-parser/AUTHORS new file mode 100644 index 00000000000..5323b685cae --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +Thomas LE ROUX +Randy Rizun +Andre Louis Caron +Simon Zimmermann +Erik Dubbelboer +Martell Malone +Bertrand Paquet +BogDan Vatra +Peter Faiman +Corey Richardson +Tóth Tamás +Cam Swords +Chris Dickinson +Uli Köhler +Charlie Somerville +Patrik Stutz +Fedor Indutny +runner +Alexis Campailla +David Wragg +Vinnie Falco +Alex Butum +Rex Feng +Alex Kocharin +Mark Koopman +Helge Heß +Alexis La Goutte +George Miroshnykov +Maciej Małecki +Marc O'Morain +Jeff Pinner +Timothy J Fontaine +Akagi201 +Romain Giraud +Jay Satiro +Arne Steen +Kjell Schubert +Olivier Mengué diff --git a/lib/nghttp2/third-party/url-parser/LICENSE-MIT b/lib/nghttp2/third-party/url-parser/LICENSE-MIT new file mode 100644 index 00000000000..1ec0ab4e174 --- /dev/null +++ b/lib/nghttp2/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/lib/nghttp2/third-party/url-parser/url_parser.c b/lib/nghttp2/third-party/url-parser/url_parser.c new file mode 100644 index 00000000000..4912ee206a0 --- /dev/null +++ b/lib/nghttp2/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 +#include +#include +#include +#include + +#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/lib/nghttp2/third-party/url-parser/url_parser.h b/lib/nghttp2/third-party/url-parser/url_parser.h new file mode 100644 index 00000000000..78b3096c531 --- /dev/null +++ b/lib/nghttp2/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 +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +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 +#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 From 4fc07a99cc9c09950e2eefc9e3803487759689bc Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 17:50:45 +0100 Subject: [PATCH 02/24] http_client: structure renamed to prevent conflict Signed-off-by: Leonardo Alminana --- include/fluent-bit/flb_http_client.h | 4 ++-- src/flb_http_client.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fluent-bit/flb_http_client.h b/include/fluent-bit/flb_http_client.h index 674b91213c1..f843be77818 100644 --- a/include/fluent-bit/flb_http_client.h +++ b/include/fluent-bit/flb_http_client.h @@ -61,7 +61,7 @@ #define FLB_HTTP_HEADER_CONNECTION "Connection" #define FLB_HTTP_HEADER_KA "keep-alive" -struct flb_http_response { +struct flb_http_client_response { int status; /* HTTP response status */ int content_length; /* Content length set by headers */ int chunked_encoding; /* Chunked transfer encoding ? */ @@ -131,7 +131,7 @@ struct flb_http_client { struct flb_http_proxy proxy; /* Response */ - struct flb_http_response resp; + struct flb_http_client_response resp; /* Reference to Callback context */ void *cb_ctx; diff --git a/src/flb_http_client.c b/src/flb_http_client.c index a3afa5264d1..4917da4fb19 100644 --- a/src/flb_http_client.c +++ b/src/flb_http_client.c @@ -270,7 +270,7 @@ static int process_chunked_data(struct flb_http_client *c) long val; char *p; char tmp[32]; - struct flb_http_response *r = &c->resp; + struct flb_http_client_response *r = &c->resp; chunk_start: p = strstr(r->chunk_processed_end, "\r\n"); From dfb2c8debff55fd8cd175fa1d7eb9556481bc56b Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 17:52:11 +0100 Subject: [PATCH 03/24] tests: http_client structure name updated to match the new one Signed-off-by: Leonardo Alminana --- tests/internal/http_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/internal/http_client.c b/tests/internal/http_client.c index 56c52b9653c..45e77e1b2ce 100644 --- a/tests/internal/http_client.c +++ b/tests/internal/http_client.c @@ -82,7 +82,7 @@ void test_http_buffer_increase() size_t s; struct test_ctx *ctx; struct flb_http_client *c; - struct flb_http_response *resp; + struct flb_http_client_response *resp; ctx = test_ctx_create(); if (!TEST_CHECK(ctx != NULL)) { From 943e4401c51dd0351f2e59ea7726994b2f9dcf30 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:01:06 +0100 Subject: [PATCH 04/24] tls: added alpn support for server sessions Signed-off-by: Leonardo Alminana --- include/fluent-bit/tls/flb_tls.h | 6 ++ src/tls/flb_tls.c | 10 +++ src/tls/openssl.c | 115 ++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/include/fluent-bit/tls/flb_tls.h b/include/fluent-bit/tls/flb_tls.h index d463361ce12..70a865f15cf 100644 --- a/include/fluent-bit/tls/flb_tls.h +++ b/include/fluent-bit/tls/flb_tls.h @@ -72,6 +72,9 @@ struct flb_tls_backend { /* destroy backend context */ void (*context_destroy) (void *); + /* Additional settings */ + int (*context_alpn_set) (void *, const char *); + /* Session management */ void *(*session_create) (struct flb_tls *, int); int (*session_destroy) (void *); @@ -106,6 +109,9 @@ struct flb_tls *flb_tls_create(int mode, const char *key_file, const char *key_passwd); int flb_tls_destroy(struct flb_tls *tls); + +int flb_tls_set_alpn(struct flb_tls *tls, const char *alpn); + int flb_tls_load_system_certificates(struct flb_tls *tls); struct mk_list *flb_tls_get_config_map(struct flb_config *config); diff --git a/src/tls/flb_tls.c b/src/tls/flb_tls.c index 8fc711ab389..8510381a850 100644 --- a/src/tls/flb_tls.c +++ b/src/tls/flb_tls.c @@ -222,6 +222,16 @@ int flb_tls_destroy(struct flb_tls *tls) return 0; } +int flb_tls_set_alpn(struct flb_tls *tls, const char *alpn) +{ + if (tls->ctx) { + return tls->api->context_alpn_set(tls->ctx, alpn); + } + + return 0; +} + + int flb_tls_net_read(struct flb_tls_session *session, void *buf, size_t len) { time_t timeout_timestamp; diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 341cf698bbf..f53f7201a86 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -39,6 +39,7 @@ struct tls_context { int debug_level; SSL_CTX *ctx; int mode; + char *alpn; pthread_mutex_t mutex; }; @@ -122,11 +123,113 @@ static void tls_context_destroy(void *ctx_backend) pthread_mutex_lock(&ctx->mutex); SSL_CTX_free(ctx->ctx); + if (ctx->alpn != NULL) { + flb_free(ctx->alpn); + } pthread_mutex_unlock(&ctx->mutex); flb_free(ctx); } +int tls_context_alpn_set(void *ctx_backend, const char *alpn) +{ + size_t wire_format_alpn_index; + char *alpn_token_context; + char *alpn_working_copy; + char *wire_format_alpn; + char *alpn_token; + int result; + struct tls_context *ctx; + + ctx = (struct tls_context *) ctx_backend; + + result = 0; + + if (alpn != NULL) { + wire_format_alpn = flb_calloc(strlen(alpn), + sizeof(char) + 1); + + if (wire_format_alpn == NULL) { + return -1; + } + + alpn_working_copy = strdup(alpn); + + if (alpn_working_copy == NULL) { + flb_free(wire_format_alpn); + + return -1; + } + + wire_format_alpn_index = 1; + alpn_token_context = NULL; + + alpn_token = strtok_r(alpn_working_copy, + ",", + &alpn_token_context); + + while (alpn_token != NULL) { + wire_format_alpn[wire_format_alpn_index] = \ + (char) strlen(alpn_token); + + strcpy(&wire_format_alpn[wire_format_alpn_index + 1], + alpn_token); + + wire_format_alpn_index += strlen(alpn_token) + 1; + + alpn_token = strtok_r(NULL, + ",", + &alpn_token_context); + } + + if (wire_format_alpn_index > 1) { + wire_format_alpn[0] = (char) wire_format_alpn_index - 1; + ctx->alpn = wire_format_alpn; + } + + free(alpn_working_copy); + } + + if (result != 0) { + result = -1; + } + + return result; +} + +static int tls_context_server_alpn_select_callback(SSL *ssl, + unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + int result; + struct tls_context *ctx; + + ctx = (struct tls_context *) arg; + + result = SSL_TLSEXT_ERR_NOACK; + + if (ctx->alpn != NULL) { + result = SSL_select_next_proto(out, + outlen, + &ctx->alpn[1], + (unsigned int) ctx->alpn[0], + in, + inlen); + + if (result == OPENSSL_NPN_NEGOTIATED) { + result = SSL_TLSEXT_ERR_OK; + } + else if (result == OPENSSL_NPN_NO_OVERLAP) { + result = SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + + return result; +} + #ifdef _MSC_VER static int windows_load_system_certificates(struct tls_context *ctx) { @@ -196,7 +299,7 @@ static int load_system_certificates(struct tls_context *ctx) } return 0; } - + static void *tls_context_create(int verify, int debug, int mode, @@ -242,6 +345,7 @@ static void *tls_context_create(int verify, ssl_ctx = SSL_CTX_new(TLS_client_method()); } #endif + if (!ssl_ctx) { flb_error("[openssl] could not create context"); return NULL; @@ -254,9 +358,16 @@ static void *tls_context_create(int verify, } ctx->ctx = ssl_ctx; ctx->mode = mode; + ctx->alpn = NULL; ctx->debug_level = debug; pthread_mutex_init(&ctx->mutex, NULL); + if (mode == FLB_TLS_SERVER_MODE) { + SSL_CTX_set_alpn_select_cb(ssl_ctx, + tls_context_server_alpn_select_callback, + ctx); + } + /* Verify peer: by default OpenSSL always verify peer */ if (verify == FLB_FALSE) { SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); @@ -388,6 +499,7 @@ static int tls_session_destroy(void *session) if (flb_socket_error(ptr->fd) == 0) { SSL_shutdown(ptr->ssl); } + SSL_free(ptr->ssl); flb_free(ptr); @@ -608,6 +720,7 @@ static struct flb_tls_backend tls_openssl = { .name = "openssl", .context_create = tls_context_create, .context_destroy = tls_context_destroy, + .context_alpn_set = tls_context_alpn_set, .session_create = tls_session_create, .session_destroy = tls_session_destroy, .net_read = tls_net_read, From 267e6537173cb39320311aec7ccc27196ef7ed6b Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:25:28 +0100 Subject: [PATCH 05/24] http_server: initial commit of the http server component Signed-off-by: Leonardo Alminana --- include/fluent-bit/flb_http_common.h | 176 ++++ .../fluent-bit/http_server/flb_http_server.h | 144 +++ .../http_server/flb_http_server_http1.h | 68 ++ .../http_server/flb_http_server_http2.h | 65 ++ src/CMakeLists.txt | 7 +- src/flb_http_common.c | 442 +++++++++ src/http_server/CMakeLists.txt | 3 + src/http_server/flb_http_server.c | 807 ++++++++++++++++ src/http_server/flb_http_server_http1.c | 491 ++++++++++ src/http_server/flb_http_server_http2.c | 858 ++++++++++++++++++ 10 files changed, 3060 insertions(+), 1 deletion(-) create mode 100644 include/fluent-bit/flb_http_common.h create mode 100755 include/fluent-bit/http_server/flb_http_server.h create mode 100644 include/fluent-bit/http_server/flb_http_server_http1.h create mode 100644 include/fluent-bit/http_server/flb_http_server_http2.h create mode 100644 src/flb_http_common.c create mode 100644 src/http_server/flb_http_server.c create mode 100644 src/http_server/flb_http_server_http1.c create mode 100644 src/http_server/flb_http_server_http2.c diff --git a/include/fluent-bit/flb_http_common.h b/include/fluent-bit/flb_http_common.h new file mode 100644 index 00000000000..e87cfe68db6 --- /dev/null +++ b/include/fluent-bit/flb_http_common.h @@ -0,0 +1,176 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_COMMON +#define FLB_HTTP_COMMON + +#include + +#include +#include + +/* These definitions are temporary and should be moved + * to monkey. + * This fallback has been added to be able to merge this + * feature with the current monkey version. + */ + +#ifndef MK_HTTP_PROTOCOL_20 +#define MK_HTTP_PROTOCOL_20 (20) +#endif + +#ifndef MK_HTTP_PROTOCOL_20_STR +#define MK_HTTP_PROTOCOL_20_STR "HTTP/2" +#endif + +#define HTTP_PROTOCOL_AUTODETECT -1 +#define HTTP_PROTOCOL_HTTP0 0 +#define HTTP_PROTOCOL_HTTP1 1 +#define HTTP_PROTOCOL_HTTP2 2 + +#define HTTP_PROTOCOL_VERSION_09 MK_HTTP_PROTOCOL_09 +#define HTTP_PROTOCOL_VERSION_10 MK_HTTP_PROTOCOL_10 +#define HTTP_PROTOCOL_VERSION_11 MK_HTTP_PROTOCOL_11 +#define HTTP_PROTOCOL_VERSION_20 MK_HTTP_PROTOCOL_20 + +#define HTTP_METHOD_GET MK_METHOD_GET +#define HTTP_METHOD_POST MK_METHOD_POST +#define HTTP_METHOD_HEAD MK_METHOD_HEAD +#define HTTP_METHOD_PUT MK_METHOD_PUT +#define HTTP_METHOD_DELETE MK_METHOD_DELETE +#define HTTP_METHOD_OPTIONS MK_METHOD_OPTIONS +#define HTTP_METHOD_UNKNOWN MK_METHOD_UNKNOWN + +#define HTTP_STREAM_ROLE_SERVER 0 +#define HTTP_STREAM_ROLE_CLIENT 1 + +#define HTTP_STREAM_STATUS_RECEIVING_HEADERS 0 +#define HTTP_STREAM_STATUS_RECEIVING_DATA 1 +#define HTTP_STREAM_STATUS_READY 2 +#define HTTP_STREAM_STATUS_PROCESSING 3 +#define HTTP_STREAM_STATUS_CLOSED 4 +#define HTTP_STREAM_STATUS_ERROR 5 + +struct flb_http_stream; +struct flb_http_server_session; + +struct flb_http_request { + int protocol_version; + int method; + cfl_sds_t path; + cfl_sds_t host; + cfl_sds_t query_string; + struct flb_hash_table *headers; + size_t content_length; + char *content_type; + cfl_sds_t body; + + struct flb_http_stream *stream; + + struct cfl_list _head; +}; + +struct flb_http_response { + int status; + cfl_sds_t message; + struct flb_hash_table *headers; + struct flb_hash_table *trailer_headers; + size_t content_length; + cfl_sds_t body; + size_t body_read_offset; + + struct flb_http_stream *stream; +}; + +struct flb_http_stream { + int32_t id; + int role; + int status; + + struct flb_http_request request; + struct flb_http_response response; + + void *parent; + void *user_data; + + int releasable; + struct cfl_list _head; +}; + +/* HTTP REQUEST */ + +int flb_http_request_init(struct flb_http_request *request); + +void flb_http_request_destroy(struct flb_http_request *request); + +char *flb_http_request_get_header(struct flb_http_request *request, + char *name); + +int flb_http_request_set_header(struct flb_http_request *request, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_request_unset_header(struct flb_http_request *request, + char *name); + +/* HTTP RESPONSE */ + +int flb_http_response_init(struct flb_http_response *response); + +void flb_http_response_destroy(struct flb_http_response *response); + +struct flb_http_response *flb_http_response_begin( + struct flb_http_server_session *session, + void *stream); + +int flb_http_response_commit(struct flb_http_response *response); + +int flb_http_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_response_set_trailer_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_response_set_status(struct flb_http_response *response, + int status); + +int flb_http_response_set_message(struct flb_http_response *response, + char *message); + +int flb_http_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +/* HTTP STREAM */ + +int flb_http_stream_init(struct flb_http_stream *stream, + void *parent, + int32_t stream_id, + int role, + void *user_data); + +struct flb_http_stream *flb_http_stream_create(void *parent, + int32_t stream_id, + int role, + void *user_data); + +void flb_http_stream_destroy(struct flb_http_stream *stream); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server.h b/include/fluent-bit/http_server/flb_http_server.h new file mode 100755 index 00000000000..aaadd91b075 --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server.h @@ -0,0 +1,144 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER +#define FLB_HTTP_SERVER + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#define HTTP_SERVER_INITIAL_BUFFER_SIZE (10 * 1024) +#define HTTP_SERVER_MAXIMUM_BUFFER_SIZE (10 * (1000 * 1024)) + +#define FLB_HTTP_SERVER_FLAG_KEEPALIVE (((uint64_t) 1) << 0) +#define FLB_HTTP_SERVER_FLAG_AUTO_INFLATE (((uint64_t) 1) << 1) + +#define HTTP_SERVER_SUCCESS 0 +#define HTTP_SERVER_PROVIDER_ERROR -1 +#define HTTP_SERVER_ALLOCATION_ERROR -2 + +#define HTTP_SERVER_UNINITIALIZED 0 +#define HTTP_SERVER_INITIALIZED 1 +#define HTTP_SERVER_RUNNING 2 +#define HTTP_SERVER_STOPPED 3 + +typedef int (*flb_http_server_request_processor_callback)( + struct flb_http_request *request, + struct flb_http_response *response); + +struct flb_http_server { + /* Internal */ + struct mk_event listener_event; + char *address; + unsigned short int port; + struct flb_tls *tls_provider; + int networking_flags; + struct flb_net_setup *networking_setup; + struct mk_event_loop *event_loop; + struct flb_config *system_context; + /* Internal */ + + uint64_t flags; + int status; + int protocol_version; + struct flb_downstream *downstream; + struct cfl_list clients; + flb_http_server_request_processor_callback + request_callback; + void *user_data; +}; + +struct flb_http_server_session { + struct flb_http1_server_session http1; + struct flb_http2_server_session http2; + + int version; + struct cfl_list request_queue; + + cfl_sds_t incoming_data; + cfl_sds_t outgoing_data; + + int releasable; + + struct flb_connection *connection; + struct flb_http_server *parent; + struct cfl_list _head; +}; + +#define FLB_HTTP_STREAM_GET_SESSION(stream, session) \ + *(session) = (typeof(*(session))) stream->parent; + +/* COMMON */ + +char *flb_http_server_convert_string_to_lowercase(char *input_buffer, + size_t length); + +int flb_http_server_strncasecmp(const uint8_t *first_buffer, + size_t first_length, + const char *second_buffer, + size_t second_length); + +/* HTTP SERVER */ + +int flb_http_server_init(struct flb_http_server *session, + int protocol_version, + uint64_t flags, + flb_http_server_request_processor_callback + request_callback, + char *address, + unsigned short int port, + struct flb_tls *tls_provider, + int networking_flags, + struct flb_net_setup *networking_setup, + struct mk_event_loop *event_loop, + struct flb_config *system_context, + void *user_data); + +int flb_http_server_start(struct flb_http_server *session); + +int flb_http_server_stop(struct flb_http_server *session); + +int flb_http_server_destroy(struct flb_http_server *session); + +/* HTTP SESSION */ + +int flb_http_server_session_init(struct flb_http_server_session *session, int version); + +struct flb_http_server_session *flb_http_server_session_create(int version); + +void flb_http_server_session_destroy(struct flb_http_server_session *session); + +int flb_http_server_session_ingest(struct flb_http_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server_http1.h b/include/fluent-bit/http_server/flb_http_server_http1.h new file mode 100644 index 00000000000..6510bbf45b6 --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server_http1.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER_HTTP1 +#define FLB_HTTP_SERVER_HTTP1 + +#include +#include +#include + +struct flb_http_server_session; +struct flb_http_stream; + +struct flb_http1_server_session { + struct mk_http_session inner_session; + struct mk_http_request inner_request; + struct mk_http_parser inner_parser; + struct mk_server inner_server; + struct flb_http_stream stream; + struct flb_http_server_session *parent; +}; + +/* RESPONSE */ + +struct flb_http_response *flb_http1_response_begin( + struct flb_http1_server_session *session, + struct flb_http_stream *stream); + +int flb_http1_response_commit(struct flb_http_response *response); + +int flb_http1_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http1_response_set_status(struct flb_http_response *response, + int status); + +int flb_http1_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +/* SESSION */ + +int flb_http1_server_session_init(struct flb_http1_server_session *session, + struct flb_http_server_session *parent); + +void flb_http1_server_session_destroy(struct flb_http1_server_session *session); + +int flb_http1_server_session_ingest(struct flb_http1_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server_http2.h b/include/fluent-bit/http_server/flb_http_server_http2.h new file mode 100644 index 00000000000..761bab868ed --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server_http2.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER_HTTP2 +#define FLB_HTTP_SERVER_HTTP2 + +#include + +#include + +struct flb_http_server_session; +struct flb_http_stream; + +struct flb_http2_server_session { + nghttp2_session *inner_session; + struct cfl_list streams; + struct flb_http_server_session *parent; +}; + +/* RESPONSE */ + +struct flb_http_response *flb_http2_response_begin( + struct flb_http2_server_session *session, + struct flb_http_stream *stream); + +int flb_http2_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http2_response_set_status(struct flb_http_response *response, + int status); + +int flb_http2_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +int flb_http2_response_commit(struct flb_http_response *response); + +/* SESSION */ + +int flb_http2_server_session_init(struct flb_http2_server_session *session, + struct flb_http_server_session *parent); + +void flb_http2_server_session_destroy(struct flb_http2_server_session *session); + +int flb_http2_server_session_ingest(struct flb_http2_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b6233d9f721..b9fcafa1860 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,7 @@ set(src flb_gzip.c flb_snappy.c flb_compression.c + flb_http_common.c flb_http_client.c flb_callback.c flb_strptime.c @@ -210,7 +211,7 @@ endif() if(FLB_REGEX) set(FLB_DEPS - ${FLB_DEPSS} + ${FLB_DEPS} onigmo-static) set(src ${src} @@ -218,6 +219,10 @@ if(FLB_REGEX) ) endif() +set(FLB_DEPS + ${FLB_DEPS} + nghttp2) + if(FLB_LUAJIT) set(extra_libs ${extra_libs} diff --git a/src/flb_http_common.c b/src/flb_http_common.c new file mode 100644 index 00000000000..d51391756c3 --- /dev/null +++ b/src/flb_http_common.c @@ -0,0 +1,442 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +/* HTTP REQUEST */ + +int flb_http_request_init(struct flb_http_request *request) +{ + flb_http_request_destroy(request); + + cfl_list_entry_init(&request->_head); + + request->headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (request->headers == NULL) { + return -1; + } + + return 0; +} + +void flb_http_request_destroy(struct flb_http_request *request) +{ + if (request->path != NULL) { + cfl_sds_destroy(request->path); + } + + if (request->host != NULL) { + cfl_sds_destroy(request->host); + } + + if (request->content_type != NULL) { + cfl_sds_destroy(request->content_type); + } + + if (request->query_string != NULL) { + cfl_sds_destroy(request->query_string); + } + + if (request->body != NULL) { + cfl_sds_destroy(request->body); + } + + if (request->headers != NULL) { + flb_hash_table_destroy(request->headers); + } + + if (!cfl_list_entry_is_orphan(&request->_head)) { + cfl_list_del(&request->_head); + } + + memset(request, 0, sizeof(struct flb_http_request)); +} + +char *flb_http_request_get_header(struct flb_http_request *request, + char *name) +{ + char *lowercase_name; + size_t value_length; + int result; + void *value; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, strlen(name)); + + if (lowercase_name == NULL) { + return NULL; + } + + result = flb_hash_table_get(request->headers, + lowercase_name, + strlen(lowercase_name), + &value, &value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return NULL; + } + + return (char *) value; +} + +int flb_http_request_set_header(struct flb_http_request *request, + char *name, size_t name_length, + char *value, size_t value_length) +{ + char *lowercase_name; + int result; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, name_length); + + if (lowercase_name == NULL) { + return -1; + } + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + result = flb_hash_table_add(request->headers, + (const char *) lowercase_name, + name_length, + (void *) value, + value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +int flb_http_request_unset_header(struct flb_http_request *request, + char *name) +{ + char *lowercase_name; + int result; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, strlen(name)); + + if (lowercase_name == NULL) { + return -1; + } + + result = flb_hash_table_del(request->headers, + (const char *) lowercase_name); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +/* HTTP RESPONSE */ + +int flb_http_response_init(struct flb_http_response *response) +{ + flb_http_response_destroy(response); + + response->headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (response->headers == NULL) { + return -1; + } + + response->trailer_headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (response->trailer_headers == NULL) { + flb_http_response_destroy(response); + + return -1; + } + + return 0; +} + +void flb_http_response_destroy(struct flb_http_response *response) +{ + if (response->message != NULL) { + cfl_sds_destroy(response->message); + } + + if (response->body != NULL) { + cfl_sds_destroy(response->body); + } + + if (response->headers != NULL) { + flb_hash_table_destroy(response->headers); + } + + if (response->trailer_headers != NULL) { + flb_hash_table_destroy(response->trailer_headers); + } + + memset(response, 0, sizeof(struct flb_http_response)); +} + +struct flb_http_response *flb_http_response_begin( + struct flb_http_server_session *session, + void *stream) +{ + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_begin(&session->http2, stream); + } + else { + return flb_http1_response_begin(&session->http1, stream); + } +} + +int flb_http_response_commit(struct flb_http_response *response) +{ + struct flb_http_server_session *session; + + if (response->body == NULL) { + flb_http_response_set_header(response, + "content-length", + strlen("content-length"), + "0", + 1); + } + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_commit(response); + } + + return flb_http1_response_commit(response); +} + +int flb_http_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + struct flb_http_server_session *session; + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_header(response, + name, name_length, + value, value_length); + } + else { + return flb_http1_response_set_header(response, + name, name_length, + value, value_length); + } +} + +int flb_http_response_set_trailer_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + char *lowercase_name; + int result; + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, name_length); + + if (lowercase_name == NULL) { + return -1; + } + + result = flb_hash_table_add(response->trailer_headers, + (const char *) lowercase_name, + name_length, + (void *) value, + value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +int flb_http_response_set_status(struct flb_http_response *response, + int status) +{ + struct flb_http_server_session *session; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + + response->status = status; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_status(response, status); + } + + return flb_http1_response_set_status(response, status); +} + +int flb_http_response_set_message(struct flb_http_response *response, + char *message) +{ + if (response->message != NULL) { + cfl_sds_destroy(response->message); + + response->message = NULL; + } + + response->message = cfl_sds_create((const char *) message); + + if (response->message == NULL) { + return -1; + } + + return 0; +} + +int flb_http_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + struct flb_http_server_session *session; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + + response->body = cfl_sds_create_len((const char *) body, body_length); + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_body(response, body, body_length); + } + + return flb_http1_response_set_body(response, body, body_length); +} + +/* HTTP STREAM */ + +int flb_http_stream_init(struct flb_http_stream *stream, + void *parent, + int32_t stream_id, + int role, + void *user_data) +{ + int result; + + stream->id = stream_id; + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + result = flb_http_request_init(&stream->request); + + if (result != 0) { + return -1; + } + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return -2; + } + + stream->role = role; + stream->parent = parent; + stream->user_data = user_data; + + stream->request.stream = stream; + stream->response.stream = stream; + + return 0; +} + +struct flb_http_stream *flb_http_stream_create(void *parent, + int32_t stream_id, + int role, + void *user_data) +{ + struct flb_http_stream *stream; + int result; + + stream = flb_calloc(1, sizeof(struct flb_http_stream)); + + if (stream == NULL) { + return NULL; + } + + stream->releasable = FLB_TRUE; + + result = flb_http_stream_init(stream, parent, stream_id, role, user_data); + + if (result != 0) { + flb_http_stream_destroy(stream); + } + + return stream; +} + +void flb_http_stream_destroy(struct flb_http_stream *stream) +{ + if (stream != NULL) { + if (!cfl_list_entry_is_orphan(&stream->_head)) { + cfl_list_del(&stream->_head); + } + + flb_free(stream); + } +} diff --git a/src/http_server/CMakeLists.txt b/src/http_server/CMakeLists.txt index acded936da8..8004475b35f 100644 --- a/src/http_server/CMakeLists.txt +++ b/src/http_server/CMakeLists.txt @@ -7,6 +7,9 @@ set(src flb_hs.c flb_hs_endpoints.c flb_hs_utils.c + flb_http_server.c + flb_http_server_http1.c + flb_http_server_http2.c ) # api/v1 diff --git a/src/http_server/flb_http_server.c b/src/http_server/flb_http_server.c new file mode 100644 index 00000000000..f5755dcb772 --- /dev/null +++ b/src/http_server/flb_http_server.c @@ -0,0 +1,807 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +static \ +int uncompress_zlib(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_zstd(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_deflate(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_snappy(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_gzip(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +/* COMMON */ + +char *flb_http_server_convert_string_to_lowercase(char *input_buffer, + size_t length) +{ + char *output_buffer; + size_t index; + + output_buffer = flb_calloc(1, length + 1); + + if (output_buffer != NULL) { + for (index = 0 ; index < length ; index++) { + output_buffer[index] = tolower(input_buffer[index]); + } + + } + + return output_buffer; +} + + +int flb_http_server_strncasecmp(const uint8_t *first_buffer, + size_t first_length, + const char *second_buffer, + size_t second_length) +{ + const char *first_buffer_; + const char *second_buffer_; + + first_buffer_ = (const char *) first_buffer; + second_buffer_ = (const char *) second_buffer; + + if (first_length == 0) { + first_length = strlen(first_buffer_); + } + + if (second_length == 0) { + second_length = strlen(second_buffer_); + } + + if (first_length < second_length) { + return -1; + } + else if (first_length > second_length) { + return 1; + } + + return strncasecmp(first_buffer_, second_buffer_, first_length); +} + + + + + +/* PRIVATE */ + +static int flb_http_server_session_read(struct flb_http_server_session *session) +{ + unsigned char input_buffer[1024]; + ssize_t result; + + result = flb_io_net_read(session->connection, + (void *) &input_buffer, + sizeof(input_buffer)); + + if (result <= 0) { + return -1; + } + + result = (ssize_t) flb_http_server_session_ingest(session, + input_buffer, + result); + + if (result < 0) { + return -1; + } + + return 0; +} + +static int flb_http_server_session_write(struct flb_http_server_session *session) +{ + size_t data_length; + size_t data_sent; + int result; + + if (session == NULL) { + return -1; + } + + if (session->outgoing_data == NULL) { + return 0; + } + + data_length = cfl_sds_len(session->outgoing_data); + + if (data_length > 0) { + result = flb_io_net_write(session->connection, + (void *) session->outgoing_data, + data_length, + &data_sent); + + if (result == -1) { + return -2; + } + + if (data_sent < data_length) { + memmove(session->outgoing_data, + &session->outgoing_data[data_sent], + data_length - data_sent); + + cfl_sds_set_len(session->outgoing_data, + data_length - data_sent); + } + else { + cfl_sds_set_len(session->outgoing_data, 0); + } + } + + return 0; +} + + + +static int flb_http_server_inflate_request_body( + struct flb_http_request *request) +{ + char *content_encoding_header_value; + char new_content_length[21]; + struct flb_http_server_session *parent_session; + cfl_sds_t inflated_body; + char *output_buffer; + size_t output_size; + struct flb_http_server *server; + int result; + + FLB_HTTP_STREAM_GET_SESSION(request->stream, &parent_session); + + server = parent_session->parent; + result = 0; + + if (request->body == NULL) { + return 0; + } + + if ((server->flags & FLB_HTTP_SERVER_FLAG_AUTO_INFLATE) == 0) { + return 0; + } + + content_encoding_header_value = flb_http_request_get_header( + request, + "content-encoding"); + + if (content_encoding_header_value == NULL) { + return 0; + } + + if (strncasecmp(content_encoding_header_value, "gzip", 4) == 0) { + result = uncompress_gzip(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "zlib", 4) == 0) { + result = uncompress_zlib(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "zstd", 4) == 0) { + result = uncompress_zstd(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "snappy", 6) == 0) { + result = uncompress_snappy(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "deflate", 4) == 0) { + result = uncompress_deflate(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + + if (result == 1) { + inflated_body = cfl_sds_create_len(output_buffer, output_size); + + flb_free(output_buffer); + + if (inflated_body == NULL) { + return -1; + } + + cfl_sds_destroy(request->body); + + request->body = inflated_body; + + snprintf(new_content_length, + sizeof(new_content_length), + "%zu", + output_size); + + flb_http_request_unset_header(request, "content-encoding"); + flb_http_request_set_header(request, + "content-length", strlen("content-length"), + new_content_length, strlen(new_content_length)); + + request->content_length = output_size; + } + + return 0; +} + +static int flb_http_server_should_connection_be_closed( + struct flb_http_request *request) +{ + char *connection_header_value; + struct flb_http_server_session *parent_session; + struct flb_http_server *server; + + FLB_HTTP_STREAM_GET_SESSION(request->stream, &parent_session); + + server = parent_session->parent; + + /* Version behaviors implemented in the following block : + * HTTP/0.9 keep-alive is opt-in + * HTTP/1.0 keep-alive is opt-in + * HTTP/1.1 keep-alive is opt-out + * HTTP/2 keep-alive is "mandatory" + */ + + if (request->protocol_version < HTTP_PROTOCOL_VERSION_20) { + if ((server->flags & FLB_HTTP_SERVER_FLAG_KEEPALIVE) == 0) { + return FLB_TRUE; + } + else { + connection_header_value = flb_http_request_get_header(request, + "connection"); + + if (connection_header_value == NULL) { + if (request->protocol_version < HTTP_PROTOCOL_VERSION_11) { + return FLB_TRUE; + } + } + else { + if (strcasecmp(connection_header_value, "keep-alive") != 0) { + return FLB_TRUE; + } + } + } + } + + return FLB_FALSE; +} + +static int flb_http_server_client_activity_event_handler(void *data) +{ + int close_connection; + struct cfl_list *backup_iterator; + struct flb_connection *connection; + struct cfl_list *iterator; + struct flb_http_response *response; + struct flb_http_request *request; + struct flb_http_server_session *session; + struct flb_http_server *server; + struct flb_http_stream *stream; + int result; + struct mk_event *event; + + connection = (struct flb_connection *) data; + + session = (struct flb_http_server_session *) connection->user_data; + + server = session->parent; + + event = &connection->event; + + if (event->mask & MK_EVENT_READ) { + result = flb_http_server_session_read(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -1; + } + } + + close_connection = FLB_FALSE; + + cfl_list_foreach_safe(iterator, + backup_iterator, + &session->request_queue) { + request = cfl_list_entry(iterator, struct flb_http_request, _head); + + stream = (struct flb_http_stream *) request->stream; + + response = flb_http_response_begin(session, stream); + + if (request->body != NULL && request->content_length == 0) { + request->content_length = cfl_sds_len(request->body); + } + + result = flb_http_server_inflate_request_body(request); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -1; + } + + if (server->request_callback != NULL) { + result = server->request_callback(request, response); + } + else { + /* Report */ + } + + close_connection = flb_http_server_should_connection_be_closed(request); + + flb_http_request_destroy(&stream->request); + } + + result = flb_http_server_session_write(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -4; + } + + if (close_connection) { + flb_http_server_session_destroy(session); + } + + return 0; +} + +static int flb_http_server_client_connection_event_handler(void *data) +{ + struct flb_connection *connection; + struct flb_http_server_session *session; + struct flb_http_server *server; + int result; + + server = (struct flb_http_server *) data; + + connection = flb_downstream_conn_get(server->downstream); + + if (connection == NULL) { + return -1; + } + + session = flb_http_server_session_create(server->protocol_version); + + if (session == NULL) { + flb_downstream_conn_release(connection); + + return -2; + } + + session->parent = server; + session->connection = connection; + + MK_EVENT_NEW(&connection->event); + + connection->user_data = (void *) session; + connection->event.type = FLB_ENGINE_EV_CUSTOM; + connection->event.handler = flb_http_server_client_activity_event_handler; + + result = mk_event_add(server->event_loop, + connection->fd, + FLB_ENGINE_EV_CUSTOM, + MK_EVENT_READ, + &connection->event); + + if (result == -1) { + flb_http_server_session_destroy(session); + + return -3; + } + + cfl_list_add(&session->_head, &server->clients); + + result = flb_http_server_session_write(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -4; + } + + return 0; +} + +/* HTTP SERVER */ + +int flb_http_server_init(struct flb_http_server *session, + int protocol_version, + uint64_t flags, + flb_http_server_request_processor_callback + request_callback, + char *address, + unsigned short int port, + struct flb_tls *tls_provider, + int networking_flags, + struct flb_net_setup *networking_setup, + struct mk_event_loop *event_loop, + struct flb_config *system_context, + void *user_data) +{ + session->status = HTTP_SERVER_UNINITIALIZED; + session->protocol_version = protocol_version; + session->flags = flags; + session->request_callback = request_callback; + session->user_data = user_data; + + session->address = address; + session->port = port; + session->tls_provider = tls_provider; + session->networking_flags = networking_flags; + session->networking_setup = networking_setup; + session->event_loop = event_loop; + session->system_context = system_context; + + session->downstream = NULL; + + cfl_list_init(&session->clients); + + MK_EVENT_NEW(&session->listener_event); + + session->status = HTTP_SERVER_INITIALIZED; + + return 0; +} + +int flb_http_server_start(struct flb_http_server *session) +{ + int result; + + if (session->tls_provider != NULL) { + result = flb_tls_set_alpn(session->tls_provider, "h2,http/1.0,http/1.1"); + + if (result != 0) { + return -1; + } + } + + session->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + session->networking_flags, + session->address, + session->port, + session->tls_provider, + session->system_context, + session->networking_setup); + + if (session->downstream == NULL) { + return -1; + } + + session->listener_event.type = FLB_ENGINE_EV_CUSTOM; + session->listener_event.handler = flb_http_server_client_connection_event_handler; + + /* Register instance into the event loop */ + result = mk_event_add(session->event_loop, + session->downstream->server_fd, + FLB_ENGINE_EV_CUSTOM, + MK_EVENT_READ, + &session->listener_event); + + if (result == -1) { + return -1; + } + + session->status = HTTP_SERVER_RUNNING; + + return 0; +} + +int flb_http_server_stop(struct flb_http_server *server) +{ + struct cfl_list *iterator_backup; + struct cfl_list *iterator; + struct flb_http_server_session *session; + + if (server->status == HTTP_SERVER_RUNNING) { + if (MK_EVENT_IS_REGISTERED((&server->listener_event))) { + mk_event_del(server->event_loop, &server->listener_event); + } + + mk_list_foreach_safe(iterator, iterator_backup, &server->clients) { + session = cfl_list_entry(iterator, + struct flb_http_server_session, + _head); + + flb_http_server_session_destroy(session); + } + + server->status = HTTP_SERVER_STOPPED; + } + + return 0; +} + +int flb_http_server_destroy(struct flb_http_server *server) +{ + flb_http_server_stop(server); + + if (server->downstream != NULL) { + flb_downstream_destroy(server->downstream); + + server->downstream = NULL; + } + + return 0; +} + +/* HTTP SESSION */ + +int flb_http_server_session_init(struct flb_http_server_session *session, int version) +{ + int result; + + memset(session, 0, sizeof(struct flb_http_server_session)); + + cfl_list_init(&session->request_queue); + cfl_list_entry_init(&session->_head); + + session->incoming_data = cfl_sds_create_size(HTTP_SERVER_INITIAL_BUFFER_SIZE); + + if (session->incoming_data == NULL) { + return -1; + } + + session->outgoing_data = cfl_sds_create_size(HTTP_SERVER_INITIAL_BUFFER_SIZE); + + if (session->outgoing_data == NULL) { + return -2; + } + + session->version = version; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + result = flb_http2_server_session_init(&session->http2, session); + + if (result != 0) { + return -3; + } + } + else if (session->version == HTTP_PROTOCOL_HTTP1) { + result = flb_http1_server_session_init(&session->http1, session); + + if (result != 0) { + return -4; + } + } + + return 0; +} + +struct flb_http_server_session *flb_http_server_session_create(int version) +{ + struct flb_http_server_session *session; + int result; + + session = flb_calloc(1, sizeof(struct flb_http_server_session)); + + if (session != NULL) { + session->releasable = FLB_TRUE; + + result = flb_http_server_session_init(session, version); + + if (result != 0) { + flb_http_server_session_destroy(session); + + session = NULL; + } + } + + return session; +} + +void flb_http_server_session_destroy(struct flb_http_server_session *session) +{ + if (session != NULL) { + if (session->connection != NULL) { + flb_downstream_conn_release(session->connection); + } + + if (!cfl_list_entry_is_orphan(&session->_head)) { + cfl_list_del(&session->_head); + } + + if (session->incoming_data != NULL) { + cfl_sds_destroy(session->incoming_data); + } + + if (session->outgoing_data != NULL) { + cfl_sds_destroy(session->outgoing_data); + } + + if (session->releasable) { + flb_free(session); + } + } +} + +int flb_http_server_session_ingest(struct flb_http_server_session *session, + unsigned char *buffer, + size_t length) +{ + cfl_sds_t resized_buffer; + int result; + + if (session->version == HTTP_PROTOCOL_AUTODETECT || + session->version == HTTP_PROTOCOL_HTTP1) { + resized_buffer = cfl_sds_cat(session->incoming_data, + (const char *) buffer, + length); + + if (resized_buffer == NULL) { + return HTTP_SERVER_ALLOCATION_ERROR; + } + + session->incoming_data = resized_buffer; + } + + if (session->version == HTTP_PROTOCOL_AUTODETECT) { + if (cfl_sds_len(session->incoming_data) >= 24) { + if (strncmp(session->incoming_data, + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", + 24) == 0) { + session->version = HTTP_PROTOCOL_HTTP2; + } + else { + session->version = HTTP_PROTOCOL_HTTP1; + } + } + else if (cfl_sds_len(session->incoming_data) >= 4) { + if (strncmp(session->incoming_data, "PRI ", 4) != 0) { + session->version = HTTP_PROTOCOL_HTTP1; + } + } + + if (session->version == HTTP_PROTOCOL_HTTP1) { + result = flb_http1_server_session_init(&session->http1, session); + + if (result != 0) { + return -1; + } + } + else if (session->version == HTTP_PROTOCOL_HTTP2) { + result = flb_http2_server_session_init(&session->http2, session); + + if (result != 0) { + return -1; + } + } + } + + if (session->version == HTTP_PROTOCOL_HTTP1) { + return flb_http1_server_session_ingest(&session->http1, + buffer, + length); + } + else if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_server_session_ingest(&session->http2, + buffer, + length); + } + + return -1; +} + + +/* PRIVATE */ + +static \ +int uncompress_zlib(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_zstd(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_deflate(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_snappy(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + int ret; + + ret = flb_snappy_uncompress_framed_data(input_buffer, + input_size, + output_buffer, + output_size); + + if (ret != 0) { + flb_error("[opentelemetry] snappy decompression failed"); + + return -1; + } + + return 1; +} + +static \ +int uncompress_gzip(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + int ret; + + ret = flb_gzip_uncompress(input_buffer, + input_size, + (void *) output_buffer, + output_size); + + if (ret == -1) { + flb_error("[opentelemetry] gzip decompression failed"); + + return -1; + } + + return 1; +} + diff --git a/src/http_server/flb_http_server_http1.c b/src/http_server/flb_http_server_http1.c new file mode 100644 index 00000000000..445a252e7a0 --- /dev/null +++ b/src/http_server/flb_http_server_http1.c @@ -0,0 +1,491 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* PRIVATE */ + +static void dummy_mk_http_session_init(struct mk_http_session *session, + struct mk_server *server) +{ + session->_sched_init = MK_TRUE; + session->pipelined = MK_FALSE; + session->counter_connections = 0; + session->close_now = MK_FALSE; + session->status = MK_REQUEST_STATUS_INCOMPLETE; + session->server = server; + session->socket = -1; + + /* creation time in unix time */ + session->init_time = time(NULL); + + session->channel = mk_channel_new(MK_CHANNEL_SOCKET, -1); + session->channel->io = session->server->network; + + /* Init session request list */ + mk_list_init(&session->request_list); + + /* Initialize the parser */ + mk_http_parser_init(&session->parser); +} + +static void dummy_mk_http_request_init(struct mk_http_session *session, + struct mk_http_request *request) +{ + memset(request, 0, sizeof(struct mk_http_request)); + + mk_http_request_init(session, request, session->server); + + request->in_headers.type = MK_STREAM_IOV; + request->in_headers.dynamic = MK_FALSE; + request->in_headers.cb_consumed = NULL; + request->in_headers.cb_finished = NULL; + request->in_headers.stream = &request->stream; + + mk_list_add(&request->in_headers._head, &request->stream.inputs); + + request->session = session; +} + +static int http1_evict_request(struct flb_http1_server_session *session) +{ + uintptr_t session_buffer_upper_bound; + uintptr_t session_buffer_lower_bound; + size_t session_buffer_length; + cfl_sds_t session_buffer; + size_t content_length; + size_t request_length; + uintptr_t request_end; + + request_end = 0; + content_length = 0; + session_buffer = session->parent->incoming_data; + + if (session_buffer == NULL) { + return -1; + } + + session_buffer_length = cfl_sds_len(session_buffer); + + if (session->inner_request.data.data != NULL) { + content_length = session->inner_request.data.len; + + request_end = (uintptr_t) session->inner_request.data.data; + request_end += content_length; + } + else { + request_end = (uintptr_t) strstr(session_buffer, + "\r\n\r\n"); + + if(request_end != 0) { + request_end += 4; + } + } + + if (request_end != 0) { + session_buffer_lower_bound = (uintptr_t) session_buffer; + session_buffer_upper_bound = (uintptr_t) &session_buffer[session_buffer_length]; + + if (request_end < session_buffer_lower_bound || + request_end > session_buffer_upper_bound) { + return -1; + } + + request_length = (size_t) (request_end - session_buffer_lower_bound); + + if (request_length == session_buffer_length) { + session_buffer_length = 0; + } + else { + session_buffer_length -= request_length; + + memmove(session_buffer, + &session_buffer[request_length], + session_buffer_length); + + session_buffer[session_buffer_length] = '\0'; + } + + cfl_sds_set_len(session_buffer, session_buffer_length); + } + + return 0; +} + +static int http1_session_process_request(struct flb_http1_server_session *session) +{ + struct mk_list *iterator; + struct mk_http_header *header; + int result; + + if (session->inner_request.uri_processed.data != NULL) { + session->stream.request.path = \ + cfl_sds_create_len(session->inner_request.uri_processed.data, + session->inner_request.uri_processed.len); + } + else { + session->stream.request.path = \ + cfl_sds_create_len(session->inner_request.uri.data, + session->inner_request.uri.len); + } + + if (session->stream.request.path == NULL) { + return -1; + } + + switch (session->inner_request.protocol) { + case MK_HTTP_PROTOCOL_09: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_09; + break; + case MK_HTTP_PROTOCOL_10: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_10; + break; + case MK_HTTP_PROTOCOL_11: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_11; + break; + default: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_10; + } + + switch (session->inner_request.method) { + case MK_METHOD_GET: + session->stream.request.method = HTTP_METHOD_GET; + break; + case MK_METHOD_POST: + session->stream.request.method = HTTP_METHOD_POST; + break; + case MK_METHOD_HEAD: + session->stream.request.method = HTTP_METHOD_HEAD; + break; + case MK_METHOD_PUT: + session->stream.request.method = HTTP_METHOD_PUT; + break; + case MK_METHOD_DELETE: + session->stream.request.method = HTTP_METHOD_DELETE; + break; + case MK_METHOD_OPTIONS: + session->stream.request.method = HTTP_METHOD_OPTIONS; + break; + default: + session->stream.request.method = HTTP_METHOD_UNKNOWN; + break; + } + + session->stream.request.content_length = session->inner_request.content_length; + + mk_list_foreach(iterator, + &session->inner_parser.header_list) { + header = mk_list_entry(iterator, struct mk_http_header, _head); + + if (header->key.data != NULL && header->key.len > 0 && + header->val.data != NULL && header->val.len > 0) { + + if (flb_http_server_strncasecmp( + (const uint8_t *) header->key.data, + header->key.len, + "host", 0) == 0) { + session->stream.request.host = \ + cfl_sds_create_len((const char *) header->val.data, + header->val.len); + + if (session->stream.request.host == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + (const uint8_t *) header->key.data, + header->key.len, + "content-type", 0) == 0) { + session->stream.request.content_type = \ + cfl_sds_create_len((const char *) header->val.data, + header->val.len); + + if (session->stream.request.content_type == NULL) { + return -1; + } + } + + result = flb_http_request_set_header(&session->stream.request, + header->key.data, + header->key.len, + (void *) header->val.data, + header->val.len); + + if (result != 0) { + return -1; + } + } + } + + if (session->stream.request.host == NULL) { + session->stream.request.host = cfl_sds_create(""); + + if (session->stream.request.host == NULL) { + return -1; + } + } + + if (session->inner_request.data.data != NULL) { + session->stream.request.body = \ + cfl_sds_create_len(session->inner_request.data.data, + session->inner_request.data.len); + + if (session->stream.request.body == NULL) { + session->stream.status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + } + + session->stream.status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&session->stream.request._head)) { + cfl_list_del(&session->stream.request._head); + } + + cfl_list_add(&session->stream.request._head, + &session->parent->request_queue); + + return 0; +} + +/* RESPONSE */ + +struct flb_http_response *flb_http1_response_begin( + struct flb_http1_server_session *session, + struct flb_http_stream *stream) +{ + int result; + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return NULL; + } + + stream->response.stream = stream; + + return &stream->response; +} + +int flb_http1_response_commit(struct flb_http_response *response) +{ + struct mk_list *header_iterator; + cfl_sds_t response_buffer; + struct flb_http_server_session *parent_session; + struct flb_hash_table_entry *header_entry; + cfl_sds_t sds_result; + struct flb_http1_server_session *session; + struct flb_http_stream *stream; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http1; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + response_buffer = cfl_sds_create_size(128); + + if (response_buffer == NULL) { + return -3; + } + + if (response->message != NULL) { + sds_result = cfl_sds_printf(&response_buffer, "HTTP/1.1 %d %s\r\n", response->status, response->message); + } + else { + sds_result = cfl_sds_printf(&response_buffer, "HTTP/1.1 %d\r\n", response->status); + } + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -4; + } + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + cfl_sds_destroy(response_buffer); + + return -5; + } + + sds_result = cfl_sds_printf(&response_buffer, + "%.*s: %.*s\r\n", + (int) header_entry->key_len, + (const char *) header_entry->key, + (int) header_entry->val_size, + (const char *) header_entry->val); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -6; + } + } + + sds_result = cfl_sds_cat(response_buffer, "\r\n", 2); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -7; + } + + if (response->body != NULL) { + sds_result = cfl_sds_cat(response_buffer, + response->body, + cfl_sds_len(response->body)); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -8; + } + + response_buffer = sds_result; + } + + sds_result = cfl_sds_cat(session->parent->outgoing_data, + response_buffer, + cfl_sds_len(response_buffer)); + + if (sds_result == NULL) { + return -9; + } + + session->parent->outgoing_data = sds_result; + + return 0; +} + + +int flb_http1_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + int result; + + result = flb_hash_table_add(response->headers, + (const char *) name, (int) name_length, + (void *) value, (ssize_t) value_length); + + if (result < 0) { + return -1; + } + + return 0; +} + +int flb_http1_response_set_status(struct flb_http_response *response, + int status) +{ + return 0; +} + +int flb_http1_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + return 0; +} + +/* SESSION */ + +int flb_http1_server_session_init(struct flb_http1_server_session *session, + struct flb_http_server_session *parent) +{ + void *user_data; + int result; + + if (parent != NULL && parent->parent != NULL) { + user_data = parent->parent->user_data; + } + else { + user_data = NULL; + } + + dummy_mk_http_session_init(&session->inner_session, &session->inner_server); + + dummy_mk_http_request_init(&session->inner_session, &session->inner_request); + + mk_http_parser_init(&session->inner_parser); + + result = flb_http_stream_init(&session->stream, parent, 0, HTTP_STREAM_ROLE_SERVER, + user_data); + + if (result != 0) { + return -1; + } + + session->parent = parent; + + return 0; +} + +void flb_http1_server_session_destroy(struct flb_http1_server_session *session) +{ + if (session->inner_session.channel != NULL) { + mk_channel_release(session->inner_session.channel); + + session->inner_session.channel = NULL; + } +} + +int flb_http1_server_session_ingest(struct flb_http1_server_session *session, + unsigned char *buffer, + size_t length) +{ + int result; + + result = mk_http_parser(&session->inner_request, + &session->inner_parser, + session->parent->incoming_data, + cfl_sds_len(session->parent->incoming_data), + &session->inner_server); + + if (result == MK_HTTP_PARSER_OK) { + result = http1_session_process_request(session); + + if (result != 0) { + session->stream.status = HTTP_STREAM_STATUS_ERROR; + + return HTTP_SERVER_PROVIDER_ERROR; + } + + http1_evict_request(session); + } + + return HTTP_SERVER_SUCCESS; +} diff --git a/src/http_server/flb_http_server_http2.c b/src/http_server/flb_http_server_http2.c new file mode 100644 index 00000000000..263ca0be305 --- /dev/null +++ b/src/http_server/flb_http_server_http2.c @@ -0,0 +1,858 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +/* PRIVATE */ +static ssize_t http2_send_callback(nghttp2_session *inner_session, + const uint8_t *data, + size_t length, + int flags, + void *user_data); + +static int http2_header_callback(nghttp2_session *inner_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); + +static int http2_frame_recv_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data); + +static int http2_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data); + +static int http2_begin_headers_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data); + +static int http2_data_chunk_recv_callback(nghttp2_session *inner_session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data); + +static ssize_t http2_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); + +static inline size_t http2_lower_value(size_t left_value, size_t right_value) +{ + if (left_value < right_value) { + return left_value; + } + + return right_value; +} + +/* RESPONSE */ + +struct flb_http_response *flb_http2_response_begin( + struct flb_http2_server_session *session, + struct flb_http_stream *stream) +{ + + int result; + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return NULL; + } + + stream->response.stream = stream; + + return &stream->response; +} + +int flb_http2_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + int result; + + result = flb_hash_table_add(response->headers, + (const char *) name, (int) name_length, + (void *) value, (ssize_t) value_length); + + if (result < 0) { + return -1; + } + + return 0; +} + +int flb_http2_response_set_status(struct flb_http_response *response, + int status) +{ + return 0; +} + +int flb_http2_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + return 0; +} + +/* +int flb_http2_response_commit(struct flb_http_response *response) +{ + char status_as_text[16]; + struct mk_list *header_iterator; + struct flb_http_server_session *parent_session; + nghttp2_data_provider data_provider; + size_t header_count; + size_t header_index; + struct flb_hash_table_entry *header_entry; + nghttp2_nv *headers; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + int result; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http2; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + header_count = response->headers->total_count + 1; + + headers = flb_calloc(header_count, sizeof(nghttp2_nv)); + + if (headers == NULL) { + return -3; + } + + snprintf(status_as_text, + sizeof(status_as_text) - 1, + "%d", + response->status); + + headers[0].name = (uint8_t *) ":status"; + headers[0].namelen = strlen(":status"); + headers[0].value = (uint8_t *) status_as_text; + headers[0].valuelen = strlen(status_as_text); + + header_index = 1; + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + return -4; + } + + headers[header_index].name = (uint8_t *) header_entry->key; + headers[header_index].namelen = header_entry->key_len; + headers[header_index].value = (uint8_t *) header_entry->val; + headers[header_index].valuelen = header_entry->val_size; + + header_index++; + } + + data_provider.source.fd = 0; + data_provider.read_callback = http2_data_source_read_callback; + + stream->status = HTTP_STREAM_STATUS_PROCESSING; + + result = nghttp2_submit_response(session->inner_session, + stream->id, + headers, + header_count, + &data_provider); + + flb_free(headers); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -5; + } + + result = nghttp2_session_send(session->inner_session); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -6; + } + + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + flb_http_response_destroy(&stream->response); + + return 0; +} +*/ +int flb_http2_response_commit(struct flb_http_response *response) +{ + size_t trailer_header_count; + char status_as_text[16]; + struct mk_list *header_iterator; + nghttp2_nv *trailer_headers; + struct flb_http_server_session *parent_session; + nghttp2_data_provider data_provider; + size_t header_count; + size_t header_index; + struct flb_hash_table_entry *header_entry; + nghttp2_nv *headers; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + int result; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http2; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + header_count = response->headers->total_count + 1; + + headers = flb_calloc(header_count, sizeof(nghttp2_nv)); + + if (headers == NULL) { + return -3; + } + + snprintf(status_as_text, + sizeof(status_as_text) - 1, + "%d", + response->status); + + headers[0].name = (uint8_t *) ":status"; + headers[0].namelen = strlen(":status"); + headers[0].value = (uint8_t *) status_as_text; + headers[0].valuelen = strlen(status_as_text); + + header_index = 1; + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + flb_free(headers); + + return -4; + } + + headers[header_index].name = (uint8_t *) header_entry->key; + headers[header_index].namelen = header_entry->key_len; + headers[header_index].value = (uint8_t *) header_entry->val; + headers[header_index].valuelen = header_entry->val_size; + + if (headers[header_index].value[0] == '\0') { + headers[header_index].valuelen = 0; + } + + header_index++; + } + + data_provider.source.fd = 0; + data_provider.read_callback = http2_data_source_read_callback; + + stream->status = HTTP_STREAM_STATUS_PROCESSING; + + result = nghttp2_submit_response(session->inner_session, + stream->id, + headers, + header_count, + &data_provider); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + flb_free(headers); + + return -5; + } + + result = nghttp2_session_send(session->inner_session); + + if (mk_list_is_empty(&response->trailer_headers->entries) != 0) { + trailer_header_count = response->trailer_headers->total_count; + + trailer_headers = flb_calloc(trailer_header_count, sizeof(nghttp2_nv)); + + if (trailer_headers == NULL) { + flb_free(headers); + + return -6; + } + + header_index = 0; + + mk_list_foreach(header_iterator, &response->trailer_headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + flb_free(trailer_headers); + flb_free(headers); + + return -7; + } + + trailer_headers[header_index].name = (uint8_t *) header_entry->key; + trailer_headers[header_index].namelen = header_entry->key_len; + trailer_headers[header_index].value = (uint8_t *) header_entry->val; + trailer_headers[header_index].valuelen = header_entry->val_size; + + if (trailer_headers[header_index].value[0] == '\0') { + trailer_headers[header_index].valuelen = 0; + } + + header_index++; + } + + result = nghttp2_submit_trailer(session->inner_session, + stream->id, + trailer_headers, + trailer_header_count); + } + else { + trailer_headers = NULL; + } + + result = nghttp2_session_send(session->inner_session); + + if (trailer_headers != NULL) { + flb_free(trailer_headers); + } + + flb_free(headers); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -8; + } + + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + flb_http_response_destroy(&stream->response); + + return 0; +} + +/* SESSION */ + +int flb_http2_server_session_init(struct flb_http2_server_session *session, + struct flb_http_server_session *parent) +{ + nghttp2_settings_entry session_settings[1]; + nghttp2_session_callbacks *callbacks; + int result; + + result = nghttp2_session_callbacks_new(&callbacks); + + if (result != 0) { + return -1; + } + + nghttp2_session_callbacks_set_send_callback(callbacks, http2_send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, http2_frame_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, http2_stream_close_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, http2_begin_headers_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, http2_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, http2_header_callback); + + result = nghttp2_session_server_new(&session->inner_session, callbacks, session); + + nghttp2_session_callbacks_del(callbacks); + + if (result != 0) { + return -2; + } + + cfl_list_init(&session->streams); + + session->parent = parent; + + session_settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + session_settings[0].value = 1; + + result = nghttp2_submit_settings(session->inner_session, + NGHTTP2_FLAG_NONE, + session_settings, + 1); + + if (result != 0) { + return -3; + } + + result = nghttp2_session_send(session->inner_session); + + if (result != 0) { + return -4; + } + + return 0; +} + +void flb_http2_server_session_destroy(struct flb_http2_server_session *session) +{ + struct cfl_list *iterator_backup; + struct cfl_list *iterator; + struct flb_http_stream *stream; + + if (session != NULL) { + cfl_list_foreach_safe(iterator, + iterator_backup, + &session->streams) { + stream = cfl_list_entry(iterator, struct flb_http_stream, _head); + + flb_http_stream_destroy(stream); + } + + nghttp2_session_del(session->inner_session); + } +} + +int flb_http2_server_session_ingest(struct flb_http2_server_session *session, + unsigned char *buffer, + size_t length) +{ + ssize_t result; + + result = nghttp2_session_mem_recv(session->inner_session, buffer, length); + + if (result < 0) { + return HTTP_SERVER_PROVIDER_ERROR; + } + + return HTTP_SERVER_SUCCESS; +} + + +/* PRIVATE */ + +static ssize_t http2_send_callback(nghttp2_session *inner_session, + const uint8_t *data, + size_t length, + int flags, + void *user_data) +{ + cfl_sds_t resized_buffer; + struct flb_http2_server_session *session; + + session = (struct flb_http2_server_session *) user_data; + + resized_buffer = cfl_sds_cat(session->parent->outgoing_data, + (const char *) data, + length); + + if (resized_buffer == NULL) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + session->parent->outgoing_data = resized_buffer; + + return length; +} + +static int http2_header_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t name_length, + const uint8_t *value, + size_t value_length, + uint8_t flags, + void *user_data) +{ + char temporary_buffer[16]; + struct flb_http_stream *stream; + int result; + + stream = nghttp2_session_get_stream_user_data(inner_session, + frame->hd.stream_id); + + if (stream == NULL) { + return 0; + } + + if (flb_http_server_strncasecmp(name, name_length, ":method", 0) == 0) { + strncpy(temporary_buffer, + (const char *) value, + http2_lower_value(sizeof(temporary_buffer), value_length + 1)); + + temporary_buffer[sizeof(temporary_buffer) - 1] = '\0'; + + if (strcasecmp(temporary_buffer, "GET") == 0) { + stream->request.method = HTTP_METHOD_GET; + } + else if (strcasecmp(temporary_buffer, "POST") == 0) { + stream->request.method = HTTP_METHOD_POST; + } + else if (strcasecmp(temporary_buffer, "HEAD") == 0) { + stream->request.method = HTTP_METHOD_HEAD; + } + else if (strcasecmp(temporary_buffer, "PUT") == 0) { + stream->request.method = HTTP_METHOD_PUT; + } + else if (strcasecmp(temporary_buffer, "DELETE") == 0) { + stream->request.method = HTTP_METHOD_DELETE; + } + else if (strcasecmp(temporary_buffer, "OPTIONS") == 0) { + stream->request.method = HTTP_METHOD_OPTIONS; + } + else { + stream->request.method = HTTP_METHOD_UNKNOWN; + } + } + else if (flb_http_server_strncasecmp(name, name_length, ":path", 0) == 0) { + stream->request.path = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.path == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, ":authority", 0) == 0) { + + stream->request.host = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.host == NULL) { + return -1; + } + + result = flb_hash_table_add(stream->request.headers, + "host", 4, + (void *) value, value_length); + + if (result < 0) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, "content-type", 0) == 0) { + + stream->request.content_type = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.content_type == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, "content-length", 0) == 0) { + strncpy(temporary_buffer, + (const char *) value, + http2_lower_value(sizeof(temporary_buffer), value_length + 1)); + + temporary_buffer[sizeof(temporary_buffer) - 1] = '\0'; + + stream->request.content_length = strtoull(temporary_buffer, NULL, 10); + } + + result = flb_http_request_set_header(&stream->request, + (char *) name, + name_length, + (void *) value, + value_length); + + if (result != 0) { + return -1; + } + + return 0; +} + +static int http2_frame_recv_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data) +{ + struct flb_http_server_session *parent_session; + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(inner_session, + frame->hd.stream_id); + + if (stream == NULL) { + return 0; + } + + switch (frame->hd.type) { + case NGHTTP2_CONTINUATION: + case NGHTTP2_HEADERS: + if ((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) != 0) { + stream->status = HTTP_STREAM_STATUS_RECEIVING_DATA; + } + else { + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + } + + break; + default: + break; + } + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) { + stream->status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&stream->request._head)) { + cfl_list_del(&stream->request._head); + } + + FLB_HTTP_STREAM_GET_SESSION(stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + cfl_list_add(&stream->request._head, + &parent_session->request_queue); + } + + return 0; +} + +static int http2_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data) +{ + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(session, stream_id); + + if (stream == NULL) { + return 0; + } + + stream->status = HTTP_STREAM_STATUS_CLOSED; + + return 0; +} + +static int http2_begin_headers_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *inner_user_data) +{ + void *user_data; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + + session = (struct flb_http2_server_session *) inner_user_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + if (session->parent != NULL && session->parent->parent != NULL) { + user_data = session->parent->parent->user_data; + } + else { + user_data = NULL; + } + + stream = flb_http_stream_create(session->parent, + frame->hd.stream_id, + HTTP_STREAM_ROLE_SERVER, + user_data); + + if (stream == NULL) { + return -1; + } + + stream->request.protocol_version = HTTP_PROTOCOL_VERSION_20; + + cfl_list_add(&stream->_head, &session->streams); + + nghttp2_session_set_stream_user_data(inner_session, + frame->hd.stream_id, + stream); + + return 0; +} + +static int http2_data_chunk_recv_callback(nghttp2_session *inner_session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data) +{ + struct flb_http_server_session *parent_session; + cfl_sds_t resized_buffer; + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(inner_session, stream_id); + + if (stream == NULL) { + return 0; + } + + if (stream->status != HTTP_STREAM_STATUS_RECEIVING_DATA) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + if (stream->request.body == NULL) { + stream->request.body = cfl_sds_create_size(len); + + if (stream->request.body == NULL) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + memcpy(stream->request.body, data, len); + + cfl_sds_set_len(stream->request.body, len); + } + else { + resized_buffer = cfl_sds_cat(stream->request.body, + (const char *) data, + len); + + if (resized_buffer == NULL) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + stream->request.body = resized_buffer; + } + + if (stream->status == HTTP_STREAM_STATUS_RECEIVING_DATA) { + if (stream->request.content_length == cfl_sds_len(stream->request.body)) { + stream->status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&stream->request._head)) { + cfl_list_del(&stream->request._head); + } + + FLB_HTTP_STREAM_GET_SESSION(stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + cfl_list_add(&stream->request._head, + &parent_session->request_queue); + } + } + + return 0; +} + +static ssize_t http2_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) +{ + size_t content_length; + size_t body_offset; + struct flb_http_stream *stream; + ssize_t result; + + stream = nghttp2_session_get_stream_user_data(session, + stream_id); + + if (stream == NULL) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if (stream->response.body != NULL) { + body_offset = stream->response.body_read_offset; + content_length = cfl_sds_len(stream->response.body) - body_offset; + } + else { + body_offset = 0; + content_length = 0; + } + + if (content_length > length) { + memcpy(buf, + &stream->response.body[body_offset], length); + + result = length; + + stream->response.body_read_offset += length; + } + else { + if (content_length > 0) { + memcpy(buf, stream->response.body, content_length); + + stream->response.body_read_offset += content_length; + } + + result = content_length; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + if (mk_list_is_empty(&stream->response.trailer_headers->entries) != 0) { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + + return result; +} + From 0e52a212a9b99379868df15c42e2a70119cef941 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:48:56 +0100 Subject: [PATCH 06/24] in_http: added http2 support Signed-off-by: Leonardo Alminana --- plugins/in_http/http.c | 103 +++++-- plugins/in_http/http.h | 23 +- plugins/in_http/http_config.c | 4 + plugins/in_http/http_prot.c | 492 ++++++++++++++++++++++++++++++++-- plugins/in_http/http_prot.h | 5 + 5 files changed, 566 insertions(+), 61 deletions(-) diff --git a/plugins/in_http/http.c b/plugins/in_http/http.c index 9d9ac2ebaa5..62f355ba293 100644 --- a/plugins/in_http/http.c +++ b/plugins/in_http/http.c @@ -24,6 +24,7 @@ #include "http.h" #include "http_conn.h" +#include "http_prot.h" #include "http_config.h" /* @@ -92,25 +93,67 @@ static int in_http_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - http_config_destroy(ctx); + http_config_destroy(ctx); - return -1; - } + return -1; + } + + ret = flb_http_server_start(&ctx->http_server); + + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - flb_input_downstream_set(ctx->downstream, ctx->ins); + http_config_destroy(ctx); + + return -1; + } + + ctx->http_server.request_callback = http_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + http_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + } if (ctx->successful_response_code != 200 && ctx->successful_response_code != 201 && @@ -120,19 +163,21 @@ static int in_http_init(struct flb_input_instance *ins, ctx->successful_response_code = 201; } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_http_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - http_config_destroy(ctx); + if (!ctx->enable_http2) { + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_http_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + http_config_destroy(ctx); - return -1; - } + return -1; + } - ctx->collector_id = ret; + ctx->collector_id = ret; + } return 0; } @@ -154,6 +199,12 @@ static int in_http_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_http, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_http, buffer_max_size), diff --git a/plugins/in_http/http.h b/plugins/in_http/http.h index fd2d83ff7d5..4298a370c9c 100644 --- a/plugins/in_http/http.h +++ b/plugins/in_http/http.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -37,21 +38,29 @@ struct flb_http { flb_sds_t tcp_port; const char *tag_key; - int collector_id; - /* Success HTTP headers */ struct mk_list *success_headers; - flb_sds_t success_headers_str; - - size_t buffer_max_size; /* Maximum buffer size */ - size_t buffer_chunk_size; /* Chunk allocation size */ struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ + flb_sds_t success_headers_str; + + size_t buffer_max_size; /* Maximum buffer size */ + size_t buffer_chunk_size; /* Chunk allocation size */ + struct mk_server *server; - struct flb_input_instance *ins; + + int collector_id; }; diff --git a/plugins/in_http/http_config.c b/plugins/in_http/http_config.c index 7db71d9bd6c..343e69925c4 100644 --- a/plugins/in_http/http_config.c +++ b/plugins/in_http/http_config.c @@ -145,6 +145,10 @@ int http_config_destroy(struct flb_http *ctx) flb_free(ctx->server); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->success_headers_str != NULL) { flb_sds_destroy(ctx->success_headers_str); } diff --git a/plugins/in_http/http_prot.c b/plugins/in_http/http_prot.c index f2c465f41a5..33a6ec2a445 100644 --- a/plugins/in_http/http_prot.c +++ b/plugins/in_http/http_prot.c @@ -21,13 +21,10 @@ #include #include #include -#include #include #include -#include - #include "http.h" #include "http_conn.h" @@ -523,12 +520,6 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, int ret; int type = -1; struct mk_http_header *header; - char *uncompressed_data; - size_t uncompressed_size = 0; - - /* used when checking content-encoding */ - int gzip_compressed = FLB_FALSE; - int gzip_ret; header = &session->parser.headers[MK_HEADER_CONTENT_TYPE]; if (header->key.data == NULL) { @@ -546,31 +537,18 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, type = HTTP_CONTENT_URLENCODED; } - gzip_compressed = flb_is_http_session_gzip_compressed(session); - - if (gzip_compressed == FLB_TRUE) { - gzip_ret = flb_gzip_uncompress(request->data.data, request->data.len, (void **)&uncompressed_data, &uncompressed_size); - if (gzip_ret == -1) { - flb_error("[http] gzip decompression failed"); - return -1; - } - } else { - uncompressed_data = request->data.data; - uncompressed_size = request->data.len; - } - if (type == -1) { send_response(conn, 400, "error: invalid 'Content-Type'\n"); return -1; } - if (uncompressed_size <= 0) { + if (request->data.len <= 0) { send_response(conn, 400, "error: no payload found\n"); return -1; } if (type == HTTP_CONTENT_JSON) { - parse_payload_json(ctx, tag, uncompressed_data, uncompressed_size); + parse_payload_json(ctx, tag, request->data.data, request->data.len); } else if (type == HTTP_CONTENT_URLENCODED) { ret = parse_payload_urlencoded(ctx, tag, request->data.data, request->data.len); @@ -579,10 +557,6 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, return -1; } } - - if (gzip_compressed) { - flb_free(uncompressed_data); - } return 0; } @@ -672,18 +646,23 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, } mk_mem_free(uri); + /* Check if we have a Host header: Hostname ; port */ mk_http_point_header(&request->host, &session->parser, MK_HEADER_HOST); + /* Header: Connection */ mk_http_point_header(&request->connection, &session->parser, MK_HEADER_CONNECTION); + /* HTTP/1.1 needs Host header */ if (!request->host.data && request->protocol == MK_HTTP_PROTOCOL_11) { flb_sds_destroy(tag); return -1; } + /* Should we close the session after this request ? */ mk_http_keepalive_check(session, request, ctx->server); + /* Content Length */ header = &session->parser.headers[MK_HEADER_CONTENT_LENGTH]; if (header->type == MK_HEADER_CONTENT_LENGTH) { @@ -693,11 +672,13 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, else { request->_content_length.data = NULL; } + if (request->method != MK_METHOD_POST) { flb_sds_destroy(tag); send_response(conn, 400, "error: invalid HTTP method\n"); return -1; } + ret = process_payload(ctx, conn, tag, session, request); flb_sds_destroy(tag); @@ -708,6 +689,7 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, return ret; } + /* * Handle an incoming request which has resulted in an http parser error. */ @@ -718,3 +700,457 @@ int http_prot_handle_error(struct flb_http *ctx, struct http_conn *conn, send_response(conn, 400, "error: invalid request\n"); return -1; } + +/* New gen HTTP server */ + +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + struct mk_list *header_iterator; + struct flb_slist_entry *header_value; + struct flb_slist_entry *header_name; + struct flb_config_map_val *header_pair; + struct flb_http *context; + + context = (struct flb_http *) response->stream->user_data; + + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (http_status == 200 || + http_status == 201 || + http_status == 204) { + + flb_config_map_foreach(header_iterator, + header_pair, + context->success_headers) { + header_name = mk_list_entry_first(header_pair->val.list, + struct flb_slist_entry, + _head); + + header_value = mk_list_entry_last(header_pair->val.list, + struct flb_slist_entry, + _head); + + flb_http_response_set_header(response, + header_name->str, 0, + header_value->str, 0); + } + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int process_pack_ng(struct flb_http *ctx, flb_sds_t tag, char *buf, size_t size) +{ + int ret; + size_t off = 0; + msgpack_unpacked result; + struct flb_time tm; + int i = 0; + msgpack_object *obj; + msgpack_object record; + flb_sds_t tag_from_record = NULL; + + flb_time_get(&tm); + + msgpack_unpacked_init(&result); + while (msgpack_unpack_next(&result, buf, size, &off) == MSGPACK_UNPACK_SUCCESS) { + if (result.data.type == MSGPACK_OBJECT_MAP) { + tag_from_record = NULL; + if (ctx->tag_key) { + obj = &result.data; + tag_from_record = tag_key(ctx, obj); + } + + ret = flb_log_event_encoder_begin_record(&ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_timestamp( + &ctx->log_encoder, + &tm); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_body_from_msgpack_object( + &ctx->log_encoder, + &result.data); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(&ctx->log_encoder); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + if (tag_from_record) { + flb_input_log_append(ctx->ins, + tag_from_record, + flb_sds_len(tag_from_record), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + + flb_sds_destroy(tag_from_record); + } + else if (tag) { + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + else { + /* use default plugin Tag (it internal name, e.g: http.0 */ + flb_input_log_append(ctx->ins, NULL, 0, + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + } + else { + flb_plg_error(ctx->ins, "Error encoding record : %d", ret); + } + + flb_log_event_encoder_reset(&ctx->log_encoder); + } + else if (result.data.type == MSGPACK_OBJECT_ARRAY) { + obj = &result.data; + for (i = 0; i < obj->via.array.size; i++) + { + record = obj->via.array.ptr[i]; + + tag_from_record = NULL; + if (ctx->tag_key) { + tag_from_record = tag_key(ctx, &record); + } + + ret = flb_log_event_encoder_begin_record(&ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_timestamp( + &ctx->log_encoder, + &tm); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_body_from_msgpack_object( + &ctx->log_encoder, + &record); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(&ctx->log_encoder); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + if (tag_from_record) { + flb_input_log_append(ctx->ins, + tag_from_record, + flb_sds_len(tag_from_record), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + + flb_sds_destroy(tag_from_record); + } + else if (tag) { + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + else { + /* use default plugin Tag (it internal name, e.g: http.0 */ + flb_input_log_append(ctx->ins, NULL, 0, + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + } + else { + flb_plg_error(ctx->ins, "Error encoding record : %d", ret); + } + + /* TODO : Optimize this + * + * This is wasteful, considering that we are emitting a series + * of records we should start and commit each one and then + * emit them all at once after the loop. + */ + + flb_log_event_encoder_reset(&ctx->log_encoder); + } + + break; + } + else { + flb_plg_error(ctx->ins, "skip record from invalid type: %i", + result.data.type); + + msgpack_unpacked_destroy(&result); + + return -1; + } + } + + msgpack_unpacked_destroy(&result); + + return 0; +} + +static ssize_t parse_payload_json_ng(flb_sds_t tag, + struct flb_http_request *request) +{ + int ret; + int out_size; + char *pack; + struct flb_pack_state pack_state; + struct flb_http *ctx; + char *payload; + size_t size; + + ctx = (struct flb_http *) request->stream->user_data; + payload = (char *) request->body; + size = cfl_sds_len(request->body); + + /* Initialize packer */ + flb_pack_state_init(&pack_state); + + /* Pack JSON as msgpack */ + ret = flb_pack_json_state(payload, size, + &pack, &out_size, &pack_state); + flb_pack_state_reset(&pack_state); + + /* Handle exceptions */ + if (ret == FLB_ERR_JSON_PART) { + flb_plg_warn(ctx->ins, "JSON data is incomplete, skipping"); + return -1; + } + else if (ret == FLB_ERR_JSON_INVAL) { + flb_plg_warn(ctx->ins, "invalid JSON message, skipping"); + return -1; + } + else if (ret == -1) { + return -1; + } + + /* Process the packaged JSON and return the last byte used */ + process_pack_ng(ctx, tag, pack, out_size); + flb_free(pack); + + return 0; +} + +static ssize_t parse_payload_urlencoded_ng(flb_sds_t tag, + struct flb_http_request *request) +{ + struct mk_list *kvs; + struct mk_list *head = NULL; + struct flb_split_entry *cur = NULL; + char **keys = NULL; + char **vals = NULL; + char *sep; + char *start; + int idx = 0; + int ret = -1; + msgpack_packer pck; + msgpack_sbuffer sbuf; + struct flb_http *ctx; + char *payload; + + ctx = (struct flb_http *) request->stream->user_data; + payload = (char *) request->body; + + /* initialize buffers */ + msgpack_sbuffer_init(&sbuf); + msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write); + + kvs = flb_utils_split(payload, '&', -1 ); + if (kvs == NULL) { + goto split_error; + } + + keys = flb_calloc(mk_list_size(kvs), sizeof(char *)); + if (keys == NULL) { + goto keys_calloc_error; + } + + vals = flb_calloc(mk_list_size(kvs), sizeof(char *)); + if (vals == NULL) { + goto vals_calloc_error; + } + + mk_list_foreach(head, kvs) { + cur = mk_list_entry(head, struct flb_split_entry, _head); + if (cur->value[0] == '\n') { + start = &cur->value[1]; + } else { + start = cur->value; + } + sep = strchr(start, '='); + if (sep == NULL) { + vals[idx] = NULL; + continue; + } + *sep++ = '\0'; + + keys[idx] = flb_sds_create_len(start, strlen(start)); + vals[idx] = flb_sds_create_len(sep, strlen(sep)); + + flb_sds_trim(keys[idx]); + flb_sds_trim(vals[idx]); + idx++; + } + + msgpack_pack_map(&pck, mk_list_size(kvs)); + for (idx = 0; idx < mk_list_size(kvs); idx++) { + msgpack_pack_str(&pck, flb_sds_len(keys[idx])); + msgpack_pack_str_body(&pck, keys[idx], flb_sds_len(keys[idx])); + + if (sds_uri_decode(vals[idx]) != 0) { + goto decode_error; + } else { + msgpack_pack_str(&pck, flb_sds_len(vals[idx])); + msgpack_pack_str_body(&pck, vals[idx], strlen(vals[idx])); + } + } + + ret = process_pack(ctx, tag, sbuf.data, sbuf.size); + +decode_error: + for (idx = 0; idx < mk_list_size(kvs); idx++) { + if (keys[idx]) { + flb_sds_destroy(keys[idx]); + } + if (vals[idx]) { + flb_sds_destroy(vals[idx]); + } + } + flb_free(vals); +vals_calloc_error: + flb_free(keys); +keys_calloc_error: + flb_utils_split_free(kvs); +split_error: + msgpack_sbuffer_destroy(&sbuf); + return ret; +} + +static int process_payload_ng(flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int type; + + type = -1; + + if (request->content_type == NULL) { + send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); + return -1; + } + + if (strcasecmp(request->content_type, "application/json") == 0) { + type = HTTP_CONTENT_JSON; + } + + if (strcasecmp(request->content_type, "application/x-www-form-urlencoded") == 0) { + type = HTTP_CONTENT_URLENCODED; + } + + if (type == -1) { + send_response_ng(response, 400, "error: invalid 'Content-Type'\n"); + return -1; + } + + if (request->body == NULL || + cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, "error: no payload found\n"); + return -1; + } + + if (type == HTTP_CONTENT_JSON) { + parse_payload_json_ng(tag, request); + } else if (type == HTTP_CONTENT_URLENCODED) { + parse_payload_urlencoded_ng(tag, request); + } + + return 0; +} + +int http_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + int i; + int ret; + int len; + flb_sds_t tag; + struct flb_http_server_session *session; + struct flb_http *context; + + context = (struct flb_http *) response->stream->user_data; + FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + /* Compose the query string using the URI */ + len = cfl_sds_len(request->path); + + if (len == 1) { + tag = NULL; /* use default tag */ + } + else { + tag = flb_sds_create(&request->path[1]); + + if (tag == NULL) { + return -1; + } + + /* Sanitize, only allow alphanum chars */ + for (i = 0; i < flb_sds_len(tag); i++) { + if (!isalnum(tag[i]) && tag[i] != '_' && tag[i] != '.') { + tag[i] = '_'; + } + } + } + + /* ToDo: Fix me */ + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + flb_sds_destroy(tag); + + return -1; + } + + if (request->method != HTTP_METHOD_POST) { + send_response_ng(response, 400, "error: invalid HTTP method\n"); + flb_sds_destroy(tag); + + return -1; + } + + ret = process_payload_ng(tag, request, response); + + flb_sds_destroy(tag); + + send_response_ng(response, context->successful_response_code, NULL); + + return ret; +} diff --git a/plugins/in_http/http_prot.h b/plugins/in_http/http_prot.h index 202ab14d2be..461ba14acbd 100644 --- a/plugins/in_http/http_prot.h +++ b/plugins/in_http/http_prot.h @@ -20,10 +20,15 @@ #ifndef FLB_IN_HTTP_PROT #define FLB_IN_HTTP_PROT +#include + int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); +int http_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + int http_prot_handle_error(struct flb_http *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); From dd28875d721cdef2c06b32bc972c13963dfe198c Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:57:49 +0100 Subject: [PATCH 07/24] in_elasticsearch: added http2 support Signed-off-by: Leonardo Alminana --- plugins/in_elasticsearch/in_elasticsearch.c | 101 +++++-- plugins/in_elasticsearch/in_elasticsearch.h | 13 +- .../in_elasticsearch_bulk_prot.c | 256 ++++++++++++++++++ .../in_elasticsearch_bulk_prot.h | 4 + 4 files changed, 345 insertions(+), 29 deletions(-) diff --git a/plugins/in_elasticsearch/in_elasticsearch.c b/plugins/in_elasticsearch/in_elasticsearch.c index 24e997511d6..3d4df78cfe5 100644 --- a/plugins/in_elasticsearch/in_elasticsearch.c +++ b/plugins/in_elasticsearch/in_elasticsearch.c @@ -25,6 +25,7 @@ #include "in_elasticsearch.h" #include "in_elasticsearch_config.h" +#include "in_elasticsearch_bulk_prot.h" #include "in_elasticsearch_bulk_conn.h" /* @@ -135,39 +136,81 @@ static int in_elasticsearch_bulk_init(struct flb_input_instance *ins, bytes_to_nodename(rand, ctx->node_name, 12); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - in_elasticsearch_config_destroy(ctx); + http_config_destroy(ctx); - return -1; - } + return -1; + } - flb_input_downstream_set(ctx->downstream, ctx->ins); + ret = flb_http_server_start(&ctx->http_server); - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_elasticsearch_bulk_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_ELASTICSEARCH input plugin"); - in_elasticsearch_config_destroy(ctx); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - return -1; - } + http_config_destroy(ctx); - ctx->collector_id = ret; + return -1; + } + + ctx->http_server.request_callback = in_elasticsearch_bulk_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + in_elasticsearch_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_elasticsearch_bulk_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_ELASTICSEARCH input plugin"); + in_elasticsearch_config_destroy(ctx); + + return -1; + } + + ctx->collector_id = ret; + } return 0; } @@ -189,6 +232,12 @@ static int in_elasticsearch_bulk_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_in_elasticsearch, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_in_elasticsearch, buffer_max_size), diff --git a/plugins/in_elasticsearch/in_elasticsearch.h b/plugins/in_elasticsearch/in_elasticsearch.h index 665995c824c..6af189559a5 100644 --- a/plugins/in_elasticsearch/in_elasticsearch.h +++ b/plugins/in_elasticsearch/in_elasticsearch.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -41,6 +42,15 @@ struct flb_in_elasticsearch { char cluster_name[16]; char node_name[12]; + struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ int collector_id; size_t buffer_max_size; /* Maximum buffer size */ @@ -49,10 +59,7 @@ struct flb_in_elasticsearch { struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ - struct flb_log_event_encoder log_encoder; - struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c index e304947df7a..123a8345489 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c +++ b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c @@ -920,3 +920,259 @@ int in_elasticsearch_bulk_prot_handle_error(struct flb_in_elasticsearch *ctx, send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + +/* New gen HTTP server */ +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *content_type, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (content_type != NULL) { + flb_http_response_set_header(response, + "content-type", 0, + content_type, 0); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_json_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + return send_response_ng(response, http_status, "application/json", message); +} + +static int send_version_message_response_ng(struct flb_http_response *response, + struct flb_in_elasticsearch *ctx, + int http_status) +{ + flb_sds_t message; + + if (http_status != 200) { + return 0; + } + + message = flb_sds_create_size(384); + + if (message == NULL) { + return -1; + } + + flb_sds_printf(&message, + ES_VERSION_RESPONSE_TEMPLATE, + ctx->es_version); + + send_json_response_ng(response, http_status, message); + + cfl_sds_destroy(message); + + return 0; +} + +static int send_dummy_sniffer_response_ng(struct flb_http_response *response, + struct flb_in_elasticsearch *ctx, + int http_status) +{ + flb_sds_t hostname; + flb_sds_t resp; + + if (http_status != 200) { + return 0; + } + + if (ctx->hostname != NULL) { + hostname = ctx->hostname; + } + else { + hostname = "localhost"; + } + + resp = flb_sds_create_size(384); + if (!resp) { + return -1; + } + + flb_sds_printf(&resp, + ES_NODES_TEMPLATE, + ctx->cluster_name, ctx->node_name, + hostname, ctx->tcp_port, ctx->buffer_max_size); + + send_json_response_ng(response, http_status, resp); + + flb_sds_destroy(resp); + + return 0; +} + +static int process_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + struct flb_in_elasticsearch *context, + flb_sds_t tag, + flb_sds_t bulk_statuses) +{ + if (request->content_type == NULL) { + send_response_ng(response, 400, NULL, "error: header 'Content-Type' is not set\n"); + + return -1; + } + + if (strncasecmp(request->content_type, "application/x-ndjson", 20) != 0 && + strncasecmp(request->content_type, "application/json", 16) != 0) { + send_response_ng(response, 400, NULL, "error: invalid 'Content-Type'\n"); + + return -1; + } + + if (request->body == NULL || cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, NULL, "error: no payload found\n"); + return -1; + } + + parse_payload_ndjson(context, tag, request->body, cfl_sds_len(request->body), bulk_statuses); + + return 0; +} + +int in_elasticsearch_bulk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + flb_sds_t bulk_statuses; + flb_sds_t bulk_response; + const char *error_str; + struct flb_http_server_session *session; + struct flb_in_elasticsearch *context; + flb_sds_t tag; + size_t len; + + bulk_statuses = NULL; + bulk_response = NULL; + + context = (struct flb_in_elasticsearch *) response->stream->user_data; + FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); + + if (request->path[0] != '/') { + send_response_ng(response, 400, NULL, "error: invalid request\n"); + return -1; + } + + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method == HTTP_METHOD_HEAD) { + send_response_ng(response, 200, NULL, NULL); + + return -1; + } + else if (request->method == HTTP_METHOD_PUT) { + send_json_response_ng(response, 200, "{}"); + + return -1; + } + else if (request->method == HTTP_METHOD_GET) { + if (strncmp(request->path, "/_nodes/http", 12) == 0) { + send_dummy_sniffer_response_ng(response, context, 200); + } + else if (strcmp(request->path, "/") == 0) { + send_version_message_response_ng(response, context, 200); + } + else { + send_json_response_ng(response, 200, "{}"); + } + + return 0; + } + else if (request->method == HTTP_METHOD_POST) { + if (strcmp(request->path, "/_bulk") == 0) { + bulk_statuses = flb_sds_create_size(context->buffer_max_size); + + if (bulk_statuses == NULL) { + return -1; + } + + bulk_response = flb_sds_create_size(context->buffer_max_size); + + if (bulk_response == NULL) { + flb_sds_destroy(bulk_statuses); + return -1; + } + + tag = flb_sds_create(context->ins->tag); + + if (tag == NULL) { + flb_sds_destroy(bulk_statuses); + flb_sds_destroy(bulk_response); + return -1; + } + + process_payload_ng(request, response, context, tag, bulk_statuses); + + flb_sds_destroy(tag); + + len = flb_sds_len(bulk_statuses); + + if (flb_sds_alloc(bulk_response) < len + 27) { + bulk_response = flb_sds_increase(bulk_response, len + 27 - flb_sds_alloc(bulk_response)); + } + + error_str = strstr(bulk_statuses, "\"status\":40"); + + if (error_str){ + flb_sds_cat(bulk_response, "{\"errors\":true,\"items\":[", 24); + } + else { + flb_sds_cat(bulk_response, "{\"errors\":false,\"items\":[", 25); + } + + flb_sds_cat(bulk_response, bulk_statuses, flb_sds_len(bulk_statuses)); + flb_sds_cat(bulk_response, "]}", 2); + + send_json_response_ng(response, 200, bulk_response); + + flb_sds_destroy(bulk_statuses); + flb_sds_destroy(bulk_response); + + } else { + send_response_ng(response, 400, NULL, "error: invalid HTTP endpoint\n"); + + return -1; + } + } + else { + send_response_ng(response, 400, NULL, "error: invalid HTTP method\n"); + + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h index 677e2e289ef..28acce9d5fd 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h +++ b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h @@ -37,4 +37,8 @@ int in_elasticsearch_bulk_prot_handle_error(struct flb_in_elasticsearch *ctx, struct mk_http_session *session, struct mk_http_request *request); + +int in_elasticsearch_bulk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif From e32ef3a1276d216de403e8c336e1e6fd5f79edcb Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:58:00 +0100 Subject: [PATCH 08/24] in_splunk: added http2 support Signed-off-by: Leonardo Alminana --- plugins/in_splunk/splunk.c | 102 +++++++++--- plugins/in_splunk/splunk.h | 21 ++- plugins/in_splunk/splunk_prot.c | 270 ++++++++++++++++++++++++++++++++ plugins/in_splunk/splunk_prot.h | 5 + 4 files changed, 364 insertions(+), 34 deletions(-) diff --git a/plugins/in_splunk/splunk.c b/plugins/in_splunk/splunk.c index 7436477c24c..0a1a37bd7cf 100644 --- a/plugins/in_splunk/splunk.c +++ b/plugins/in_splunk/splunk.c @@ -24,6 +24,7 @@ #include "splunk.h" #include "splunk_conn.h" +#include "splunk_prot.h" #include "splunk_config.h" /* @@ -92,39 +93,82 @@ static int in_splunk_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - splunk_config_destroy(ctx); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - return -1; - } + splunk_config_destroy(ctx); - flb_input_downstream_set(ctx->downstream, ctx->ins); + return -1; + } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_splunk_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - splunk_config_destroy(ctx); + ret = flb_http_server_start(&ctx->http_server); - return -1; - } + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); + + splunk_config_destroy(ctx); - ctx->collector_id = ret; + return -1; + } + + ctx->http_server.request_callback = splunk_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + splunk_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_splunk_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + splunk_config_destroy(ctx); + + return -1; + } + + ctx->collector_id = ret; + } return 0; } @@ -162,6 +206,12 @@ static void in_splunk_resume(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_splunk, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_splunk, buffer_max_size), diff --git a/plugins/in_splunk/splunk.h b/plugins/in_splunk/splunk.h index 42f6bdd9184..e55bbc2a897 100644 --- a/plugins/in_splunk/splunk.h +++ b/plugins/in_splunk/splunk.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -36,24 +37,28 @@ struct flb_splunk { flb_sds_t tcp_port; const char *tag_key; - int collector_id; - /* Success HTTP headers */ struct mk_list *success_headers; - flb_sds_t success_headers_str; - - size_t buffer_max_size; /* Maximum buffer size */ - size_t buffer_chunk_size; /* Chunk allocation size */ /* Token Auth */ flb_sds_t auth_header; struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ + flb_sds_t success_headers_str; + int collector_id; + size_t buffer_max_size; /* Maximum buffer size */ + size_t buffer_chunk_size; /* Chunk allocation size */ struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ - struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_splunk/splunk_prot.c b/plugins/in_splunk/splunk_prot.c index 64490c57d5b..77ccc51b0e0 100644 --- a/plugins/in_splunk/splunk_prot.c +++ b/plugins/in_splunk/splunk_prot.c @@ -779,3 +779,273 @@ int splunk_prot_handle_error(struct flb_splunk *ctx, struct splunk_conn *conn, send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + + + + +/* New gen HTTP server */ + +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_json_message_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + flb_http_response_set_header(response, + "content-type", 0, + "application/json", 0); + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int validate_auth_header_ng(struct flb_splunk *ctx, struct flb_http_request *request) +{ + char *auth_header; + + if (ctx->auth_header == NULL) { + return SPLUNK_AUTH_UNAUTH; + } + + auth_header = flb_http_request_get_header(request, "authorization"); + + if (auth_header == NULL) { + return SPLUNK_AUTH_MISSING_CRED; + } + + if (auth_header != NULL && strlen(auth_header) > 0) { + if (strncmp(ctx->auth_header, + auth_header, + strlen(ctx->auth_header)) == 0) { + return SPLUNK_AUTH_SUCCESS; + } + else { + return SPLUNK_AUTH_UNAUTHORIZED; + } + } + else { + return SPLUNK_AUTH_MISSING_CRED; + } + + return SPLUNK_AUTH_SUCCESS; +} + +static int process_hec_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + flb_sds_t tag, + struct flb_splunk *ctx) +{ + int type = -1; + + if (request->content_type == NULL) { + send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); + + return -1; + } + + if (strcasecmp(request->content_type, "application/json") == 0) { + type = HTTP_CONTENT_JSON; + } + else if (strcasecmp(request->content_type, "text/plain") == 0) { + type = HTTP_CONTENT_TEXT; + } + else { + /* Not neccesary to specify content-type for Splunk HEC. */ + flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + + type = HTTP_CONTENT_UNKNOWN; + } + + if (request->body == NULL || cfl_sds_len(request->body) <= 0) { + send_response_ng(response, 400, "error: no payload found\n"); + + return -1; + } + + handle_hec_payload(ctx, type, tag, request->body, cfl_sds_len(request->body)); + + return 0; +} + +static int process_hec_raw_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + flb_sds_t tag, + struct flb_splunk *ctx) +{ + if (request->content_type == NULL) { + send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); + + return -1; + } + else if (strcasecmp(request->content_type, "text/plain") != 0) { + /* Not neccesary to specify content-type for Splunk HEC. */ + flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + } + + if (request->body == NULL || cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, "error: no payload found\n"); + + return -1; + } + + /* Always handle as raw type of payloads here */ + return process_raw_payload_pack(ctx, tag, request->body, cfl_sds_len(request->body)); +} + +int splunk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + struct flb_http_server_session *session; + struct flb_splunk *context; + int ret; + flb_sds_t tag; + + context = (struct flb_splunk *) response->stream->user_data; + FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method == HTTP_METHOD_GET) { + /* Handle health minotoring of splunk hec endpoint for load balancers */ + if (strcasecmp(request->path, "/services/collector/health") == 0) { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":200}"); + } + else { + send_response_ng(response, 400, "error: invalid HTTP endpoint\n"); + } + + return 0; + } + + /* Under services/collector endpoints are required for + * authentication if provided splunk_token */ + ret = validate_auth_header_ng(context, request); + + if (ret < 0) { + send_response_ng(response, 401, "error: unauthorized\n"); + + if (ret == SPLUNK_AUTH_MISSING_CRED) { + flb_plg_warn(context->ins, "missing credentials in request headers"); + } + else if (ret == SPLUNK_AUTH_UNAUTHORIZED) { + flb_plg_warn(context->ins, "wrong credentials in request headers"); + } + + return -1; + } + + /* Handle every ingested payload cleanly */ + flb_log_event_encoder_reset(&context->log_encoder); + + if (request->method != HTTP_METHOD_POST) { + /* HEAD, PUT, PATCH, and DELETE methods are prohibited to use.*/ + send_response_ng(response, 400, "error: invalid HTTP method\n"); + + return -1; + } + + tag = flb_sds_create(context->ins->tag); + + if (tag == NULL) { + return -1; + } + + if (strcasecmp(request->path, "/services/collector/raw") == 0) { + ret = process_hec_raw_payload_ng(request, response, tag, context); + + if (!ret) { + send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); + } + else { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":0}"); + } + + ret = 0; + } + else if (strcasecmp(request->path, "/services/collector/event") == 0 || + strcasecmp(request->path, "/services/collector") == 0) { + ret = process_hec_payload_ng(request, response, tag, context); + + if (!ret) { + send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); + } + else { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":0}"); + } + + ret = 0; + } + else { + send_response_ng(response, 400, "error: invalid HTTP endpoint\n"); + + ret = -1; + } + + flb_sds_destroy(tag); + + + return ret; +} \ No newline at end of file diff --git a/plugins/in_splunk/splunk_prot.h b/plugins/in_splunk/splunk_prot.h index 472f43a952c..99e704e8f96 100644 --- a/plugins/in_splunk/splunk_prot.h +++ b/plugins/in_splunk/splunk_prot.h @@ -25,6 +25,8 @@ #define SPLUNK_AUTH_MISSING_CRED -1 #define SPLUNK_AUTH_UNAUTHORIZED -2 +#include + int splunk_prot_handle(struct flb_splunk *ctx, struct splunk_conn *conn, struct mk_http_session *session, struct mk_http_request *request); @@ -33,4 +35,7 @@ int splunk_prot_handle_error(struct flb_splunk *ctx, struct splunk_conn *conn, struct mk_http_session *session, struct mk_http_request *request); +int splunk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif From 9dfb20b035b89a3554c2749e1c5142b702cfdac6 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Tue, 12 Mar 2024 18:58:13 +0100 Subject: [PATCH 09/24] in_opentelemetry: added http2 and grpc support Signed-off-by: Leonardo Alminana --- plugins/in_opentelemetry/opentelemetry.c | 105 +++- plugins/in_opentelemetry/opentelemetry.h | 9 +- plugins/in_opentelemetry/opentelemetry_prot.c | 520 ++++++++++++++++++ plugins/in_opentelemetry/opentelemetry_prot.h | 6 + 4 files changed, 611 insertions(+), 29 deletions(-) diff --git a/plugins/in_opentelemetry/opentelemetry.c b/plugins/in_opentelemetry/opentelemetry.c index 48056fcae15..e01eeacc936 100644 --- a/plugins/in_opentelemetry/opentelemetry.c +++ b/plugins/in_opentelemetry/opentelemetry.c @@ -25,6 +25,7 @@ #include "http_conn.h" #include "opentelemetry.h" +#include "opentelemetry_prot.h" #include "opentelemetry_config.h" /* @@ -89,25 +90,80 @@ static int in_opentelemetry_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - opentelemetry_config_destroy(ctx); + opentelemetry_config_destroy(ctx); - return -1; - } + return -1; + } + + ret = flb_http_server_start(&ctx->http_server); + + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - flb_input_downstream_set(ctx->downstream, ctx->ins); + opentelemetry_config_destroy(ctx); + + return -1; + } + + ctx->http_server.request_callback = opentelemetry_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + opentelemetry_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_opentelemetry_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + opentelemetry_config_destroy(ctx); + return -1; + } + + ctx->collector_id = ret; + } flb_plg_info(ctx->ins, "listening on %s:%s", ctx->listen, ctx->tcp_port); @@ -119,19 +175,6 @@ static int in_opentelemetry_init(struct flb_input_instance *ins, ctx->successful_response_code = 201; } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_opentelemetry_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - opentelemetry_config_destroy(ctx); - return -1; - } - - ctx->collector_id = ret; - return 0; } @@ -152,6 +195,12 @@ static int in_opentelemetry_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_opentelemetry, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_opentelemetry, buffer_max_size), diff --git a/plugins/in_opentelemetry/opentelemetry.h b/plugins/in_opentelemetry/opentelemetry.h index de42b48f549..dafc88fa8c4 100644 --- a/plugins/in_opentelemetry/opentelemetry.h +++ b/plugins/in_opentelemetry/opentelemetry.h @@ -25,6 +25,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -37,6 +38,13 @@ struct flb_opentelemetry { bool raw_traces; int tag_from_uri; + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ size_t buffer_max_size; /* Maximum buffer size */ size_t buffer_chunk_size; /* Chunk allocation size */ @@ -45,7 +53,6 @@ struct flb_opentelemetry { struct mk_list connections; /* linked list of connections */ struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_opentelemetry/opentelemetry_prot.c b/plugins/in_opentelemetry/opentelemetry_prot.c index 189f3c262c0..f993a82fbc1 100644 --- a/plugins/in_opentelemetry/opentelemetry_prot.c +++ b/plugins/in_opentelemetry/opentelemetry_prot.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include @@ -1682,3 +1684,521 @@ int opentelemetry_prot_handle_error(struct flb_opentelemetry *ctx, struct http_c send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + + + + + + +/* New gen HTTP server */ +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_grpc_response_ng(struct flb_http_response *response, + uint8_t *message_buffer, + size_t message_length, + int grpc_status, + char *grpc_message) +{ + char grpc_status_as_string[16]; + uint32_t wire_message_length; + size_t body_buffer_size; + cfl_sds_t body_buffer; + + body_buffer_size = 5 + message_length; + + if (body_buffer_size < 65) { + body_buffer_size = 65; + } + + body_buffer = cfl_sds_create_size(body_buffer_size); + + if (body_buffer == NULL) { + return -1; + } + + sprintf(grpc_status_as_string, "%u", grpc_status); + + wire_message_length = (uint32_t) message_length; + + cfl_sds_cat(body_buffer, "\x00----", 5); + + ((uint8_t *) body_buffer)[1] = (wire_message_length & 0xFF000000) >> 24; + ((uint8_t *) body_buffer)[2] = (wire_message_length & 0x00FF0000) >> 16; + ((uint8_t *) body_buffer)[3] = (wire_message_length & 0x0000FF00) >> 8; + ((uint8_t *) body_buffer)[4] = (wire_message_length & 0x000000FF) >> 0; + + if (message_buffer != NULL) { + cfl_sds_cat(body_buffer, (char *) message_buffer, message_length); + } + + flb_http_response_set_status(response, 200); + + flb_http_response_set_body(response, + (unsigned char *) body_buffer, + 5 + message_length); + + flb_http_response_set_header(response, + "content-type", 0, + "application/grpc", 0); + + flb_http_response_set_trailer_header(response, + "grpc-status", 0, + grpc_status_as_string, 0); + + flb_http_response_set_trailer_header(response, + "grpc-message", 0, + grpc_message, 0); + + flb_http_response_commit(response); + + cfl_sds_destroy(body_buffer); + + return 0; +} + +static int send_export_logs_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Logs__V1__ExportLogsServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__logs__v1__export_logs_service_response__init(&message); + + message_length = opentelemetry__proto__collector__logs__v1__export_logs_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__logs__v1__export_logs_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = ""; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} + +static int send_export_metrics_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Metrics__V1__ExportMetricsServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__init(&message); + + message_length = opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = "-"; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} + +static int send_export_traces_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Trace__V1__ExportTraceServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__trace__v1__export_trace_service_response__init(&message); + + message_length = opentelemetry__proto__collector__trace__v1__export_trace_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__trace__v1__export_trace_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = "-"; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} +static int process_payload_metrics_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct cfl_list decoded_contexts; + struct cfl_list *iterator; + struct cmt *context; + size_t offset; + int result; + + offset = 0; + + if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + result = cmt_decode_opentelemetry_create(&decoded_contexts, + &request->body[5], + cfl_sds_len(request->body) - 5, + &offset); + } + else { + result = cmt_decode_opentelemetry_create(&decoded_contexts, + request->body, + cfl_sds_len(request->body), + &offset); + } + + if (result == CMT_DECODE_OPENTELEMETRY_SUCCESS) { + cfl_list_foreach(iterator, &decoded_contexts) { + context = cfl_list_entry(iterator, struct cmt, _head); + + result = flb_input_metrics_append(ctx->ins, NULL, 0, context); + + if (result != 0) { + flb_plg_debug(ctx->ins, "could not ingest metrics context : %d", result); + } + } + + cmt_decode_opentelemetry_destroy(&decoded_contexts); + } + + return 0; +} + + +static int process_payload_traces_proto_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct ctrace *decoded_context; + size_t offset; + int result; + + offset = 0; + + if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + result = ctr_decode_opentelemetry_create(&decoded_context, + &request->body[5], + cfl_sds_len(request->body) - 5, + &offset); + } + else { + result = ctr_decode_opentelemetry_create(&decoded_context, + request->body, + cfl_sds_len(request->body), + &offset); + } + + if (result == 0) { + result = flb_input_trace_append(ctx->ins, NULL, 0, decoded_context); + ctr_decode_opentelemetry_destroy(decoded_context); + } + + return result; +} + +static int process_payload_raw_traces_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int ret; + int root_type; + char *out_buf = NULL; + size_t out_size; + + msgpack_packer mp_pck; + msgpack_sbuffer mp_sbuf; + + msgpack_sbuffer_init(&mp_sbuf); + msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write); + + msgpack_pack_array(&mp_pck, 2); + flb_pack_time_now(&mp_pck); + + /* Check if the incoming payload is a valid JSON message and convert it to msgpack */ + ret = flb_pack_json(request->body, cfl_sds_len(request->body), + &out_buf, &out_size, &root_type, NULL); + + if (ret == 0 && root_type == JSMN_OBJECT) { + /* JSON found, pack it msgpack representation */ + msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size); + } + else { + /* the content might be a binary payload or invalid JSON */ + msgpack_pack_map(&mp_pck, 1); + msgpack_pack_str_with_body(&mp_pck, "trace", 5); + msgpack_pack_str_with_body(&mp_pck, request->body, cfl_sds_len(request->body)); + } + + /* release 'out_buf' if it was allocated */ + if (out_buf) { + flb_free(out_buf); + } + + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), mp_sbuf.data, mp_sbuf.size); + msgpack_sbuffer_destroy(&mp_sbuf); + + return 0; +} + +static int process_payload_traces_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int result; + + if (ctx->raw_traces) { + result = process_payload_raw_traces_ng(ctx, tag, request, response); + } + else { + result = process_payload_traces_proto_ng(ctx, tag, request, response); + } + + return result; +} + +static int process_payload_logs_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct flb_log_event_encoder *encoder; + int ret; + + encoder = flb_log_event_encoder_create(FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2); + + if (encoder == NULL) { + return -1; + } + + if (request->content_type == NULL) { + flb_error("[otel] content type missing"); + + ret = -1; + } + else if (strcasecmp(request->content_type, "application/json") == 0) { + ret = json_payload_to_msgpack(ctx, + encoder, + request->body, + cfl_sds_len(request->body)); + } + else if (strcasecmp(request->content_type, "application/x-protobuf") == 0) { + ret = binary_payload_to_msgpack(encoder, + (uint8_t *) request->body, + cfl_sds_len(request->body)); + } + else if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + ret = binary_payload_to_msgpack(encoder, + &((uint8_t *) request->body)[5], + (cfl_sds_len(request->body)) - 5); + } + else { + flb_error("[otel] Unsupported content type %s", request->content_type); + + ret = -1; + } + + if (ret == 0) { + ret = flb_input_log_append(ctx->ins, + tag, + flb_sds_len(tag), + encoder->output_buffer, + encoder->output_length); + } + + flb_log_event_encoder_destroy(encoder); + + return ret; +} + + +static int send_export_service_response_ng(struct flb_http_response *response, + int result, + char payload_type) +{ + switch (payload_type) { + case 'M': + return send_export_metrics_service_response_ng(response, result); + case 'T': + return send_export_traces_service_response_ng(response, result); + case 'L': + return send_export_logs_service_response_ng(response, result); + default: + return -1; + } +} + +int opentelemetry_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + char payload_type; + int grpc_request; + struct flb_http_server_session *session; + struct flb_opentelemetry *context; + int result; + + context = (struct flb_opentelemetry *) response->stream->user_data; + FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + if (strcmp(request->path, "/v1/metrics") == 0 || + strcmp(request->path, "/v1/traces") == 0 || + strcmp(request->path, "/v1/logs") == 0) { + grpc_request = FLB_FALSE; + } + else if(strcmp(request->path, "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.traces.v1.TracesService/Export") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.logs.v1.LogsService/Export") == 0) { + grpc_request = FLB_TRUE; + } + else { + send_response_ng(response, 400, "error: invalid endpoint\n"); + + return -1; + } + + /* ToDo: Fix me */ + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method != HTTP_METHOD_POST) { + send_response_ng(response, 400, "error: invalid HTTP method\n"); + + return -1; + } + + if (strcmp(request->path, "/v1/metrics") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") == 0) { + payload_type = 'M'; + result = process_payload_metrics_ng(context, context->ins->tag, request, response); + } + else if (strcmp(request->path, "/v1/traces") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.traces.v1.TracesService/Export") == 0) { + payload_type = 'T'; + result = process_payload_traces_ng(context, context->ins->tag, request, response); + } + else if (strcmp(request->path, "/v1/logs") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.logs.v1.LogsService/Export") == 0) { + payload_type = 'L'; + result = process_payload_logs_ng(context, context->ins->tag, request, response); + } + + if (grpc_request) { + send_export_service_response_ng(response, result, payload_type); + } + else { + send_response_ng(response, context->successful_response_code, NULL); + } + + return result; +} diff --git a/plugins/in_opentelemetry/opentelemetry_prot.h b/plugins/in_opentelemetry/opentelemetry_prot.h index 1a9113c8065..39165c8087b 100644 --- a/plugins/in_opentelemetry/opentelemetry_prot.h +++ b/plugins/in_opentelemetry/opentelemetry_prot.h @@ -20,6 +20,8 @@ #ifndef FLB_IN_OPENTELEMETRY_PROT #define FLB_IN_OPENTELEMETRY_PROT +#include + int opentelemetry_prot_handle(struct flb_opentelemetry *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); @@ -28,4 +30,8 @@ int opentelemetry_prot_handle_error(struct flb_opentelemetry *ctx, struct http_c struct mk_http_session *session, struct mk_http_request *request); + +int opentelemetry_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif From 73ac296f05c2fffd37db83ee355510b7bee8ed57 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 13:56:48 +0100 Subject: [PATCH 10/24] in_elasticsearch: fixed the usage of the wrong config destructor Signed-off-by: Leonardo Alminana --- plugins/in_elasticsearch/in_elasticsearch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/in_elasticsearch/in_elasticsearch.c b/plugins/in_elasticsearch/in_elasticsearch.c index 3d4df78cfe5..d3963d7ff34 100644 --- a/plugins/in_elasticsearch/in_elasticsearch.c +++ b/plugins/in_elasticsearch/in_elasticsearch.c @@ -155,7 +155,7 @@ static int in_elasticsearch_bulk_init(struct flb_input_instance *ins, "could not initialize http server on %s:%u. Aborting", ins->host.listen, ins->host.port); - http_config_destroy(ctx); + in_elasticsearch_config_destroy(ctx); return -1; } @@ -167,7 +167,7 @@ static int in_elasticsearch_bulk_init(struct flb_input_instance *ins, "could not start http server on %s:%u. Aborting", ins->host.listen, ins->host.port); - http_config_destroy(ctx); + in_elasticsearch_config_destroy(ctx); return -1; } From 49a4c6f5540c96da5fccc9f59e283bee1e313a52 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 13:57:41 +0100 Subject: [PATCH 11/24] in_splunk: fixed an error related to error handling Signed-off-by: Leonardo Alminana --- plugins/in_splunk/splunk_prot.c | 36 ++++++++++++++------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/plugins/in_splunk/splunk_prot.c b/plugins/in_splunk/splunk_prot.c index 77ccc51b0e0..4595015753a 100644 --- a/plugins/in_splunk/splunk_prot.c +++ b/plugins/in_splunk/splunk_prot.c @@ -890,23 +890,19 @@ static int process_hec_payload_ng(struct flb_http_request *request, { int type = -1; - if (request->content_type == NULL) { - send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); - - return -1; - } - - if (strcasecmp(request->content_type, "application/json") == 0) { - type = HTTP_CONTENT_JSON; - } - else if (strcasecmp(request->content_type, "text/plain") == 0) { - type = HTTP_CONTENT_TEXT; - } - else { - /* Not neccesary to specify content-type for Splunk HEC. */ - flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + type = HTTP_CONTENT_UNKNOWN; - type = HTTP_CONTENT_UNKNOWN; + if (request->content_type != NULL) { + if (strcasecmp(request->content_type, "application/json") == 0) { + type = HTTP_CONTENT_JSON; + } + else if (strcasecmp(request->content_type, "text/plain") == 0) { + type = HTTP_CONTENT_TEXT; + } + else { + /* Not neccesary to specify content-type for Splunk HEC. */ + flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + } } if (request->body == NULL || cfl_sds_len(request->body) <= 0) { @@ -915,9 +911,7 @@ static int process_hec_payload_ng(struct flb_http_request *request, return -1; } - handle_hec_payload(ctx, type, tag, request->body, cfl_sds_len(request->body)); - - return 0; + return handle_hec_payload(ctx, type, tag, request->body, cfl_sds_len(request->body)); } static int process_hec_raw_payload_ng(struct flb_http_request *request, @@ -1016,7 +1010,7 @@ int splunk_prot_handle_ng(struct flb_http_request *request, if (strcasecmp(request->path, "/services/collector/raw") == 0) { ret = process_hec_raw_payload_ng(request, response, tag, context); - if (!ret) { + if (ret != 0) { send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); } else { @@ -1029,7 +1023,7 @@ int splunk_prot_handle_ng(struct flb_http_request *request, strcasecmp(request->path, "/services/collector") == 0) { ret = process_hec_payload_ng(request, response, tag, context); - if (!ret) { + if (ret != 0) { send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); } else { From 0e2488b9fcc8d2d58ac0c5861c154bcd1ffce8a0 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 16:40:42 +0100 Subject: [PATCH 12/24] http_server: fixed cleanup errors Signed-off-by: Leonardo Alminana --- .../http_server/flb_http_server_http1.h | 1 + .../http_server/flb_http_server_http2.h | 1 + src/flb_http_common.c | 2 +- src/http_server/flb_http_server.c | 8 +++-- src/http_server/flb_http_server_http1.c | 14 +++++++-- src/http_server/flb_http_server_http2.c | 30 +++++++++++-------- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/include/fluent-bit/http_server/flb_http_server_http1.h b/include/fluent-bit/http_server/flb_http_server_http1.h index 6510bbf45b6..1f16d2424af 100644 --- a/include/fluent-bit/http_server/flb_http_server_http1.h +++ b/include/fluent-bit/http_server/flb_http_server_http1.h @@ -32,6 +32,7 @@ struct flb_http1_server_session { struct mk_http_request inner_request; struct mk_http_parser inner_parser; struct mk_server inner_server; + int initialized; struct flb_http_stream stream; struct flb_http_server_session *parent; }; diff --git a/include/fluent-bit/http_server/flb_http_server_http2.h b/include/fluent-bit/http_server/flb_http_server_http2.h index 761bab868ed..f8225d348f3 100644 --- a/include/fluent-bit/http_server/flb_http_server_http2.h +++ b/include/fluent-bit/http_server/flb_http_server_http2.h @@ -29,6 +29,7 @@ struct flb_http_stream; struct flb_http2_server_session { nghttp2_session *inner_session; + int initialized; struct cfl_list streams; struct flb_http_server_session *parent; }; diff --git a/src/flb_http_common.c b/src/flb_http_common.c index d51391756c3..acaaa7a25d1 100644 --- a/src/flb_http_common.c +++ b/src/flb_http_common.c @@ -202,7 +202,7 @@ void flb_http_response_destroy(struct flb_http_response *response) } if (response->headers != NULL) { - flb_hash_table_destroy(response->headers); + flb_hash_table_destroy(response->headers); } if (response->trailer_headers != NULL) { diff --git a/src/http_server/flb_http_server.c b/src/http_server/flb_http_server.c index f5755dcb772..ca6f07d3497 100644 --- a/src/http_server/flb_http_server.c +++ b/src/http_server/flb_http_server.c @@ -378,6 +378,7 @@ static int flb_http_server_client_activity_event_handler(void *data) close_connection = flb_http_server_should_connection_be_closed(request); flb_http_request_destroy(&stream->request); + flb_http_response_destroy(&stream->response); } result = flb_http_server_session_write(session); @@ -625,10 +626,10 @@ struct flb_http_server_session *flb_http_server_session_create(int version) session = flb_calloc(1, sizeof(struct flb_http_server_session)); if (session != NULL) { - session->releasable = FLB_TRUE; - result = flb_http_server_session_init(session, version); + session->releasable = FLB_TRUE; + if (result != 0) { flb_http_server_session_destroy(session); @@ -658,6 +659,9 @@ void flb_http_server_session_destroy(struct flb_http_server_session *session) cfl_sds_destroy(session->outgoing_data); } + flb_http1_server_session_destroy(&session->http1); + flb_http2_server_session_destroy(&session->http2); + if (session->releasable) { flb_free(session); } diff --git a/src/http_server/flb_http_server_http1.c b/src/http_server/flb_http_server_http1.c index 445a252e7a0..b05dd7c848d 100644 --- a/src/http_server/flb_http_server_http1.c +++ b/src/http_server/flb_http_server_http1.c @@ -382,6 +382,8 @@ int flb_http1_response_commit(struct flb_http_response *response) response_buffer, cfl_sds_len(response_buffer)); + cfl_sds_destroy(response_buffer); + if (sds_result == NULL) { return -9; } @@ -436,6 +438,8 @@ int flb_http1_server_session_init(struct flb_http1_server_session *session, user_data = NULL; } + session->initialized = FLB_TRUE; + dummy_mk_http_session_init(&session->inner_session, &session->inner_server); dummy_mk_http_request_init(&session->inner_session, &session->inner_request); @@ -456,10 +460,14 @@ int flb_http1_server_session_init(struct flb_http1_server_session *session, void flb_http1_server_session_destroy(struct flb_http1_server_session *session) { - if (session->inner_session.channel != NULL) { - mk_channel_release(session->inner_session.channel); + if (session->initialized) { + if (session->inner_session.channel != NULL) { + mk_channel_release(session->inner_session.channel); + + session->inner_session.channel = NULL; + } - session->inner_session.channel = NULL; + session->initialized = FLB_FALSE; } } diff --git a/src/http_server/flb_http_server_http2.c b/src/http_server/flb_http_server_http2.c index 263ca0be305..7075e94a470 100644 --- a/src/http_server/flb_http_server_http2.c +++ b/src/http_server/flb_http_server_http2.c @@ -387,8 +387,6 @@ int flb_http2_response_commit(struct flb_http_response *response) stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; - flb_http_response_destroy(&stream->response); - return 0; } @@ -401,6 +399,12 @@ int flb_http2_server_session_init(struct flb_http2_server_session *session, nghttp2_session_callbacks *callbacks; int result; + session->parent = parent; + session->initialized = FLB_TRUE; + session->inner_session = NULL; + + cfl_list_init(&session->streams); + result = nghttp2_session_callbacks_new(&callbacks); if (result != 0) { @@ -427,10 +431,6 @@ int flb_http2_server_session_init(struct flb_http2_server_session *session, return -2; } - cfl_list_init(&session->streams); - - session->parent = parent; - session_settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; session_settings[0].value = 1; @@ -459,15 +459,19 @@ void flb_http2_server_session_destroy(struct flb_http2_server_session *session) struct flb_http_stream *stream; if (session != NULL) { - cfl_list_foreach_safe(iterator, - iterator_backup, - &session->streams) { - stream = cfl_list_entry(iterator, struct flb_http_stream, _head); + if (session->initialized) { + cfl_list_foreach_safe(iterator, + iterator_backup, + &session->streams) { + stream = cfl_list_entry(iterator, struct flb_http_stream, _head); - flb_http_stream_destroy(stream); - } + flb_http_stream_destroy(stream); + } + + nghttp2_session_del(session->inner_session); - nghttp2_session_del(session->inner_session); + session->initialized = FLB_FALSE; + } } } From bbe413685167bcbd47edb027ab1aa596875f2447 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 17:11:37 +0100 Subject: [PATCH 13/24] in_elasticsearch: added missing cleanup code Signed-off-by: Leonardo Alminana --- plugins/in_elasticsearch/in_elasticsearch_config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/in_elasticsearch/in_elasticsearch_config.c b/plugins/in_elasticsearch/in_elasticsearch_config.c index 687c7695ad4..e8c53f12bc7 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_config.c +++ b/plugins/in_elasticsearch/in_elasticsearch_config.c @@ -92,6 +92,10 @@ int in_elasticsearch_config_destroy(struct flb_in_elasticsearch *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } From 0036121095115cb17d5034f9dd9368935fb1d0bf Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 17:11:50 +0100 Subject: [PATCH 14/24] in_opentelemetry: added missing cleanup code Signed-off-by: Leonardo Alminana --- plugins/in_opentelemetry/opentelemetry_config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/in_opentelemetry/opentelemetry_config.c b/plugins/in_opentelemetry/opentelemetry_config.c index f6896f979d7..3e6b947e6cf 100644 --- a/plugins/in_opentelemetry/opentelemetry_config.c +++ b/plugins/in_opentelemetry/opentelemetry_config.c @@ -80,6 +80,10 @@ int opentelemetry_config_destroy(struct flb_opentelemetry *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } From e1c62f0735c6ea137f46a3a0001c5a7bb4c2c0f3 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 17:11:58 +0100 Subject: [PATCH 15/24] in_splunk: added missing cleanup code Signed-off-by: Leonardo Alminana --- plugins/in_splunk/splunk_config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/in_splunk/splunk_config.c b/plugins/in_splunk/splunk_config.c index baaf82fc22c..8a7f3dbc999 100644 --- a/plugins/in_splunk/splunk_config.c +++ b/plugins/in_splunk/splunk_config.c @@ -168,6 +168,10 @@ int splunk_config_destroy(struct flb_splunk *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } From cf158f75f743e158a518176a78a289d018116a12 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:24:03 +0100 Subject: [PATCH 16/24] http_server: removed problematic macro Signed-off-by: Leonardo Alminana --- include/fluent-bit/http_server/flb_http_server.h | 3 --- src/flb_http_common.c | 8 ++++---- src/http_server/flb_http_server.c | 4 ++-- src/http_server/flb_http_server_http1.c | 2 +- src/http_server/flb_http_server_http2.c | 6 +++--- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/include/fluent-bit/http_server/flb_http_server.h b/include/fluent-bit/http_server/flb_http_server.h index aaadd91b075..d4fb5d17e29 100755 --- a/include/fluent-bit/http_server/flb_http_server.h +++ b/include/fluent-bit/http_server/flb_http_server.h @@ -94,9 +94,6 @@ struct flb_http_server_session { struct cfl_list _head; }; -#define FLB_HTTP_STREAM_GET_SESSION(stream, session) \ - *(session) = (typeof(*(session))) stream->parent; - /* COMMON */ char *flb_http_server_convert_string_to_lowercase(char *input_buffer, diff --git a/src/flb_http_common.c b/src/flb_http_common.c index acaaa7a25d1..fbd4273356c 100644 --- a/src/flb_http_common.c +++ b/src/flb_http_common.c @@ -236,7 +236,7 @@ int flb_http_response_commit(struct flb_http_response *response) 1); } - FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + session = (struct flb_http_server_session *) response->stream->parent; if (session->version == HTTP_PROTOCOL_HTTP2) { return flb_http2_response_commit(response); @@ -264,7 +264,7 @@ int flb_http_response_set_header(struct flb_http_response *response, } } - FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + session = (struct flb_http_server_session *) response->stream->parent; if (session->version == HTTP_PROTOCOL_HTTP2) { return flb_http2_response_set_header(response, @@ -325,7 +325,7 @@ int flb_http_response_set_status(struct flb_http_response *response, { struct flb_http_server_session *session; - FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + session = (struct flb_http_server_session *) response->stream->parent; response->status = status; @@ -359,7 +359,7 @@ int flb_http_response_set_body(struct flb_http_response *response, { struct flb_http_server_session *session; - FLB_HTTP_STREAM_GET_SESSION(response->stream, &session); + session = (struct flb_http_server_session *) response->stream->parent; response->body = cfl_sds_create_len((const char *) body, body_length); diff --git a/src/http_server/flb_http_server.c b/src/http_server/flb_http_server.c index ca6f07d3497..302ded9be6a 100644 --- a/src/http_server/flb_http_server.c +++ b/src/http_server/flb_http_server.c @@ -190,7 +190,7 @@ static int flb_http_server_inflate_request_body( struct flb_http_server *server; int result; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &parent_session); + parent_session = (struct flb_http_server_session *) request->stream->parent; server = parent_session->parent; result = 0; @@ -278,7 +278,7 @@ static int flb_http_server_should_connection_be_closed( struct flb_http_server_session *parent_session; struct flb_http_server *server; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &parent_session); + parent_session = (struct flb_http_server_session *) request->stream->parent; server = parent_session->parent; diff --git a/src/http_server/flb_http_server_http1.c b/src/http_server/flb_http_server_http1.c index b05dd7c848d..334a2274e83 100644 --- a/src/http_server/flb_http_server_http1.c +++ b/src/http_server/flb_http_server_http1.c @@ -294,7 +294,7 @@ int flb_http1_response_commit(struct flb_http_response *response) struct flb_http1_server_session *session; struct flb_http_stream *stream; - FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + parent_session = (struct flb_http_server_session *) response->stream->parent; if (parent_session == NULL) { return -1; diff --git a/src/http_server/flb_http_server_http2.c b/src/http_server/flb_http_server_http2.c index 7075e94a470..dc9c106664a 100644 --- a/src/http_server/flb_http_server_http2.c +++ b/src/http_server/flb_http_server_http2.c @@ -243,7 +243,7 @@ int flb_http2_response_commit(struct flb_http_response *response) struct flb_http_stream *stream; int result; - FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + parent_session = (struct flb_http_server_session *) response->stream->parent; if (parent_session == NULL) { return -1; @@ -659,7 +659,7 @@ static int http2_frame_recv_callback(nghttp2_session *inner_session, cfl_list_del(&stream->request._head); } - FLB_HTTP_STREAM_GET_SESSION(stream, &parent_session); + parent_session = (struct flb_http_server_session *) stream->parent; if (parent_session == NULL) { return -1; @@ -790,7 +790,7 @@ static int http2_data_chunk_recv_callback(nghttp2_session *inner_session, cfl_list_del(&stream->request._head); } - FLB_HTTP_STREAM_GET_SESSION(stream, &parent_session); + parent_session = (struct flb_http_server_session *) stream->parent; if (parent_session == NULL) { return -1; From bc9e90f6e64a830c02acdc28a1837d8f920e13df Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:24:40 +0100 Subject: [PATCH 17/24] in_splunk: removed unused code Signed-off-by: Leonardo Alminana --- plugins/in_splunk/splunk_prot.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/in_splunk/splunk_prot.c b/plugins/in_splunk/splunk_prot.c index 4595015753a..0da2bd64b03 100644 --- a/plugins/in_splunk/splunk_prot.c +++ b/plugins/in_splunk/splunk_prot.c @@ -942,13 +942,11 @@ static int process_hec_raw_payload_ng(struct flb_http_request *request, int splunk_prot_handle_ng(struct flb_http_request *request, struct flb_http_response *response) { - struct flb_http_server_session *session; - struct flb_splunk *context; - int ret; - flb_sds_t tag; + struct flb_splunk *context; + int ret; + flb_sds_t tag; context = (struct flb_splunk *) response->stream->user_data; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); if (request->path[0] != '/') { send_response_ng(response, 400, "error: invalid request\n"); From 0440c47745a0037734be96f842dddcf10e3caa7e Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:24:53 +0100 Subject: [PATCH 18/24] in_elasticsearch: removed unused code Signed-off-by: Leonardo Alminana --- .../in_elasticsearch/in_elasticsearch_bulk_prot.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c index 123a8345489..ca9f7acfefc 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c +++ b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c @@ -1063,19 +1063,17 @@ static int process_payload_ng(struct flb_http_request *request, int in_elasticsearch_bulk_prot_handle_ng(struct flb_http_request *request, struct flb_http_response *response) { - flb_sds_t bulk_statuses; - flb_sds_t bulk_response; - const char *error_str; - struct flb_http_server_session *session; - struct flb_in_elasticsearch *context; - flb_sds_t tag; - size_t len; + flb_sds_t bulk_statuses; + flb_sds_t bulk_response; + const char *error_str; + struct flb_in_elasticsearch *context; + flb_sds_t tag; + size_t len; bulk_statuses = NULL; bulk_response = NULL; context = (struct flb_in_elasticsearch *) response->stream->user_data; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); if (request->path[0] != '/') { send_response_ng(response, 400, NULL, "error: invalid request\n"); From 627741c0eb1c6cf18bd18ec131e47dd5b0ef1679 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:25:14 +0100 Subject: [PATCH 19/24] in_opentelemetry: removed unused code Signed-off-by: Leonardo Alminana --- plugins/in_opentelemetry/opentelemetry_prot.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/in_opentelemetry/opentelemetry_prot.c b/plugins/in_opentelemetry/opentelemetry_prot.c index f993a82fbc1..80dcc1108a7 100644 --- a/plugins/in_opentelemetry/opentelemetry_prot.c +++ b/plugins/in_opentelemetry/opentelemetry_prot.c @@ -2135,12 +2135,10 @@ int opentelemetry_prot_handle_ng(struct flb_http_request *request, { char payload_type; int grpc_request; - struct flb_http_server_session *session; struct flb_opentelemetry *context; int result; context = (struct flb_opentelemetry *) response->stream->user_data; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); if (request->path[0] != '/') { send_response_ng(response, 400, "error: invalid request\n"); From edd0c727a5f269611795d99a8793cca46dfdd833 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:25:25 +0100 Subject: [PATCH 20/24] in_http: removed unused code Signed-off-by: Leonardo Alminana --- plugins/in_http/http_prot.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/in_http/http_prot.c b/plugins/in_http/http_prot.c index 33a6ec2a445..3b7507a0415 100644 --- a/plugins/in_http/http_prot.c +++ b/plugins/in_http/http_prot.c @@ -1098,11 +1098,9 @@ int http_prot_handle_ng(struct flb_http_request *request, int ret; int len; flb_sds_t tag; - struct flb_http_server_session *session; struct flb_http *context; context = (struct flb_http *) response->stream->user_data; - FLB_HTTP_STREAM_GET_SESSION(request->stream, &session); if (request->path[0] != '/') { send_response_ng(response, 400, "error: invalid request\n"); From 6581ed8c68d2f0969212824aa3ff1f5d4257dc18 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 18:43:02 +0100 Subject: [PATCH 21/24] tls: added compatibility macro Signed-off-by: Leonardo Alminana --- src/tls/openssl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tls/openssl.c b/src/tls/openssl.c index f53f7201a86..14b77e09c6a 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -26,6 +26,11 @@ #include #include +#ifdef FLB_SYSTEM_WINDOWS + #define strtok_r(str, delimiter, context) \ + strtok_s(str, delimiter, context) +#endif + /* * OPENSSL_VERSION_NUMBER has the following semantics * @@ -198,7 +203,7 @@ int tls_context_alpn_set(void *ctx_backend, const char *alpn) } static int tls_context_server_alpn_select_callback(SSL *ssl, - unsigned char **out, + const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, From cc270615f7845e81d642a973f2a307f423939dcb Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 20:53:08 +0100 Subject: [PATCH 22/24] build: fixed nghttp2 related linkage issues Signed-off-by: Leonardo Alminana --- CMakeLists.txt | 3 +++ src/CMakeLists.txt | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5438fc5ffb0..b5ee6c82dc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -566,6 +566,9 @@ add_subdirectory(${FLB_PATH_LIB_CTRACES} EXCLUDE_FROM_ALL) # Nghttp2 options set(ENABLE_LIB_ONLY ON) set(ENABLE_STATIC_LIB ON) +set(ENABLE_SHARED_LIB OFF) + +add_definitions(-DNGHTTP2_STATICLIB) add_subdirectory(${FLB_PATH_LIB_NGHTTP2}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b9fcafa1860..78e1876a315 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -219,10 +219,6 @@ if(FLB_REGEX) ) endif() -set(FLB_DEPS - ${FLB_DEPS} - nghttp2) - if(FLB_LUAJIT) set(extra_libs ${extra_libs} @@ -377,6 +373,7 @@ set(FLB_DEPS c-ares snappy-c lwrb + nghttp2_static ) if(OPENSSL_FOUND) From f208a725e3f439e7d5fdec2c5da5d09d4c7b77e4 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Wed, 13 Mar 2024 21:26:23 +0100 Subject: [PATCH 23/24] build: attempted to fix nghttp2 related linkage issues again Signed-off-by: Leonardo Alminana --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5ee6c82dc5..41f8bf1e20b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,13 +564,13 @@ add_subdirectory(${FLB_PATH_LIB_CMETRICS} EXCLUDE_FROM_ALL) add_subdirectory(${FLB_PATH_LIB_CTRACES} EXCLUDE_FROM_ALL) # Nghttp2 options -set(ENABLE_LIB_ONLY ON) -set(ENABLE_STATIC_LIB ON) -set(ENABLE_SHARED_LIB OFF) +FLB_OPTION(ENABLE_LIB_ONLY ON) +FLB_OPTION(ENABLE_STATIC_LIB ON) +FLB_OPTION(ENABLE_SHARED_LIB OFF) -add_definitions(-DNGHTTP2_STATICLIB) +FLB_DEFINITION(NGHTTP2_STATICLIB) -add_subdirectory(${FLB_PATH_LIB_NGHTTP2}) +add_subdirectory(${FLB_PATH_LIB_NGHTTP2} EXCLUDE_FROM_ALL) # C-Ares (DNS library) FLB_OPTION(CARES_STATIC ON) From 0e707d07a0bd03613b9a8cacdac05634947f3208 Mon Sep 17 00:00:00 2001 From: Leonardo Alminana Date: Thu, 14 Mar 2024 19:05:28 +0100 Subject: [PATCH 24/24] build: attempted to fix nghttp2 related additional linkage issues Signed-off-by: Leonardo Alminana --- lib/nghttp2/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nghttp2/CMakeLists.txt b/lib/nghttp2/CMakeLists.txt index 99a2b77f3be..4681db75346 100644 --- a/lib/nghttp2/CMakeLists.txt +++ b/lib/nghttp2/CMakeLists.txt @@ -52,6 +52,10 @@ endif() include(GNUInstallDirs) +# This is necessary to ensure that we link openssl statically +set(OPENSSL_USE_STATIC_LIBS ON) +set(OPENSSL_MSVC_STATIC_RT ON) + # For documentation find_package(Python3 COMPONENTS Interpreter)