From d710a65c8b50bc3d4d0920dc6e865296f42edd5e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 21:37:08 +0200 Subject: Adding upstream version 1.59.0. Signed-off-by: Daniel Baumann --- .clang-format | 215 + .github/dependabot.yml | 6 + .github/workflows/build.yml | 530 + .github/workflows/fuzz.yml | 24 + .gitignore | 56 + .gitmodules | 7 + AUTHORS | 158 + CMakeLists.txt | 491 + CMakeOptions.txt | 28 + CONTRIBUTION | 18 + COPYING | 23 + ChangeLog | 0 Dockerfile.android | 121 + LICENSE | 1 + Makefile.am | 61 + NEWS | 0 README | 1 + README.rst | 1474 ++ android-config | 37 + android-env | 40 + author.py | 52 + bpf/CMakeLists.txt | 13 + bpf/Makefile.am | 40 + bpf/reuseport_kern.c | 663 + cmake/ExtractValidFlags.cmake | 31 + cmake/FindCUnit.cmake | 40 + cmake/FindJansson.cmake | 40 + cmake/FindJemalloc.cmake | 40 + cmake/FindLibbpf.cmake | 32 + cmake/FindLibcares.cmake | 40 + cmake/FindLibev.cmake | 38 + cmake/FindLibevent.cmake | 97 + cmake/FindLibnghttp3.cmake | 41 + cmake/FindLibngtcp2.cmake | 41 + cmake/FindLibngtcp2_crypto_quictls.cmake | 43 + cmake/FindSystemd.cmake | 19 + cmake/PickyWarningsC.cmake | 163 + cmake/PickyWarningsCXX.cmake | 117 + cmake/Version.cmake | 11 + cmakeconfig.h.in | 101 + configure.ac | 1185 ++ contrib/.gitignore | 3 + contrib/CMakeLists.txt | 12 + contrib/Makefile.am | 51 + contrib/nghttpx-init.in | 164 + contrib/nghttpx-logrotate | 11 + contrib/nghttpx-upstart.conf.in | 8 + contrib/nghttpx.service.in | 17 + contrib/tlsticketupdate.go | 112 + contrib/usr.sbin.nghttpx | 16 + doc/.gitignore | 19 + doc/CMakeLists.txt | 352 + doc/Makefile.am | 366 + doc/README.rst | 160 + doc/_exts/rubydomain/LICENSE.rubydomain | 28 + doc/_exts/rubydomain/__init__.py | 14 + doc/_exts/rubydomain/rubydomain.py | 703 + doc/bash_completion/h2load | 19 + doc/bash_completion/make_bash_completion.py | 75 + doc/bash_completion/nghttp | 19 + doc/bash_completion/nghttpd | 19 + doc/bash_completion/nghttpx | 19 + doc/building-android-binary.rst.in | 1 + doc/conf.py.in | 252 + doc/contribute.rst.in | 1 + doc/docutils.conf | 2 + doc/h2load-howto.rst.in | 1 + doc/h2load.1 | 530 + doc/h2load.1.rst | 434 + doc/h2load.h2r | 120 + doc/index.rst.in | 1 + doc/make.bat | 170 + doc/mkapiref.py | 343 + doc/nghttp.1 | 336 + doc/nghttp.1.rst | 276 + doc/nghttp.h2r | 57 + doc/nghttp2.h.rst.in | 4 + doc/nghttp2ver.h.rst.in | 4 + doc/nghttpd.1 | 236 + doc/nghttpd.1.rst | 186 + doc/nghttpd.h2r | 4 + doc/nghttpx-howto.rst.in | 1 + doc/nghttpx.1 | 2770 ++++ doc/nghttpx.1.rst | 2526 ++++ doc/nghttpx.h2r | 719 + doc/package_README.rst.in | 1 + doc/programmers-guide.rst | 526 + doc/security.rst | 1 + doc/sources/building-android-binary.rst | 127 + doc/sources/contribute.rst | 56 + doc/sources/h2load-howto.rst | 142 + doc/sources/index.rst | 50 + doc/sources/nghttpx-howto.rst | 642 + doc/sources/security.rst | 33 + doc/sources/tutorial-client.rst | 464 + doc/sources/tutorial-hpack.rst | 126 + doc/sources/tutorial-server.rst | 577 + doc/tutorial-client.rst.in | 6 + doc/tutorial-hpack.rst.in | 6 + doc/tutorial-server.rst.in | 6 + docker/Dockerfile | 78 + docker/README.rst | 25 + examples/.gitignore | 5 + examples/CMakeLists.txt | 37 + examples/Makefile.am | 54 + examples/client.c | 699 + examples/deflate.c | 206 + examples/libevent-client.c | 595 + examples/libevent-server.c | 787 ++ fedora/spdylay.spec | 75 + fuzz/README.rst | 33 + ...5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 | Bin 0 -> 61 bytes ...63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a | Bin 0 -> 85 bytes ...93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 | Bin 0 -> 16466 bytes ...bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a | Bin 0 -> 86 bytes ...5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd | Bin 0 -> 82 bytes ...667980a41547272ad42377149edcf130b2bf0b76804c61f | 2 + ...36f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 | Bin 0 -> 86 bytes ...8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 | Bin 0 -> 76 bytes ...d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 | Bin 0 -> 96 bytes ...4c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 | Bin 0 -> 61 bytes ...857ed84e8c2f917946ec7ef3f4720535478b41e097a798a | Bin 0 -> 73 bytes ...31a8a158af429421570e7363a3b75441edc5d740513b0dc | Bin 0 -> 3603 bytes ...b0d429edfac603133e0144dba08836f90b1ae164b328800 | Bin 0 -> 88 bytes ...f6957847b76c09921e984796f6dc482859b119cf4879300 | Bin 0 -> 77 bytes ...039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 | Bin 0 -> 86 bytes ...d09a65432789b45aff588c606536e93824b89739a6d07ab | Bin 0 -> 85 bytes ...7052454d935ebc543f4d1305e318ccd2ff407517636bed8 | Bin 0 -> 78 bytes ...e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 | Bin 0 -> 84 bytes ...7dfb0ea37c900f13f429b750c87e6175b234b881bda6248 | Bin 0 -> 98 bytes ...9e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 | Bin 0 -> 98 bytes ...151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 | Bin 0 -> 2487 bytes ...ab35c7c55330e80d8121a0cff19633a56eba8f2182a59df | Bin 0 -> 71 bytes ...0b38601339096b80a382afa1083a19c4deab11be847502f | Bin 0 -> 77 bytes ...4fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 | Bin 0 -> 61 bytes ...6ff9748ae962bb543dd5ca188dabc30897726f87403fbce | Bin 0 -> 93 bytes ...9b55af68a89c9750332ae5063e36401eae150ce63188fe0 | Bin 0 -> 86 bytes ...41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 | Bin 0 -> 65 bytes ...079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 | Bin 0 -> 3603 bytes ...f58c58a4336f1386633bac75dea2c4b64c02541e7320933 | Bin 0 -> 114 bytes ...60893aab9a74dd392cbdae104307e8512e5e4113739e93a | Bin 0 -> 86 bytes ...1f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c | Bin 0 -> 85 bytes ...d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d | Bin 0 -> 75 bytes ...f14490881328f80b5d3c942de3b1304a0382923ce896f8f | Bin 0 -> 89 bytes ...fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca | Bin 0 -> 61 bytes ...f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c | Bin 0 -> 90 bytes ...510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf | Bin 0 -> 17632 bytes ...39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 | Bin 0 -> 86 bytes ...ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a | Bin 0 -> 93 bytes ...1c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 | Bin 0 -> 83 bytes ...a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 | Bin 0 -> 82 bytes ...8c326391023a0981789c2351817996e0c253bfed708ad82 | Bin 0 -> 58 bytes ...3269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab | Bin 0 -> 63 bytes ...08b54153c480754054a57777f22a00d377d745d78e9d193 | Bin 0 -> 73 bytes ...fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 | Bin 0 -> 84 bytes ...df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 | Bin 0 -> 86 bytes ...0641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 | Bin 0 -> 91 bytes ...ccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a | Bin 0 -> 3593 bytes ...774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 | Bin 0 -> 85 bytes ...cbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e | Bin 0 -> 72 bytes ...4bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa | Bin 0 -> 85 bytes ...06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a | Bin 0 -> 63 bytes ...3de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 | Bin 0 -> 82 bytes ...3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 | Bin 0 -> 85 bytes ...2a55c90554c93278034ebacc24792509a32aeba466df4e8 | Bin 0 -> 96 bytes ...a1ba889d60c1995b7fab68ded6ab052814008d990862c23 | Bin 0 -> 85 bytes ...761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e | Bin 0 -> 95 bytes ...80f2661d387692063ce2ae73b3e5401b716326967b4ce0c | Bin 0 -> 73 bytes ...4b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d | Bin 0 -> 84 bytes ...b521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 | Bin 0 -> 71 bytes ...3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 | Bin 0 -> 63 bytes ...b0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 | Bin 0 -> 87 bytes ...5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e | Bin 0 -> 65 bytes ...a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea | Bin 0 -> 89 bytes ...a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f | Bin 0 -> 86 bytes ...a534223da1173666aaeae9788b144fa2c723204d55cc0a2 | Bin 0 -> 61 bytes ...dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 | Bin 0 -> 87 bytes ...ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a | Bin 0 -> 92 bytes ...1483803e3e553623d4fc382324d8b8ba53ebf83f0457707 | Bin 0 -> 74 bytes ...aafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 | Bin 0 -> 77 bytes ...820e75328f893972df210ab75cdb67f620b370ee5cddf45 | Bin 0 -> 60 bytes ...1a24c2d0a01173ea80ccc584b659947b64ffefddab7fada | Bin 0 -> 73 bytes ...df139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 | Bin 0 -> 80 bytes ...b409c0f7d85086319d4177524fad58dc01743434765902a | Bin 0 -> 101 bytes ...95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 | Bin 0 -> 61 bytes ...1b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 | Bin 0 -> 103 bytes ...a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 | Bin 0 -> 73 bytes ...b2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb | Bin 0 -> 73 bytes ...3a8679caf527d5f10667e0a38790f28f32af61efa930eef | Bin 0 -> 58 bytes ...78c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 | Bin 0 -> 65 bytes ...a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 | Bin 0 -> 100 bytes ...e40f8b07b2fd54985ef27c99670bed582ce904569b95702 | Bin 0 -> 81 bytes ...2a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 | Bin 0 -> 65 bytes ...c9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 | Bin 0 -> 73 bytes ...c3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 | Bin 0 -> 83 bytes ...9d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 | Bin 0 -> 85 bytes ...d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 | Bin 0 -> 3606 bytes ...5c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 | Bin 0 -> 84 bytes ...ad1261b48d08b52752a41633279ff2e9e474eebf508250f | Bin 0 -> 91 bytes ...f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 | Bin 0 -> 102 bytes ...ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 | Bin 0 -> 75 bytes ...6572deb105290328add76123b4a99ad4e78189e1337ae1b | Bin 0 -> 65 bytes ...f7774ec483a4b493668ca1448948c62f641d176838306d5 | Bin 0 -> 73 bytes ...59515211893e7681fef6da4b623392d402fb40736dc1beb | Bin 0 -> 71 bytes ...d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 | Bin 0 -> 63 bytes ...92ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 | Bin 0 -> 104 bytes ...88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a | Bin 0 -> 39 bytes ...805018c177382ab3278a019935fa50b3e0d7971c28c40d9 | Bin 0 -> 63 bytes ...16085c5a009696cd5f659f85fc10ef76dc140851ffcc423 | Bin 0 -> 87 bytes ...1f229e5de18d06d885b50be9136778b4937437f0d70738d | Bin 0 -> 101 bytes ...c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e | Bin 0 -> 117 bytes ...4cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 | Bin 0 -> 98 bytes ...52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc | Bin 0 -> 70 bytes ...6749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad | Bin 0 -> 85 bytes ...a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b | Bin 0 -> 86 bytes ...c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 | Bin 0 -> 72 bytes ...403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 | Bin 0 -> 58 bytes ...b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 | Bin 0 -> 102 bytes ...86981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 | Bin 0 -> 63 bytes ...182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 | Bin 0 -> 62 bytes ...080b8b53931aa6bfd4ce95771c748372626414d5c37e105 | Bin 0 -> 3593 bytes ...02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d | Bin 0 -> 81 bytes ...f99d4ff4970fb339f440867ebedf02eaab75fb555e293cf | Bin 0 -> 76 bytes ...f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a | Bin 0 -> 98 bytes ...66972a945b7fd0035f6dba48d886160fdf1974aae8dee65 | Bin 0 -> 84 bytes ...9f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 | Bin 0 -> 16465 bytes ...09c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 | Bin 0 -> 62 bytes ...c4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 | Bin 0 -> 65 bytes ...741aa3d6e269f8b4bc785089040be666f480464cb13b4df | Bin 0 -> 102 bytes ...62d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 | Bin 0 -> 81 bytes ...ac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 | Bin 0 -> 60 bytes ...7f74e1c1ef87680a96a1aca613180110df26259eb36c433 | Bin 0 -> 79 bytes ...f9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 | Bin 0 -> 73 bytes ...b30db1089ad6af12beea18f895be6f18d42962721d6e3ee | Bin 0 -> 102 bytes ...05e6acac77778fe630b278f167321a46d861ac8ad56fd76 | Bin 0 -> 85 bytes ...0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a | Bin 0 -> 63 bytes ...0a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 | Bin 0 -> 72 bytes ...834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc | Bin 0 -> 115 bytes ...18f46bd936130b0d06332ab062a48f41b206ce696428e03 | Bin 0 -> 65 bytes ...3b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e | Bin 0 -> 72 bytes ...50f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 | Bin 0 -> 90 bytes ...482cb57fde916a7d8db293427159f3b31bbc23b6b285116 | Bin 0 -> 85 bytes ...c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 | Bin 0 -> 91 bytes ...4a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 | Bin 0 -> 3606 bytes ...8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb | Bin 0 -> 191 bytes ...fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 | Bin 0 -> 5070 bytes ...37628084e3946d556086c9991cce7962e9e69a3eed406aa | Bin 0 -> 18740 bytes fuzz/fuzz_frames.cc | 160 + fuzz/fuzz_target.cc | 79 + fuzz/fuzz_target_fdp.cc | 99 + genauthoritychartbl.py | 32 + gendowncasetbl.py | 30 + genheaderfunc.py | 48 + genlibtokenlookup.py | 143 + genmethodchartbl.py | 29 + genmethodfunc.py | 52 + gennghttpxfun.py | 242 + gennmchartbl.py | 28 + genpathchartbl.py | 23 + gentokenlookup.py | 69 + genvchartbl.py | 26 + git-clang-format | 484 + go.mod | 24 + go.sum | 57 + help2rst.py | 192 + integration-tests/.gitignore | 3 + integration-tests/CMakeLists.txt | 45 + integration-tests/Makefile.am | 52 + integration-tests/alt-server.crt | 21 + integration-tests/alt-server.key | 28 + 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 + integration-tests/req-return.rb | 12 + integration-tests/req-set-header.rb | 7 + integration-tests/resp-return.rb | 12 + integration-tests/resp-set-header.rb | 7 + integration-tests/server.crt | 21 + integration-tests/server.key | 28 + integration-tests/server_tester.go | 819 ++ integration-tests/server_tester_http3.go | 90 + integration-tests/setenv.in | 13 + lib/.gitignore | 3 + lib/CMakeLists.txt | 80 + lib/Makefile.am | 81 + lib/Makefile.msvc | 254 + lib/includes/CMakeLists.txt | 4 + lib/includes/Makefile.am | 26 + lib/includes/nghttp2/nghttp2.h | 5941 ++++++++ lib/includes/nghttp2/nghttp2ver.h.in | 42 + lib/libnghttp2.pc.in | 33 + lib/nghttp2_alpn.c | 70 + lib/nghttp2_alpn.h | 34 + lib/nghttp2_buf.c | 527 + lib/nghttp2_buf.h | 412 + lib/nghttp2_callbacks.c | 175 + lib/nghttp2_callbacks.h | 125 + lib/nghttp2_debug.c | 60 + lib/nghttp2_debug.h | 43 + lib/nghttp2_extpri.c | 41 + lib/nghttp2_extpri.h | 65 + lib/nghttp2_frame.c | 1214 ++ lib/nghttp2_frame.h | 637 + lib/nghttp2_hd.c | 2357 ++++ lib/nghttp2_hd.h | 440 + lib/nghttp2_hd_huffman.c | 144 + lib/nghttp2_hd_huffman.h | 72 + lib/nghttp2_hd_huffman_data.c | 4980 +++++++ lib/nghttp2_helper.c | 803 ++ lib/nghttp2_helper.h | 122 + lib/nghttp2_http.c | 631 + lib/nghttp2_http.h | 100 + lib/nghttp2_int.h | 58 + lib/nghttp2_map.c | 338 + lib/nghttp2_map.h | 138 + lib/nghttp2_mem.c | 74 + lib/nghttp2_mem.h | 45 + lib/nghttp2_net.h | 91 + lib/nghttp2_option.c | 152 + lib/nghttp2_option.h | 152 + lib/nghttp2_outbound_item.c | 130 + lib/nghttp2_outbound_item.h | 166 + lib/nghttp2_pq.c | 183 + lib/nghttp2_pq.h | 124 + lib/nghttp2_priority_spec.c | 52 + lib/nghttp2_priority_spec.h | 42 + lib/nghttp2_queue.c | 85 + lib/nghttp2_queue.h | 51 + lib/nghttp2_ratelim.c | 75 + lib/nghttp2_ratelim.h | 57 + lib/nghttp2_rcbuf.c | 102 + lib/nghttp2_rcbuf.h | 80 + lib/nghttp2_session.c | 8395 ++++++++++++ lib/nghttp2_session.h | 974 ++ lib/nghttp2_stream.c | 1016 ++ lib/nghttp2_stream.h | 441 + lib/nghttp2_submit.c | 900 ++ lib/nghttp2_submit.h | 34 + lib/nghttp2_time.c | 63 + lib/nghttp2_time.h | 38 + lib/nghttp2_version.c | 38 + lib/sfparse.c | 1146 ++ lib/sfparse.h | 409 + lib/version.rc.in | 40 + m4/ax_check_compile_flag.m4 | 74 + m4/ax_cxx_compile_stdcxx.m4 | 1018 ++ m4/libxml2.m4 | 188 + makebashcompletion | 7 + makemanpages | 12 + makerelease.sh | 23 + mkcipherlist.py | 325 + mkhufftbl.py | 468 + mkstatichdtbl.py | 36 + nghttpx.conf.sample | 29 + pre-commit | 27 + proxy.pac.sample | 6 + releasechk | 6 + script/CMakeLists.txt | 5 + script/Makefile.am | 25 + script/README.rst | 10 + script/fetch-ocsp-response | 253 + src/.gitignore | 13 + src/CMakeLists.txt | 243 + src/HtmlParser.cc | 217 + src/HtmlParser.h | 94 + src/HttpServer.cc | 2248 ++++ src/HttpServer.h | 253 + src/Makefile.am | 257 + src/allocator.h | 273 + src/app_helper.cc | 518 + src/app_helper.h | 98 + src/base64.h | 225 + src/base64_test.cc | 121 + src/base64_test.h | 39 + src/buffer.h | 78 + src/buffer_test.cc | 78 + src/buffer_test.h | 38 + src/ca-config.json | 17 + src/ca.nghttp2.org-key.pem | 27 + src/ca.nghttp2.org.csr | 17 + src/ca.nghttp2.org.csr.json | 17 + src/ca.nghttp2.org.pem | 22 + src/comp_helper.c | 133 + src/comp_helper.h | 57 + src/deflatehd.cc | 450 + src/h2load.cc | 3292 +++++ src/h2load.h | 510 + src/h2load_http1_session.cc | 306 + src/h2load_http1_session.h | 60 + src/h2load_http2_session.cc | 314 + src/h2load_http2_session.h | 54 + src/h2load_http3_session.cc | 474 + src/h2load_http3_session.h | 84 + src/h2load_quic.cc | 844 ++ src/h2load_quic.h | 38 + src/h2load_session.h | 59 + src/http-parser.patch | 28 + src/http2.cc | 2096 +++ src/http2.h | 457 + src/http2_test.cc | 1249 ++ src/http2_test.h | 54 + src/http3.cc | 206 + src/http3.h | 123 + src/inflatehd.cc | 289 + src/libevent_util.cc | 162 + src/libevent_util.h | 75 + src/memchunk.h | 664 + src/memchunk_test.cc | 340 + src/memchunk_test.h | 47 + src/network.h | 67 + src/nghttp.cc | 3115 +++++ src/nghttp.h | 310 + src/nghttp2_config.h | 32 + src/nghttp2_gzip.c | 87 + src/nghttp2_gzip.h | 122 + src/nghttp2_gzip_test.c | 111 + src/nghttp2_gzip_test.h | 42 + src/nghttpd.cc | 502 + src/quic.cc | 60 + src/quic.h | 56 + src/shrpx-unittest.cc | 246 + src/shrpx.cc | 5329 ++++++++ src/shrpx.h | 58 + src/shrpx_accept_handler.cc | 111 + src/shrpx_accept_handler.h | 54 + src/shrpx_api_downstream_connection.cc | 478 + src/shrpx_api_downstream_connection.h | 114 + src/shrpx_client_handler.cc | 1703 +++ src/shrpx_client_handler.h | 236 + src/shrpx_config.cc | 4694 +++++++ src/shrpx_config.h | 1450 ++ src/shrpx_config_test.cc | 249 + src/shrpx_config_test.h | 42 + src/shrpx_connect_blocker.cc | 143 + src/shrpx_connect_blocker.h | 86 + src/shrpx_connection.cc | 1275 ++ src/shrpx_connection.h | 203 + src/shrpx_connection_handler.cc | 1319 ++ src/shrpx_connection_handler.h | 322 + src/shrpx_dns_resolver.cc | 353 + src/shrpx_dns_resolver.h | 118 + src/shrpx_dns_tracker.cc | 328 + src/shrpx_dns_tracker.h | 121 + src/shrpx_downstream.cc | 1189 ++ src/shrpx_downstream.h | 628 + src/shrpx_downstream_connection.cc | 48 + src/shrpx_downstream_connection.h | 81 + src/shrpx_downstream_connection_pool.cc | 66 + src/shrpx_downstream_connection_pool.h | 53 + src/shrpx_downstream_queue.cc | 175 + src/shrpx_downstream_queue.h | 116 + src/shrpx_downstream_test.cc | 231 + src/shrpx_downstream_test.h | 44 + src/shrpx_dual_dns_resolver.cc | 93 + src/shrpx_dual_dns_resolver.h | 69 + src/shrpx_error.h | 47 + src/shrpx_exec.cc | 138 + src/shrpx_exec.h | 47 + src/shrpx_health_monitor_downstream_connection.cc | 116 + src/shrpx_health_monitor_downstream_connection.h | 64 + src/shrpx_http.cc | 280 + src/shrpx_http.h | 96 + src/shrpx_http2_downstream_connection.cc | 621 + src/shrpx_http2_downstream_connection.h | 88 + src/shrpx_http2_session.cc | 2426 ++++ src/shrpx_http2_session.h | 296 + src/shrpx_http2_upstream.cc | 2404 ++++ src/shrpx_http2_upstream.h | 150 + src/shrpx_http3_upstream.cc | 2906 ++++ src/shrpx_http3_upstream.h | 193 + src/shrpx_http_downstream_connection.cc | 1617 +++ src/shrpx_http_downstream_connection.h | 124 + src/shrpx_http_test.cc | 168 + src/shrpx_http_test.h | 42 + src/shrpx_https_upstream.cc | 1582 +++ src/shrpx_https_upstream.h | 113 + src/shrpx_io_control.cc | 66 + src/shrpx_io_control.h | 58 + src/shrpx_live_check.cc | 792 ++ src/shrpx_live_check.h | 125 + src/shrpx_log.cc | 1008 ++ src/shrpx_log.h | 318 + src/shrpx_log_config.cc | 127 + src/shrpx_log_config.h | 79 + src/shrpx_memcached_connection.cc | 777 ++ src/shrpx_memcached_connection.h | 155 + src/shrpx_memcached_dispatcher.cc | 53 + src/shrpx_memcached_dispatcher.h | 63 + src/shrpx_memcached_request.h | 59 + src/shrpx_memcached_result.h | 50 + src/shrpx_mruby.cc | 238 + src/shrpx_mruby.h | 89 + src/shrpx_mruby_module.cc | 113 + src/shrpx_mruby_module.h | 52 + src/shrpx_mruby_module_env.cc | 500 + src/shrpx_mruby_module_env.h | 42 + src/shrpx_mruby_module_request.cc | 367 + src/shrpx_mruby_module_request.h | 42 + src/shrpx_mruby_module_response.cc | 398 + src/shrpx_mruby_module_response.h | 42 + src/shrpx_null_downstream_connection.cc | 88 + src/shrpx_null_downstream_connection.h | 68 + src/shrpx_process.h | 37 + src/shrpx_quic.cc | 393 + src/shrpx_quic.h | 138 + src/shrpx_quic_connection_handler.cc | 761 ++ src/shrpx_quic_connection_handler.h | 142 + src/shrpx_quic_listener.cc | 132 + src/shrpx_quic_listener.h | 51 + src/shrpx_rate_limit.cc | 123 + src/shrpx_rate_limit.h | 68 + src/shrpx_router.cc | 420 + src/shrpx_router.h | 110 + src/shrpx_router_test.cc | 184 + src/shrpx_router_test.h | 40 + src/shrpx_signal.cc | 138 + src/shrpx_signal.h | 60 + src/shrpx_tls.cc | 2465 ++++ src/shrpx_tls.h | 321 + src/shrpx_tls_test.cc | 339 + src/shrpx_tls_test.h | 42 + src/shrpx_upstream.h | 112 + src/shrpx_worker.cc | 1347 ++ src/shrpx_worker.h | 480 + src/shrpx_worker_process.cc | 701 + src/shrpx_worker_process.h | 67 + src/shrpx_worker_test.cc | 247 + src/shrpx_worker_test.h | 38 + src/ssl_compat.h | 48 + src/template.h | 548 + src/template_test.cc | 204 + src/template_test.h | 39 + src/test.example.com-key.pem | 27 + src/test.example.com.csr | 17 + src/test.example.com.csr.json | 14 + src/test.example.com.pem | 23 + src/test.nghttp2.org-key.pem | 27 + src/test.nghttp2.org.csr | 19 + src/test.nghttp2.org.csr.json | 19 + src/test.nghttp2.org.pem | 24 + src/testdata/Makefile.am | 27 + src/testdata/ipaddr.crt | 10 + src/testdata/nosan.crt | 9 + src/testdata/nosan_ip.crt | 9 + src/testdata/verify_hostname.crt | 10 + src/timegm.c | 88 + src/timegm.h | 49 + src/tls.cc | 125 + src/tls.h | 104 + src/util.cc | 1796 +++ src/util.h | 971 ++ src/util_test.cc | 707 + src/util_test.h | 75 + src/xsi_strerror.c | 50 + src/xsi_strerror.h | 55 + tests/.gitignore | 3 + tests/CMakeLists.txt | 55 + tests/Makefile.am | 93 + tests/failmalloc.c | 79 + tests/failmalloc_test.c | 576 + tests/failmalloc_test.h | 38 + tests/main.c | 473 + tests/malloc_wrapper.c | 85 + tests/malloc_wrapper.h | 65 + tests/nghttp2_alpn_test.c | 95 + tests/nghttp2_alpn_test.h | 34 + tests/nghttp2_buf_test.c | 344 + tests/nghttp2_buf_test.h | 42 + tests/nghttp2_extpri_test.c | 52 + tests/nghttp2_extpri_test.h | 35 + tests/nghttp2_frame_test.c | 735 + tests/nghttp2_frame_test.h | 47 + tests/nghttp2_hd_test.c | 1577 +++ tests/nghttp2_hd_test.h | 55 + tests/nghttp2_helper_test.c | 195 + tests/nghttp2_helper_test.h | 37 + tests/nghttp2_http_test.c | 206 + tests/nghttp2_http_test.h | 35 + tests/nghttp2_map_test.c | 208 + tests/nghttp2_map_test.h | 38 + tests/nghttp2_pq_test.c | 228 + tests/nghttp2_pq_test.h | 36 + tests/nghttp2_queue_test.c | 50 + tests/nghttp2_queue_test.h | 34 + tests/nghttp2_ratelim_test.c | 101 + tests/nghttp2_ratelim_test.h | 35 + tests/nghttp2_session_test.c | 13438 +++++++++++++++++++ tests/nghttp2_session_test.h | 184 + tests/nghttp2_stream_test.c | 31 + tests/nghttp2_stream_test.h | 32 + tests/nghttp2_test_helper.c | 435 + tests/nghttp2_test_helper.h | 158 + tests/testdata/Makefile.am | 23 + tests/testdata/cacert.pem | 14 + tests/testdata/index.html | 1 + tests/testdata/privkey.pem | 9 + third-party/CMakeLists.txt | 81 + third-party/Makefile.am | 593 + third-party/build_config.rb | 34 + third-party/llhttp/LICENSE-MIT | 22 + third-party/llhttp/README.md | 482 + third-party/llhttp/common.gypi | 46 + third-party/llhttp/include/llhttp.h | 871 ++ third-party/llhttp/llhttp.gyp | 22 + third-party/llhttp/src/api.c | 494 + third-party/llhttp/src/http.c | 150 + third-party/llhttp/src/llhttp.c | 9537 +++++++++++++ third-party/url-parser/.gitignore | 1 + 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 + 613 files changed, 179321 insertions(+) create mode 100644 .clang-format create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/fuzz.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 CMakeOptions.txt create mode 100644 CONTRIBUTION create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Dockerfile.android create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 README.rst create mode 100755 android-config create mode 100755 android-env create mode 100755 author.py create mode 100644 bpf/CMakeLists.txt create mode 100644 bpf/Makefile.am create mode 100644 bpf/reuseport_kern.c create mode 100644 cmake/ExtractValidFlags.cmake create mode 100644 cmake/FindCUnit.cmake create mode 100644 cmake/FindJansson.cmake create mode 100644 cmake/FindJemalloc.cmake create mode 100644 cmake/FindLibbpf.cmake create mode 100644 cmake/FindLibcares.cmake create mode 100644 cmake/FindLibev.cmake create mode 100644 cmake/FindLibevent.cmake create mode 100644 cmake/FindLibnghttp3.cmake create mode 100644 cmake/FindLibngtcp2.cmake create mode 100644 cmake/FindLibngtcp2_crypto_quictls.cmake create mode 100644 cmake/FindSystemd.cmake create mode 100644 cmake/PickyWarningsC.cmake create mode 100644 cmake/PickyWarningsCXX.cmake create mode 100644 cmake/Version.cmake create mode 100644 cmakeconfig.h.in create mode 100644 configure.ac create mode 100644 contrib/.gitignore create mode 100644 contrib/CMakeLists.txt create mode 100644 contrib/Makefile.am create mode 100755 contrib/nghttpx-init.in create mode 100644 contrib/nghttpx-logrotate create mode 100644 contrib/nghttpx-upstart.conf.in create mode 100644 contrib/nghttpx.service.in create mode 100644 contrib/tlsticketupdate.go create mode 100644 contrib/usr.sbin.nghttpx create mode 100644 doc/.gitignore create mode 100644 doc/CMakeLists.txt create mode 100644 doc/Makefile.am create mode 100644 doc/README.rst create mode 100644 doc/_exts/rubydomain/LICENSE.rubydomain create mode 100644 doc/_exts/rubydomain/__init__.py create mode 100644 doc/_exts/rubydomain/rubydomain.py create mode 100644 doc/bash_completion/h2load create mode 100755 doc/bash_completion/make_bash_completion.py create mode 100644 doc/bash_completion/nghttp create mode 100644 doc/bash_completion/nghttpd create mode 100644 doc/bash_completion/nghttpx create mode 100644 doc/building-android-binary.rst.in create mode 100644 doc/conf.py.in create mode 100644 doc/contribute.rst.in create mode 100644 doc/docutils.conf create mode 100644 doc/h2load-howto.rst.in create mode 100644 doc/h2load.1 create mode 100644 doc/h2load.1.rst create mode 100644 doc/h2load.h2r create mode 100644 doc/index.rst.in create mode 100644 doc/make.bat create mode 100755 doc/mkapiref.py create mode 100644 doc/nghttp.1 create mode 100644 doc/nghttp.1.rst create mode 100644 doc/nghttp.h2r create mode 100644 doc/nghttp2.h.rst.in create mode 100644 doc/nghttp2ver.h.rst.in create mode 100644 doc/nghttpd.1 create mode 100644 doc/nghttpd.1.rst create mode 100644 doc/nghttpd.h2r create mode 100644 doc/nghttpx-howto.rst.in create mode 100644 doc/nghttpx.1 create mode 100644 doc/nghttpx.1.rst create mode 100644 doc/nghttpx.h2r create mode 100644 doc/package_README.rst.in create mode 100644 doc/programmers-guide.rst create mode 100644 doc/security.rst create mode 100644 doc/sources/building-android-binary.rst create mode 100644 doc/sources/contribute.rst create mode 100644 doc/sources/h2load-howto.rst create mode 100644 doc/sources/index.rst create mode 100644 doc/sources/nghttpx-howto.rst create mode 100644 doc/sources/security.rst create mode 100644 doc/sources/tutorial-client.rst create mode 100644 doc/sources/tutorial-hpack.rst create mode 100644 doc/sources/tutorial-server.rst create mode 100644 doc/tutorial-client.rst.in create mode 100644 doc/tutorial-hpack.rst.in create mode 100644 doc/tutorial-server.rst.in create mode 100644 docker/Dockerfile create mode 100644 docker/README.rst create mode 100644 examples/.gitignore create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Makefile.am create mode 100644 examples/client.c create mode 100644 examples/deflate.c create mode 100644 examples/libevent-client.c create mode 100644 examples/libevent-server.c create mode 100644 fedora/spdylay.spec create mode 100644 fuzz/README.rst create mode 100644 fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 create mode 100644 fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a create mode 100644 fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 create mode 100644 fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a create mode 100644 fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd create mode 100644 fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f create mode 100644 fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 create mode 100644 fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 create mode 100644 fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 create mode 100644 fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 create mode 100644 fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a create mode 100644 fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc create mode 100644 fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 create mode 100644 fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 create mode 100644 fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 create mode 100644 fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab create mode 100644 fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 create mode 100644 fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 create mode 100644 fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 create mode 100644 fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 create mode 100644 fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 create mode 100644 fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df create mode 100644 fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f create mode 100644 fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 create mode 100644 fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce create mode 100644 fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 create mode 100644 fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 create mode 100644 fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 create mode 100644 fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 create mode 100644 fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a create mode 100644 fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c create mode 100644 fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d create mode 100644 fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f create mode 100644 fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca create mode 100644 fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c create mode 100644 fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf create mode 100644 fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 create mode 100644 fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a create mode 100644 fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 create mode 100644 fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 create mode 100644 fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 create mode 100644 fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab create mode 100644 fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 create mode 100644 fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 create mode 100644 fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 create mode 100644 fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 create mode 100644 fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a create mode 100644 fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 create mode 100644 fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e create mode 100644 fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa create mode 100644 fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a create mode 100644 fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 create mode 100644 fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 create mode 100644 fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 create mode 100644 fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 create mode 100644 fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e create mode 100644 fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c create mode 100644 fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d create mode 100644 fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 create mode 100644 fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 create mode 100644 fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 create mode 100644 fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e create mode 100644 fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea create mode 100644 fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f create mode 100644 fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 create mode 100644 fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 create mode 100644 fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a create mode 100644 fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 create mode 100644 fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 create mode 100644 fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 create mode 100644 fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada create mode 100644 fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 create mode 100644 fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a create mode 100644 fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 create mode 100644 fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 create mode 100644 fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 create mode 100644 fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb create mode 100644 fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef create mode 100644 fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 create mode 100644 fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 create mode 100644 fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 create mode 100644 fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 create mode 100644 fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 create mode 100644 fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 create mode 100644 fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 create mode 100644 fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 create mode 100644 fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 create mode 100644 fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f create mode 100644 fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 create mode 100644 fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 create mode 100644 fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b create mode 100644 fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 create mode 100644 fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb create mode 100644 fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 create mode 100644 fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 create mode 100644 fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a create mode 100644 fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 create mode 100644 fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 create mode 100644 fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d create mode 100644 fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e create mode 100644 fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 create mode 100644 fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc create mode 100644 fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad create mode 100644 fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b create mode 100644 fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 create mode 100644 fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 create mode 100644 fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 create mode 100644 fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 create mode 100644 fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 create mode 100644 fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 create mode 100644 fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d create mode 100644 fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf create mode 100644 fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a create mode 100644 fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 create mode 100644 fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 create mode 100644 fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 create mode 100644 fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 create mode 100644 fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df create mode 100644 fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 create mode 100644 fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 create mode 100644 fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 create mode 100644 fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 create mode 100644 fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee create mode 100644 fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 create mode 100644 fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a create mode 100644 fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 create mode 100644 fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc create mode 100644 fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 create mode 100644 fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e create mode 100644 fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 create mode 100644 fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 create mode 100644 fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 create mode 100644 fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 create mode 100644 fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb create mode 100644 fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 create mode 100644 fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa create mode 100644 fuzz/fuzz_frames.cc create mode 100644 fuzz/fuzz_target.cc create mode 100644 fuzz/fuzz_target_fdp.cc create mode 100755 genauthoritychartbl.py create mode 100755 gendowncasetbl.py create mode 100755 genheaderfunc.py create mode 100755 genlibtokenlookup.py create mode 100755 genmethodchartbl.py create mode 100755 genmethodfunc.py create mode 100755 gennghttpxfun.py create mode 100755 gennmchartbl.py create mode 100755 genpathchartbl.py create mode 100644 gentokenlookup.py create mode 100755 genvchartbl.py create mode 100755 git-clang-format create mode 100644 go.mod create mode 100644 go.sum create mode 100755 help2rst.py create mode 100644 integration-tests/.gitignore create mode 100644 integration-tests/CMakeLists.txt create mode 100644 integration-tests/Makefile.am create mode 100644 integration-tests/alt-server.crt create mode 100644 integration-tests/alt-server.key create mode 100644 integration-tests/config.go.in create mode 100644 integration-tests/nghttpx_http1_test.go create mode 100644 integration-tests/nghttpx_http2_test.go create mode 100644 integration-tests/nghttpx_http3_test.go create mode 100644 integration-tests/req-return.rb create mode 100644 integration-tests/req-set-header.rb create mode 100644 integration-tests/resp-return.rb create mode 100644 integration-tests/resp-set-header.rb create mode 100644 integration-tests/server.crt create mode 100644 integration-tests/server.key create mode 100644 integration-tests/server_tester.go create mode 100644 integration-tests/server_tester_http3.go create mode 100644 integration-tests/setenv.in create mode 100644 lib/.gitignore create mode 100644 lib/CMakeLists.txt create mode 100644 lib/Makefile.am create mode 100644 lib/Makefile.msvc create mode 100644 lib/includes/CMakeLists.txt create mode 100644 lib/includes/Makefile.am create mode 100644 lib/includes/nghttp2/nghttp2.h create mode 100644 lib/includes/nghttp2/nghttp2ver.h.in create mode 100644 lib/libnghttp2.pc.in create mode 100644 lib/nghttp2_alpn.c create mode 100644 lib/nghttp2_alpn.h create mode 100644 lib/nghttp2_buf.c create mode 100644 lib/nghttp2_buf.h create mode 100644 lib/nghttp2_callbacks.c create mode 100644 lib/nghttp2_callbacks.h create mode 100644 lib/nghttp2_debug.c create mode 100644 lib/nghttp2_debug.h create mode 100644 lib/nghttp2_extpri.c create mode 100644 lib/nghttp2_extpri.h create mode 100644 lib/nghttp2_frame.c create mode 100644 lib/nghttp2_frame.h create mode 100644 lib/nghttp2_hd.c create mode 100644 lib/nghttp2_hd.h create mode 100644 lib/nghttp2_hd_huffman.c create mode 100644 lib/nghttp2_hd_huffman.h create mode 100644 lib/nghttp2_hd_huffman_data.c create mode 100644 lib/nghttp2_helper.c create mode 100644 lib/nghttp2_helper.h create mode 100644 lib/nghttp2_http.c create mode 100644 lib/nghttp2_http.h create mode 100644 lib/nghttp2_int.h create mode 100644 lib/nghttp2_map.c create mode 100644 lib/nghttp2_map.h create mode 100644 lib/nghttp2_mem.c create mode 100644 lib/nghttp2_mem.h create mode 100644 lib/nghttp2_net.h create mode 100644 lib/nghttp2_option.c create mode 100644 lib/nghttp2_option.h create mode 100644 lib/nghttp2_outbound_item.c create mode 100644 lib/nghttp2_outbound_item.h create mode 100644 lib/nghttp2_pq.c create mode 100644 lib/nghttp2_pq.h create mode 100644 lib/nghttp2_priority_spec.c create mode 100644 lib/nghttp2_priority_spec.h create mode 100644 lib/nghttp2_queue.c create mode 100644 lib/nghttp2_queue.h create mode 100644 lib/nghttp2_ratelim.c create mode 100644 lib/nghttp2_ratelim.h create mode 100644 lib/nghttp2_rcbuf.c create mode 100644 lib/nghttp2_rcbuf.h create mode 100644 lib/nghttp2_session.c create mode 100644 lib/nghttp2_session.h create mode 100644 lib/nghttp2_stream.c create mode 100644 lib/nghttp2_stream.h create mode 100644 lib/nghttp2_submit.c create mode 100644 lib/nghttp2_submit.h create mode 100644 lib/nghttp2_time.c create mode 100644 lib/nghttp2_time.h create mode 100644 lib/nghttp2_version.c create mode 100644 lib/sfparse.c create mode 100644 lib/sfparse.h create mode 100644 lib/version.rc.in create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/ax_cxx_compile_stdcxx.m4 create mode 100644 m4/libxml2.m4 create mode 100755 makebashcompletion create mode 100755 makemanpages create mode 100755 makerelease.sh create mode 100755 mkcipherlist.py create mode 100755 mkhufftbl.py create mode 100755 mkstatichdtbl.py create mode 100644 nghttpx.conf.sample create mode 100755 pre-commit create mode 100644 proxy.pac.sample create mode 100755 releasechk create mode 100644 script/CMakeLists.txt create mode 100644 script/Makefile.am create mode 100644 script/README.rst create mode 100755 script/fetch-ocsp-response create mode 100644 src/.gitignore create mode 100644 src/CMakeLists.txt create mode 100644 src/HtmlParser.cc create mode 100644 src/HtmlParser.h create mode 100644 src/HttpServer.cc create mode 100644 src/HttpServer.h create mode 100644 src/Makefile.am create mode 100644 src/allocator.h create mode 100644 src/app_helper.cc create mode 100644 src/app_helper.h create mode 100644 src/base64.h create mode 100644 src/base64_test.cc create mode 100644 src/base64_test.h create mode 100644 src/buffer.h create mode 100644 src/buffer_test.cc create mode 100644 src/buffer_test.h create mode 100644 src/ca-config.json create mode 100644 src/ca.nghttp2.org-key.pem create mode 100644 src/ca.nghttp2.org.csr create mode 100644 src/ca.nghttp2.org.csr.json create mode 100644 src/ca.nghttp2.org.pem create mode 100644 src/comp_helper.c create mode 100644 src/comp_helper.h create mode 100644 src/deflatehd.cc create mode 100644 src/h2load.cc create mode 100644 src/h2load.h create mode 100644 src/h2load_http1_session.cc create mode 100644 src/h2load_http1_session.h create mode 100644 src/h2load_http2_session.cc create mode 100644 src/h2load_http2_session.h create mode 100644 src/h2load_http3_session.cc create mode 100644 src/h2load_http3_session.h create mode 100644 src/h2load_quic.cc create mode 100644 src/h2load_quic.h create mode 100644 src/h2load_session.h create mode 100644 src/http-parser.patch create mode 100644 src/http2.cc create mode 100644 src/http2.h create mode 100644 src/http2_test.cc create mode 100644 src/http2_test.h create mode 100644 src/http3.cc create mode 100644 src/http3.h create mode 100644 src/inflatehd.cc create mode 100644 src/libevent_util.cc create mode 100644 src/libevent_util.h create mode 100644 src/memchunk.h create mode 100644 src/memchunk_test.cc create mode 100644 src/memchunk_test.h create mode 100644 src/network.h create mode 100644 src/nghttp.cc create mode 100644 src/nghttp.h create mode 100644 src/nghttp2_config.h create mode 100644 src/nghttp2_gzip.c create mode 100644 src/nghttp2_gzip.h create mode 100644 src/nghttp2_gzip_test.c create mode 100644 src/nghttp2_gzip_test.h create mode 100644 src/nghttpd.cc create mode 100644 src/quic.cc create mode 100644 src/quic.h create mode 100644 src/shrpx-unittest.cc create mode 100644 src/shrpx.cc create mode 100644 src/shrpx.h create mode 100644 src/shrpx_accept_handler.cc create mode 100644 src/shrpx_accept_handler.h create mode 100644 src/shrpx_api_downstream_connection.cc create mode 100644 src/shrpx_api_downstream_connection.h create mode 100644 src/shrpx_client_handler.cc create mode 100644 src/shrpx_client_handler.h create mode 100644 src/shrpx_config.cc create mode 100644 src/shrpx_config.h create mode 100644 src/shrpx_config_test.cc create mode 100644 src/shrpx_config_test.h create mode 100644 src/shrpx_connect_blocker.cc create mode 100644 src/shrpx_connect_blocker.h create mode 100644 src/shrpx_connection.cc create mode 100644 src/shrpx_connection.h create mode 100644 src/shrpx_connection_handler.cc create mode 100644 src/shrpx_connection_handler.h create mode 100644 src/shrpx_dns_resolver.cc create mode 100644 src/shrpx_dns_resolver.h create mode 100644 src/shrpx_dns_tracker.cc create mode 100644 src/shrpx_dns_tracker.h create mode 100644 src/shrpx_downstream.cc create mode 100644 src/shrpx_downstream.h create mode 100644 src/shrpx_downstream_connection.cc create mode 100644 src/shrpx_downstream_connection.h create mode 100644 src/shrpx_downstream_connection_pool.cc create mode 100644 src/shrpx_downstream_connection_pool.h create mode 100644 src/shrpx_downstream_queue.cc create mode 100644 src/shrpx_downstream_queue.h create mode 100644 src/shrpx_downstream_test.cc create mode 100644 src/shrpx_downstream_test.h create mode 100644 src/shrpx_dual_dns_resolver.cc create mode 100644 src/shrpx_dual_dns_resolver.h create mode 100644 src/shrpx_error.h create mode 100644 src/shrpx_exec.cc create mode 100644 src/shrpx_exec.h create mode 100644 src/shrpx_health_monitor_downstream_connection.cc create mode 100644 src/shrpx_health_monitor_downstream_connection.h create mode 100644 src/shrpx_http.cc create mode 100644 src/shrpx_http.h create mode 100644 src/shrpx_http2_downstream_connection.cc create mode 100644 src/shrpx_http2_downstream_connection.h create mode 100644 src/shrpx_http2_session.cc create mode 100644 src/shrpx_http2_session.h create mode 100644 src/shrpx_http2_upstream.cc create mode 100644 src/shrpx_http2_upstream.h create mode 100644 src/shrpx_http3_upstream.cc create mode 100644 src/shrpx_http3_upstream.h create mode 100644 src/shrpx_http_downstream_connection.cc create mode 100644 src/shrpx_http_downstream_connection.h create mode 100644 src/shrpx_http_test.cc create mode 100644 src/shrpx_http_test.h create mode 100644 src/shrpx_https_upstream.cc create mode 100644 src/shrpx_https_upstream.h create mode 100644 src/shrpx_io_control.cc create mode 100644 src/shrpx_io_control.h create mode 100644 src/shrpx_live_check.cc create mode 100644 src/shrpx_live_check.h create mode 100644 src/shrpx_log.cc create mode 100644 src/shrpx_log.h create mode 100644 src/shrpx_log_config.cc create mode 100644 src/shrpx_log_config.h create mode 100644 src/shrpx_memcached_connection.cc create mode 100644 src/shrpx_memcached_connection.h create mode 100644 src/shrpx_memcached_dispatcher.cc create mode 100644 src/shrpx_memcached_dispatcher.h create mode 100644 src/shrpx_memcached_request.h create mode 100644 src/shrpx_memcached_result.h create mode 100644 src/shrpx_mruby.cc create mode 100644 src/shrpx_mruby.h create mode 100644 src/shrpx_mruby_module.cc create mode 100644 src/shrpx_mruby_module.h create mode 100644 src/shrpx_mruby_module_env.cc create mode 100644 src/shrpx_mruby_module_env.h create mode 100644 src/shrpx_mruby_module_request.cc create mode 100644 src/shrpx_mruby_module_request.h create mode 100644 src/shrpx_mruby_module_response.cc create mode 100644 src/shrpx_mruby_module_response.h create mode 100644 src/shrpx_null_downstream_connection.cc create mode 100644 src/shrpx_null_downstream_connection.h create mode 100644 src/shrpx_process.h create mode 100644 src/shrpx_quic.cc create mode 100644 src/shrpx_quic.h create mode 100644 src/shrpx_quic_connection_handler.cc create mode 100644 src/shrpx_quic_connection_handler.h create mode 100644 src/shrpx_quic_listener.cc create mode 100644 src/shrpx_quic_listener.h create mode 100644 src/shrpx_rate_limit.cc create mode 100644 src/shrpx_rate_limit.h create mode 100644 src/shrpx_router.cc create mode 100644 src/shrpx_router.h create mode 100644 src/shrpx_router_test.cc create mode 100644 src/shrpx_router_test.h create mode 100644 src/shrpx_signal.cc create mode 100644 src/shrpx_signal.h create mode 100644 src/shrpx_tls.cc create mode 100644 src/shrpx_tls.h create mode 100644 src/shrpx_tls_test.cc create mode 100644 src/shrpx_tls_test.h create mode 100644 src/shrpx_upstream.h create mode 100644 src/shrpx_worker.cc create mode 100644 src/shrpx_worker.h create mode 100644 src/shrpx_worker_process.cc create mode 100644 src/shrpx_worker_process.h create mode 100644 src/shrpx_worker_test.cc create mode 100644 src/shrpx_worker_test.h create mode 100644 src/ssl_compat.h create mode 100644 src/template.h create mode 100644 src/template_test.cc create mode 100644 src/template_test.h create mode 100644 src/test.example.com-key.pem create mode 100644 src/test.example.com.csr create mode 100644 src/test.example.com.csr.json create mode 100644 src/test.example.com.pem create mode 100644 src/test.nghttp2.org-key.pem create mode 100644 src/test.nghttp2.org.csr create mode 100644 src/test.nghttp2.org.csr.json create mode 100644 src/test.nghttp2.org.pem create mode 100644 src/testdata/Makefile.am create mode 100644 src/testdata/ipaddr.crt create mode 100644 src/testdata/nosan.crt create mode 100644 src/testdata/nosan_ip.crt create mode 100644 src/testdata/verify_hostname.crt create mode 100644 src/timegm.c create mode 100644 src/timegm.h create mode 100644 src/tls.cc create mode 100644 src/tls.h create mode 100644 src/util.cc create mode 100644 src/util.h create mode 100644 src/util_test.cc create mode 100644 src/util_test.h create mode 100644 src/xsi_strerror.c create mode 100644 src/xsi_strerror.h create mode 100644 tests/.gitignore create mode 100644 tests/CMakeLists.txt create mode 100644 tests/Makefile.am create mode 100644 tests/failmalloc.c create mode 100644 tests/failmalloc_test.c create mode 100644 tests/failmalloc_test.h create mode 100644 tests/main.c create mode 100644 tests/malloc_wrapper.c create mode 100644 tests/malloc_wrapper.h create mode 100644 tests/nghttp2_alpn_test.c create mode 100644 tests/nghttp2_alpn_test.h create mode 100644 tests/nghttp2_buf_test.c create mode 100644 tests/nghttp2_buf_test.h create mode 100644 tests/nghttp2_extpri_test.c create mode 100644 tests/nghttp2_extpri_test.h create mode 100644 tests/nghttp2_frame_test.c create mode 100644 tests/nghttp2_frame_test.h create mode 100644 tests/nghttp2_hd_test.c create mode 100644 tests/nghttp2_hd_test.h create mode 100644 tests/nghttp2_helper_test.c create mode 100644 tests/nghttp2_helper_test.h create mode 100644 tests/nghttp2_http_test.c create mode 100644 tests/nghttp2_http_test.h create mode 100644 tests/nghttp2_map_test.c create mode 100644 tests/nghttp2_map_test.h create mode 100644 tests/nghttp2_pq_test.c create mode 100644 tests/nghttp2_pq_test.h create mode 100644 tests/nghttp2_queue_test.c create mode 100644 tests/nghttp2_queue_test.h create mode 100644 tests/nghttp2_ratelim_test.c create mode 100644 tests/nghttp2_ratelim_test.h create mode 100644 tests/nghttp2_session_test.c create mode 100644 tests/nghttp2_session_test.h create mode 100644 tests/nghttp2_stream_test.c create mode 100644 tests/nghttp2_stream_test.h create mode 100644 tests/nghttp2_test_helper.c create mode 100644 tests/nghttp2_test_helper.h create mode 100644 tests/testdata/Makefile.am create mode 100644 tests/testdata/cacert.pem create mode 100644 tests/testdata/index.html create mode 100644 tests/testdata/privkey.pem create mode 100644 third-party/CMakeLists.txt create mode 100644 third-party/Makefile.am create mode 100644 third-party/build_config.rb create mode 100644 third-party/llhttp/LICENSE-MIT create mode 100644 third-party/llhttp/README.md create mode 100644 third-party/llhttp/common.gypi create mode 100644 third-party/llhttp/include/llhttp.h create mode 100644 third-party/llhttp/llhttp.gyp create mode 100644 third-party/llhttp/src/api.c create mode 100644 third-party/llhttp/src/http.c create mode 100644 third-party/llhttp/src/llhttp.c create mode 100644 third-party/url-parser/.gitignore create mode 100644 third-party/url-parser/AUTHORS create mode 100644 third-party/url-parser/LICENSE-MIT create mode 100644 third-party/url-parser/url_parser.c create mode 100644 third-party/url-parser/url_parser.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4aa455a --- /dev/null +++ b/.clang-format @@ -0,0 +1,215 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: AfterHash +IndentExternBlock: AfterExternBlock +IndentRequiresClause: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RequiresClausePosition: OwnLine +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c139c7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..51dcdfe --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,530 @@ +name: build + +on: [push, pull_request] + +permissions: read-all + +env: + LIBBPF_VERSION: v1.3.0 + OPENSSL1_VERSION: 1_1_1w+quic + OPENSSL3_VERSION: 3.1.4+quic + BORINGSSL_VERSION: f42be90d665b6a376177648ccbb76fbbd6497c13 + AWSLC_VERSION: v1.20.0 + NGHTTP3_VERSION: v1.1.0 + NGTCP2_VERSION: v1.2.0 + +jobs: + build-cache: + strategy: + matrix: + os: [ubuntu-22.04, macos-12] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Restore libbpf cache + id: cache-libbpf + uses: actions/cache@v3 + if: runner.os == 'Linux' + with: + path: libbpf/build + key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }} + - name: Restore OpenSSL v1.1.1 cache + id: cache-openssl1 + uses: actions/cache@v3 + with: + path: openssl1/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }} + - name: Restore OpenSSL v3.x cache + id: cache-openssl3 + uses: actions/cache@v3 + with: + path: openssl3/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }} + - name: Restore BoringSSL cache + id: cache-boringssl + uses: actions/cache@v3 + with: + path: | + boringssl/build/crypto/libcrypto.a + boringssl/build/ssl/libssl.a + boringssl/include + key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }} + - name: Restore aws-lc cache + id: cache-awslc + uses: actions/cache@v3 + with: + path: | + aws-lc/build/crypto/libcrypto.a + aws-lc/build/ssl/libssl.a + aws-lc/include + key: ${{ runner.os }}-awslc-${{ env.AWSLC_VERSION }} + - name: Restore nghttp3 cache + id: cache-nghttp3 + uses: actions/cache@v3 + with: + path: nghttp3/build + key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }} + - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache + id: cache-ngtcp2-openssl1 + uses: actions/cache@v3 + with: + path: ngtcp2-openssl1/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }} + - name: Restore ngtcp2 + quictls/openssl v3.x cache + id: cache-ngtcp2-openssl3 + uses: actions/cache@v3 + with: + path: ngtcp2-openssl3/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }} + - id: settings + if: | + (steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux') || + steps.cache-openssl1.outputs.cache-hit != 'true' || + steps.cache-openssl3.outputs.cache-hit != 'true' || + steps.cache-boringssl.outputs.cache-hit != 'true' || + steps.cache-awslc.outputs.cache-hit != 'true' || + steps.cache-nghttp3.outputs.cache-hit != 'true' || + steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true' || + steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true' + run: | + echo 'needs-build=true' >> $GITHUB_OUTPUT + - name: Linux setup + if: runner.os == 'Linux' && steps.settings.outputs.needs-build == 'true' + run: | + sudo apt-get install \ + g++-12 \ + clang-15 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + libelf-dev \ + cmake \ + cmake-data + - name: MacOS setup + if: runner.os == 'macOS' && steps.settings.outputs.needs-build == 'true' + run: | + brew install \ + autoconf \ + automake \ + pkg-config \ + libtool + - name: Build libbpf + if: steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux' + run: | + git clone -b ${{ env.LIBBPF_VERSION }} https://github.com/libbpf/libbpf + cd libbpf + make -C src install PREFIX=$PWD/build + - name: Build quictls/openssl v1.1.1 + if: steps.cache-openssl1.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b OpenSSL_${{ env.OPENSSL1_VERSION }} https://github.com/quictls/openssl openssl1 + cd openssl1 + ./config --prefix=$PWD/build + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make install_sw + - name: Build quictls/openssl v3.x + if: steps.cache-openssl3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b openssl-${{ env.OPENSSL3_VERSION }} https://github.com/quictls/openssl openssl3 + cd openssl3 + ./config enable-ktls --prefix=$PWD/build --libdir=$PWD/build/lib + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make install_sw + - name: Build BoringSSL + if: steps.cache-boringssl.outputs.cache-hit != 'true' + run: | + git clone https://boringssl.googlesource.com/boringssl + cd boringssl + git checkout ${{ env.BORINGSSL_VERSION }} + mkdir build + cd build + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + - name: Build aws-lc + if: steps.cache-awslc.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b "${AWSLC_VERSION}" https://github.com/aws/aws-lc + cd aws-lc + cmake -B build -DDISABLE_GO=ON + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" -C build + - name: Build nghttp3 + if: steps.cache-nghttp3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGHTTP3_VERSION}} https://github.com/ngtcp2/nghttp3 + cd nghttp3 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + - name: Build ngtcp2 + quictls/openssl v1.1.1 + BoringSSL + if: steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl1 + cd ngtcp2-openssl1 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="../openssl1/build/lib/pkgconfig" \ + BORINGSSL_CFLAGS="-I$PWD/../boringssl/include/" \ + BORINGSSL_LIBS="-L$PWD/../boringssl/build/ssl -lssl -L$PWD/../boringssl/build/crypto -lcrypto" \ + --with-boringssl + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + - name: Build ngtcp2 + quictls/openssl v3.x + aws-lc + if: steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl3 + cd ngtcp2-openssl3 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="../openssl3/build/lib/pkgconfig" \ + BORINGSSL_CFLAGS="-I$PWD/../aws-lc/include/" \ + BORINGSSL_LIBS="-L$PWD/../aws-lc/build/ssl -lssl -L$PWD/../aws-lc/build/crypto -lcrypto" \ + --with-boringssl + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + + build: + needs: + - build-cache + + strategy: + matrix: + os: [ubuntu-22.04, macos-12] + compiler: [gcc, clang] + buildtool: [autotools, cmake] + http3: [http3, no-http3] + openssl: [openssl1, openssl3, boringssl, awslc] + exclude: + - os: macos-12 + openssl: openssl3 + - http3: no-http3 + openssl: openssl3 + - os: macos-12 + compiler: gcc + - # disable macos cmake because of include path issue + os: macos-12 + buildtool: cmake + - os: macos-12 + openssl: boringssl + - openssl: boringssl + buildtool: cmake + - openssl: boringssl + compiler: gcc + - os: macos-12 + openssl: awslc + - openssl: awslc + buildtool: cmake + - openssl: awslc + compiler: gcc + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get install \ + g++-12 \ + clang-15 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + zlib1g-dev \ + libcunit1-dev \ + libssl-dev \ + libxml2-dev \ + libev-dev \ + libevent-dev \ + libjansson-dev \ + libjemalloc-dev \ + libc-ares-dev \ + libelf-dev \ + cmake \ + cmake-data + echo 'CPPFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined -g' >> $GITHUB_ENV + echo 'LDFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined' >> $GITHUB_ENV + - name: MacOS setup + if: runner.os == 'macOS' + run: | + brew install \ + libev \ + libevent \ + c-ares \ + cunit \ + libressl \ + autoconf \ + automake \ + pkg-config \ + libtool + echo 'PKG_CONFIG_PATH=/usr/local/opt/libressl/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig' >> $GITHUB_ENV + - name: Setup clang (Linux) + if: runner.os == 'Linux' && matrix.compiler == 'clang' + run: | + echo 'CC=clang-15' >> $GITHUB_ENV + echo 'CXX=clang++-15' >> $GITHUB_ENV + - name: Setup clang (MacOS) + if: runner.os == 'macOS' && matrix.compiler == 'clang' + run: | + echo 'CC=clang' >> $GITHUB_ENV + echo 'CXX=clang++' >> $GITHUB_ENV + - name: Setup gcc (Linux) + if: runner.os == 'Linux' && matrix.compiler == 'gcc' + run: | + echo 'CC=gcc-12' >> $GITHUB_ENV + echo 'CXX=g++-12' >> $GITHUB_ENV + - name: Setup gcc (MacOS) + if: runner.os == 'macOS' && matrix.compiler == 'gcc' + run: | + echo 'CC=gcc' >> $GITHUB_ENV + echo 'CXX=g++' >> $GITHUB_ENV + - name: Restore libbpf cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux' + with: + path: libbpf/build + key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }} + fail-on-cache-miss: true + - name: Set libbpf variables + if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux' + run: | + cd libbpf + + EXTRA_AUTOTOOLS_OPTS="--with-libbpf" + EXTRA_CMAKE_OPTS="-DWITH_LIBBPF=1" + + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV + - name: Restore quictls/openssl v1.1.1 cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl1' + with: + path: openssl1/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }} + fail-on-cache-miss: true + - name: Restore quictls/openssl v3.x cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3' + with: + path: openssl3/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }} + fail-on-cache-miss: true + - name: Restore BoringSSL cache + uses: actions/cache/restore@v3 + if: matrix.openssl == 'boringssl' + with: + path: | + boringssl/build/crypto/libcrypto.a + boringssl/build/ssl/libssl.a + boringssl/include + key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }} + fail-on-cache-miss: true + - name: Restore aws-lc cache + uses: actions/cache/restore@v3 + if: matrix.openssl == 'awslc' + with: + path: | + aws-lc/build/crypto/libcrypto.a + aws-lc/build/ssl/libssl.a + aws-lc/include + key: ${{ runner.os }}-awslc-${{ env.AWSLC_VERSION }} + fail-on-cache-miss: true + - name: Set BoringSSL variables + if: matrix.openssl == 'boringssl' + run: | + cd boringssl + + OPENSSL_CFLAGS="-I$PWD/include/" + OPENSSL_LIBS="-L$PWD/build/ssl -lssl -L$PWD/build/crypto -lcrypto -pthread" + EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --without-neverbleed --without-jemalloc" + + echo 'OPENSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'OPENSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'BORINGSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'BORINGSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + - name: Set aws-lc variables + if: matrix.openssl == 'awslc' + run: | + cd aws-lc + + OPENSSL_CFLAGS="-I$PWD/include/" + OPENSSL_LIBS="-L$PWD/build/ssl -lssl -L$PWD/build/crypto -lcrypto -pthread" + EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --without-neverbleed --without-jemalloc" + + echo 'OPENSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'OPENSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'BORINGSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'BORINGSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + - name: Restore nghttp3 cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' + with: + path: nghttp3/build + key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }} + fail-on-cache-miss: true + - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache + BoringSSL + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && (matrix.openssl == 'openssl1' || matrix.openssl == 'boringssl') + with: + path: ngtcp2-openssl1/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }} + fail-on-cache-miss: true + - name: Restore ngtcp2 + quictls/openssl v3.x cache + aws-lc + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && (matrix.openssl == 'openssl3' || matrix.openssl == 'awslc') + with: + path: ngtcp2-openssl3/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }} + fail-on-cache-miss: true + - name: Setup extra environment variables for HTTP/3 + if: matrix.http3 == 'http3' + run: | + PKG_CONFIG_PATH="$PWD/openssl1/build/lib/pkgconfig:$PWD/openssl3/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2-openssl1/build/lib/pkgconfig:$PWD/ngtcp2-openssl3/build/lib/pkgconfig:$PWD/libbpf/build/lib64/pkgconfig:$PKG_CONFIG_PATH" + LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl1/build/lib -Wl,-rpath,$PWD/openssl3/build/lib -Wl,-rpath,$PWD/libbpf/build/lib64" + EXTRA_AUTOTOOLS_OPTS="--enable-http3 $EXTRA_AUTOTOOLS_OPTS" + EXTRA_CMAKE_OPTS="-DENABLE_HTTP3=1 $EXTRA_CMAKE_OPTS" + + echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV + echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV + - name: Setup git submodules + run: | + git submodule update --init + - name: Configure autotools + run: | + autoreconf -i + ./configure + - name: Configure cmake (Linux) + if: matrix.buildtool == 'cmake' && runner.os == 'Linux' + run: | + make dist + VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"') + tar xf nghttp2-$VERSION.tar.gz + cd nghttp2-$VERSION + echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV + + cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . + - name: Configure cmake (MacOS) + if: matrix.buildtool == 'cmake' && runner.os == 'macOS' + run: | + make dist + VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"') + tar xf nghttp2-$VERSION.tar.gz + cd nghttp2-$VERSION + echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV + + # This fixes infamous 'stdio.h not found' error. + echo 'SDKROOT='"$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV + + cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . + - name: Build nghttp2 with autotools (Linux) + if: matrix.buildtool == 'autotools' && runner.os == 'Linux' + run: | + make -j"$(nproc)" distcheck \ + DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" + - name: Build nghttp2 with autotools (MacOS) + if: matrix.buildtool == 'autotools' && runner.os == 'macOS' + run: | + make -j"$(sysctl -n hw.ncpu)" distcheck \ + DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" + - name: Build nghttp2 with cmake + if: matrix.buildtool == 'cmake' + run: | + cd $NGHTTP2_CMAKE_DIR + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + - uses: actions/setup-go@v5 + if: matrix.buildtool == 'cmake' + with: + go-version-file: go.mod + - name: Integration test + # Integration tests for nghttpx; autotools erases build + # artifacts. + if: matrix.buildtool == 'cmake' + run: | + cd $NGHTTP2_CMAKE_DIR/integration-tests + make it + + build-cross: + strategy: + matrix: + host: [x86_64-w64-mingw32, i686-w64-mingw32] + + runs-on: ubuntu-22.04 + + env: + HOST: ${{ matrix.host }} + + steps: + - uses: actions/checkout@v4 + - name: Linux setup + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install \ + gcc-mingw-w64 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + wine + - name: Build CUnit + run: | + curl -LO https://jaist.dl.sourceforge.net/project/cunit/CUnit/2.1-3/CUnit-2.1-3.tar.bz2 + tar xf CUnit-2.1-3.tar.bz2 + cd CUnit-2.1-3 + ./bootstrap + ./configure --disable-shared --host="$HOST" --prefix="$PWD/build" + make -j$(nproc) install + - name: Configure autotools + run: | + autoreconf -i && \ + ./configure --enable-werror --enable-lib-only --with-cunit \ + --host="$HOST" PKG_CONFIG_PATH="$PWD/CUnit-2.1-3/build/lib/pkgconfig" \ + CFLAGS="-g -O2 -D_WIN32_WINNT=0x0600" + - name: Build nghttp2 + run: | + make -j$(nproc) + make -j$(nproc) check TESTS="" + - name: Run tests + if: matrix.host == 'x86_64-w64-mingw32' + run: | + cd tests + wine main.exe + + build-windows: + strategy: + matrix: + arch: [x86, x64] + include: + - arch: x86 + platform: Win32 + - arch: x64 + platform: x64 + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - uses: microsoft/setup-msbuild@v1 + - run: | + vcpkg --triplet=${{ matrix.arch }}-windows install cunit + - name: Configure cmake + run: | + mkdir build + cd build + cmake -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_GENERATOR_PLATFORM=${{ matrix.platform }} -DVCPKG_TARGET_TRIPLET=${{ matrix.arch}}-windows .. + - name: Build nghttp2 + run: | + cmake --build build + cmake --build build --target check diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..720b25f --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +permissions: read-all +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'nghttp2' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'nghttp2' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() + with: + name: artifacts + path: ./out/artifacts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c089da --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# emacs backup file +*~ + +# autotools +*.la +*.lo +*.m4 +*.o +*.pyc +.deps/ +.libs/ +INSTALL +Makefile +Makefile.in +autom4te.cache/ +compile +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing +stamp-h1 +test-driver + +# cmake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake +build.ninja +rules.ninja +.ninja_deps +.ninja_log +lib*.so +lib*.so.* +lib*.a +# generated by "make test" with cmake +Testing/ + +# test logs generated by `make check` +*.log +*.trs + +lib/MSVC_obj/ +_VC_ROOT/ +.depend.MSVC +*.pyd +*.egg-info/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..393c808 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "third-party/mruby"] + path = third-party/mruby + url = https://github.com/mruby/mruby +[submodule "third-party/neverbleed"] + path = third-party/neverbleed + url = https://github.com/tatsuhiro-t/neverbleed.git + branch = nghttp2 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c5c5eb5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,158 @@ +nghttp2 project was started as a fork of spdylay project [1]. Both +projects were started by Tatsuhiro Tsujikawa, who is still the main +author of these projects. Meanwhile, we have many contributions, and +we are not here without them. We sincerely thank you to all who made +a contribution. Here is the all individuals/organizations who +contributed to nghttp2 and spdylay project at which we forked. These +names are retrieved from git commit log. If you have made a +contribution, but you are missing in the list, please let us know via +github issues [2]. + +[1] https://github.com/tatsuhiro-t/spdylay +[2] https://github.com/nghttp2/nghttp2/issues + +-------- + +187j3x1 +Adam Gołębiowski +Alek Storm +Alex Nalivko +Alexandr Vlasov +Alexandros Konstantinakis-Karmis +Alexis La Goutte +Amir Livneh +Amir Pakdel +Anders Bakken +Andreas Pohl +Andrew Penkrat +Andy Davies +Angus Gratton +Anna Henningsen +Ant Bryan +Anthony Alayo +Asra Ali +Benedikt Christoph Wolters +Benjamin Peterson +Bernard Spil +Bernhard Walle +Brendan Heinonen +Brian Card +Brian Suh +Daniel Bevenius +Daniel Evers +Daniel Stenberg +Dave Reisner +David Beitey +David Korczynski +David Weekly +Dimitris Apostolou +Dmitri Tikhonov +Dmitriy Vetutnev +Don +Dylan Plecki +Etienne Cimon +Fabian Möller +Fabian Wiesel +Fred Sundvik +Gabi Davar +Gaël PORTAY +Geoff Hill +George Liu +Gitai +Google Inc. +Hajime Fujita +Jacky Tian +Jacky_Yin +Jacob Champion +James M Snell +Jan Kundrát +Jan-E +Janusz Dziemidowicz +Jay Satiro +Jeff 'Raid' Baitis +Jianqing Wang +Jim Morrison +Josh Braegger +José F. Calcerrada +Kamil Dudka +Kazuho Oku +Kenny (kang-yen) Peng +Kenny Peng +Kit Chan +Kyle Schomp +LazyHamster +Leo Neat +Lorenz Nickel +Lucas Pardue +MATSUMOTO Ryosuke +Marc Bachmann +Marcelo Trylesinski +Matt Rudary +Matt Way +Michael Kaufmann +Mike Conlen +Mike Frysinger +Mike Lothian +Nicholas Hurley +Nora Shoemaker +Paweł Wegner +Pedro Santos +Peeyush Aggarwal +Peter Wu +Piotr Sikora +PufferOverflow +Raul Gutierrez Segales +Remo E +Renaud +Reza Tavakoli +Richard Wolfert +Rick Lei +Ross Smith II +Rudi Heitbaum +Ryo Ota +Scott Mitchell +Sebastiaan Deckers +Shelley Vohr +Simon Frankenberger +Simone Basso +Soham Sinha +Stefan Eissing +Stephen Ludin +Sunpoet Po-Chuan Hsieh +Svante Signell +Syohei YOSHIDA +Tapanito +Tatsuhiko Kubo +Tatsuhiro Tsujikawa +Tobias Geerinckx-Rice +Tom Harwood +Tomas Krizek +Tomasz Buchert +Tomasz Torcz +Vernon Tang +Viacheslav Biriukov +Viktor Szakats +Viktor Szépe +Wenfeng Liu +William A Rowe Jr +Xiaoguang Sun +Zhuoyun Wei +acesso +ayanamist +bxshi +clemahieu +dalf +dawg +es +fangdingjun +hrxi +jwchoi +kumagi +lhuang04 +lstefani +makovich +mod-h2-dev +moparisthebest +robaho +snnn +yuuki-kodama diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..161a7ee --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,491 @@ +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa +# Copyright (c) 2016 Peter Wu +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +cmake_minimum_required(VERSION 3.5) +# XXX using 1.8.90 instead of 1.9.0-DEV +project(nghttp2 VERSION 1.59.0) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +set(LT_CURRENT 40) +set(LT_REVISION 0) +set(LT_AGE 26) + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +include(Version) + +math(EXPR LT_SOVERSION "${LT_CURRENT} - ${LT_AGE}") +set(LT_VERSION "${LT_SOVERSION}.${LT_AGE}.${LT_REVISION}") +set(PACKAGE_VERSION "${PROJECT_VERSION}") +HexVersion(PACKAGE_VERSION_NUM ${PROJECT_VERSION_MAJOR} ${PROJECT_VERSION_MINOR} ${PROJECT_VERSION_PATCH}) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the build type" FORCE) + + # Include "None" as option to disable any additional (optimization) flags, + # relying on just CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (which are empty by + # default). These strings are presented in cmake-gui. + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "None" "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +include(GNUInstallDirs) + +# For documentation +find_package(Python3 COMPONENTS Interpreter) + +# Auto-detection of features that can be toggled +if(NOT ENABLE_LIB_ONLY) + find_package(Libev 4.11) + find_package(Libcares 1.7.5) + find_package(ZLIB 1.2.3) +endif() + +find_package(OpenSSL 1.1.1) +find_package(Libngtcp2 1.0.0) +find_package(Libngtcp2_crypto_quictls 1.0.0) +if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND) + set(HAVE_LIBNGTCP2_CRYPTO_QUICTLS 1) +endif() +find_package(Libnghttp3 1.1.0) +if(WITH_LIBBPF) + find_package(Libbpf 0.7.0) + if(NOT LIBBPF_FOUND) + message(FATAL_ERROR "libbpf was requested (WITH_LIBBPF=1) but not found.") + endif() +endif() +if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND) + set(ENABLE_APP_DEFAULT ON) +else() + set(ENABLE_APP_DEFAULT OFF) +endif() +find_package(Systemd 209) +find_package(Jansson 2.5) +set(ENABLE_HPACK_TOOLS_DEFAULT ${JANSSON_FOUND}) +# 2.0.8 is required because we use evconnlistener_set_error_cb() +find_package(Libevent 2.0.8 COMPONENTS core extra openssl) +set(ENABLE_EXAMPLES_DEFAULT ${LIBEVENT_OPENSSL_FOUND}) + +find_package(LibXml2 2.6.26) +set(WITH_LIBXML2_DEFAULT ${LIBXML2_FOUND}) +find_package(Jemalloc) +set(WITH_JEMALLOC_DEFAULT ${JEMALLOC_FOUND}) + +include(CMakeOptions.txt) + +if(ENABLE_LIB_ONLY AND (ENABLE_APP OR ENABLE_HPACK_TOOLS OR ENABLE_EXAMPLES)) + # Remember when disabled options are disabled for later diagnostics. + set(ENABLE_LIB_ONLY_DISABLED_OTHERS 1) +else() + set(ENABLE_LIB_ONLY_DISABLED_OTHERS 0) +endif() +if(ENABLE_LIB_ONLY) + set(ENABLE_APP OFF) + set(ENABLE_HPACK_TOOLS OFF) + set(ENABLE_EXAMPLES OFF) +endif() + +# Do not disable assertions based on CMAKE_BUILD_TYPE. +foreach(_build_type "Release" "MinSizeRel" "RelWithDebInfo") + foreach(_lang C CXX) + string(TOUPPER "CMAKE_${_lang}_FLAGS_${_build_type}" _var) + string(REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " ${_var} "${${_var}}") + endforeach() +endforeach() + +if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + set(HINT_NORETURN "__attribute__((noreturn))") +else() + set(HINT_NORETURN) +endif() + +include(ExtractValidFlags) +foreach(_cxx1x_flag -std=c++14) + extract_valid_cxx_flags(_cxx1x_flag_supported ${_cxx1x_flag}) + if(_cxx1x_flag_supported) + set(CXX1XCXXFLAGS ${_cxx1x_flag}) + break() + endif() +endforeach() + +include(CMakePushCheckState) +include(CheckCXXSourceCompiles) +cmake_push_check_state() +set(CMAKE_REQUIRED_DEFINITIONS "${CXX1XCXXFLAGS}") +# Check that std::future is available. +check_cxx_source_compiles(" +#include +#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("unistd.h" HAVE_UNISTD_H) +check_include_file("windows.h" HAVE_WINDOWS_H) + +include(CheckTypeSize) +# Checks for typedefs, structures, and compiler characteristics. +# AC_TYPE_SIZE_T +check_type_size("ssize_t" SIZEOF_SSIZE_T) +if(SIZEOF_SSIZE_T STREQUAL "") + # ssize_t is a signed type in POSIX storing at least -1. + # Set it to "int" to match the behavior of AC_TYPE_SSIZE_T (autotools). + set(ssize_t int) +endif() +# AC_TYPE_UINT8_T +# AC_TYPE_UINT16_T +# AC_TYPE_UINT32_T +# AC_TYPE_UINT64_T +# AC_TYPE_INT8_T +# AC_TYPE_INT16_T +# AC_TYPE_INT32_T +# AC_TYPE_INT64_T +# AC_TYPE_OFF_T +# AC_TYPE_PID_T +# AC_TYPE_UID_T +# XXX To support inline for crappy compilers, see https://cmake.org/Wiki/CMakeTestInline +# AC_C_INLINE +# XXX is AC_SYS_LARGEFILE still needed for modern systems? +# add_definitions(-D_FILE_OFFSET_BITS=64) + +include(CheckStructHasMember) +check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_STRUCT_TM_TM_GMTOFF) + +# Checks for library functions. +include(CheckFunctionExists) +check_function_exists(_Exit HAVE__EXIT) +check_function_exists(accept4 HAVE_ACCEPT4) +check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) +check_function_exists(mkostemp HAVE_MKOSTEMP) + +check_symbol_exists(GetTickCount64 sysinfoapi.h HAVE_GETTICKCOUNT64) + +include(CheckSymbolExists) +# XXX does this correctly detect initgroups (un)availability on cygwin? +check_symbol_exists(initgroups grp.h HAVE_DECL_INITGROUPS) +if(NOT HAVE_DECL_INITGROUPS AND HAVE_UNISTD_H) + # FreeBSD declares initgroups() in unistd.h + check_symbol_exists(initgroups unistd.h HAVE_DECL_INITGROUPS2) + if(HAVE_DECL_INITGROUPS2) + set(HAVE_DECL_INITGROUPS 1) + endif() +endif() + +check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_DECL_CLOCK_MONOTONIC) + +set(WARNCFLAGS) +set(WARNCXXFLAGS) +if(CMAKE_C_COMPILER_ID MATCHES "MSVC") + if(ENABLE_WERROR) + set(WARNCFLAGS /WX) + set(WARNCXXFLAGS /WX) + endif() +else() + if(ENABLE_WERROR) + set(WARNCFLAGS "-Werror") + set(WARNCXXFLAGS "-Werror") + endif() + + include(PickyWarningsC) + include(PickyWarningsCXX) +endif() + +if(ENABLE_STATIC_CRT) + foreach(lang C CXX) + foreach(suffix "" _DEBUG _MINSIZEREL _RELEASE _RELWITHDEBINFO) + set(var "CMAKE_${lang}_FLAGS${suffix}") + string(REPLACE "/MD" "/MT" ${var} "${${var}}") + endforeach() + endforeach() +endif() + +if(ENABLE_DEBUG) + set(DEBUGBUILD 1) +endif() + +# Some platform does not have working std::future. We disable +# threading for those platforms. +if(NOT ENABLE_THREADS OR NOT HAVE_STD_FUTURE) + set(NOTHREADS 1) +endif() + +add_definitions(-DHAVE_CONFIG_H) +configure_file(cmakeconfig.h.in config.h) +# autotools-compatible names +# Sphinx expects relative paths in the .rst files. Use the fact that the files +# below are all one directory level deep. +file(RELATIVE_PATH top_srcdir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_SOURCE_DIR}") +file(RELATIVE_PATH top_builddir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_BINARY_DIR}") +set(abs_top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}") +set(abs_top_builddir "${CMAKE_CURRENT_BINARY_DIR}") +# libnghttp2.pc (pkg-config file) +set(prefix "${CMAKE_INSTALL_PREFIX}") +set(exec_prefix "${CMAKE_INSTALL_PREFIX}") +set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") +set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") +set(VERSION "${PACKAGE_VERSION}") +# For init scripts and systemd service file (in contrib/) +set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") +set(sbindir "${CMAKE_INSTALL_FULL_SBINDIR}") +foreach(name + lib/libnghttp2.pc + lib/includes/nghttp2/nghttp2ver.h + integration-tests/config.go + integration-tests/setenv + doc/conf.py + doc/index.rst + doc/package_README.rst + doc/tutorial-client.rst + doc/tutorial-server.rst + doc/tutorial-hpack.rst + doc/nghttpx-howto.rst + doc/h2load-howto.rst + doc/building-android-binary.rst + doc/nghttp2.h.rst + doc/nghttp2ver.h.rst + doc/contribute.rst +) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +if(APPLE) + add_definitions(-D__APPLE_USE_RFC_3542) +endif() + +include_directories( + "${CMAKE_CURRENT_BINARY_DIR}" # for config.h +) +# For use in src/CMakeLists.txt +set(PKGDATADIR "${CMAKE_INSTALL_FULL_DATADIR}/${CMAKE_PROJECT_NAME}") +set(PKGLIBDIR "${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_PROJECT_NAME}") + +install(FILES README.rst DESTINATION "${CMAKE_INSTALL_DOCDIR}") + +add_subdirectory(lib) +#add_subdirectory(lib/includes) +add_subdirectory(third-party) +add_subdirectory(src) +add_subdirectory(examples) +add_subdirectory(tests) +#add_subdirectory(tests/testdata) +add_subdirectory(integration-tests) +if(ENABLE_DOC) + add_subdirectory(doc) +endif() +add_subdirectory(contrib) +add_subdirectory(script) +add_subdirectory(bpf) + + +string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type) +message(STATUS "summary of build options: + + Package version: ${VERSION} + Library version: ${LT_CURRENT}:${LT_REVISION}:${LT_AGE} + Install prefix: ${CMAKE_INSTALL_PREFIX} + Target system: ${CMAKE_SYSTEM_NAME} + Compiler: + Build type: ${CMAKE_BUILD_TYPE} + C compiler: ${CMAKE_C_COMPILER} + CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} + C++ compiler: ${CMAKE_CXX_COMPILER} + CXXFLAGS: ${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS} + WARNCFLAGS: ${WARNCFLAGS} + CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} + WARNCXXFLAGS: ${WARNCXXFLAGS} + Python: + Python: ${Python3_EXECUTABLE} + Python3_VERSION: ${Python3_VERSION} + Test: + CUnit: ${HAVE_CUNIT} (LIBS='${CUNIT_LIBRARIES}') + Failmalloc: ${ENABLE_FAILMALLOC} + Libs: + OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}') + Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}') + Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}') + Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}') + Libngtcp2: ${HAVE_LIBNGTCP2} (LIBS='${LIBNGTCP2_LIBRARIES}') + Libngtcp2_crypto_quictls: ${HAVE_LIBNGTCP2_CRYPTO_QUICTLS} (LIBS='${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES}') + Libnghttp3: ${HAVE_LIBNGHTTP3} (LIBS='${LIBNGHTTP3_LIBRARIES}') + Libbpf: ${HAVE_LIBBPF} (LIBS='${LIBBPF_LIBRARIES}') + Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}') + Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}') + Jemalloc: ${HAVE_JEMALLOC} (LIBS='${JEMALLOC_LIBRARIES}') + Zlib: ${HAVE_ZLIB} (LIBS='${ZLIB_LIBRARIES}') + Systemd: ${HAVE_SYSTEMD} (LIBS='${SYSTEMD_LIBRARIES}') + Third-party: + http-parser: ${ENABLE_THIRD_PARTY} + MRuby: ${HAVE_MRUBY} + Neverbleed: ${HAVE_NEVERBLEED} + Features: + Applications: ${ENABLE_APP} + HPACK tools: ${ENABLE_HPACK_TOOLS} + Examples: ${ENABLE_EXAMPLES} + Threading: ${ENABLE_THREADS} + HTTP/3(EXPERIMENTAL): ${ENABLE_HTTP3} +") +if(ENABLE_LIB_ONLY_DISABLED_OTHERS) + message("Only the library will be built. To build other components " + "(such as applications and examples), set ENABLE_LIB_ONLY=OFF.") +endif() diff --git a/CMakeOptions.txt b/CMakeOptions.txt new file mode 100644 index 0000000..238d5b1 --- /dev/null +++ b/CMakeOptions.txt @@ -0,0 +1,28 @@ +# Features that can be enabled for cmake (see CMakeLists.txt) + +option(ENABLE_WERROR "Turn on compile time warnings") +option(ENABLE_DEBUG "Turn on debug output") +option(ENABLE_THREADS "Turn on threading in apps" ON) +option(ENABLE_APP "Build applications (nghttp, nghttpd, nghttpx and h2load)" + ${ENABLE_APP_DEFAULT}) +option(ENABLE_HPACK_TOOLS "Build HPACK tools" + ${ENABLE_HPACK_TOOLS_DEFAULT}) +option(ENABLE_EXAMPLES "Build examples" + ${ENABLE_EXAMPLES_DEFAULT}) +option(ENABLE_FAILMALLOC "Build failmalloc test program" ON) +option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENABLE_APP=0 -DENABLE_EXAMPLES=0 -DENABLE_HPACK_TOOLS=0") +option(ENABLE_STATIC_LIB "Build libnghttp2 in static mode also") +option(ENABLE_SHARED_LIB "Build libnghttp2 as a shared library" ON) +option(ENABLE_STATIC_CRT "Build libnghttp2 against the MS LIBCMT[d]") +option(ENABLE_HTTP3 "Enable HTTP/3 support" OFF) +option(ENABLE_DOC "Build documentation" ON) + +option(WITH_LIBXML2 "Use libxml2" + ${WITH_LIBXML2_DEFAULT}) +option(WITH_JEMALLOC "Use jemalloc" + ${WITH_JEMALLOC_DEFAULT}) +option(WITH_MRUBY "Use mruby") +option(WITH_NEVERBLEED "Use neverbleed") +option(WITH_LIBBPF "Use libbpf") + +# vim: ft=cmake: diff --git a/CONTRIBUTION b/CONTRIBUTION new file mode 100644 index 0000000..8a2aae3 --- /dev/null +++ b/CONTRIBUTION @@ -0,0 +1,18 @@ +[The text below was composed based on 1.2. License section of +curl/libcurl project.] + +When contributing with code, you agree to put your changes and new +code under the same license nghttp2 is already using unless stated and +agreed otherwise. + +When changing existing source code, you do not alter the copyright of +the original file(s). The copyright will still be owned by the +original creator(s) or those who have been assigned copyright by the +original author(s). + +By submitting a patch to the nghttp2 project, you are assumed to have +the right to the code and to be allowed by your employer or whatever +to hand over that patch/code to us. We will credit you for your +changes as far as possible, to give credit but also to keep a trace +back to who made what changes. Please always provide us with your +full real name when contributing! diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8020179 --- /dev/null +++ b/COPYING @@ -0,0 +1,23 @@ +The MIT License + +Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa +Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile.android b/Dockerfile.android new file mode 100644 index 0000000..96ce7c4 --- /dev/null +++ b/Dockerfile.android @@ -0,0 +1,121 @@ +# vim: ft=dockerfile: +# Dockerfile to build nghttp2 android binary +# +# $ sudo docker build -t nghttp2-android - < Dockerfile.android +# +# After successful build, android binaries are located under +# /root/build/nghttp2. You can copy the binary using docker cp. For +# example, to copy nghttpx binary to host file system location +# /path/to/dest, do this: +# +# $ sudo docker run -v /path/to/dest:/out nghttp2-android cp /root/build/nghttp2/src/nghttpx /out + + +# Only use standalone-toolchain for reduce size +FROM ubuntu:22.04 +MAINTAINER Tatsuhiro Tsujikawa + +ENV NDK_VERSION r25b +ENV NDK /root/android-ndk-$NDK_VERSION +ENV TOOLCHAIN $NDK/toolchains/llvm/prebuilt/linux-x86_64 +ENV TARGET aarch64-linux-android +ENV API 33 +ENV AR $TOOLCHAIN/bin/llvm-ar +ENV CC $TOOLCHAIN/bin/$TARGET$API-clang +ENV CXX $TOOLCHAIN/bin/$TARGET$API-clang++ +ENV LD $TOOLCHAIN/bin/ld +ENV RANDLIB $TOOLCHAIN/bin/llvm-ranlib +ENV STRIP $TOOLCHAIN/bin/llvm-strip +ENV PREFIX /root/usr/local + +WORKDIR /root +RUN apt-get update && \ + apt-get install -y unzip make binutils autoconf \ + automake autotools-dev libtool pkg-config git \ + curl dpkg-dev libxml2-dev genisoimage libc6-i386 \ + lib32stdc++6 && \ + rm -rf /var/cache/apt/* + +# Download NDK +RUN curl -L -O https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-linux.zip && \ + unzip -q android-ndk-$NDK_VERSION-linux.zip && \ + rm android-ndk-$NDK_VERSION-linux.zip + +# Setup version of libraries +ENV OPENSSL_VERSION 1.1.1q +ENV LIBEV_VERSION 4.33 +ENV ZLIB_VERSION 1.2.13 +ENV CARES_VERSION 1.18.1 +ENV NGHTTP2_VERSION master + +WORKDIR /root/build +RUN curl -L -O https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz && \ + tar xf openssl-$OPENSSL_VERSION.tar.gz && \ + rm openssl-$OPENSSL_VERSION.tar.gz + +WORKDIR /root/build/openssl-$OPENSSL_VERSION +RUN export ANDROID_NDK_HOME=$NDK PATH=$TOOLCHAIN/bin:$PATH && \ + ./Configure no-shared --prefix=$PREFIX android-arm64 && \ + make && make install_sw + +WORKDIR /root/build +RUN curl -L -O http://dist.schmorp.de/libev/Attic/libev-$LIBEV_VERSION.tar.gz && \ + tar xf libev-$LIBEV_VERSION.tar.gz && \ + rm libev-$LIBEV_VERSION.tar.gz + +WORKDIR /root/build/libev-$LIBEV_VERSION +RUN ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared \ + --enable-static \ + CPPFLAGS=-I$PREFIX/include \ + LDFLAGS=-L$PREFIX/lib && \ + make install + +WORKDIR /root/build +RUN curl -L -O https://zlib.net/zlib-$ZLIB_VERSION.tar.gz && \ + tar xf zlib-$ZLIB_VERSION.tar.gz && \ + rm zlib-$ZLIB_VERSION.tar.gz + +WORKDIR /root/build/zlib-$ZLIB_VERSION +RUN HOST=$TARGET \ + ./configure \ + --prefix=$PREFIX \ + --libdir=$PREFIX/lib \ + --includedir=$PREFIX/include \ + --static && \ + make install + + +WORKDIR /root/build +RUN curl -L -O https://c-ares.haxx.se/download/c-ares-$CARES_VERSION.tar.gz && \ + tar xf c-ares-$CARES_VERSION.tar.gz && \ + rm c-ares-$CARES_VERSION.tar.gz + +WORKDIR /root/build/c-ares-$CARES_VERSION +RUN ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared && \ + make install + +WORKDIR /root/build +RUN git clone https://github.com/nghttp2/nghttp2 -b $NGHTTP2_VERSION --depth 1 +WORKDIR /root/build/nghttp2 +RUN autoreconf -i && \ + ./configure \ + --enable-app \ + --disable-shared \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --without-libxml2 \ + --disable-examples \ + --disable-threads \ + CPPFLAGS="-fPIE -I$PREFIX/include" \ + PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \ + LDFLAGS="-fPIE -pie -L$PREFIX/lib" && \ + make && \ + $STRIP src/nghttpx src/nghttpd src/nghttp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..45d9408 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +See COPYING diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..683b989 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,61 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = lib third-party src bpf examples tests integration-tests \ + doc contrib script + +ACLOCAL_AMFLAGS = -I m4 + +dist_doc_DATA = README.rst + +EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-env \ + Dockerfile.android \ + cmakeconfig.h.in \ + CMakeLists.txt \ + CMakeOptions.txt \ + cmake/ExtractValidFlags.cmake \ + cmake/FindJemalloc.cmake \ + cmake/FindLibev.cmake \ + cmake/FindCUnit.cmake \ + cmake/Version.cmake \ + cmake/FindLibevent.cmake \ + cmake/FindJansson.cmake \ + cmake/FindLibcares.cmake \ + cmake/FindSystemd.cmake \ + cmake/FindLibbpf.cmake \ + cmake/FindLibnghttp3.cmake \ + cmake/FindLibngtcp2.cmake \ + cmake/FindLibngtcp2_crypto_quictls.cmake \ + cmake/PickyWarningsC.cmake \ + cmake/PickyWarningsCXX.cmake + +.PHONY: clang-format + +# Format source files using clang-format. Don't format source files +# under third-party directory since we are not responsible for their +# coding style. +clang-format: + CLANGFORMAT=`git config --get clangformat.binary`; \ + test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \ + $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/nghttp2/*.h \ + src/*.{c,cc,h} examples/*.c \ + tests/*.{c,h} bpf/*.c fuzz/*.cc diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..5ccc0ea --- /dev/null +++ b/README @@ -0,0 +1 @@ +See README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..2af1bcc --- /dev/null +++ b/README.rst @@ -0,0 +1,1474 @@ +nghttp2 - HTTP/2 C Library +========================== + +This is an implementation of the Hypertext Transfer Protocol version 2 +in C. + +The framing layer of HTTP/2 is implemented as a reusable C library. +On top of that, we have implemented an HTTP/2 client, server and +proxy. We have also developed load test and benchmarking tools for +HTTP/2. + +An HPACK encoder and decoder are available as a public API. + +Development Status +------------------ + +nghttp2 was originally developed based on `RFC 7540 +`_ 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 and HTTP/3) + + This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and + ``http/1.1`` via ALPN and requires TLSv1.2 for HTTP/2 + connection. + + It also supports HTTP/3. + +* http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct) + + ``h2c`` and ``http/1.1``. + +Requirements +------------ + +The following package is required to build the libnghttp2 library: + +* pkg-config >= 0.20 + +To build and run the unit test programs, the following package is +required: + +* cunit >= 2.1 + +To build the documentation, you need to install: + +* sphinx (http://sphinx-doc.org/) + +If you need libnghttp2 (C library) only, then the above packages are +all you need. Use ``--enable-lib-only`` to ensure that only +libnghttp2 is built. This avoids potential build error related to +building bundled applications. + +To build and run the application programs (``nghttp``, ``nghttpd``, +``nghttpx`` and ``h2load``) in the ``src`` directory, the following packages +are required: + +* OpenSSL >= 1.1.1; or LibreSSL >= 3.8.1; or aws-lc >= 1.19.0; or + BoringSSL +* libev >= 4.11 +* zlib >= 1.2.3 +* libc-ares >= 1.7.5 + +To enable ``-a`` option (getting linked assets from the downloaded +resource) in ``nghttp``, the following package is required: + +* libxml2 >= 2.6.26 + +To enable systemd support in nghttpx, the following package is +required: + +* libsystemd-dev >= 209 + +The HPACK tools require the following package: + +* jansson >= 2.5 + +To build sources under the examples directory, libevent is required: + +* libevent-openssl >= 2.0.8 + +To mitigate heap fragmentation in long running server programs +(``nghttpd`` and ``nghttpx``), jemalloc is recommended: + +* jemalloc + + .. note:: + + Alpine Linux currently does not support malloc replacement + due to musl limitations. See details in issue `#762 `_. + +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 managed by git submodule under third-party/mruby +directory. Currently, mruby support for nghttpx is disabled by +default. To enable mruby support, use ``--with-mruby`` configure +option. Note that at the time of this writing, libmruby-dev and mruby +packages in Debian/Ubuntu are not usable for nghttp2, since they do +not enable C++ ABI. To build mruby, the following packages are +required: + +* ruby +* bison + +nghttpx supports `neverbleed `_, +privilege separation engine for OpenSSL. In short, it minimizes the +risk of private key leakage when serious bug like Heartbleed is +exploited. The neverbleed is disabled by default. To enable it, use +``--with-neverbleed`` configure option. + +To enable the experimental HTTP/3 support for h2load and nghttpx, the +following libraries are required: + +* `OpenSSL with QUIC support + `_; or + LibreSSL (does not support 0RTT); or aws-lc; or + `BoringSSL `_ (commit + f42be90d665b6a376177648ccbb76fbbd6497c13) +* `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.2.0 https://github.com/ngtcp2/ngtcp2 + $ cd ngtcp2 + $ autoreconf -i + $ ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig" + $ make -j$(nproc) + $ make install + $ cd .. + +If your Linux distribution does not have libbpf-dev >= 0.7.0, build +from source: + +.. code-block:: text + + $ git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf + $ cd libbpf + $ PREFIX=$PWD/build make -C src install + $ cd .. + +Build nghttp2: + +.. code-block:: text + + $ git clone https://github.com/nghttp2/nghttp2 + $ cd nghttp2 + $ git submodule update --init + $ autoreconf -i + $ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \ + CC=clang-14 CXX=clang++-14 \ + PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \ + LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/lib -Wl,-rpath,$PWD/../libbpf/build/lib64" + $ make -j$(nproc) + +The eBPF program ``reuseport_kern.o`` should be found under bpf +directory. Pass ``--quic-bpf-program-file=bpf/reuseport_kern.o`` +option to nghttpx to load it. See also `HTTP/3 section in nghttpx - +HTTP/2 proxy - HOW-TO +`_. + +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 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 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, forward secrecy and HTTP/2. +``nghttpx`` also offers the functionality to share session cache and +ticket keys among multiple ``nghttpx`` instances via memcached. + +``nghttpx`` has 2 operation modes: + +================== ======================== ================ ============= +Mode option Frontend Backend Note +================== ======================== ================ ============= +default mode HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy +``--http2-proxy`` HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy +================== ======================== ================ ============= + +The interesting mode at the moment is the default mode. It works like +a reverse proxy and listens for HTTP/3, HTTP/2, and HTTP/1.1 and can +be deployed as a SSL/TLS terminator for existing web server. + +In all modes, the frontend connections are encrypted by SSL/TLS by +default. To disable encryption, use the ``no-tls`` keyword in +``--frontend`` option. If encryption is disabled, incoming HTTP/1.1 +connections can be upgraded to HTTP/2 through HTTP Upgrade. On the +other hard, backend connections are not encrypted by default. To +encrypt backend connections, use ``tls`` keyword in ``--backend`` +option. + +``nghttpx`` supports a configuration file. See the ``--conf`` option and +sample configuration file ``nghttpx.conf.sample``. + +In the default mode, ``nghttpx`` works as reverse proxy to the backend +server: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server + [reverse proxy] + +With the ``--http2-proxy`` option, it works as forward proxy, and it +is so called secure HTTP/2 proxy: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy + [secure proxy] (e.g., Squid, ATS) + +The ``Client`` in the above example needs to be configured to use +``nghttpx`` as secure proxy. + +At the time of this writing, both Chrome and Firefox support secure +HTTP/2 proxy. One way to configure Chrome to use a secure proxy is to +create a proxy.pac script like this: + +.. code-block:: javascript + + function FindProxyForURL(url, host) { + return "HTTPS SERVERADDR:PORT"; + } + +``SERVERADDR`` and ``PORT`` is the hostname/address and port of the +machine nghttpx is running on. Please note that Chrome requires a valid +certificate for secure proxy. + +Then run Chrome with the following arguments: + +.. code-block:: text + + $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn + +The backend HTTP/2 connections can be tunneled through an HTTP proxy. +The proxy is specified using ``--backend-http-proxy-uri``. The +following figure illustrates how nghttpx talks to the outside HTTP/2 +proxy through an HTTP proxy: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) -- + + --===================---> HTTP/2 Proxy + (HTTP proxy tunnel) (e.g., nghttpx -s) + +Benchmarking tool +----------------- + +The ``h2load`` program is a benchmarking tool for HTTP/3, HTTP/2, and +HTTP/1.1. The UI of ``h2load`` is heavily inspired by ``weighttp`` +(https://github.com/lighttpd/weighttp). The typical usage is as +follows: + +.. code-block:: text + + $ h2load -n100000 -c100 -m100 https://localhost:8443/ + starting benchmark... + spawning thread #0: 100 concurrent clients, 100000 total requests + Protocol: TLSv1.2 + Cipher: ECDHE-RSA-AES128-GCM-SHA256 + Server Temp Key: ECDH P-256 256 bits + progress: 10% done + progress: 20% done + progress: 30% done + progress: 40% done + progress: 50% done + progress: 60% done + progress: 70% done + progress: 80% done + progress: 90% done + progress: 100% done + + finished in 771.26ms, 129658 req/s, 4.71MB/s + requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored + status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx + traffic: 3812300 bytes total, 1009900 bytes headers, 1000000 bytes data + min max mean sd +/- sd + time for request: 25.12ms 124.55ms 51.07ms 15.36ms 84.87% + time for connect: 208.94ms 254.67ms 241.38ms 7.95ms 63.00% + time to 1st byte: 209.11ms 254.80ms 241.51ms 7.94ms 63.00% + +The above example issued total 100,000 requests, using 100 concurrent +clients (in other words, 100 HTTP/2 sessions), and a maximum of 100 streams +per client. With the ``-t`` option, ``h2load`` will use multiple native +threads to avoid saturating a single core on client side. + +.. warning:: + + **Don't use this tool against publicly available servers.** That is + considered a DOS attack. Please only use it against your private + servers. + +If the experimental HTTP/3 is enabled, h2load can send requests to +HTTP/3 server. To do this, specify ``h3`` to ``--alpn-list`` option +like so: + +.. code-block:: text + + $ h2load --alpn-list h3 https://127.0.0.1:4433 + +For nghttp2 v1.58 or earlier, use ``--npn-list`` instead of +``--alpn-list``. + +HPACK tools +----------- + +The ``src`` directory contains the HPACK tools. The ``deflatehd`` program is a +command-line header compression tool. The ``inflatehd`` program is a +command-line header decompression tool. Both tools read input from +stdin and write output to stdout. Errors are written to stderr. +They take JSON as input and output. We (mostly) use the same JSON data +format described at https://github.com/http2jp/hpack-test-case. + +deflatehd - header compressor ++++++++++++++++++++++++++++++ + +The ``deflatehd`` program reads JSON data or HTTP/1-style header fields from +stdin and outputs compressed header block in JSON. + +For the JSON input, the root JSON object must include a ``cases`` key. +Its value has to include the sequence of input header set. They share +the same compression context and are processed in the order they +appear. Each item in the sequence is a JSON object and it must +include a ``headers`` key. Its value is an array of JSON objects, +which includes exactly one name/value pair. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "headers": [ + { ":method": "GET" }, + { ":path": "/" } + ] + }, + { + "headers": [ + { ":method": "POST" }, + { ":path": "/" } + ] + } + ] + } + + +With the ``-t`` option, the program can accept more familiar HTTP/1 style +header field blocks. Each header set is delimited by an empty line: + +Example: + +.. code-block:: text + + :method: GET + :scheme: https + :path: / + + :method: POST + user-agent: nghttp2 + +The output is in JSON object. It should include a ``cases`` key and its +value is an array of JSON objects, which has at least the following keys: + +seq + The index of header set in the input. + +input_length + The sum of the length of the name/value pairs in the input. + +output_length + The length of the compressed header block. + +percentage_of_original_size + ``output_length`` / ``input_length`` * 100 + +wire + The compressed header block as a hex string. + +headers + The input header set. + +header_table_size + The header table size adjusted before deflating the header set. + +Examples: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "input_length": 66, + "output_length": 20, + "percentage_of_original_size": 30.303030303030305, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + , + { + "seq": 1, + "input_length": 74, + "output_length": 10, + "percentage_of_original_size": 13.513513513513514, + "wire": "88448504252dd5918485", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + ] + } + + +The output can be used as the input for ``inflatehd`` and +``deflatehd``. + +With the ``-d`` option, the extra ``header_table`` key is added and its +associated value includes the state of dynamic header table after the +corresponding header set was processed. The value includes at least +the following keys: + +entries + The entry in the header table. If ``referenced`` is ``true``, it + is in the reference set. The ``size`` includes the overhead (32 + bytes). The ``index`` corresponds to the index of header table. + The ``name`` is the header field name and the ``value`` is the + header field value. + +size + The sum of the spaces entries occupied, this includes the + entry overhead. + +max_size + The maximum header table size. + +deflate_size + The sum of the spaces entries occupied within + ``max_deflate_size``. + +max_deflate_size + The maximum header table size the encoder uses. This can be smaller + than ``max_size``. In this case, the encoder only uses up to first + ``max_deflate_size`` buffer. Since the header table size is still + ``max_size``, the encoder has to keep track of entries outside the + ``max_deflate_size`` but inside the ``max_size`` and make sure + that they are no longer referenced. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "input_length": 66, + "output_length": 20, + "percentage_of_original_size": 30.303030303030305, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096, + "header_table": { + "entries": [ + { + "index": 1, + "name": "user-agent", + "value": "nghttp2", + "referenced": true, + "size": 49 + }, + { + "index": 2, + "name": ":scheme", + "value": "https", + "referenced": true, + "size": 44 + }, + { + "index": 3, + "name": ":path", + "value": "/", + "referenced": true, + "size": 38 + }, + { + "index": 4, + "name": ":method", + "value": "GET", + "referenced": true, + "size": 42 + }, + { + "index": 5, + "name": ":authority", + "value": "example.org", + "referenced": true, + "size": 53 + } + ], + "size": 226, + "max_size": 4096, + "deflate_size": 226, + "max_deflate_size": 4096 + } + } + , + { + "seq": 1, + "input_length": 74, + "output_length": 10, + "percentage_of_original_size": 13.513513513513514, + "wire": "88448504252dd5918485", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096, + "header_table": { + "entries": [ + { + "index": 1, + "name": ":method", + "value": "POST", + "referenced": true, + "size": 43 + }, + { + "index": 2, + "name": "user-agent", + "value": "nghttp2", + "referenced": true, + "size": 49 + }, + { + "index": 3, + "name": ":scheme", + "value": "https", + "referenced": true, + "size": 44 + }, + { + "index": 4, + "name": ":path", + "value": "/", + "referenced": false, + "size": 38 + }, + { + "index": 5, + "name": ":method", + "value": "GET", + "referenced": false, + "size": 42 + }, + { + "index": 6, + "name": ":authority", + "value": "example.org", + "referenced": true, + "size": 53 + } + ], + "size": 269, + "max_size": 4096, + "deflate_size": 269, + "max_deflate_size": 4096 + } + } + ] + } + +inflatehd - header decompressor ++++++++++++++++++++++++++++++++ + +The ``inflatehd`` program reads JSON data from stdin and outputs decompressed +name/value pairs in JSON. + +The root JSON object must include the ``cases`` key. Its value has to +include the sequence of compressed header blocks. They share the same +compression context and are processed in the order they appear. Each +item in the sequence is a JSON object and it must have at least a +``wire`` key. Its value is a compressed header block as a hex string. + +Example: + +.. code-block:: json + + { + "cases": + [ + { "wire": "8285" }, + { "wire": "8583" } + ] + } + +The output is a JSON object. It should include a ``cases`` key and its +value is an array of JSON objects, which has at least following keys: + +seq + The index of the header set in the input. + +headers + A JSON array that includes decompressed name/value pairs. + +wire + The compressed header block as a hex string. + +header_table_size + The header table size adjusted before inflating compressed header + block. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + , + { + "seq": 1, + "wire": "88448504252dd5918485", + "headers": [ + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + "user-agent": "nghttp2" + }, + { + ":scheme": "https" + }, + { + ":authority": "example.org" + } + ], + "header_table_size": 4096 + } + ] + } + +The output can be used as the input for ``deflatehd`` and +``inflatehd``. + +With the ``-d`` option, the extra ``header_table`` key is added and its +associated value includes the state of the dynamic header table after the +corresponding header set was processed. The format is the same as +``deflatehd``. + +Contribution +------------ + +[This text was composed based on 1.2. License section of curl/libcurl +project.] + +When contributing with code, you agree to put your changes and new +code under the same license nghttp2 is already using unless stated and +agreed otherwise. + +When changing existing source code, do not alter the copyright of +the original file(s). The copyright will still be owned by the +original creator(s) or those who have been assigned copyright by the +original author(s). + +By submitting a patch to the nghttp2 project, you (or your employer, as +the case may be) agree to assign the copyright of your submission to us. +.. the above really needs to be reworded to pass legal muster. +We will credit you for your +changes as far as possible, to give credit but also to keep a trace +back to who made what changes. Please always provide us with your +full real name when contributing! + +See `Contribution Guidelines +`_ 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/android-config b/android-config new file mode 100755 index 0000000..4a137de --- /dev/null +++ b/android-config @@ -0,0 +1,37 @@ +#!/bin/sh +# +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2013 Tatsuhiro Tsujikawa +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +. ./android-env + +./configure \ + --disable-shared \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --without-libxml2 \ + --disable-examples \ + --disable-threads \ + CPPFLAGS="-fPIE -I$PREFIX/include" \ + PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \ + LDFLAGS="-fPIE -pie -L$PREFIX/lib" diff --git a/android-env b/android-env new file mode 100755 index 0000000..4412fcf --- /dev/null +++ b/android-env @@ -0,0 +1,40 @@ +#!/bin/sh +# +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2022 nghttp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if [ -z "$NDK" ]; then + echo 'No $NDK specified.' + exit 1 +fi + +export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 +export TARGET=aarch64-linux-android +export API=33 +export AR=$TOOLCHAIN/bin/llvm-ar +export CC=$TOOLCHAIN/bin/$TARGET$API-clang +export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++ +export LD=$TOOLCHAIN/bin/ld +export RANDLIB=$TOOLCHAIN/bin/llvm-ranlib +export STRIP=$TOOLCHAIN/bin/llvm-strip +export PREFIX=$NDK/usr/local diff --git a/author.py b/author.py new file mode 100755 index 0000000..4bf97c4 --- /dev/null +++ b/author.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# script to extract commit author's name from standard input. The +# input should be :, one per line. +# This script expects the input is created by git-log command: +# +# git log --format=%aN:%aE +# +# This script removes duplicates based on email address, breaking a +# tie with longer author name. Among the all author names extract the +# previous step, we remove duplicate by case-insensitive match. +# +# So we can do this in one line: +# +# git log --format=%aN:%aE | sort | uniq | ./author.py > authors + +import sys + +edict = {} + +for line in sys.stdin: + author, email = line.strip().split(':', 1) + if email in edict: + an = edict[email] + if len(an) < len(author) or an > author: + sys.stderr.write( + 'eliminated {} in favor of {}\n'.format(an, author)) + edict[email] = author + else: + sys.stderr.write( + 'eliminated {} in favor of {}\n'.format(author, an)) + else: + edict[email] = author + +names = list(sorted(edict.values())) + +ndict = {} + +for name in names: + lowname = name.lower() + if lowname in ndict: + an = ndict[lowname] + if an > name: + sys.stderr.write('eliminated {} in favor of {}\n'.format(an, name)) + ndict[lowname] = name + else: + sys.stderr.write('eliminated {} in favor of {}\n'.format(name, an)) + else: + ndict[lowname] = name + +for name in sorted(ndict.values()): + print(name) diff --git a/bpf/CMakeLists.txt b/bpf/CMakeLists.txt new file mode 100644 index 0000000..904b821 --- /dev/null +++ b/bpf/CMakeLists.txt @@ -0,0 +1,13 @@ +if(LIBBPF_FOUND) + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + COMMAND ${CMAKE_C_COMPILER} ${BPFCFLAGS} ${EXTRABPFCFLAGS} -I${LIBBPF_INCLUDE_DIRS} -target bpf -c reuseport_kern.c -o "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + + add_custom_target(bpf ALL + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + VERBATIM) + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CMAKE_PROJECT_NAME}") +endif() diff --git a/bpf/Makefile.am b/bpf/Makefile.am new file mode 100644 index 0000000..9017fd9 --- /dev/null +++ b/bpf/Makefile.am @@ -0,0 +1,40 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2021 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +EXTRA_DIST = CMakeLists.txt reuseport_kern.c + +if HAVE_LIBBPF + +bpf_pkglibdir = $(pkglibdir) +bpf_pkglib_DATA = reuseport_kern.o + +all: $(builddir)/reuseport_kern.o + +$(builddir)/reuseport_kern.o: reuseport_kern.c + $(CC) @LIBBPF_CFLAGS@ @BPFCFLAGS@ @EXTRABPFCFLAGS@ \ + -target bpf -c $< -o $@ + +clean-local: + -rm -f reuseport_kern.o + +endif # HAVE_LIBBPF diff --git a/bpf/reuseport_kern.c b/bpf/reuseport_kern.c new file mode 100644 index 0000000..74c08c5 --- /dev/null +++ b/bpf/reuseport_kern.c @@ -0,0 +1,663 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#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/cmake/ExtractValidFlags.cmake b/cmake/ExtractValidFlags.cmake new file mode 100644 index 0000000..ccd57dc --- /dev/null +++ b/cmake/ExtractValidFlags.cmake @@ -0,0 +1,31 @@ +# Convenience function that checks the availability of certain +# C or C++ compiler flags and returns valid ones as a string. + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) + +function(extract_valid_c_flags varname) + set(valid_flags) + foreach(flag IN LISTS ARGN) + string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag}) + set(flag_var "C_FLAG_${flag_var}") + check_c_compiler_flag("${flag}" "${flag_var}") + if(${flag_var}) + set(valid_flags "${valid_flags} ${flag}") + endif() + endforeach() + set(${varname} "${valid_flags}" PARENT_SCOPE) +endfunction() + +function(extract_valid_cxx_flags varname) + set(valid_flags) + foreach(flag IN LISTS ARGN) + string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag}) + set(flag_var "CXX_FLAG_${flag_var}") + check_cxx_compiler_flag("${flag}" "${flag_var}") + if(${flag_var}) + set(valid_flags "${valid_flags} ${flag}") + endif() + endforeach() + set(${varname} "${valid_flags}" PARENT_SCOPE) +endfunction() diff --git a/cmake/FindCUnit.cmake b/cmake/FindCUnit.cmake new file mode 100644 index 0000000..ada87c1 --- /dev/null +++ b/cmake/FindCUnit.cmake @@ -0,0 +1,40 @@ +# - Try to find cunit +# Once done this will define +# CUNIT_FOUND - System has cunit +# CUNIT_INCLUDE_DIRS - The cunit include directories +# CUNIT_LIBRARIES - The libraries needed to use cunit + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_CUNIT QUIET cunit) + +find_path(CUNIT_INCLUDE_DIR + NAMES CUnit/CUnit.h + HINTS ${PC_CUNIT_INCLUDE_DIRS} +) +find_library(CUNIT_LIBRARY + NAMES cunit + HINTS ${PC_CUNIT_LIBRARY_DIRS} +) + +if(CUNIT_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+CU_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${CUNIT_INCLUDE_DIR}/CUnit/CUnit.h" + CUNIT_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + CUNIT_VERSION "${CUNIT_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set CUNIT_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(CUnit REQUIRED_VARS + CUNIT_LIBRARY CUNIT_INCLUDE_DIR + VERSION_VAR CUNIT_VERSION) + +if(CUNIT_FOUND) + set(CUNIT_LIBRARIES ${CUNIT_LIBRARY}) + set(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR}) +endif() + +mark_as_advanced(CUNIT_INCLUDE_DIR CUNIT_LIBRARY) diff --git a/cmake/FindJansson.cmake b/cmake/FindJansson.cmake new file mode 100644 index 0000000..4c4bcb7 --- /dev/null +++ b/cmake/FindJansson.cmake @@ -0,0 +1,40 @@ +# - Try to find jansson +# Once done this will define +# JANSSON_FOUND - System has jansson +# JANSSON_INCLUDE_DIRS - The jansson include directories +# JANSSON_LIBRARIES - The libraries needed to use jansson + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_JANSSON QUIET jansson) + +find_path(JANSSON_INCLUDE_DIR + NAMES jansson.h + HINTS ${PC_JANSSON_INCLUDE_DIRS} +) +find_library(JANSSON_LIBRARY + NAMES jansson + HINTS ${PC_JANSSON_LIBRARY_DIRS} +) + +if(JANSSON_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+JANSSON_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${JANSSON_INCLUDE_DIR}/jansson.h" + JANSSON_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + JANSSON_VERSION "${JANSSON_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JANSSON_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Jansson REQUIRED_VARS + JANSSON_LIBRARY JANSSON_INCLUDE_DIR + VERSION_VAR JANSSON_VERSION) + +if(JANSSON_FOUND) + set(JANSSON_LIBRARIES ${JANSSON_LIBRARY}) + set(JANSSON_INCLUDE_DIRS ${JANSSON_INCLUDE_DIR}) +endif() + +mark_as_advanced(JANSSON_INCLUDE_DIR JANSSON_LIBRARY) diff --git a/cmake/FindJemalloc.cmake b/cmake/FindJemalloc.cmake new file mode 100644 index 0000000..b7815fa --- /dev/null +++ b/cmake/FindJemalloc.cmake @@ -0,0 +1,40 @@ +# - Try to find jemalloc +# Once done this will define +# JEMALLOC_FOUND - System has jemalloc +# JEMALLOC_INCLUDE_DIRS - The jemalloc include directories +# JEMALLOC_LIBRARIES - The libraries needed to use jemalloc + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_JEMALLOC QUIET jemalloc) + +find_path(JEMALLOC_INCLUDE_DIR + NAMES jemalloc/jemalloc.h + HINTS ${PC_JEMALLOC_INCLUDE_DIRS} +) +find_library(JEMALLOC_LIBRARY + NAMES jemalloc + HINTS ${PC_JEMALLOC_LIBRARY_DIRS} +) + +if(JEMALLOC_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+JEMALLOC_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${JEMALLOC_INCLUDE_DIR}/jemalloc/jemalloc.h" + JEMALLOC_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + JEMALLOC_VERSION "${JEMALLOC_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Jemalloc REQUIRED_VARS + JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR + VERSION_VAR JEMALLOC_VERSION) + +if(JEMALLOC_FOUND) + set(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY}) + set(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR}) +endif() + +mark_as_advanced(JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY) diff --git a/cmake/FindLibbpf.cmake b/cmake/FindLibbpf.cmake new file mode 100644 index 0000000..7f76255 --- /dev/null +++ b/cmake/FindLibbpf.cmake @@ -0,0 +1,32 @@ +# - Try to find libbpf +# Once done this will define +# LIBBPF_FOUND - System has libbpf +# LIBBPF_INCLUDE_DIRS - The libbpf include directories +# LIBBPF_LIBRARIES - The libraries needed to use libbpf + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBBPF QUIET libbpf) + +find_path(LIBBPF_INCLUDE_DIR + NAMES bpf/bpf.h + HINTS ${PC_LIBBPF_INCLUDE_DIRS} +) +find_library(LIBBPF_LIBRARY + NAMES bpf + HINTS ${PC_LIBBPF_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBBPF_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libbpf REQUIRED_VARS + LIBBPF_LIBRARY LIBBPF_INCLUDE_DIR + VERSION_VAR LIBBPF_VERSION) + +if(LIBBPF_FOUND) + set(LIBBPF_LIBRARIES ${LIBBPF_LIBRARY}) + set(LIBBPF_INCLUDE_DIRS ${LIBBPF_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBBPF_INCLUDE_DIR LIBBPF_LIBRARY) diff --git a/cmake/FindLibcares.cmake b/cmake/FindLibcares.cmake new file mode 100644 index 0000000..1fe56ce --- /dev/null +++ b/cmake/FindLibcares.cmake @@ -0,0 +1,40 @@ +# - Try to find libcares +# Once done this will define +# LIBCARES_FOUND - System has libcares +# LIBCARES_INCLUDE_DIRS - The libcares include directories +# LIBCARES_LIBRARIES - The libraries needed to use libcares + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBCARES QUIET libcares) + +find_path(LIBCARES_INCLUDE_DIR + NAMES ares.h + HINTS ${PC_LIBCARES_INCLUDE_DIRS} +) +find_library(LIBCARES_LIBRARY + NAMES cares + HINTS ${PC_LIBCARES_LIBRARY_DIRS} +) + +if(LIBCARES_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+ARES_VERSION_STR[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBCARES_INCLUDE_DIR}/ares_version.h" + LIBCARES_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBCARES_VERSION "${LIBCARES_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBCARES_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libcares REQUIRED_VARS + LIBCARES_LIBRARY LIBCARES_INCLUDE_DIR + VERSION_VAR LIBCARES_VERSION) + +if(LIBCARES_FOUND) + set(LIBCARES_LIBRARIES ${LIBCARES_LIBRARY}) + set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBCARES_INCLUDE_DIR LIBCARES_LIBRARY) diff --git a/cmake/FindLibev.cmake b/cmake/FindLibev.cmake new file mode 100644 index 0000000..71e4508 --- /dev/null +++ b/cmake/FindLibev.cmake @@ -0,0 +1,38 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIRS - The libev include directories +# LIBEV_LIBRARIES - The libraries needed to use libev + +find_path(LIBEV_INCLUDE_DIR + NAMES ev.h +) +find_library(LIBEV_LIBRARY + NAMES ev +) + +if(LIBEV_INCLUDE_DIR) + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libev REQUIRED_VARS + LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION) + +if(LIBEV_FOUND) + set(LIBEV_LIBRARIES ${LIBEV_LIBRARY}) + set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) diff --git a/cmake/FindLibevent.cmake b/cmake/FindLibevent.cmake new file mode 100644 index 0000000..e8a3cef --- /dev/null +++ b/cmake/FindLibevent.cmake @@ -0,0 +1,97 @@ +# - Try to find libevent +#.rst +# FindLibevent +# ------------ +# +# Find Libevent include directories and libraries. Invoke as:: +# +# find_package(Libevent +# [version] [EXACT] # Minimum or exact version +# [REQUIRED] # Fail if Libevent is not found +# [COMPONENT ...]) # 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/cmake/FindLibnghttp3.cmake b/cmake/FindLibnghttp3.cmake new file mode 100644 index 0000000..ecd01f6 --- /dev/null +++ b/cmake/FindLibnghttp3.cmake @@ -0,0 +1,41 @@ +# - Try to find libnghttp3 +# Once done this will define +# LIBNGHTTP3_FOUND - System has libnghttp3 +# LIBNGHTTP3_INCLUDE_DIRS - The libnghttp3 include directories +# LIBNGHTTP3_LIBRARIES - The libraries needed to use libnghttp3 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGHTTP3 QUIET libnghttp3) + +find_path(LIBNGHTTP3_INCLUDE_DIR + NAMES nghttp3/nghttp3.h + HINTS ${PC_LIBNGHTTP3_INCLUDE_DIRS} +) +find_library(LIBNGHTTP3_LIBRARY + NAMES nghttp3 + HINTS ${PC_LIBNGHTTP3_LIBRARY_DIRS} +) + +if(LIBNGHTTP3_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGHTTP3_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGHTTP3_INCLUDE_DIR}/nghttp3/version.h" + LIBNGHTTP3_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGHTTP3_VERSION "${LIBNGHTTP3_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGHTTP3_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libnghttp3 REQUIRED_VARS + LIBNGHTTP3_LIBRARY LIBNGHTTP3_INCLUDE_DIR + VERSION_VAR LIBNGHTTP3_VERSION) + +if(LIBNGHTTP3_FOUND) + set(LIBNGHTTP3_LIBRARIES ${LIBNGHTTP3_LIBRARY}) + set(LIBNGHTTP3_INCLUDE_DIRS ${LIBNGHTTP3_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGHTTP3_INCLUDE_DIR LIBNGHTTP3_LIBRARY) diff --git a/cmake/FindLibngtcp2.cmake b/cmake/FindLibngtcp2.cmake new file mode 100644 index 0000000..c670114 --- /dev/null +++ b/cmake/FindLibngtcp2.cmake @@ -0,0 +1,41 @@ +# - Try to find libngtcp2 +# Once done this will define +# LIBNGTCP2_FOUND - System has libngtcp2 +# LIBNGTCP2_INCLUDE_DIRS - The libngtcp2 include directories +# LIBNGTCP2_LIBRARIES - The libraries needed to use libngtcp2 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2 QUIET libngtcp2) + +find_path(LIBNGTCP2_INCLUDE_DIR + NAMES ngtcp2/ngtcp2.h + HINTS ${PC_LIBNGTCP2_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_LIBRARY + NAMES ngtcp2 + HINTS ${PC_LIBNGTCP2_LIBRARY_DIRS} +) + +if(LIBNGTCP2_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_VERSION "${LIBNGTCP2_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGTCP2_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libngtcp2 REQUIRED_VARS + LIBNGTCP2_LIBRARY LIBNGTCP2_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_VERSION) + +if(LIBNGTCP2_FOUND) + set(LIBNGTCP2_LIBRARIES ${LIBNGTCP2_LIBRARY}) + set(LIBNGTCP2_INCLUDE_DIRS ${LIBNGTCP2_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_INCLUDE_DIR LIBNGTCP2_LIBRARY) diff --git a/cmake/FindLibngtcp2_crypto_quictls.cmake b/cmake/FindLibngtcp2_crypto_quictls.cmake new file mode 100644 index 0000000..3d55b63 --- /dev/null +++ b/cmake/FindLibngtcp2_crypto_quictls.cmake @@ -0,0 +1,43 @@ +# - Try to find libngtcp2_crypto_quictls +# Once done this will define +# LIBNGTCP2_CRYPTO_QUICTLS_FOUND - System has libngtcp2_crypto_quictls +# LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS - The libngtcp2_crypto_quictls include directories +# LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES - The libraries needed to use libngtcp2_crypto_quictls + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2_CRYPTO_QUICTLS QUIET libngtcp2_crypto_quictls) + +find_path(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + NAMES ngtcp2/ngtcp2_crypto_quictls.h + HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY + NAMES ngtcp2_crypto_quictls + HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY_DIRS} +) + +if(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_CRYPTO_QUICTLS_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_CRYPTO_QUICTLS_VERSION "${LIBNGTCP2_CRYPTO_QUICTLS_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set +# LIBNGTCP2_CRYPTO_QUICTLS_FOUND to TRUE if all listed variables are +# TRUE and the requested version matches. +find_package_handle_standard_args(Libngtcp2_crypto_quictls REQUIRED_VARS + LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY + LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_CRYPTO_QUICTLS_VERSION) + +if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND) + set(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY}) + set(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY) diff --git a/cmake/FindSystemd.cmake b/cmake/FindSystemd.cmake new file mode 100644 index 0000000..e7534e5 --- /dev/null +++ b/cmake/FindSystemd.cmake @@ -0,0 +1,19 @@ +# - Try to find systemd +# Once done this will define +# SYSTEMD_FOUND - System has systemd +# SYSTEMD_INCLUDE_DIRS - The systemd include directories +# SYSTEMD_LIBRARIES - The libraries needed to use systemd + +include(FeatureSummary) +set_package_properties(Systemd PROPERTIES + URL "http://freedesktop.org/wiki/Software/systemd/" + DESCRIPTION "System and Service Manager") + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_SYSTEMD QUIET libsystemd) +find_library(SYSTEMD_LIBRARIES NAMES systemd ${PC_SYSTEMD_LIBRARY_DIRS}) +find_path(SYSTEMD_INCLUDE_DIRS systemd/sd-login.h HINTS ${PC_SYSTEMD_INCLUDE_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Systemd DEFAULT_MSG SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) +mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) diff --git a/cmake/PickyWarningsC.cmake b/cmake/PickyWarningsC.cmake new file mode 100644 index 0000000..50eb789 --- /dev/null +++ b/cmake/PickyWarningsC.cmake @@ -0,0 +1,163 @@ +# nghttp2 +# +# Copyright (c) 2023 nghttp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# C + +include(CheckCCompilerFlag) + +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER_ID MATCHES "Clang") + + # https://clang.llvm.org/docs/DiagnosticsReference.html + # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html + + # WPICKY_ENABLE = Options we want to enable as-is. + # WPICKY_DETECT = Options we want to test first and enable if available. + + # Prefer the -Wextra alias with clang. + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + set(WPICKY_ENABLE "-Wextra") + else() + set(WPICKY_ENABLE "-W") + endif() + + list(APPEND WPICKY_ENABLE + -Wall + ) + + # ---------------------------------- + # Add new options here, if in doubt: + # ---------------------------------- + set(WPICKY_DETECT + ) + + # Assume these options always exist with both clang and gcc. + # Require clang 3.0 / gcc 2.95 or later. + list(APPEND WPICKY_ENABLE + -Wconversion # clang 3.0 gcc 2.95 + -Winline # clang 1.0 gcc 1.0 + -Wmissing-declarations # clang 1.0 gcc 2.7 + -Wmissing-prototypes # clang 1.0 gcc 1.0 + -Wnested-externs # clang 1.0 gcc 2.7 + -Wpointer-arith # clang 1.0 gcc 1.4 + -Wshadow # clang 1.0 gcc 2.95 + -Wundef # clang 1.0 gcc 2.95 + -Wwrite-strings # clang 1.0 gcc 1.4 + ) + + # Always enable with clang, version dependent with gcc + set(WPICKY_COMMON_OLD + -Waddress # clang 3.0 gcc 4.3 + -Wattributes # clang 3.0 gcc 4.1 + -Wcast-align # clang 1.0 gcc 4.2 + -Wdeclaration-after-statement # clang 1.0 gcc 3.4 + -Wdiv-by-zero # clang 3.0 gcc 4.1 + -Wempty-body # clang 3.0 gcc 4.3 + -Wendif-labels # clang 1.0 gcc 3.3 + -Wfloat-equal # clang 1.0 gcc 2.96 (3.0) + -Wformat-nonliteral # clang 3.0 gcc 4.1 + -Wformat-security # clang 3.0 gcc 4.1 + -Wmissing-field-initializers # clang 3.0 gcc 4.1 + -Wmissing-noreturn # clang 3.0 gcc 4.1 + -Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0) # This is required because we pass format string as "const char*" + # -Wpadded # clang 3.0 gcc 4.1 # Not used because we cannot change public structs + -Wredundant-decls # clang 3.0 gcc 4.1 + -Wsign-conversion # clang 3.0 gcc 4.3 + -Wstrict-prototypes # clang 1.0 gcc 3.3 + # -Wswitch-enum # clang 3.0 gcc 4.1 # Not used because this basically disallows default case + -Wunreachable-code # clang 3.0 gcc 4.1 + -Wunused-macros # clang 3.0 gcc 4.1 + -Wunused-parameter # clang 3.0 gcc 4.1 + -Wvla # clang 2.8 gcc 4.3 + ) + + set(WPICKY_COMMON + -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0 + ) + + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + -Wshorten-64-to-32 # clang 1.0 + -Wlanguage-extension-token # clang 3.0 + ) + # Enable based on compiler version + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3)) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON} + -Wunreachable-code-break # clang 3.5 appleclang 6.0 + -Wheader-guard # clang 3.4 appleclang 5.1 + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3)) + list(APPEND WPICKY_ENABLE + -Wmissing-variable-declarations # clang 3.2 appleclang 4.6 + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 9.4)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + else() # gcc + list(APPEND WPICKY_DETECT + ${WPICKY_COMMON} + ) + # Enable based on compiler version + if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + -Wclobbered # gcc 4.3 + ) + endif() + endif() + + # + + unset(_wpicky) + + foreach(_CCOPT IN LISTS WPICKY_ENABLE) + set(_wpicky "${_wpicky} ${_CCOPT}") + endforeach() + + foreach(_CCOPT IN LISTS WPICKY_DETECT) + # surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new + # test result in. + string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname) + # GCC only warns about unknown -Wno- options if there are also other diagnostic messages, + # so test for the positive form instead + string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}") + check_c_compiler_flag(${_CCOPT_ON} ${_optvarname}) + if(${_optvarname}) + set(_wpicky "${_wpicky} ${_CCOPT}") + endif() + endforeach() + + set(WARNCFLAGS "${WARNCFLAGS} ${_wpicky}") +endif() diff --git a/cmake/PickyWarningsCXX.cmake b/cmake/PickyWarningsCXX.cmake new file mode 100644 index 0000000..4699733 --- /dev/null +++ b/cmake/PickyWarningsCXX.cmake @@ -0,0 +1,117 @@ +# nghttp2 +# +# Copyright (c) 2023 nghttp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# C++ + +include(CheckCXXCompilerFlag) + +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + + # https://clang.llvm.org/docs/DiagnosticsReference.html + # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html + + # WPICKY_ENABLE = Options we want to enable as-is. + # WPICKY_DETECT = Options we want to test first and enable if available. + + set(WPICKY_ENABLE "-Wall") + + # ---------------------------------- + # Add new options here, if in doubt: + # ---------------------------------- + set(WPICKY_DETECT + ) + + # Assume these options always exist with both clang and gcc. + # Require clang 3.0 / gcc 2.95 or later. + list(APPEND WPICKY_ENABLE + ) + + # Always enable with clang, version dependent with gcc + set(WPICKY_COMMON_OLD + -Wformat-security # clang 3.0 gcc 4.1 + ) + + set(WPICKY_COMMON + ) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + ) + # Enable based on compiler version + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.3)) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON} + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.9) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.4)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + else() # gcc + list(APPEND WPICKY_DETECT + ${WPICKY_COMMON} + ) + # Enable based on compiler version + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.3) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + ) + endif() + endif() + + # + + unset(_wpicky) + + foreach(_CCOPT IN LISTS WPICKY_ENABLE) + set(_wpicky "${_wpicky} ${_CCOPT}") + endforeach() + + foreach(_CCOPT IN LISTS WPICKY_DETECT) + # surprisingly, CHECK_CXX_COMPILER_FLAG needs a new variable to store each new + # test result in. + string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname) + # GCC only warns about unknown -Wno- options if there are also other diagnostic messages, + # so test for the positive form instead + string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}") + check_cxx_compiler_flag(${_CCOPT_ON} ${_optvarname}) + if(${_optvarname}) + set(_wpicky "${_wpicky} ${_CCOPT}") + endif() + endforeach() + + set(WARNCXXFLAGS "${WARNCXXFLAGS} ${_wpicky}") +endif() diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..8ac4849 --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,11 @@ +# Converts a version such as 1.2.255 to 0x0102ff +function(HexVersion version_hex_var major minor patch) + math(EXPR version_dec "${major} * 256 * 256 + ${minor} * 256 + ${patch}") + set(version_hex "0x") + foreach(i RANGE 5 0 -1) + math(EXPR num "(${version_dec} >> (4 * ${i})) & 15") + string(SUBSTRING "0123456789abcdef" ${num} 1 num_hex) + set(version_hex "${version_hex}${num_hex}") + endforeach() + set(${version_hex_var} "${version_hex}" PARENT_SCOPE) +endfunction() diff --git a/cmakeconfig.h.in b/cmakeconfig.h.in new file mode 100644 index 0000000..a011d00 --- /dev/null +++ b/cmakeconfig.h.in @@ -0,0 +1,101 @@ +/* Hint to the compiler that a function never returns */ +#define NGHTTP2_NORETURN @HINT_NORETURN@ + +/* Define to `int' if does not define. */ +#cmakedefine ssize_t @ssize_t@ + +/* Define to 1 if you have the `std::map::emplace`. */ +#cmakedefine HAVE_STD_MAP_EMPLACE 1 + +/* Define to 1 if you have `libjansson` library. */ +#cmakedefine HAVE_JANSSON 1 + +/* Define to 1 if you have `libxml2` library. */ +#cmakedefine HAVE_LIBXML2 1 + +/* Define to 1 if you have `mruby` library. */ +#cmakedefine HAVE_MRUBY 1 + +/* Define to 1 if you have `neverbleed` library. */ +#cmakedefine HAVE_NEVERBLEED 1 + +/* Define to 1 if you have the `_Exit` function. */ +#cmakedefine HAVE__EXIT 1 + +/* Define to 1 if you have the `accept4` function. */ +#cmakedefine HAVE_ACCEPT4 1 + +/* Define to 1 if you have the `clock_gettime` function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `mkostemp` function. */ +#cmakedefine HAVE_MKOSTEMP 1 + +/* Define to 1 if you have the `GetTickCount64` function. */ +#cmakedefine HAVE_GETTICKCOUNT64 1 + +/* Define to 1 if you have the `initgroups` function. */ +#cmakedefine01 HAVE_DECL_INITGROUPS + +/* Define to 1 if you have the `CLOCK_MONOTONIC` defined. */ +#cmakedefine01 HAVE_DECL_CLOCK_MONOTONIC + +/* Define to 1 to enable debug output. */ +#cmakedefine DEBUGBUILD 1 + +/* Define to 1 if you want to disable threads. */ +#cmakedefine NOTHREADS 1 + +/* Define to 1 if you have the 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_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/configure.ac b/configure.ac new file mode 100644 index 0000000..defc113 --- /dev/null +++ b/configure.ac @@ -0,0 +1,1185 @@ +dnl nghttp2 - HTTP/2 C Library + +dnl Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa + +dnl Permission is hereby granted, free of charge, to any person obtaining +dnl a copy of this software and associated documentation files (the +dnl "Software"), to deal in the Software without restriction, including +dnl without limitation the rights to use, copy, modify, merge, publish, +dnl distribute, sublicense, and/or sell copies of the Software, and to +dnl permit persons to whom the Software is furnished to do so, subject to +dnl the following conditions: + +dnl The above copyright notice and this permission notice shall be +dnl included in all copies or substantial portions of the Software. + +dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +dnl EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +dnl MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +dnl NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +dnl LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +dnl Do not change user variables! +dnl https://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html + +AC_PREREQ(2.61) +AC_INIT([nghttp2], [1.59.0], [t-tujikawa@users.sourceforge.net]) +AC_CONFIG_AUX_DIR([.]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +AC_USE_SYSTEM_EXTENSIONS + +LT_PREREQ([2.2.6]) +LT_INIT() + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects]) + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl See versioning rule: +dnl https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +AC_SUBST(LT_CURRENT, 40) +AC_SUBST(LT_REVISION, 0) +AC_SUBST(LT_AGE, 26) + +major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` +minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"` +patch=`echo $PACKAGE_VERSION |cut -d. -f3 | cut -d- -f1 | sed -e "s/[^0-9]//g"` + +PACKAGE_VERSION_NUM=`printf "0x%02x%02x%02x" "$major" "$minor" "$patch"` + +AC_SUBST(PACKAGE_VERSION_NUM) + +dnl Checks for command-line options +AC_ARG_ENABLE([werror], + [AS_HELP_STRING([--enable-werror], + [Turn on compile time warnings])], + [werror=$enableval], [werror=no]) + +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug], + [Turn on debug output])], + [debug=$enableval], [debug=no]) + +AC_ARG_ENABLE([threads], + [AS_HELP_STRING([--disable-threads], + [Turn off threading in apps])], + [threads=$enableval], [threads=yes]) + +AC_ARG_ENABLE([app], + [AS_HELP_STRING([--enable-app], + [Build applications (nghttp, nghttpd, nghttpx and h2load) [default=check]])], + [request_app=$enableval], [request_app=check]) + +AC_ARG_ENABLE([hpack-tools], + [AS_HELP_STRING([--enable-hpack-tools], + [Build HPACK tools [default=check]])], + [request_hpack_tools=$enableval], [request_hpack_tools=check]) + +AC_ARG_ENABLE([examples], + [AS_HELP_STRING([--enable-examples], + [Build examples [default=check]])], + [request_examples=$enableval], [request_examples=check]) + +AC_ARG_ENABLE([failmalloc], + [AS_HELP_STRING([--disable-failmalloc], + [Do not build failmalloc test program])], + [request_failmalloc=$enableval], [request_failmalloc=yes]) + +AC_ARG_ENABLE([lib-only], + [AS_HELP_STRING([--enable-lib-only], + [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools])], + [request_lib_only=$enableval], [request_lib_only=no]) + +AC_ARG_ENABLE([http3], + [AS_HELP_STRING([--enable-http3], + [(EXPERIMENTAL) Enable HTTP/3. This requires ngtcp2, nghttp3, and a custom OpenSSL.])], + [request_http3=$enableval], [request_http3=no]) + +AC_ARG_WITH([libxml2], + [AS_HELP_STRING([--with-libxml2], + [Use libxml2 [default=check]])], + [request_libxml2=$withval], [request_libxml2=check]) + +AC_ARG_WITH([jansson], + [AS_HELP_STRING([--with-jansson], + [Use jansson [default=check]])], + [request_jansson=$withval], [request_jansson=check]) + +AC_ARG_WITH([zlib], + [AS_HELP_STRING([--with-zlib], + [Use zlib [default=check]])], + [request_zlib=$withval], [request_zlib=check]) + +AC_ARG_WITH([libevent-openssl], + [AS_HELP_STRING([--with-libevent-openssl], + [Use libevent_openssl [default=check]])], + [request_libevent_openssl=$withval], [request_libevent_openssl=check]) + +AC_ARG_WITH([libcares], + [AS_HELP_STRING([--with-libcares], + [Use libc-ares [default=check]])], + [request_libcares=$withval], [request_libcares=check]) + +AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl], + [Use openssl [default=check]])], + [request_openssl=$withval], [request_openssl=check]) + +AC_ARG_WITH([libev], + [AS_HELP_STRING([--with-libev], + [Use libev [default=check]])], + [request_libev=$withval], [request_libev=check]) + +AC_ARG_WITH([cunit], + [AS_HELP_STRING([--with-cunit], + [Use cunit [default=check]])], + [request_cunit=$withval], [request_cunit=check]) + +AC_ARG_WITH([jemalloc], + [AS_HELP_STRING([--with-jemalloc], + [Use jemalloc [default=check]])], + [request_jemalloc=$withval], [request_jemalloc=check]) + +AC_ARG_WITH([systemd], + [AS_HELP_STRING([--with-systemd], + [Enable systemd support in nghttpx [default=check]])], + [request_systemd=$withval], [request_systemd=check]) + +AC_ARG_WITH([mruby], + [AS_HELP_STRING([--with-mruby], + [Use mruby [default=no]])], + [request_mruby=$withval], [request_mruby=no]) + +AC_ARG_WITH([neverbleed], + [AS_HELP_STRING([--with-neverbleed], + [Use neverbleed [default=no]])], + [request_neverbleed=$withval], [request_neverbleed=no]) + +AC_ARG_WITH([libngtcp2], + [AS_HELP_STRING([--with-libngtcp2], + [Use libngtcp2 [default=check]])], + [request_libngtcp2=$withval], [request_libngtcp2=check]) + +AC_ARG_WITH([libnghttp3], + [AS_HELP_STRING([--with-libnghttp3], + [Use libnghttp3 [default=check]])], + [request_libnghttp3=$withval], [request_libnghttp3=check]) + +AC_ARG_WITH([libbpf], + [AS_HELP_STRING([--with-libbpf], + [Use libbpf [default=no]])], + [request_libbpf=$withval], [request_libbpf=no]) + +dnl Define variables +AC_ARG_VAR([LIBEV_CFLAGS], [C compiler flags for libev, skipping any checks]) +AC_ARG_VAR([LIBEV_LIBS], [linker flags for libev, skipping any checks]) + +AC_ARG_VAR([JEMALLOC_CFLAGS], + [C compiler flags for jemalloc, skipping any checks]) +AC_ARG_VAR([JEMALLOC_LIBS], [linker flags for jemalloc, skipping any checks]) + +AC_ARG_VAR([LIBTOOL_LDFLAGS], + [libtool specific flags (e.g., -static-libtool-libs)]) + +AC_ARG_VAR([BPFCFLAGS], [C compiler flags for bpf program]) + +dnl Checks for programs +AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_MKDIR_P + +PKG_PROG_PKG_CONFIG([0.20]) + +AM_PATH_PYTHON([3.8],, [:]) + +if [test "x$request_lib_only" = "xyes"]; then + request_app=no + request_hpack_tools=no + request_examples=no + request_http3=no + request_libxml2=no + request_jansson=no + request_zlib=no + request_libevent_openssl=no + request_libcares=no + request_openssl=no + request_libev=no + request_jemalloc=no + request_systemd=no + request_mruby=no + request_neverbleed=no + request_libngtcp2=no + request_libnghttp3=no + request_libbpf=no +fi + +if test "x$GCC" = "xyes" -o "x$CC" = "xclang" ; then + AC_DEFINE([NGHTTP2_NORETURN], [__attribute__((noreturn))], [Hint to the compiler that a function never return]) +else + AC_DEFINE([NGHTTP2_NORETURN], , [Hint to the compiler that a function never return]) +fi + +save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS= + +AX_CXX_COMPILE_STDCXX([14], [], [optional]) + +CXX1XCXXFLAGS="$CXXFLAGS" +CXXFLAGS="$save_CXXFLAGS" +AC_SUBST([CXX1XCXXFLAGS]) + +AC_LANG_PUSH(C++) + +save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $CXX1XCXXFLAGS" + +# Check that std::future is available. +AC_MSG_CHECKING([whether std::future is available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[[ +#include +#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.1.1], + [have_openssl=yes], [have_openssl=no]) + if test "x${have_openssl}" = "xno"; then + AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) + else + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$OPENSSL_CFLAGS $CFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + + # quictls/openssl has SSL_provide_quic_data. boringssl also has + # it. We will deal with it later. + have_ssl_provide_quic_data=no + AC_MSG_CHECKING([for SSL_provide_quic_data]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + ]], [[ + 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 \ + 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 + +# 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/contrib/.gitignore b/contrib/.gitignore new file mode 100644 index 0000000..85acde3 --- /dev/null +++ b/contrib/.gitignore @@ -0,0 +1,3 @@ +nghttpx-init +nghttpx.service +nghttpx-upstart.conf diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt new file mode 100644 index 0000000..f598f7b --- /dev/null +++ b/contrib/CMakeLists.txt @@ -0,0 +1,12 @@ +set(CONFIGFILES + nghttpx-init + nghttpx.service + nghttpx-upstart.conf +) + +# Note that the execute permissions of nghttpx-init is preserved +foreach(name IN LISTS CONFIGFILES) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +# set(EXTRA_DIST ${CONFIGFILES} nghttpx-logrotate tlsticketupdate.go) diff --git a/contrib/Makefile.am b/contrib/Makefile.am new file mode 100644 index 0000000..5a02e2f --- /dev/null +++ b/contrib/Makefile.am @@ -0,0 +1,51 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2014 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +configfiles = nghttpx-init nghttpx.service nghttpx-upstart.conf + +EXTRA_DIST = \ + CMakeLists.txt \ + $(configfiles:%=%.in) \ + nghttpx-logrotate \ + tlsticketupdate.go + +edit = sed -e 's|@bindir[@]|$(bindir)|g' + +nghttpx-init: $(srcdir)/nghttpx-init.in + rm -f $@ $@.tmp + $(edit) $< > $@.tmp + chmod +x $@.tmp + mv $@.tmp $@ + +nghttpx.service: $(srcdir)/nghttpx.service.in + $(edit) $< > $@ + +nghttpx-upstart.conf: $(srcdir)/nghttpx-upstart.conf.in + $(edit) $< > $@ + +$(configfiles): Makefile + +all-local: $(configfiles) + +clean-local: + -rm -f nghttpx-init.tmp $(configfiles) diff --git a/contrib/nghttpx-init.in b/contrib/nghttpx-init.in new file mode 100755 index 0000000..2b3c76e --- /dev/null +++ b/contrib/nghttpx-init.in @@ -0,0 +1,164 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: nghttpx +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: nghttpx initscript +# Description: nghttpx initscript +### END INIT INFO + +# Author: Tatsuhiro Tsujikawa +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin +DESC="HTTP/2 reverse proxy" +NAME=nghttpx +# Depending on the configuration, binary may be located under @sbindir@ +DAEMON=@bindir@/$NAME +PIDFILE=/var/run/$NAME.pid +DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE --daemon" +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + #start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + #[ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + upgrade) + log_daemon_msg "Upgrading $DESC" "$NAME" + oldpid=`pidofproc -p $PIDFILE $NAME` + case "$?" in + 0) + log_progress_msg "Sending SIGUSR2 to $oldpid..." + kill -USR2 $oldpid + log_progress_msg "Waiting for new binary..." + for i in 1 2 3 4 5 ; do + sleep 1 + newpid=`pidofproc -p $PIDFILE $NAME` + if [ "$newpid" != "$oldpid" ] ; then + break + fi + done + if [ "$newpid" != "$oldpid" ] ; then + log_progress_msg "Sending SIGQUIT to $oldpid..." + kill -QUIT $oldpid + log_end_msg 0 + else + log_progress_msg "New binary failed to start" + log_end_msg 1 + fi + ;; + *) + log_progress_msg "pidofproc() failed" + log_end_msg 1 + ;; + esac + ;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload|upgrade}" >&2 + exit 3 + ;; +esac + +: diff --git a/contrib/nghttpx-logrotate b/contrib/nghttpx-logrotate new file mode 100644 index 0000000..c715ced --- /dev/null +++ b/contrib/nghttpx-logrotate @@ -0,0 +1,11 @@ +/var/log/nghttpx/*.log { + weekly + rotate 52 + missingok + compress + delaycompress + notifempty + postrotate + [ -s /var/run/nghttpx.pid ] && kill -USR1 `cat /var/run/nghttpx.pid` 2> /dev/null || true + endscript +} diff --git a/contrib/nghttpx-upstart.conf.in b/contrib/nghttpx-upstart.conf.in new file mode 100644 index 0000000..0b79916 --- /dev/null +++ b/contrib/nghttpx-upstart.conf.in @@ -0,0 +1,8 @@ +# vim: ft=upstart: + +description "HTTP/2 reverse proxy" + +start on runlevel [2] +stop on runlevel [016] + +exec @bindir@/nghttpx diff --git a/contrib/nghttpx.service.in b/contrib/nghttpx.service.in new file mode 100644 index 0000000..06fb736 --- /dev/null +++ b/contrib/nghttpx.service.in @@ -0,0 +1,17 @@ +[Unit] +Description=HTTP/2 proxy +Documentation=man:nghttpx +After=network.target + +[Service] +Type=notify +ExecStart=@bindir@/nghttpx --conf=/etc/nghttpx/nghttpx.conf +ExecReload=/bin/kill --signal HUP $MAINPID +KillSignal=SIGQUIT +PrivateTmp=yes +ProtectHome=yes +ProtectSystem=full +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/contrib/tlsticketupdate.go b/contrib/tlsticketupdate.go new file mode 100644 index 0000000..ff3d759 --- /dev/null +++ b/contrib/tlsticketupdate.go @@ -0,0 +1,112 @@ +// +// nghttp2 - HTTP/2 C Library +// +// Copyright (c) 2015 Tatsuhiro Tsujikawa +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +package main + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "flag" + "fmt" + "log" + "time" + + "github.com/bradfitz/gomemcache/memcache" +) + +func makeKey(len int) []byte { + b := make([]byte, len) + if _, err := rand.Read(b); err != nil { + log.Fatalf("rand.Read: %v", err) + } + return b +} + +func main() { + var host = flag.String("host", "127.0.0.1", "memcached host") + var port = flag.Int("port", 11211, "memcached port") + var cipher = flag.String("cipher", "aes-128-cbc", "cipher for TLS ticket encryption") + var interval = flag.Int("interval", 3600, "interval to update TLS ticket keys") + + flag.Parse() + + var keylen int + switch *cipher { + case "aes-128-cbc": + keylen = 48 + case "aes-256-cbc": + keylen = 80 + default: + log.Fatalf("cipher: unknown cipher %v", cipher) + } + + mc := memcache.New(fmt.Sprintf("%v:%v", *host, *port)) + + keys := [][]byte{ + makeKey(keylen), // current encryption key + makeKey(keylen), // next encryption key; now decryption only + } + + for { + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.BigEndian, uint32(1)); err != nil { + log.Fatalf("failed to write version: %v", err) + } + + for _, key := range keys { + if err := binary.Write(buf, binary.BigEndian, uint16(keylen)); err != nil { + log.Fatalf("failed to write length: %v", err) + } + if _, err := buf.Write(key); err != nil { + log.Fatalf("buf.Write: %v", err) + } + } + + mc.Set(&memcache.Item{ + Key: "nghttpx:tls-ticket-key", + Value: buf.Bytes(), + Expiration: int32((*interval) + 300), + }) + + <-time.After(time.Duration(*interval) * time.Second) + + // rotate keys. the last key is now encryption key. + // generate new key and append it to the last, so that + // we can at least decrypt TLS ticket encrypted by new + // key on the host which does not get new key yet. + // keep at most past 11 keys as decryption only key + n := len(keys) + 1 + if n > 13 { + n = 13 + } + newKeys := make([][]byte, n) + newKeys[0] = keys[len(keys)-1] + copy(newKeys[1:], keys[0:n-2]) + newKeys[n-1] = makeKey(keylen) + + keys = newKeys + } + +} diff --git a/contrib/usr.sbin.nghttpx b/contrib/usr.sbin.nghttpx new file mode 100644 index 0000000..891ff52 --- /dev/null +++ b/contrib/usr.sbin.nghttpx @@ -0,0 +1,16 @@ +#include + +/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/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..ed9e634 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,19 @@ +# generated files +apiref.rst +building-android-binary.rst +conf.py +contribute.rst +enums.rst +h2load-howto.rst +index.rst +macros.rst +manual/ +nghttp2.h.rst +nghttp2_*.rst +nghttp2ver.h.rst +nghttpx-howto.rst +package_README.rst +tutorial-client.rst +tutorial-hpack.rst +tutorial-server.rst +types.rst diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..531791f --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,352 @@ +# Generated documents +set(APIDOCS + macros.rst + enums.rst + types.rst + nghttp2_check_header_name.rst + nghttp2_check_header_value.rst + nghttp2_hd_deflate_bound.rst + nghttp2_hd_deflate_change_table_size.rst + nghttp2_hd_deflate_del.rst + nghttp2_hd_deflate_get_dynamic_table_size.rst + nghttp2_hd_deflate_get_max_dynamic_table_size.rst + nghttp2_hd_deflate_get_num_table_entries.rst + nghttp2_hd_deflate_get_table_entry.rst + nghttp2_hd_deflate_hd.rst + nghttp2_hd_deflate_hd_vec.rst + nghttp2_hd_deflate_new.rst + nghttp2_hd_deflate_new2.rst + nghttp2_hd_inflate_change_table_size.rst + nghttp2_hd_inflate_del.rst + nghttp2_hd_inflate_end_headers.rst + nghttp2_hd_inflate_get_dynamic_table_size.rst + nghttp2_hd_inflate_get_max_dynamic_table_size.rst + nghttp2_hd_inflate_get_num_table_entries.rst + nghttp2_hd_inflate_get_table_entry.rst + nghttp2_hd_inflate_hd.rst + nghttp2_hd_inflate_hd2.rst + nghttp2_hd_inflate_new.rst + nghttp2_hd_inflate_new2.rst + nghttp2_http2_strerror.rst + nghttp2_is_fatal.rst + nghttp2_nv_compare_name.rst + nghttp2_option_del.rst + nghttp2_option_new.rst + nghttp2_option_set_builtin_recv_extension_type.rst + nghttp2_option_set_max_deflate_dynamic_table_size.rst + nghttp2_option_set_max_reserved_remote_streams.rst + nghttp2_option_set_max_send_header_block_length.rst + nghttp2_option_set_no_auto_ping_ack.rst + nghttp2_option_set_no_auto_window_update.rst + nghttp2_option_set_no_http_messaging.rst + nghttp2_option_set_no_recv_client_magic.rst + nghttp2_option_set_peer_max_concurrent_streams.rst + nghttp2_option_set_user_recv_extension_type.rst + nghttp2_option_set_max_settings.rst + nghttp2_pack_settings_payload.rst + nghttp2_priority_spec_check_default.rst + nghttp2_priority_spec_default_init.rst + nghttp2_priority_spec_init.rst + nghttp2_rcbuf_decref.rst + nghttp2_rcbuf_get_buf.rst + nghttp2_rcbuf_incref.rst + nghttp2_rcbuf_is_static.rst + nghttp2_select_next_protocol.rst + nghttp2_session_callbacks_del.rst + nghttp2_session_callbacks_new.rst + nghttp2_session_callbacks_set_before_frame_send_callback.rst + nghttp2_session_callbacks_set_data_source_read_length_callback.rst + nghttp2_session_callbacks_set_error_callback.rst + nghttp2_session_callbacks_set_on_begin_frame_callback.rst + nghttp2_session_callbacks_set_on_begin_headers_callback.rst + nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst + nghttp2_session_callbacks_set_on_frame_not_send_callback.rst + nghttp2_session_callbacks_set_on_frame_recv_callback.rst + nghttp2_session_callbacks_set_on_frame_send_callback.rst + nghttp2_session_callbacks_set_on_header_callback.rst + nghttp2_session_callbacks_set_on_header_callback2.rst + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst + nghttp2_session_callbacks_set_on_invalid_header_callback.rst + nghttp2_session_callbacks_set_on_invalid_header_callback2.rst + nghttp2_session_callbacks_set_on_stream_close_callback.rst + nghttp2_session_callbacks_set_pack_extension_callback.rst + nghttp2_session_callbacks_set_recv_callback.rst + nghttp2_session_callbacks_set_select_padding_callback.rst + nghttp2_session_callbacks_set_send_callback.rst + nghttp2_session_callbacks_set_send_data_callback.rst + nghttp2_session_callbacks_set_unpack_extension_callback.rst + nghttp2_session_change_stream_priority.rst + nghttp2_session_check_request_allowed.rst + nghttp2_session_check_server_session.rst + nghttp2_session_client_new.rst + nghttp2_session_client_new2.rst + nghttp2_session_client_new3.rst + nghttp2_session_consume.rst + nghttp2_session_consume_connection.rst + nghttp2_session_consume_stream.rst + nghttp2_session_create_idle_stream.rst + nghttp2_session_del.rst + nghttp2_session_find_stream.rst + nghttp2_session_get_effective_local_window_size.rst + nghttp2_session_get_effective_recv_data_length.rst + nghttp2_session_get_hd_deflate_dynamic_table_size.rst + nghttp2_session_get_hd_inflate_dynamic_table_size.rst + nghttp2_session_get_last_proc_stream_id.rst + nghttp2_session_get_local_settings.rst + nghttp2_session_get_local_window_size.rst + nghttp2_session_get_next_stream_id.rst + nghttp2_session_get_outbound_queue_size.rst + nghttp2_session_get_remote_settings.rst + nghttp2_session_get_remote_window_size.rst + nghttp2_session_get_root_stream.rst + nghttp2_session_get_stream_effective_local_window_size.rst + nghttp2_session_get_stream_effective_recv_data_length.rst + nghttp2_session_get_stream_local_close.rst + nghttp2_session_get_stream_local_window_size.rst + nghttp2_session_get_stream_remote_close.rst + nghttp2_session_get_stream_remote_window_size.rst + nghttp2_session_get_stream_user_data.rst + nghttp2_session_mem_recv.rst + nghttp2_session_mem_send.rst + nghttp2_session_recv.rst + nghttp2_session_resume_data.rst + nghttp2_session_send.rst + nghttp2_session_server_new.rst + nghttp2_session_server_new2.rst + nghttp2_session_server_new3.rst + nghttp2_session_set_local_window_size.rst + nghttp2_session_set_next_stream_id.rst + nghttp2_session_set_stream_user_data.rst + nghttp2_session_terminate_session.rst + nghttp2_session_terminate_session2.rst + nghttp2_session_upgrade.rst + nghttp2_session_upgrade2.rst + nghttp2_session_want_read.rst + nghttp2_session_want_write.rst + nghttp2_set_debug_vprintf_callback.rst + nghttp2_stream_get_first_child.rst + nghttp2_stream_get_next_sibling.rst + nghttp2_stream_get_parent.rst + nghttp2_stream_get_previous_sibling.rst + nghttp2_stream_get_state.rst + nghttp2_stream_get_sum_dependency_weight.rst + nghttp2_stream_get_weight.rst + nghttp2_strerror.rst + nghttp2_submit_altsvc.rst + nghttp2_submit_data.rst + nghttp2_submit_extension.rst + nghttp2_submit_goaway.rst + nghttp2_submit_headers.rst + nghttp2_submit_ping.rst + nghttp2_submit_priority.rst + nghttp2_submit_push_promise.rst + nghttp2_submit_request.rst + nghttp2_submit_response.rst + nghttp2_submit_rst_stream.rst + nghttp2_submit_settings.rst + nghttp2_submit_shutdown_notice.rst + nghttp2_submit_trailer.rst + nghttp2_submit_window_update.rst + nghttp2_version.rst +) + +set(MAN_PAGES + nghttp.1 + nghttpd.1 + nghttpx.1 + h2load.1 +) + +# Other .rst files from the source tree that need to be copied +# XXX move them to sources/ and create .in files? +set(RST_FILES + README.rst + programmers-guide.rst + nghttp.1.rst + nghttpd.1.rst + nghttpx.1.rst + h2load.1.rst +) + +# XXX unused for now +set(EXTRA_DIST + mkapiref.py + ${RST_FILES} + ${APIDOCS} + sources/index.rst + sources/tutorial-client.rst + sources/tutorial-server.rst + sources/tutorial-hpack.rst + sources/nghttpx-howto.rst + sources/h2load-howto.rst + sources/building-android-binary.rst + sources/contribute.rst + _exts/rubydomain/LICENSE.rubydomain + _exts/rubydomain/__init__.py + _exts/rubydomain/rubydomain.py + _themes/sphinx_rtd_theme/__init__.py + _themes/sphinx_rtd_theme/breadcrumbs.html + _themes/sphinx_rtd_theme/footer.html + _themes/sphinx_rtd_theme/layout.html + _themes/sphinx_rtd_theme/layout_old.html + _themes/sphinx_rtd_theme/search.html + _themes/sphinx_rtd_theme/searchbox.html + _themes/sphinx_rtd_theme/static/css/badge_only.css + _themes/sphinx_rtd_theme/static/css/theme.css + _themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff + _themes/sphinx_rtd_theme/static/js/theme.js + _themes/sphinx_rtd_theme/theme.conf + _themes/sphinx_rtd_theme/versions.html + ${MAN_PAGES} + bash_completion/nghttp + bash_completion/nghttpd + bash_completion/nghttpx + bash_completion/h2load +) + +# Based on Makefile for Sphinx documentation + +# You can set these variables from the command line. +set(SPHINXOPTS) +set(SPHINXBUILD sphinx-build) +set(PAPER) +set(BUILDDIR manual) + +# Internal variables. +set(PAPEROPT_a4 -D latex_paper_size=a4) +set(PAPEROPT_letter -D latex_paper_size=letter) +set(ALLSPHINXOPTS -d ${BUILDDIR}/doctrees ${PAPEROPT_${PAPER}} ${SPHINXOPTS} .) + +# "Please use `make ' 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/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..7d7f31c --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,366 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +man_MANS = nghttp.1 nghttpd.1 nghttpx.1 h2load.1 + +APIDOCS= \ + macros.rst \ + enums.rst \ + types.rst \ + nghttp2_check_authority.rst \ + nghttp2_check_header_name.rst \ + nghttp2_check_header_value.rst \ + nghttp2_check_header_value_rfc9113.rst \ + nghttp2_check_method.rst \ + nghttp2_check_path.rst \ + nghttp2_extpri_parse_priority.rst \ + nghttp2_hd_deflate_bound.rst \ + nghttp2_hd_deflate_change_table_size.rst \ + nghttp2_hd_deflate_del.rst \ + nghttp2_hd_deflate_get_dynamic_table_size.rst \ + nghttp2_hd_deflate_get_max_dynamic_table_size.rst \ + nghttp2_hd_deflate_get_num_table_entries.rst \ + nghttp2_hd_deflate_get_table_entry.rst \ + nghttp2_hd_deflate_hd.rst \ + nghttp2_hd_deflate_hd_vec.rst \ + nghttp2_hd_deflate_new.rst \ + nghttp2_hd_deflate_new2.rst \ + nghttp2_hd_inflate_change_table_size.rst \ + nghttp2_hd_inflate_del.rst \ + nghttp2_hd_inflate_end_headers.rst \ + nghttp2_hd_inflate_get_dynamic_table_size.rst \ + nghttp2_hd_inflate_get_max_dynamic_table_size.rst \ + nghttp2_hd_inflate_get_num_table_entries.rst \ + nghttp2_hd_inflate_get_table_entry.rst \ + nghttp2_hd_inflate_hd.rst \ + nghttp2_hd_inflate_hd2.rst \ + nghttp2_hd_inflate_new.rst \ + nghttp2_hd_inflate_new2.rst \ + nghttp2_http2_strerror.rst \ + nghttp2_is_fatal.rst \ + nghttp2_nv_compare_name.rst \ + nghttp2_option_del.rst \ + nghttp2_option_new.rst \ + nghttp2_option_set_builtin_recv_extension_type.rst \ + nghttp2_option_set_max_deflate_dynamic_table_size.rst \ + nghttp2_option_set_max_reserved_remote_streams.rst \ + nghttp2_option_set_max_send_header_block_length.rst \ + nghttp2_option_set_no_auto_ping_ack.rst \ + nghttp2_option_set_no_auto_window_update.rst \ + nghttp2_option_set_no_closed_streams.rst \ + nghttp2_option_set_no_http_messaging.rst \ + nghttp2_option_set_no_recv_client_magic.rst \ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation.rst \ + nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_server_fallback_rfc7540_priorities.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ + nghttp2_option_set_max_outbound_ack.rst \ + nghttp2_option_set_max_settings.rst \ + nghttp2_option_set_stream_reset_rate_limit.rst \ + nghttp2_pack_settings_payload.rst \ + nghttp2_priority_spec_check_default.rst \ + nghttp2_priority_spec_default_init.rst \ + nghttp2_priority_spec_init.rst \ + nghttp2_rcbuf_decref.rst \ + nghttp2_rcbuf_get_buf.rst \ + nghttp2_rcbuf_incref.rst \ + nghttp2_rcbuf_is_static.rst \ + nghttp2_select_next_protocol.rst \ + nghttp2_select_alpn.rst \ + nghttp2_session_callbacks_del.rst \ + nghttp2_session_callbacks_new.rst \ + nghttp2_session_callbacks_set_before_frame_send_callback.rst \ + nghttp2_session_callbacks_set_data_source_read_length_callback.rst \ + nghttp2_session_callbacks_set_error_callback.rst \ + nghttp2_session_callbacks_set_error_callback2.rst \ + nghttp2_session_callbacks_set_on_begin_frame_callback.rst \ + nghttp2_session_callbacks_set_on_begin_headers_callback.rst \ + nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst \ + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst \ + nghttp2_session_callbacks_set_on_frame_not_send_callback.rst \ + nghttp2_session_callbacks_set_on_frame_recv_callback.rst \ + nghttp2_session_callbacks_set_on_frame_send_callback.rst \ + nghttp2_session_callbacks_set_on_header_callback.rst \ + nghttp2_session_callbacks_set_on_header_callback2.rst \ + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \ + nghttp2_session_callbacks_set_on_invalid_header_callback.rst \ + nghttp2_session_callbacks_set_on_invalid_header_callback2.rst \ + nghttp2_session_callbacks_set_on_stream_close_callback.rst \ + nghttp2_session_callbacks_set_pack_extension_callback.rst \ + nghttp2_session_callbacks_set_recv_callback.rst \ + nghttp2_session_callbacks_set_select_padding_callback.rst \ + nghttp2_session_callbacks_set_send_callback.rst \ + nghttp2_session_callbacks_set_send_data_callback.rst \ + nghttp2_session_callbacks_set_unpack_extension_callback.rst \ + nghttp2_session_change_extpri_stream_priority.rst \ + nghttp2_session_change_stream_priority.rst \ + nghttp2_session_check_request_allowed.rst \ + nghttp2_session_check_server_session.rst \ + nghttp2_session_client_new.rst \ + nghttp2_session_client_new2.rst \ + nghttp2_session_client_new3.rst \ + nghttp2_session_consume.rst \ + nghttp2_session_consume_connection.rst \ + nghttp2_session_consume_stream.rst \ + nghttp2_session_create_idle_stream.rst \ + nghttp2_session_del.rst \ + nghttp2_session_find_stream.rst \ + nghttp2_session_get_effective_local_window_size.rst \ + nghttp2_session_get_effective_recv_data_length.rst \ + nghttp2_session_get_extpri_stream_priority.rst \ + nghttp2_session_get_hd_deflate_dynamic_table_size.rst \ + nghttp2_session_get_hd_inflate_dynamic_table_size.rst \ + nghttp2_session_get_last_proc_stream_id.rst \ + nghttp2_session_get_local_settings.rst \ + nghttp2_session_get_local_window_size.rst \ + nghttp2_session_get_next_stream_id.rst \ + nghttp2_session_get_outbound_queue_size.rst \ + nghttp2_session_get_remote_settings.rst \ + nghttp2_session_get_remote_window_size.rst \ + nghttp2_session_get_root_stream.rst \ + nghttp2_session_get_stream_effective_local_window_size.rst \ + nghttp2_session_get_stream_effective_recv_data_length.rst \ + nghttp2_session_get_stream_local_close.rst \ + nghttp2_session_get_stream_local_window_size.rst \ + nghttp2_session_get_stream_remote_close.rst \ + nghttp2_session_get_stream_remote_window_size.rst \ + nghttp2_session_get_stream_user_data.rst \ + nghttp2_session_mem_recv.rst \ + nghttp2_session_mem_send.rst \ + nghttp2_session_recv.rst \ + nghttp2_session_resume_data.rst \ + nghttp2_session_send.rst \ + nghttp2_session_server_new.rst \ + nghttp2_session_server_new2.rst \ + nghttp2_session_server_new3.rst \ + nghttp2_session_set_local_window_size.rst \ + nghttp2_session_set_next_stream_id.rst \ + nghttp2_session_set_stream_user_data.rst \ + nghttp2_session_set_user_data.rst \ + nghttp2_session_terminate_session.rst \ + nghttp2_session_terminate_session2.rst \ + nghttp2_session_upgrade.rst \ + nghttp2_session_upgrade2.rst \ + nghttp2_session_want_read.rst \ + nghttp2_session_want_write.rst \ + nghttp2_set_debug_vprintf_callback.rst \ + nghttp2_stream_get_first_child.rst \ + nghttp2_stream_get_next_sibling.rst \ + nghttp2_stream_get_parent.rst \ + nghttp2_stream_get_previous_sibling.rst \ + nghttp2_stream_get_state.rst \ + nghttp2_stream_get_sum_dependency_weight.rst \ + nghttp2_stream_get_weight.rst \ + nghttp2_strerror.rst \ + nghttp2_submit_altsvc.rst \ + nghttp2_submit_data.rst \ + nghttp2_submit_extension.rst \ + nghttp2_submit_goaway.rst \ + nghttp2_submit_headers.rst \ + nghttp2_submit_origin.rst \ + nghttp2_submit_ping.rst \ + nghttp2_submit_priority.rst \ + nghttp2_submit_priority_update.rst \ + nghttp2_submit_push_promise.rst \ + nghttp2_submit_request.rst \ + nghttp2_submit_response.rst \ + nghttp2_submit_rst_stream.rst \ + nghttp2_submit_settings.rst \ + nghttp2_submit_shutdown_notice.rst \ + nghttp2_submit_trailer.rst \ + nghttp2_submit_window_update.rst \ + nghttp2_version.rst + +RST_FILES = \ + README.rst \ + programmers-guide.rst \ + nghttp.1.rst \ + nghttpd.1.rst \ + nghttpx.1.rst \ + h2load.1.rst + +EXTRA_DIST = \ + CMakeLists.txt \ + mkapiref.py \ + $(RST_FILES) \ + $(APIDOCS) \ + sources/index.rst \ + sources/tutorial-client.rst \ + sources/tutorial-server.rst \ + sources/tutorial-hpack.rst \ + sources/nghttpx-howto.rst \ + sources/h2load-howto.rst \ + sources/building-android-binary.rst \ + sources/contribute.rst \ + sources/security.rst \ + _exts/rubydomain/LICENSE.rubydomain \ + _exts/rubydomain/__init__.py \ + _exts/rubydomain/rubydomain.py \ + $(man_MANS) \ + bash_completion/nghttp \ + bash_completion/nghttpd \ + bash_completion/nghttpx \ + bash_completion/h2load + +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD ?= sphinx-build +PAPER = +BUILDDIR = manual + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' 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/doc/README.rst b/doc/README.rst new file mode 100644 index 0000000..549e550 --- /dev/null +++ b/doc/README.rst @@ -0,0 +1,160 @@ +nghttp2 Documentation +===================== + +The documentation of nghttp2 is generated using Sphinx. This +directory contains the source files to be processed by Sphinx. The +source file for API reference is generated using a script called +``mkapiref.py`` from the nghttp2 C source code. + +Generating API reference +------------------------ + +As described earlier, we use ``mkapiref.py`` to generate rst formatted +text of API reference from C source code. The ``mkapiref.py`` is not +so flexible and it requires that C source code is formatted in rather +strict rules. + +To generate API reference, just run ``make html``. It runs +``mkapiref.py`` and then run Sphinx to build the entire document. + +The ``mkapiref.py`` reads C source code and searches the comment block +starts with ``/**``. In other words, it only processes the comment +block starting ``/**``. The comment block must end with ``*/``. The +``mkapiref.py`` requires that which type of the object this comment +block refers to. To specify the type of the object, the next line +must contain the so-called action keyword. Currently, the following +action keywords are supported: ``@function``, ``@functypedef``, +``@enum``, ``@struct`` and ``@union``. The following sections +describes each action keyword. + +@function +######### + +``@function`` is used to refer to the function. The comment block is +used for the document for the function. After the script sees the end +of the comment block, it consumes the lines as the function +declaration until the line which ends with ``;`` is encountered. + +In Sphinx doc, usually the function argument is formatted like +``*this*``. But in C, ``*`` is used for dereferencing a pointer and +we must escape ``*`` with a back slash. To avoid this, we format the +argument like ``|this|``. The ``mkapiref.py`` translates it with +``*this*``, as escaping ``*`` inside ``|`` and ``|`` as necessary. +Note that this shadows the substitution feature of Sphinx. + +The example follows:: + + /** + * @function + * + * Submits PING frame to the |session|. + */ + int nghttp2_submit_ping(nghttp2_session *session); + + +@functypedef +############ + +``@functypedef`` is used to refer to the typedef of the function +pointer. The formatting rule is pretty much the same with +``@function``, but this outputs ``type`` domain, rather than +``function`` domain. + +The example follows:: + + /** + * @functypedef + * + * Callback function invoked when |session| wants to send data to + * remote peer. + */ + typedef ssize_t (*nghttp2_send_callback) + (nghttp2_session *session, + const uint8_t *data, size_t length, int flags, void *user_data); + +@enum +##### + +``@enum`` is used to refer to the enum. Currently, only enum typedefs +are supported. The comment block is used for the document for the +enum type itself. To document each values, put comment block starting +with the line ``/**`` and ending with the ``*/`` just before the enum +value. When the line starts with ``}`` is encountered, the +``mkapiref.py`` extracts strings next to ``}`` as the name of enum. + +At the time of this writing, Sphinx does not support enum type. So we +use ``type`` domain for enum it self and ``macro`` domain for each +value. To refer to the enum value, use ``:enum:`` pseudo role. The +``mkapiref.py`` replaces it with ``:macro:``. By doing this, when +Sphinx will support enum officially, we can replace ``:enum:`` with +the official role easily. + +The example follows:: + + /** + * @enum + * Error codes used in the nghttp2 library. + */ + typedef enum { + /** + * Invalid argument passed. + */ + NGHTTP2_ERR_INVALID_ARGUMENT = -501, + /** + * Zlib error. + */ + NGHTTP2_ERR_ZLIB = -502, + } nghttp2_error; + +@struct +####### + +``@struct`` is used to refer to the struct. Currently, only struct +typedefs are supported. The comment block is used for the document for +the struct type itself.To document each member, put comment block +starting with the line ``/**`` and ending with the ``*/`` just before +the member. When the line starts with ``}`` is encountered, the +``mkapiref.py`` extracts strings next to ``}`` as the name of struct. +The block-less typedef is also supported. In this case, typedef +declaration must be all in one line and the ``mkapiref.py`` uses last +word as the name of struct. + +Some examples follow:: + + /** + * @struct + * The control frame header. + */ + typedef struct { + /** + * SPDY protocol version. + */ + uint16_t version; + /** + * The type of this control frame. + */ + uint16_t type; + /** + * The control frame flags. + */ + uint8_t flags; + /** + * The length field of this control frame. + */ + int32_t length; + } nghttp2_ctrl_hd; + + /** + * @struct + * + * The primary structure to hold the resources needed for a SPDY + * session. The details of this structure is hidden from the public + * API. + */ + typedef struct nghttp2_session nghttp2_session; + +@union +###### + +``@union`` is used to refer to the union. Currently, ``@union`` is an +alias of ``@struct``. diff --git a/doc/_exts/rubydomain/LICENSE.rubydomain b/doc/_exts/rubydomain/LICENSE.rubydomain new file mode 100644 index 0000000..a560d75 --- /dev/null +++ b/doc/_exts/rubydomain/LICENSE.rubydomain @@ -0,0 +1,28 @@ +If not otherwise noted, the extensions in this package are licensed +under the following license. + +Copyright (c) 2010 by the contributors (see AUTHORS file). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/_exts/rubydomain/__init__.py b/doc/_exts/rubydomain/__init__.py new file mode 100644 index 0000000..b5a7dc2 --- /dev/null +++ b/doc/_exts/rubydomain/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" + sphinxcontrib + ~~~~~~~~~~~~~ + + This package is a namespace package that contains all extensions + distributed in the ``sphinx-contrib`` distribution. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +__import__('pkg_resources').declare_namespace(__name__) + diff --git a/doc/_exts/rubydomain/rubydomain.py b/doc/_exts/rubydomain/rubydomain.py new file mode 100644 index 0000000..db35233 --- /dev/null +++ b/doc/_exts/rubydomain/rubydomain.py @@ -0,0 +1,703 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.ruby + ~~~~~~~~~~~~~~~~~~~ + + The Ruby domain. + + :copyright: Copyright 2010 by SHIBUKAWA Yoshiki + :license: BSD, see LICENSE for details. +""" + +import re + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive + +from sphinx import addnodes +from sphinx import version_info +from sphinx.roles import XRefRole +from sphinx.locale import _ +from sphinx.domains import Domain, ObjType, Index +from sphinx.directives import ObjectDescription +from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field, GroupedField, TypedField + +# REs for Ruby signatures +rb_sig_re = re.compile( + r'''^ ([\w.]*\.)? # class name(s) + (\$?\w+\??!?) \s* # thing name + (?: \((.*)\) # optional: arguments + (?:\s* -> \s* (.*))? # return annotation + )? $ # and nothing more + ''', re.VERBOSE) + +rb_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' + +separators = { + 'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#', + 'function':'.', 'classmethod':'.', 'class':'::', 'module':'::', + 'global':'', 'const':'::'} + +rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?") + + +def _iteritems(d): + + for k in d: + yield k, d[k] + + +def ruby_rsplit(fullname): + items = [item for item in rb_separator.findall(fullname)] + return ''.join(items[:-2]), items[-1] + + +class RubyObject(ObjectDescription): + """ + Description of a general Ruby object. + """ + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + } + + doc_field_types = [ + TypedField('parameter', label=_('Parameters'), + names=('param', 'parameter', 'arg', 'argument'), + typerolename='obj', typenames=('paramtype', 'type')), + TypedField('variable', label=_('Variables'), rolename='obj', + names=('var', 'ivar', 'cvar'), + typerolename='obj', typenames=('vartype',)), + GroupedField('exceptions', label=_('Raises'), rolename='exc', + names=('raises', 'raise', 'exception', 'except'), + can_collapse=True), + Field('returnvalue', label=_('Returns'), has_arg=False, + names=('returns', 'return')), + Field('returntype', label=_('Return type'), has_arg=False, + names=('rtype',)), + ] + + def get_signature_prefix(self, sig): + """ + May return a prefix to put before the object name in the signature. + """ + return '' + + def needs_arglist(self): + """ + May return true if an empty argument list is to be generated even if + the document contains none. + """ + return False + + def handle_signature(self, sig, signode): + """ + Transform a Ruby signature into RST nodes. + Returns (fully qualified name of the thing, classname if any). + + If inside a class, the current class name is handled intelligently: + * it is stripped from the displayed name if present + * it is added to the full name (return value) if not present + """ + m = rb_sig_re.match(sig) + if m is None: + raise ValueError + name_prefix, name, arglist, retann = m.groups() + if not name_prefix: + name_prefix = "" + # determine module and class name (if applicable), as well as full name + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + classname = self.env.temp_data.get('rb:class') + if self.objtype == 'global': + add_module = False + modname = None + classname = None + fullname = name + elif classname: + add_module = False + if name_prefix and name_prefix.startswith(classname): + fullname = name_prefix + name + # class name is given again in the signature + name_prefix = name_prefix[len(classname):].lstrip('.') + else: + separator = separators[self.objtype] + fullname = classname + separator + name_prefix + name + else: + add_module = True + if name_prefix: + classname = name_prefix.rstrip('.') + fullname = name_prefix + name + else: + classname = '' + fullname = name + + signode['module'] = modname + signode['class'] = self.class_name = classname + signode['fullname'] = fullname + + sig_prefix = self.get_signature_prefix(sig) + if sig_prefix: + signode += addnodes.desc_annotation(sig_prefix, sig_prefix) + + if name_prefix: + signode += addnodes.desc_addname(name_prefix, name_prefix) + # exceptions are a special case, since they are documented in the + # 'exceptions' module. + elif add_module and self.env.config.add_module_names: + if self.objtype == 'global': + nodetext = '' + signode += addnodes.desc_addname(nodetext, nodetext) + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + if modname and modname != 'exceptions': + nodetext = modname + separators[self.objtype] + signode += addnodes.desc_addname(nodetext, nodetext) + + signode += addnodes.desc_name(name, name) + if not arglist: + if self.needs_arglist(): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + signode += addnodes.desc_parameterlist() + + stack = [signode[-1]] + for token in rb_paramlist_re.split(arglist): + if token == '[': + opt = addnodes.desc_optional() + stack[-1] += opt + stack.append(opt) + elif token == ']': + try: + stack.pop() + except IndexError: + raise ValueError + elif not token or token == ',' or token.isspace(): + pass + else: + token = token.strip() + stack[-1] += addnodes.desc_parameter(token, token) + if len(stack) != 1: + raise ValueError + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + + def get_index_text(self, modname, name): + """ + Return the text for the index entry of the object. + """ + raise NotImplementedError('must be implemented in subclasses') + + def _is_class_member(self): + return self.objtype.endswith('method') or self.objtype.startswith('attr') + + def add_target_and_index(self, name_cls, sig, signode): + if self.objtype == 'global': + modname = '' + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + separator = separators[self.objtype] + if self._is_class_member(): + if signode['class']: + prefix = modname and modname + '::' or '' + else: + prefix = modname and modname + separator or '' + else: + prefix = modname and modname + separator or '' + fullname = prefix + name_cls[0] + # note target + if fullname not in self.state.document.ids: + signode['names'].append(fullname) + signode['ids'].append(fullname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + objects = self.env.domaindata['rb']['objects'] + if fullname in objects: + self.env.warn( + self.env.docname, + 'duplicate object description of %s, ' % fullname + + 'other instance in ' + + self.env.doc2path(objects[fullname][0]), + self.lineno) + objects[fullname] = (self.env.docname, self.objtype) + + indextext = self.get_index_text(modname, name_cls) + if indextext: + self.indexnode['entries'].append( + _make_index('single', indextext, fullname, fullname)) + + def before_content(self): + # needed for automatic qualification of members (reset in subclasses) + self.clsname_set = False + + def after_content(self): + if self.clsname_set: + self.env.temp_data['rb:class'] = None + + +class RubyModulelevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def needs_arglist(self): + return self.objtype == 'function' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'function': + if not modname: + return _('%s() (global function)') % name_cls[0] + return _('%s() (module function in %s)') % (name_cls[0], modname) + else: + return '' + + +class RubyGloballevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def get_index_text(self, modname, name_cls): + if self.objtype == 'global': + return _('%s (global variable)') % name_cls[0] + else: + return '' + + +class RubyEverywhere(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype == 'method' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'method': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname and add_modules: + return _('%s() (%s::%s method)') % (methname, modname, + clsname) + else: + return _('%s() (%s method)') % (methname, clsname) + else: + return '' + + +class RubyClasslike(RubyObject): + """ + Description of a class-like object (classes, exceptions). + """ + + def get_signature_prefix(self, sig): + return self.objtype + ' ' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'class': + if not modname: + return _('%s (class)') % name_cls[0] + return _('%s (class in %s)') % (name_cls[0], modname) + elif self.objtype == 'exception': + return name_cls[0] + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + if self.names: + self.env.temp_data['rb:class'] = self.names[0][0] + self.clsname_set = True + + +class RubyClassmember(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype.endswith('method') + + def get_signature_prefix(self, sig): + if self.objtype == 'classmethod': + return "classmethod %s." % self.class_name + elif self.objtype == 'attr_reader': + return "attribute [R] " + elif self.objtype == 'attr_writer': + return "attribute [W] " + elif self.objtype == 'attr_accessor': + return "attribute [R/W] " + return '' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'classmethod': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + return '%s()' % name + if modname: + return _('%s() (%s.%s class method)') % (methname, modname, + clsname) + else: + return _('%s() (%s class method)') % (methname, clsname) + elif self.objtype.startswith('attr'): + try: + clsname, attrname = ruby_rsplit(name) + except ValueError: + return name + if modname and add_modules: + return _('%s (%s.%s attribute)') % (attrname, modname, clsname) + else: + return _('%s (%s attribute)') % (attrname, clsname) + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + lastname = self.names and self.names[-1][1] + if lastname and not self.env.temp_data.get('rb:class'): + self.env.temp_data['rb:class'] = lastname.strip('.') + self.clsname_set = True + + +class RubyModule(Directive): + """ + Directive to mark description of a new module. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'platform': lambda x: x, + 'synopsis': lambda x: x, + 'noindex': directives.flag, + 'deprecated': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + noindex = 'noindex' in self.options + env.temp_data['rb:module'] = modname + env.domaindata['rb']['modules'][modname] = \ + (env.docname, self.options.get('synopsis', ''), + self.options.get('platform', ''), 'deprecated' in self.options) + targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) + self.state.document.note_explicit_target(targetnode) + ret = [targetnode] + # XXX this behavior of the module directive is a mess... + if 'platform' in self.options: + platform = self.options['platform'] + node = nodes.paragraph() + node += nodes.emphasis('', _('Platforms: ')) + node += nodes.Text(platform, platform) + ret.append(node) + # the synopsis isn't printed; in fact, it is only used in the + # modindex currently + if not noindex: + indextext = _('%s (module)') % modname + inode = addnodes.index(entries=[_make_index( + 'single', indextext, 'module-' + modname, modname)]) + ret.append(inode) + return ret + +def _make_index(entrytype, entryname, target, ignored, key=None): + # Sphinx 1.4 introduced backward incompatible changes, it now + # requires 5 tuples. Last one is categorization key. See + # http://www.sphinx-doc.org/en/stable/extdev/nodes.html#sphinx.addnodes.index + if version_info >= (1, 4, 0, '', 0): + return (entrytype, entryname, target, ignored, key) + else: + return (entrytype, entryname, target, ignored) + +class RubyCurrentModule(Directive): + """ + This directive is just to tell Sphinx that we're documenting + stuff in module foo, but links to module foo won't lead here. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + if modname == 'None': + env.temp_data['rb:module'] = None + else: + env.temp_data['rb:module'] = modname + return [] + + +class RubyXRefRole(XRefRole): + def process_link(self, env, refnode, has_explicit_title, title, target): + if not has_explicit_title: + title = title.lstrip('.') # only has a meaning for the target + title = title.lstrip('#') + if title.startswith("::"): + title = title[2:] + target = target.lstrip('~') # only has a meaning for the title + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[0:1] == '~': + m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title) + if m: + title = m.group(1) + if not title.startswith("$"): + refnode['rb:module'] = env.temp_data.get('rb:module') + refnode['rb:class'] = env.temp_data.get('rb:class') + # if the first character is a dot, search more specific namespaces first + # else search builtins first + if target[0:1] == '.': + target = target[1:] + refnode['refspecific'] = True + return title, target + + +class RubyModuleIndex(Index): + """ + Index subclass to provide the Ruby module index. + """ + + name = 'modindex' + localname = _('Ruby Module Index') + shortname = _('modules') + + def generate(self, docnames=None): + content = {} + # list of prefixes to ignore + ignores = self.domain.env.config['modindex_common_prefix'] + ignores = sorted(ignores, key=len, reverse=True) + # list of all modules, sorted by module name + modules = sorted(_iteritems(self.domain.data['modules']), + key=lambda x: x[0].lower()) + # sort out collapsible modules + prev_modname = '' + num_toplevels = 0 + for modname, (docname, synopsis, platforms, deprecated) in modules: + if docnames and docname not in docnames: + continue + + for ignore in ignores: + if modname.startswith(ignore): + modname = modname[len(ignore):] + stripped = ignore + break + else: + stripped = '' + + # we stripped the whole module name? + if not modname: + modname, stripped = stripped, '' + + entries = content.setdefault(modname[0].lower(), []) + + package = modname.split('::')[0] + if package != modname: + # it's a submodule + if prev_modname == package: + # first submodule - make parent a group head + entries[-1][1] = 1 + elif not prev_modname.startswith(package): + # submodule without parent in list, add dummy entry + entries.append([stripped + package, 1, '', '', '', '', '']) + subtype = 2 + else: + num_toplevels += 1 + subtype = 0 + + qualifier = deprecated and _('Deprecated') or '' + entries.append([stripped + modname, subtype, docname, + 'module-' + stripped + modname, platforms, + qualifier, synopsis]) + prev_modname = modname + + # apply heuristics when to collapse modindex at page load: + # only collapse if number of toplevel modules is larger than + # number of submodules + collapse = len(modules) - num_toplevels < num_toplevels + + # sort by first letter + content = sorted(_iteritems(content)) + + return content, collapse + + +class RubyDomain(Domain): + """Ruby language domain.""" + name = 'rb' + label = 'Ruby' + object_types = { + 'function': ObjType(_('function'), 'func', 'obj'), + 'global': ObjType(_('global variable'), 'global', 'obj'), + 'method': ObjType(_('method'), 'meth', 'obj'), + 'class': ObjType(_('class'), 'class', 'obj'), + 'exception': ObjType(_('exception'), 'exc', 'obj'), + 'classmethod': ObjType(_('class method'), 'meth', 'obj'), + 'attr_reader': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_writer': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_accessor': ObjType(_('attribute'), 'attr', 'obj'), + 'const': ObjType(_('const'), 'const', 'obj'), + 'module': ObjType(_('module'), 'mod', 'obj'), + } + + directives = { + 'function': RubyModulelevel, + 'global': RubyGloballevel, + 'method': RubyEverywhere, + 'const': RubyEverywhere, + 'class': RubyClasslike, + 'exception': RubyClasslike, + 'classmethod': RubyClassmember, + 'attr_reader': RubyClassmember, + 'attr_writer': RubyClassmember, + 'attr_accessor': RubyClassmember, + 'module': RubyModule, + 'currentmodule': RubyCurrentModule, + } + + roles = { + 'func': RubyXRefRole(fix_parens=False), + 'global':RubyXRefRole(), + 'class': RubyXRefRole(), + 'exc': RubyXRefRole(), + 'meth': RubyXRefRole(fix_parens=False), + 'attr': RubyXRefRole(), + 'const': RubyXRefRole(), + 'mod': RubyXRefRole(), + 'obj': RubyXRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + 'modules': {}, # modname -> docname, synopsis, platform, deprecated + } + indices = [ + RubyModuleIndex, + ] + + def clear_doc(self, docname): + for fullname, (fn, _) in list(self.data['objects'].items()): + if fn == docname: + del self.data['objects'][fullname] + for modname, (fn, _, _, _) in list(self.data['modules'].items()): + if fn == docname: + del self.data['modules'][modname] + + def find_obj(self, env, modname, classname, name, type, searchorder=0): + """ + Find a Ruby object for "name", perhaps using the given module and/or + classname. + """ + # skip parens + if name[-2:] == '()': + name = name[:-2] + + if not name: + return None, None + + objects = self.data['objects'] + + newname = None + if searchorder == 1: + if modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif name in objects: + newname = name + else: + if name in objects: + newname = name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + # special case: object methods + elif type in ('func', 'meth') and '.' not in name and \ + 'object.' + name in objects: + newname = 'object.' + name + if newname is None: + return None, None + return newname, objects[newname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + if (typ == 'mod' or + typ == 'obj' and target in self.data['modules']): + docname, synopsis, platform, deprecated = \ + self.data['modules'].get(target, ('','','', '')) + if not docname: + return None + else: + title = '%s%s%s' % ((platform and '(%s) ' % platform), + synopsis, + (deprecated and ' (deprecated)' or '')) + return make_refnode(builder, fromdocname, docname, + 'module-' + target, contnode, title) + else: + modname = node.get('rb:module') + clsname = node.get('rb:class') + searchorder = node.hasattr('refspecific') and 1 or 0 + name, obj = self.find_obj(env, modname, clsname, + target, typ, searchorder) + if not obj: + return None + else: + return make_refnode(builder, fromdocname, obj[0], name, + contnode, name) + + def get_objects(self): + for modname, info in _iteritems(self.data['modules']): + yield (modname, modname, 'module', info[0], 'module-' + modname, 0) + for refname, (docname, type) in _iteritems(self.data['objects']): + yield (refname, refname, type, docname, refname, 1) + + +def setup(app): + app.add_domain(RubyDomain) diff --git a/doc/bash_completion/h2load b/doc/bash_completion/h2load new file mode 100644 index 0000000..2b2d4ab --- /dev/null +++ b/doc/bash_completion/h2load @@ -0,0 +1,19 @@ +_h2load() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--requests --clients --threads --input-file --max-concurrent-streams --max-frame-size --window-bits --connection-window-bits --header --ciphers --tls13-ciphers --no-tls-proto --data --rate --rate-period --duration --warm-up-time --connection-active-timeout --connection-inactivity-timeout --timing-script-file --base-uri --alpn-list --h1 --header-table-size --encoder-header-table-size --log-file --qlog-file-base --connect-to --rps --groups --no-udp-gso --max-udp-payload-size --ktls --verbose --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _h2load h2load diff --git a/doc/bash_completion/make_bash_completion.py b/doc/bash_completion/make_bash_completion.py new file mode 100755 index 0000000..e3a535b --- /dev/null +++ b/doc/bash_completion/make_bash_completion.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import subprocess +import io +import re +import sys +import os.path + +class Option: + def __init__(self, long_opt, short_opt): + self.long_opt = long_opt + self.short_opt = short_opt + +def get_all_options(cmd): + opt_pattern = re.compile(r' (?:(-.), )?(--[^\s\[=]+)(\[)?') + proc = subprocess.Popen([cmd, "--help"], stdout=subprocess.PIPE) + stdoutdata, _ = proc.communicate() + cur_option = None + opts = {} + for line in io.StringIO(stdoutdata.decode('utf-8')): + match = opt_pattern.match(line) + if not match: + continue + long_opt = match.group(2) + short_opt = match.group(1) + opts[long_opt] = Option(long_opt, short_opt) + + return opts + +def output_case(out, name, opts): + out.write('''\ +_{name}() +{{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${{COMP_WORDBREAKS//=}} + + cmd=${{COMP_WORDS[0]}} + _get_comp_words_by_ref cur prev +'''.format(name=name)) + + # Complete option name. + out.write('''\ + case $cur in + -*) + COMPREPLY=( $( compgen -W '\ +''') + for opt in opts.values(): + out.write(opt.long_opt) + out.write(' ') + + out.write('''\ +' -- "$cur" ) ) + ;; +''') + # If no option found for completion then complete with files. + out.write('''\ + *) + _filedir + return 0 + esac + return 0 +}} +complete -F _{name} {name} +'''.format(name=name)) + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Generates bash_completion using `/path/to/cmd --help'") + print("Usage: make_bash_completion.py /path/to/cmd") + exit(1) + name = os.path.basename(sys.argv[1]) + opts = get_all_options(sys.argv[1]) + output_case(sys.stdout, name, opts) diff --git a/doc/bash_completion/nghttp b/doc/bash_completion/nghttp new file mode 100644 index 0000000..dd48403 --- /dev/null +++ b/doc/bash_completion/nghttp @@ -0,0 +1,19 @@ +_nghttp() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--verbose --null-out --remote-name --timeout --window-bits --connection-window-bits --get-assets --stat --header --trailer --cert --key --data --multiply --upgrade --weight --peer-max-concurrent-streams --header-table-size --encoder-header-table-size --padding --har --color --continuation --no-content-length --no-dep --hexdump --no-push --max-concurrent-streams --expect-continue --no-verify-peer --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttp nghttp diff --git a/doc/bash_completion/nghttpd b/doc/bash_completion/nghttpd new file mode 100644 index 0000000..7dd4d9b --- /dev/null +++ b/doc/bash_completion/nghttpd @@ -0,0 +1,19 @@ +_nghttpd() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--address --daemon --verify-client --htdocs --verbose --no-tls --header-table-size --encoder-header-table-size --color --push --padding --max-concurrent-streams --workers --error-gzip --window-bits --connection-window-bits --dh-param-file --early-response --trailer --hexdump --echo-upload --mime-types-file --no-content-length --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttpd nghttpd diff --git a/doc/bash_completion/nghttpx b/doc/bash_completion/nghttpx new file mode 100644 index 0000000..a735cd2 --- /dev/null +++ b/doc/bash_completion/nghttpx @@ -0,0 +1,19 @@ +_nghttpx() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--backend --frontend --backlog --backend-address-family --backend-http-proxy-uri --workers --single-thread --read-rate --read-burst --write-rate --write-burst --worker-read-rate --worker-read-burst --worker-write-rate --worker-write-burst --worker-frontend-connections --backend-connections-per-host --backend-connections-per-frontend --rlimit-nofile --rlimit-memlock --backend-request-buffer --backend-response-buffer --fastopen --no-kqueue --frontend-http2-read-timeout --frontend-http3-read-timeout --frontend-read-timeout --frontend-write-timeout --frontend-keep-alive-timeout --stream-read-timeout --stream-write-timeout --backend-read-timeout --backend-write-timeout --backend-connect-timeout --backend-keep-alive-timeout --listener-disable-timeout --frontend-http2-setting-timeout --backend-http2-settings-timeout --backend-max-backoff --ciphers --tls13-ciphers --client-ciphers --tls13-client-ciphers --ecdh-curves --insecure --cacert --private-key-passwd-file --subcert --dh-param-file --alpn-list --verify-client --verify-client-cacert --verify-client-tolerate-expired --client-private-key-file --client-cert-file --tls-min-proto-version --tls-max-proto-version --tls-ticket-key-file --tls-ticket-key-memcached --tls-ticket-key-memcached-address-family --tls-ticket-key-memcached-interval --tls-ticket-key-memcached-max-retry --tls-ticket-key-memcached-max-fail --tls-ticket-key-cipher --tls-ticket-key-memcached-cert-file --tls-ticket-key-memcached-private-key-file --fetch-ocsp-response-file --ocsp-update-interval --ocsp-startup --no-verify-ocsp --no-ocsp --tls-session-cache-memcached --tls-session-cache-memcached-address-family --tls-session-cache-memcached-cert-file --tls-session-cache-memcached-private-key-file --tls-dyn-rec-warmup-threshold --tls-dyn-rec-idle-timeout --no-http2-cipher-block-list --client-no-http2-cipher-block-list --tls-sct-dir --psk-secrets --client-psk-secrets --tls-no-postpone-early-data --tls-max-early-data --tls-ktls --frontend-http2-max-concurrent-streams --backend-http2-max-concurrent-streams --frontend-http2-window-size --frontend-http2-connection-window-size --backend-http2-window-size --backend-http2-connection-window-size --http2-no-cookie-crumbling --padding --no-server-push --frontend-http2-optimize-write-buffer-size --frontend-http2-optimize-window-size --frontend-http2-encoder-dynamic-table-size --frontend-http2-decoder-dynamic-table-size --backend-http2-encoder-dynamic-table-size --backend-http2-decoder-dynamic-table-size --http2-proxy --log-level --accesslog-file --accesslog-syslog --accesslog-format --accesslog-write-early --errorlog-file --errorlog-syslog --syslog-facility --add-x-forwarded-for --strip-incoming-x-forwarded-for --no-add-x-forwarded-proto --no-strip-incoming-x-forwarded-proto --add-forwarded --strip-incoming-forwarded --forwarded-by --forwarded-for --no-via --no-strip-incoming-early-data --no-location-rewrite --host-rewrite --altsvc --http2-altsvc --add-request-header --add-response-header --request-header-field-buffer --max-request-header-fields --response-header-field-buffer --max-response-header-fields --error-page --server-name --no-server-rewrite --redirect-https-port --require-http-scheme --api-max-request-body --dns-cache-timeout --dns-lookup-timeout --dns-max-try --frontend-max-requests --frontend-http2-dump-request-header --frontend-http2-dump-response-header --frontend-frame-debug --daemon --pid-file --user --single-process --max-worker-processes --worker-process-grace-shutdown-period --mruby-file --ignore-per-pattern-mruby-error --frontend-quic-idle-timeout --frontend-quic-debug-log --quic-bpf-program-file --frontend-quic-early-data --frontend-quic-qlog-dir --frontend-quic-require-token --frontend-quic-congestion-controller --frontend-quic-secret-file --quic-server-id --frontend-quic-initial-rtt --no-quic-bpf --frontend-http3-window-size --frontend-http3-connection-window-size --frontend-http3-max-window-size --frontend-http3-max-connection-window-size --frontend-http3-max-concurrent-streams --conf --include --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttpx nghttpx diff --git a/doc/building-android-binary.rst.in b/doc/building-android-binary.rst.in new file mode 100644 index 0000000..2c77c98 --- /dev/null +++ b/doc/building-android-binary.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/building-android-binary.rst diff --git a/doc/conf.py.in b/doc/conf.py.in new file mode 100644 index 0000000..228bdb1 --- /dev/null +++ b/doc/conf.py.in @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# +# nghttp2 documentation build configuration file, created by +# sphinx-quickstart on Sun Mar 11 22:57:49 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +sys.path.insert(0, os.path.abspath('@top_srcdir@/doc/_exts')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['rubydomain.rubydomain'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['@top_srcdir@/_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'nghttp2' +copyright = u'2012, 2015, 2016, Tatsuhiro Tsujikawa' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '@PACKAGE_VERSION@' +# The full version, including alpha/beta/rc tags. +release = '@PACKAGE_VERSION@' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['manual', 'README.rst', '*-header.rst', 'sources'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'c:func' +primary_domain = 'c' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The default language to highlight source code in. The default is 'python'. +highlight_language = 'c' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['@top_srcdir@/doc/_themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " 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/doc/contribute.rst.in b/doc/contribute.rst.in new file mode 100644 index 0000000..6a229d4 --- /dev/null +++ b/doc/contribute.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/contribute.rst diff --git a/doc/docutils.conf b/doc/docutils.conf new file mode 100644 index 0000000..f5208a8 --- /dev/null +++ b/doc/docutils.conf @@ -0,0 +1,2 @@ +[parsers] +smart_quotes=no diff --git a/doc/h2load-howto.rst.in b/doc/h2load-howto.rst.in new file mode 100644 index 0000000..252069d --- /dev/null +++ b/doc/h2load-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/h2load-howto.rst diff --git a/doc/h2load.1 b/doc/h2load.1 new file mode 100644 index 0000000..df052ab --- /dev/null +++ b/doc/h2load.1 @@ -0,0 +1,530 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "H2LOAD" "1" "Jan 21, 2024" "1.59.0" "nghttp2" +.SH NAME +h2load \- HTTP/2 benchmarking tool +.SH SYNOPSIS +.sp +\fBh2load\fP [OPTIONS]... [URI]... +.SH DESCRIPTION +.sp +benchmarking tool for HTTP/2 server +.INDENT 0.0 +.TP +.B +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 \-\-alpn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. The parameter must be delimited +by a single comma only and any white spaces are treated +as a part of protocol string. +.sp +Default: \fBh2,h2\-16,h2\-14,http/1.1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-h1 +Short hand for \fI\%\-\-alpn\-list\fP=http/1.1 +\fI\%\-\-no\-tls\-proto\fP=http/1.1, which effectively force +http/1.1 for both http and https URI. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-header\-table\-size= +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/doc/h2load.1.rst b/doc/h2load.1.rst new file mode 100644 index 0000000..85ed651 --- /dev/null +++ b/doc/h2load.1.rst @@ -0,0 +1,434 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: h2load + +h2load(1) +========= + +SYNOPSIS +-------- + +**h2load** [OPTIONS]... [URI]... + +DESCRIPTION +----------- + +benchmarking tool for HTTP/2 server + +.. describe:: + + 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:: --alpn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. The parameter must be delimited + by a single comma only and any white spaces are treated + as a part of protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --h1 + + Short hand for :option:`--alpn-list`\=http/1.1 + :option:`--no-tls-proto`\=http/1.1, which effectively force + http/1.1 for both http and https URI. + +.. option:: --header-table-size= + + 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/doc/h2load.h2r b/doc/h2load.h2r new file mode 100644 index 0000000..50d9977 --- /dev/null +++ b/doc/h2load.h2r @@ -0,0 +1,120 @@ +.. _h2load-1-output: + +OUTPUT +------ + +requests + total + The number of requests h2load was instructed to make. + started + The number of requests h2load has started. + done + The number of requests completed. + succeeded + The number of requests completed successfully. Only HTTP status + code 2xx or3xx are considered as success. + failed + The number of requests failed, including HTTP level failures + (non-successful HTTP status code). + errored + The number of requests failed, except for HTTP level failures. + This is the subset of the number reported in ``failed`` and most + likely the network level failures or stream was reset by + RST_STREAM. + timeout + The number of requests whose connection timed out before they were + completed. This is the subset of the number reported in + ``errored``. + +status codes + The number of status code h2load received. + +traffic + total + The number of bytes received from the server "on the wire". If + requests were made via TLS, this value is the number of decrypted + bytes. + headers + The number of response header bytes from the server without + decompression. The ``space savings`` shows efficiency of header + compression. Let ``decompressed(headers)`` to the number of bytes + used for header fields after decompression. The ``space savings`` + is calculated by (1 - ``headers`` / ``decompressed(headers)``) * + 100. For HTTP/1.1, this is usually 0.00%, since it does not have + header compression. For HTTP/2, it shows some insightful numbers. + data + The number of response body bytes received from the server. + +time for request + min + The minimum time taken for request and response. + max + The maximum time taken for request and response. + mean + The mean time taken for request and response. + sd + The standard deviation of the time taken for request and response. + +/- sd + The fraction of the number of requests within standard deviation + range (mean +/- sd) against total number of successful requests. + +time for connect + min + The minimum time taken to connect to a server including TLS + handshake. + max + The maximum time taken to connect to a server including TLS + handshake. + mean + The mean time taken to connect to a server including TLS + handshake. + sd + The standard deviation of the time taken to connect to a server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +time for 1st byte (of (decrypted in case of TLS) application data) + min + The minimum time taken to get 1st byte from a server. + max + The maximum time taken to get 1st byte from a server. + mean + The mean time taken to get 1st byte from a server. + sd + The standard deviation of the time taken to get 1st byte from a + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +req/s + min + The minimum request per second among all clients. + max + The maximum request per second among all clients. + mean + The mean request per second among all clients. + sd + The standard deviation of request per second among all clients. + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +FLOW CONTROL +------------ + +h2load sets large flow control window by default, and effectively +disables flow control to avoid under utilization of server +performance. To set smaller flow control window, use :option:`-w` and +:option:`-W` options. For example, use ``-w16 -W16`` to set default +window size described in HTTP/2 protocol specification. + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`nghttpx(1)` diff --git a/doc/index.rst.in b/doc/index.rst.in new file mode 100644 index 0000000..2a49369 --- /dev/null +++ b/doc/index.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/index.rst diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..2e0500d --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,170 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` 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/doc/mkapiref.py b/doc/mkapiref.py new file mode 100755 index 0000000..df59fbc --- /dev/null +++ b/doc/mkapiref.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2020 nghttp2 contributors +# Copyright (c) 2020 ngtcp2 contributors +# Copyright (c) 2012 Tatsuhiro Tsujikawa +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Generates API reference from C source code. + +import re, sys, argparse, os.path + +class FunctionDoc: + def __init__(self, name, content, domain, filename): + self.name = name + self.content = content + self.domain = domain + if self.domain == 'function': + self.funcname = re.search(r'(nghttp2_[^ )]+)\(', self.name).group(1) + self.filename = filename + + def write(self, out): + out.write('.. {}:: {}\n'.format(self.domain, self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class StructDoc: + def __init__(self, name, content, members, member_domain): + self.name = name + self.content = content + self.members = members + self.member_domain = member_domain + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. {}:: {}\n'.format(self.member_domain, name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class EnumDoc: + def __init__(self, name, content, members): + self.name = name + self.content = content + self.members = members + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. enum:: {}\n'.format(name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class MacroDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. macro:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class MacroSectionDoc: + def __init__(self, content): + self.content = content + + def write(self, out): + out.write('\n') + c = ' '.join(self.content).strip() + out.write(c) + out.write('\n') + out.write('-' * len(c)) + out.write('\n\n') + +class TypedefDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. type:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +def make_api_ref(infile): + macros = [] + enums = [] + types = [] + functions = [] + while True: + line = infile.readline() + if not line: + break + elif line == '/**\n': + line = infile.readline() + doctype = line.split()[1] + if doctype == '@function': + functions.append(process_function('function', infile)) + elif doctype == '@functypedef': + types.append(process_function('type', infile)) + elif doctype == '@struct' or doctype == '@union': + types.append(process_struct(infile)) + elif doctype == '@enum': + enums.append(process_enum(infile)) + elif doctype == '@macro': + macros.append(process_macro(infile)) + elif doctype == '@macrosection': + macros.append(process_macrosection(infile)) + elif doctype == '@typedef': + types.append(process_typedef(infile)) + return macros, enums, types, functions + +def output( + title, indexfile, macrosfile, enumsfile, typesfile, funcsdir, + macros, enums, types, functions): + indexfile.write(''' +{title} +{titledecoration} + +.. toctree:: + :maxdepth: 1 + + {macros} + {enums} + {types} +'''.format( + title=title, titledecoration='='*len(title), + macros=os.path.splitext(os.path.basename(macrosfile.name))[0], + enums=os.path.splitext(os.path.basename(enumsfile.name))[0], + types=os.path.splitext(os.path.basename(typesfile.name))[0], +)) + + for doc in functions: + indexfile.write(' {}\n'.format(doc.funcname)) + + macrosfile.write(''' +Macros +====== +''') + for doc in macros: + doc.write(macrosfile) + + enumsfile.write(''' +Enums +===== +''') + for doc in enums: + doc.write(enumsfile) + + typesfile.write(''' +Types (structs, unions and typedefs) +==================================== +''') + for doc in types: + doc.write(typesfile) + + for doc in functions: + with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f: + f.write(''' +{funcname} +{secul} + +Synopsis +-------- + +*#include * + +'''.format(funcname=doc.funcname, secul='='*len(doc.funcname), + filename=doc.filename)) + doc.write(f) + +def process_macro(infile): + content = read_content(infile) + line = infile.readline() + macro_name = line.split()[1] + return MacroDoc(macro_name, content) + +def process_macrosection(infile): + content = read_content(infile) + return MacroSectionDoc(content) + +def process_typedef(infile): + content = read_content(infile) + typedef = infile.readline() + typedef = re.sub(r';\n$', '', typedef) + typedef = re.sub(r'typedef ', '', typedef) + return TypedefDoc(typedef, content) + +def process_enum(infile): + members = [] + enum_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + items = line.split() + member_name = items[0].rstrip(',') + if len(items) >= 3: + member_content.insert(0, '(``{}``) '\ + .format(' '.join(items[2:]).rstrip(','))) + members.append((member_name, member_content)) + elif line.startswith('}'): + enum_name = line.rstrip().split()[1] + enum_name = re.sub(r';$', '', enum_name) + break + return EnumDoc(enum_name, content, members) + +def process_struct(infile): + members = [] + struct_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + member_name = line.rstrip().rstrip(';') + members.append((member_name, member_content)) + elif line.startswith('}') or\ + (line.startswith('typedef ') and line.endswith(';\n')): + if line.startswith('}'): + index = 1 + else: + index = 3 + struct_name = line.rstrip().split()[index] + struct_name = re.sub(r';$', '', struct_name) + break + return StructDoc(struct_name, content, members, 'member') + +def process_function(domain, infile): + content = read_content(infile) + func_proto = [] + while True: + line = infile.readline() + if not line: + break + elif line == '\n': + break + else: + func_proto.append(line) + func_proto = ''.join(func_proto) + func_proto = re.sub(r';\n$', '', func_proto) + func_proto = re.sub(r'\s+', ' ', func_proto) + func_proto = re.sub(r'NGHTTP2_EXTERN ', '', func_proto) + func_proto = re.sub(r'typedef ', '', func_proto) + filename = os.path.basename(infile.name) + return FunctionDoc(func_proto, content, domain, filename) + +def read_content(infile): + content = [] + while True: + line = infile.readline() + if not line: + break + if re.match(r'\s*\*/\n', line): + break + else: + content.append(transform_content(line.rstrip())) + return content + +def arg_repl(matchobj): + return '*{}*'.format(matchobj.group(1).replace('*', '\\*')) + +def transform_content(content): + content = re.sub(r'^\s+\* ?', '', content) + content = re.sub(r'\|([^\s|]+)\|', arg_repl, content) + return content + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Generate API reference") + parser.add_argument('--title', default='API Reference', + help='title of index page') + parser.add_argument('index', type=argparse.FileType('w'), + help='index output file') + parser.add_argument('macros', type=argparse.FileType('w'), + help='macros section output file. The filename should be macros.rst') + parser.add_argument('enums', type=argparse.FileType('w'), + help='enums section output file. The filename should be enums.rst') + parser.add_argument('types', type=argparse.FileType('w'), + help='types section output file. The filename should be types.rst') + parser.add_argument('funcsdir', + help='functions doc output dir') + parser.add_argument('files', nargs='+', type=argparse.FileType('r'), + help='source file') + args = parser.parse_args() + macros = [] + enums = [] + types = [] + funcs = [] + for infile in args.files: + m, e, t, f = make_api_ref(infile) + macros.extend(m) + enums.extend(e) + types.extend(t) + funcs.extend(f) + funcs.sort(key=lambda x: x.funcname) + output( + args.title, + args.index, args.macros, args.enums, args.types, args.funcsdir, + macros, enums, types, funcs) diff --git a/doc/nghttp.1 b/doc/nghttp.1 new file mode 100644 index 0000000..332d9c6 --- /dev/null +++ b/doc/nghttp.1 @@ -0,0 +1,336 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTP" "1" "Jan 21, 2024" "1.59.0" "nghttp2" +.SH NAME +nghttp \- HTTP/2 client +.SH SYNOPSIS +.sp +\fBnghttp\fP [OPTIONS]... ... +.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/doc/nghttp.1.rst b/doc/nghttp.1.rst new file mode 100644 index 0000000..e10f3ee --- /dev/null +++ b/doc/nghttp.1.rst @@ -0,0 +1,276 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttp + +nghttp(1) +========= + +SYNOPSIS +-------- + +**nghttp** [OPTIONS]... ... + +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/doc/nghttp.h2r b/doc/nghttp.h2r new file mode 100644 index 0000000..9d2a90e --- /dev/null +++ b/doc/nghttp.h2r @@ -0,0 +1,57 @@ +DEPENDENCY BASED PRIORITY +------------------------- + +nghttp sends priority hints to server by default unless +:option:`--no-dep` is used. nghttp mimics the way Firefox employs to +manages dependency using idle streams. We follows the behaviour of +Firefox Nightly as of April, 2015, and nghttp's behaviour is very +static and could be different from Firefox in detail. But reproducing +the same behaviour of Firefox is not our goal. The goal is provide +the easy way to test out the dependency priority in server +implementation. + +When connection is established, nghttp sends 5 PRIORITY frames to idle +streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency +tree: + +.. code-block:: text + + +-----+ + |id=0 | + +-----+ + ^ ^ ^ + w=201 / | \ w=1 + / | \ + / w=101| \ + +-----+ +-----+ +-----+ + |id=3 | |id=5 | |id=7 | + +-----+ +-----+ +-----+ + ^ ^ + w=1 | w=1 | + | | + +-----+ +-----+ + |id=11| |id=9 | + +-----+ +-----+ + +In the above figure, ``id`` means stream ID, and ``w`` means weight. +The stream 0 is non-existence stream, and forms the root of the tree. +The stream 7 and 9 are not used for now. + +The URIs given in the command-line depend on stream 11 with the weight +given in :option:`-p` option, which defaults to 16. + +If :option:`-a` option is used, nghttp parses the resource pointed by +URI given in command-line as html, and extracts resource links from +it. When requesting those resources, nghttp uses dependency according +to its resource type. + +For CSS, and Javascript files inside "head" element, they depend on +stream 3 with the weight 2. The Javascript files outside "head" +element depend on stream 5 with the weight 2. The mages depend on +stream 11 with the weight 12. The other resources (e.g., icon) depend +on stream 11 with the weight 2. + +SEE ALSO +-------- + +:manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/doc/nghttp2.h.rst.in b/doc/nghttp2.h.rst.in new file mode 100644 index 0000000..29e641d --- /dev/null +++ b/doc/nghttp2.h.rst.in @@ -0,0 +1,4 @@ +nghttp2.h +========= + +.. literalinclude:: @top_srcdir@/lib/includes/nghttp2/nghttp2.h diff --git a/doc/nghttp2ver.h.rst.in b/doc/nghttp2ver.h.rst.in new file mode 100644 index 0000000..c6aa779 --- /dev/null +++ b/doc/nghttp2ver.h.rst.in @@ -0,0 +1,4 @@ +nghttp2ver.h +============ + +.. literalinclude:: @top_builddir@/lib/includes/nghttp2/nghttp2ver.h diff --git a/doc/nghttpd.1 b/doc/nghttpd.1 new file mode 100644 index 0000000..219a365 --- /dev/null +++ b/doc/nghttpd.1 @@ -0,0 +1,236 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTPD" "1" "Jan 21, 2024" "1.59.0" "nghttp2" +.SH NAME +nghttpd \- HTTP/2 server +.SH SYNOPSIS +.sp +\fBnghttpd\fP [OPTION]... [ ] +.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/doc/nghttpd.1.rst b/doc/nghttpd.1.rst new file mode 100644 index 0000000..654a025 --- /dev/null +++ b/doc/nghttpd.1.rst @@ -0,0 +1,186 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpd + +nghttpd(1) +========== + +SYNOPSIS +-------- + +**nghttpd** [OPTION]... [ ] + +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/doc/nghttpd.h2r b/doc/nghttpd.h2r new file mode 100644 index 0000000..e346cd1 --- /dev/null +++ b/doc/nghttpd.h2r @@ -0,0 +1,4 @@ +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/doc/nghttpx-howto.rst.in b/doc/nghttpx-howto.rst.in new file mode 100644 index 0000000..082ce51 --- /dev/null +++ b/doc/nghttpx-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/nghttpx-howto.rst diff --git a/doc/nghttpx.1 b/doc/nghttpx.1 new file mode 100644 index 0000000..e9742a5 --- /dev/null +++ b/doc/nghttpx.1 @@ -0,0 +1,2770 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTPX" "1" "Jan 21, 2024" "1.59.0" "nghttp2" +.SH NAME +nghttpx \- HTTP/2 proxy +.SH SYNOPSIS +.sp +\fBnghttpx\fP [OPTIONS]... [ ] +.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 \-\-alpn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. The parameter must be delimited +by a single comma only and any white spaces are treated +as a part of protocol string. +.sp +Default: \fBh2,h2\-16,h2\-14,http/1.1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verify\-client +Require and verify client certificate. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verify\-client\-cacert= +Path 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/doc/nghttpx.1.rst b/doc/nghttpx.1.rst new file mode 100644 index 0000000..03109e4 --- /dev/null +++ b/doc/nghttpx.1.rst @@ -0,0 +1,2526 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpx + +nghttpx(1) +========== + +SYNOPSIS +-------- + +**nghttpx** [OPTIONS]... [ ] + +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:: --alpn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. The parameter must be delimited + by a single comma only and any white spaces are treated + as a part of protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --verify-client + + Require and verify client certificate. + +.. option:: --verify-client-cacert= + + Path 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/doc/nghttpx.h2r b/doc/nghttpx.h2r new file mode 100644 index 0000000..12ac718 --- /dev/null +++ b/doc/nghttpx.h2r @@ -0,0 +1,719 @@ +FILES +----- + +*/etc/nghttpx/nghttpx.conf* + The default configuration file path nghttpx searches at startup. + The configuration file path can be changed using :option:`--conf` + option. + + Those lines which are staring ``#`` are treated as comment. + + The option name in the configuration file is the long command-line + option name with leading ``--`` stripped (e.g., ``frontend``). Put + ``=`` between option name and value. Don't put extra leading or + trailing spaces. + + When specifying arguments including characters which have special + meaning to a shell, we usually use quotes so that shell does not + interpret them. When writing this configuration file, quotes for + this purpose must not be used. For example, specify additional + request header field, do this: + + .. code-block:: text + + add-request-header=foo: bar + + instead of: + + .. code-block:: text + + add-request-header="foo: bar" + + The options which do not take argument in the command-line *take* + argument in the configuration file. Specify ``yes`` as an argument + (e.g., ``http2-proxy=yes``). If other string is given, it is + ignored. + + To specify private key and certificate file which are given as + positional arguments in command-line, use ``private-key-file`` and + ``certificate-file``. + + :option:`--conf` option cannot be used in the configuration file and + will be ignored if specified. + +Error log + Error log is written to stderr by default. It can be configured + using :option:`--errorlog-file`. The format of log message is as + follows: + + (:) + + + 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/doc/package_README.rst.in b/doc/package_README.rst.in new file mode 100644 index 0000000..dfa6b2d --- /dev/null +++ b/doc/package_README.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/README.rst diff --git a/doc/programmers-guide.rst b/doc/programmers-guide.rst new file mode 100644 index 0000000..820cd20 --- /dev/null +++ b/doc/programmers-guide.rst @@ -0,0 +1,526 @@ +Programmers' Guide +================== + +Architecture +------------ + +The most notable point in nghttp2 library architecture is it does not +perform any I/O. nghttp2 only performs HTTP/2 protocol stuff based on +input byte strings. It will call callback functions set by +applications while processing input. The output of nghttp2 is just +byte string. An application is responsible to send these output to +the remote peer. The callback functions may be called while producing +output. + +Not doing I/O makes embedding nghttp2 library in the existing code +base very easy. Usually, the existing applications have its own I/O +event loops. It is very hard to use nghttp2 in that situation if +nghttp2 does its own I/O. It also makes light weight language wrapper +for nghttp2 easy with the same reason. The down side is that an +application author has to write more code to write complete +application using nghttp2. This is especially true for simple "toy" +application. For the real applications, however, this is not the +case. This is because you probably want to support HTTP/1 which +nghttp2 does not provide, and to do that, you will need to write your +own HTTP/1 stack or use existing third-party library, and bind them +together with nghttp2 and I/O event loop. In this point, not +performing I/O in nghttp2 has more point than doing it. + +The primary object that an application uses is :type:`nghttp2_session` +object, which is opaque struct and its details are hidden in order to +ensure the upgrading its internal architecture without breaking the +backward compatibility. An application can set callbacks to +:type:`nghttp2_session` object through the dedicated object and +functions, and it also interacts with it via many API function calls. + +An application can create as many :type:`nghttp2_session` object as it +wants. But single :type:`nghttp2_session` object must be used by a +single thread at the same time. This is not so hard to enforce since +most event-based architecture applications use is single thread per +core, and handling one connection I/O is done by single thread. + +To feed input to :type:`nghttp2_session` object, one can use +`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` functions. +They behave similarly, and the difference is that +`nghttp2_session_recv()` will use :type:`nghttp2_read_callback` to get +input. On the other hand, `nghttp2_session_mem_recv()` will take +input as its parameter. If in doubt, use `nghttp2_session_mem_recv()` +since it is simpler, and could be faster since it avoids calling +callback function. + +To get output from :type:`nghttp2_session` object, one can use +`nghttp2_session_send()` or `nghttp2_session_mem_send()`. The +difference between them is that the former uses +:type:`nghttp2_send_callback` to pass output to an application. On +the other hand, the latter returns the output to the caller. If in +doubt, use `nghttp2_session_mem_send()` since it is simpler. But +`nghttp2_session_send()` might be easier to use if the output buffer +an application has is fixed sized. + +In general, an application should call `nghttp2_session_mem_send()` +when it gets input from underlying connection. Since there is great +chance to get something pushed into transmission queue while the call +of `nghttp2_session_mem_send()`, it is recommended to call +`nghttp2_session_mem_recv()` after `nghttp2_session_mem_send()`. + +There is a question when we are safe to close HTTP/2 session without +waiting for the closure of underlying connection. We offer 2 API +calls for this: `nghttp2_session_want_read()` and +`nghttp2_session_want_write()`. If they both return 0, application +can destroy :type:`nghttp2_session`, and then close the underlying +connection. But make sure that the buffered output has been +transmitted to the peer before closing the connection when +`nghttp2_session_mem_send()` is used, since +`nghttp2_session_want_write()` does not take into account the +transmission of the buffered data outside of :type:`nghttp2_session`. + +Includes +-------- + +To use the public APIs, include ``nghttp2/nghttp2.h``:: + + #include + +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/doc/security.rst b/doc/security.rst new file mode 100644 index 0000000..00b0c9c --- /dev/null +++ b/doc/security.rst @@ -0,0 +1 @@ +.. include:: ../doc/sources/security.rst diff --git a/doc/sources/building-android-binary.rst b/doc/sources/building-android-binary.rst new file mode 100644 index 0000000..339ac44 --- /dev/null +++ b/doc/sources/building-android-binary.rst @@ -0,0 +1,127 @@ +Building Android binary +======================= + +In this article, we briefly describe how to build Android binary using +`Android NDK `_ cross-compiler on +Debian Linux. + +The easiest way to build android binary is use Dockerfile.android. +See Dockerfile.android for more details. If you cannot use +Dockerfile.android for whatever reason, continue to read the rest of +this article. + +We offer ``android-config`` script to make the build easier. To make +the script work, NDK directory must be set to ``NDK`` environment +variable. NDK directory is the directory where NDK is unpacked: + +.. code-block:: text + + $ unzip android-ndk-$NDK_VERSION-linux.zip + $ cd android-ndk-$NDK_VERSION + $ export NDK=$PWD + +The dependent libraries, such as OpenSSL, libev, and c-ares should be +built with the same NDK toolchain and installed under +``$NDK/usr/local``. We recommend to build these libraries as static +library to make the deployment easier. libxml2 support is currently +disabled. + +Although zlib comes with Android NDK, it seems not to be a part of +public API, so we have to built it for our own. That also provides us +proper .pc file as a bonus. + +Before running ``android-config``, ``NDK`` environment variable must +be set to point to the correct path. + +You need to set ``NGHTTP2`` environment variable to the absolute path +to the source directory of nghttp2. + +To configure OpenSSL, use the following script: + +.. code-block:: sh + + #!/bin/sh + + . $NGHTTP2/android-env + + export ANDROID_NDK_HOME=$NDK + export PATH=$TOOLCHAIN/bin:$PATH + + ./Configure no-shared --prefix=$PREFIX android-arm64 + +And run the following script to build and install without +documentation: + +.. code-block:: sh + + #!/bin/sh + + . $NGHTTP2/android-env + + export PATH=$TOOLCHAIN/bin:$PATH + + make install_sw + +To configure libev, use the following script: + +.. code-block:: sh + + #!/bin/sh + + . $NGHTTP2/android-env + + ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared \ + --enable-static \ + CPPFLAGS=-I$PREFIX/include \ + LDFLAGS=-L$PREFIX/lib + +And run ``make install`` to build and install. + +To configure c-ares, use the following script: + +.. code-block:: sh + + #!/bin/sh -e + + . $NGHTTP2/android-env + + ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared + +And run ``make install`` to build and install. + +To configure zlib, use the following script: + +.. code-block:: sh + + #!/bin/sh -e + + . $NGHTTP2/android-env + + export HOST=$TARGET + + ./configure \ + --prefix=$PREFIX \ + --libdir=$PREFIX/lib \ + --includedir=$PREFIX/include \ + --static + +And run ``make install`` to build and install. + +After prerequisite libraries are prepared, run ``android-config`` and +then ``make`` to compile nghttp2 source files. + +If all went well, application binaries, such as nghttpx, are created +under src directory. Strip debugging information from the binary +using the following command: + +.. code-block:: text + + $ $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip src/nghttpx diff --git a/doc/sources/contribute.rst b/doc/sources/contribute.rst new file mode 100644 index 0000000..1e39f2b --- /dev/null +++ b/doc/sources/contribute.rst @@ -0,0 +1,56 @@ +Contribution Guidelines +======================= + +[This text was composed based on 1.2. License section of curl/libcurl +project.] + +When contributing with code, you agree to put your changes and new +code under the same license nghttp2 is already using unless stated and +agreed otherwise. + +When changing existing source code, you do not alter the copyright of +the original file(s). The copyright will still be owned by the +original creator(s) or those who have been assigned copyright by the +original author(s). + +By submitting a patch to the nghttp2 project, you are assumed to have +the right to the code and to be allowed by your employer or whatever +to hand over that patch/code to us. We will credit you for your +changes as far as possible, to give credit but also to keep a trace +back to who made what changes. Please always provide us with your +full real name when contributing! + +Coding style +------------ + +We use clang-format to format source code consistently. The +clang-format configuration file .clang-format is located at the root +directory. Since clang-format produces slightly different results +between versions, we currently use clang-format-15. + +To detect any violation to the coding style, we recommend to setup git +pre-commit hook to check coding style of the changes you introduced. +The pre-commit file is located at the root directory. Copy it under +.git/hooks and make sure that it is executable. The pre-commit script +uses clang-format-diff.py to detect any style errors. If it is not in +your PATH or it exists under different name (e.g., +clang-format-diff-14 in debian), either add it to PATH variable or add +git option ``clangformatdiff.binary`` to point to the script. + +For emacs users, integrating clang-format to emacs is very easy. +clang-format.el should come with clang distribution. If it is not +found, download it from `here +`_. +And add these lines to your .emacs file: + +.. code-block:: lisp + + ;; From + ;; https://code.google.com/p/chromium/wiki/Emacs#Use_Google's_C++_style! + (load "//clang-format.el") + (add-hook 'c-mode-common-hook + (function (lambda () (local-set-key (kbd "TAB") + 'clang-format-region)))) + +You can find other editor integration in +http://clang.llvm.org/docs/ClangFormat.html. diff --git a/doc/sources/h2load-howto.rst b/doc/sources/h2load-howto.rst new file mode 100644 index 0000000..879a0eb --- /dev/null +++ b/doc/sources/h2load-howto.rst @@ -0,0 +1,142 @@ +.. program:: h2load + +h2load - HTTP/2 benchmarking tool - HOW-TO +========================================== + +:doc:`h2load.1` is benchmarking tool for HTTP/2 and HTTP/1.1. It +supports SSL/TLS and clear text for all supported protocols. + +Compiling from source +--------------------- + +h2load is compiled alongside nghttp2 and requires that the +``--enable-app`` flag is passed to ``./configure`` and `required +dependencies `_ are +available during compilation. For details on compiling, see `nghttp2: +Building from Git +`_. + +Basic Usage +----------- + +In order to set benchmark settings, specify following 3 options. + +:option:`-n` + The number of total requests. Default: 1 + +:option:`-c` + The number of concurrent clients. Default: 1 + +:option:`-m` + The max concurrent streams to issue per client. Default: 1 + +For SSL/TLS connection, the protocol will be negotiated via ALPN. You +can set specific protocols in :option:`--alpn-list` option. For +cleartext connection, the default protocol is HTTP/2. To change the +protocol in cleartext connection, use :option:`--no-tls-proto` option. +For convenience, :option:`--h1` option forces HTTP/1.1 for both +cleartext and SSL/TLS connections. + +Here is a command-line to perform benchmark to URI \https://localhost +using total 100000 requests, 100 concurrent clients and 10 max +concurrent streams: + +.. code-block:: text + + $ h2load -n100000 -c100 -m10 https://localhost + +The benchmarking result looks like this: + +.. code-block:: text + + finished in 7.08s, 141164.80 req/s, 555.33MB/s + requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout + status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx + traffic: 4125025824 bytes total, 11023424 bytes headers (space savings 93.07%), 4096000000 bytes data + min max mean sd +/- sd + time for request: 15.31ms 146.85ms 69.78ms 9.26ms 92.43% + time for connect: 1.08ms 25.04ms 10.71ms 9.80ms 64.00% + time to 1st byte: 25.36ms 184.96ms 79.11ms 53.97ms 78.00% + req/s (client) : 1412.04 1447.84 1426.52 10.57 63.00% + +See the h2load manual page :ref:`h2load-1-output` section for the +explanation of the above numbers. + +Timing-based load-testing +------------------------- + +As of v1.26.0, h2load supports timing-based load-testing. This method +performs load-testing in terms of a given duration instead of a +pre-defined number of requests. The new option :option:`--duration` +specifies how long the load-testing takes. For example, +``--duration=10`` makes h2load perform load-testing against a server +for 10 seconds. You can also specify a “warming-up” period with +:option:`--warm-up-time`. If :option:`--duration` is used, +:option:`-n` option is ignored. + +The following command performs load-testing for 10 seconds after 5 +seconds warming up period: + +.. code-block:: text + + $ h2load -c100 -m100 --duration=10 --warm-up-time=5 https://localhost + +Flow Control +------------ + +HTTP/2 has flow control and it may affect benchmarking results. By +default, h2load uses large enough flow control window, which +effectively disables flow control. To adjust receiver flow control +window size, there are following options: + +:option:`-w` + Sets the stream level initial window size to + (2**)-1. + +:option:`-W` + Sets the connection level initial window size to + (2**)-1. + +Multi-Threading +--------------- + +Sometimes benchmarking client itself becomes a bottleneck. To remedy +this situation, use :option:`-t` option to specify the number of native +thread to use. + +:option:`-t` + The number of native threads. Default: 1 + +Selecting protocol for clear text +--------------------------------- + +By default, if \http:// URI is given, HTTP/2 protocol is used. To +change the protocol to use for clear text, use :option:`-p` option. + +Multiple URIs +------------- + +If multiple URIs are specified, they are used in round robin manner. + +.. note:: + + Please note that h2load uses scheme, host and port in the first URI + and ignores those parts in the rest of the URIs. + +UNIX domain socket +------------------ + +To request against UNIX domain socket, use :option:`--base-uri`, and +specify ``unix:`` followed by the path to UNIX domain socket. For +example, if UNIX domain socket is ``/tmp/nghttpx.sock``, use +``--base-uri=unix:/tmp/nghttpx.sock``. h2load uses scheme, host and +port in the first URI in command-line or input file. + +HTTP/3 +------ + +h2load supports HTTP/3 if it is built with HTTP/3 enabled. HTTP/3 +support is experimental. + +In order to send HTTP/3 request, specify ``h3`` to +:option:`--alpn-list`. diff --git a/doc/sources/index.rst b/doc/sources/index.rst new file mode 100644 index 0000000..b03c348 --- /dev/null +++ b/doc/sources/index.rst @@ -0,0 +1,50 @@ +.. nghttp2 documentation main file, created by + sphinx-quickstart on Sun Mar 11 22:57:49 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +nghttp2 - HTTP/2 C Library +============================ + +This is an implementation of Hypertext Transfer Protocol version 2. + +The project is hosted at `github.com/nghttp2/nghttp2 +`_. + +Contents: + +.. toctree:: + :maxdepth: 2 + + package_README + contribute + security + building-android-binary + tutorial-client + tutorial-server + tutorial-hpack + nghttp.1 + nghttpd.1 + nghttpx.1 + h2load.1 + nghttpx-howto + h2load-howto + programmers-guide + apiref + nghttp2.h + nghttp2ver.h + Source + Issues + nghttp2.org + +Released Versions +================= + +https://github.com/nghttp2/nghttp2/releases + +Resources +--------- + +* HTTP/2 https://tools.ietf.org/html/rfc7540 +* HPACK https://tools.ietf.org/html/rfc7541 +* HTTP Alternative Services https://tools.ietf.org/html/rfc7838 diff --git a/doc/sources/nghttpx-howto.rst b/doc/sources/nghttpx-howto.rst new file mode 100644 index 0000000..50412f7 --- /dev/null +++ b/doc/sources/nghttpx-howto.rst @@ -0,0 +1,642 @@ +.. program:: nghttpx + +nghttpx - HTTP/2 proxy - HOW-TO +=============================== + +:doc:`nghttpx.1` is a proxy translating protocols between HTTP/2 and +other protocols (e.g., HTTP/1). It operates in several modes and each +mode may require additional programs to work with. This article +describes each operation mode and explains the intended use-cases. It +also covers some useful options later. + +Default mode +------------ + +If nghttpx is invoked without :option:`--http2-proxy`, it operates in +default mode. In this mode, it works as reverse proxy (gateway) for +HTTP/3, HTTP/2 and HTTP/1 clients to backend servers. This is also +known as "HTTP/2 router". + +By default, frontend connection is encrypted using SSL/TLS. So +server's private key and certificate must be supplied to the command +line (or through configuration file). In this case, the frontend +protocol selection will be done via ALPN. + +To turn off encryption on frontend connection, use ``no-tls`` keyword +in :option:`--frontend` option. HTTP/2 and HTTP/1 are available on +the frontend, and an HTTP/1 connection can be upgraded to HTTP/2 using +HTTP Upgrade. Starting HTTP/2 connection by sending HTTP/2 connection +preface is also supported. + +In order to receive HTTP/3 traffic, use ``quic`` parameter in +:option:`--frontend` option (.e.g, ``--frontend='*,443;quic'``) + +nghttpx can listen on multiple frontend addresses. This is achieved +by using multiple :option:`--frontend` options. For each frontend +address, TLS can be enabled or disabled. + +By default, backend connections are not encrypted. To enable TLS +encryption on backend connections, use ``tls`` keyword in +:option:`--backend` option. Using patterns and ``proto`` keyword in +:option:`--backend` option, backend application protocol can be +specified per host/request path pattern. It means that you can use +both HTTP/2 and HTTP/1 in backend connections at the same time. Note +that default backend protocol is HTTP/1.1. To use HTTP/2 in backend, +you have to specify ``h2`` in ``proto`` keyword in :option:`--backend` +explicitly. + +The backend is supposed to be a Web server. For example, to make +nghttpx listen to encrypted HTTP/2 requests at port 8443, and a +backend Web server is configured to listen to HTTP requests at port +8080 on the same host, run nghttpx command-line like this: + +.. code-block:: text + + $ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt + +Then an HTTP/2 enabled client can access the nghttpx server using HTTP/2. For +example, you can send a GET request using nghttp: + +.. code-block:: text + + $ nghttp -nv https://localhost:8443/ + +HTTP/2 proxy mode +----------------- + +If nghttpx is invoked with :option:`--http2-proxy` (or its shorthand +:option:`-s`) option, it operates in HTTP/2 proxy mode. The supported +protocols in frontend and backend connections are the same as in `default +mode`_. The difference is that this mode acts like a forward proxy and +assumes the backend is an HTTP proxy server (e.g., Squid, Apache Traffic +Server). HTTP/1 requests must include an absolute URI in request line. + +By default, the frontend connection is encrypted. So this mode is +also called secure proxy. + +To turn off encryption on the frontend connection, use ``no-tls`` keyword +in :option:`--frontend` option. + +The backend must be an HTTP proxy server. nghttpx supports multiple +backend server addresses. It translates incoming requests to HTTP +request to backend server. The backend server performs real proxy +work for each request, for example, dispatching requests to the origin +server and caching contents. + +The backend connection is not encrypted by default. To enable +encryption, use ``tls`` keyword in :option:`--backend` option. The +default backend protocol is HTTP/1.1. To use HTTP/2 in backend +connection, use :option:`--backend` option, and specify ``h2`` in +``proto`` keyword explicitly. + +For example, to make nghttpx listen to encrypted HTTP/2 requests at +port 8443, and a backend HTTP proxy server is configured to listen to +HTTP/1 requests at port 8080 on the same host, run nghttpx command-line +like this: + +.. code-block:: text + + $ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt + +At the time of this writing, Firefox 41 and Chromium v46 can use +nghttpx as HTTP/2 proxy. + +To make Firefox or Chromium use nghttpx as HTTP/2 proxy, user has to +create proxy.pac script file like this: + +.. code-block:: javascript + + function FindProxyForURL(url, host) { + return "HTTPS SERVERADDR:PORT"; + } + +``SERVERADDR`` and ``PORT`` is the hostname/address and port of the +machine nghttpx is running. Please note that both Firefox and +Chromium require valid certificate for secure proxy. + +For Firefox, open Preference window and select Advanced then click +Network tab. Clicking Connection Settings button will show the +dialog. Select "Automatic proxy configuration URL" and enter the path +to proxy.pac file, something like this: + +.. code-block:: text + + file:///path/to/proxy.pac + +For Chromium, use following command-line: + +.. code-block:: text + + $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn + +As HTTP/1 proxy server, Squid may work as out-of-box. Traffic server +requires to be configured as forward proxy. Here is the minimum +configuration items to edit: + +.. code-block:: text + + CONFIG proxy.config.reverse_proxy.enabled INT 0 + CONFIG proxy.config.url_remap.remap_required INT 0 + +Consult Traffic server `documentation +`_ +to know how to configure traffic server as forward proxy and its +security implications. + +ALPN support +------------ + +ALPN support requires OpenSSL >= 1.0.2. + +Disable frontend SSL/TLS +------------------------ + +The frontend connections are encrypted with SSL/TLS by default. To +turn off SSL/TLS, use ``no-tls`` keyword in :option:`--frontend` +option. If this option is used, the private key and certificate are +not required to run nghttpx. + +Enable backend SSL/TLS +---------------------- + +The backend connections are not encrypted by default. To enable +SSL/TLS encryption, use ``tls`` keyword in :option:`--backend` option. + +Enable SSL/TLS on memcached connection +-------------------------------------- + +By default, memcached connection is not encrypted. To enable +encryption, use ``tls`` keyword in +:option:`--tls-ticket-key-memcached` for TLS ticket key, and +:option:`--tls-session-cache-memcached` for TLS session cache. + +Specifying additional server certificates +----------------------------------------- + +nghttpx accepts additional server private key and certificate pairs +using :option:`--subcert` option. It can be used multiple times. + +Specifying additional CA certificate +------------------------------------ + +By default, nghttpx tries to read CA certificate from system. But +depending on the system you use, this may fail or is not supported. +To specify CA certificate manually, use :option:`--cacert` option. +The specified file must be PEM format and can contain multiple +certificates. + +By default, nghttpx validates server's certificate. If you want to +turn off this validation, knowing this is really insecure and what you +are doing, you can use :option:`--insecure` option to disable +certificate validation. + +Read/write rate limit +--------------------- + +nghttpx supports transfer rate limiting on frontend connections. You +can do rate limit per frontend connection for reading and writing +individually. + +To perform rate limit for reading, use :option:`--read-rate` and +:option:`--read-burst` options. For writing, use +:option:`--write-rate` and :option:`--write-burst`. + +Please note that rate limit is performed on top of TCP and nothing to +do with HTTP/2 flow control. + +Rewriting location header field +------------------------------- + +nghttpx automatically rewrites location response header field if the +following all conditions satisfy: + +* In the default mode (:option:`--http2-proxy` is not used) +* :option:`--no-location-rewrite` is not used +* URI in location header field is an absolute URI +* URI in location header field includes non empty host component. +* host (without port) in URI in location header field must match the + host appearing in ``:authority`` or ``host`` header field. + +When rewrite happens, URI scheme is replaced with the ones used in +frontend, and authority is replaced with which appears in +``:authority``, or ``host`` request header field. ``:authority`` +header field has precedence over ``host``. + +Hot swapping +------------ + +nghttpx supports hot swapping using signals. The hot swapping in +nghttpx is multi step process. First send USR2 signal to nghttpx +process. It will do fork and execute new executable, using same +command-line arguments and environment variables. + +As of nghttpx version 1.20.0, that is all you have to do. The new +main process sends QUIT signal to the original process, when it is +ready to serve requests, to shut it down gracefully. + +For earlier versions of nghttpx, you have to do one more thing. At +this point, both current and new processes can accept requests. To +gracefully shutdown current process, send QUIT signal to current +nghttpx process. When all existing frontend connections are done, the +current process will exit. At this point, only new nghttpx process +exists and serves incoming requests. + +If you want to just reload configuration file without executing new +binary, send SIGHUP to nghttpx main process. + +Re-opening log files +-------------------- + +When rotating log files, it is desirable to re-open log files after +log rotation daemon renamed existing log files. To tell nghttpx to +re-open log files, send USR1 signal to nghttpx process. It will +re-open files specified by :option:`--accesslog-file` and +:option:`--errorlog-file` options. + +Multiple frontend addresses +--------------------------- + +nghttpx can listen on multiple frontend addresses. To specify them, +just use :option:`--frontend` (or its shorthand :option:`-f`) option +repeatedly. TLS can be enabled or disabled per frontend address +basis. For example, to listen on port 443 with TLS enabled, and on +port 80 without TLS: + +.. code-block:: text + + frontend=*,443 + frontend=*,80;no-tls + + +Multiple backend addresses +-------------------------- + +nghttpx supports multiple backend addresses. To specify them, just +use :option:`--backend` (or its shorthand :option:`-b`) option +repeatedly. For example, to use ``192.168.0.10:8080`` and +``192.168.0.11:8080``, use command-line like this: +``-b192.168.0.10,8080 -b192.168.0.11,8080``. In configuration file, +this looks like: + +.. code-block:: text + + backend=192.168.0.10,8080 + backend=192.168.0.11,8008 + +nghttpx can route request to different backend according to request +host and path. For example, to route request destined to host +``doc.example.com`` to backend server ``docserv:3000``, you can write +like so: + +.. code-block:: text + + backend=docserv,3000;doc.example.com/ + +When you write this option in command-line, you should enclose +argument with single or double quotes, since the character ``;`` has a +special meaning in shell. + +To route, request to request path ``/foo`` to backend server +``[::1]:8080``, you can write like so: + +.. code-block:: text + + backend=::1,8080;/foo + +If the last character of path pattern is ``/``, all request paths +which start with that pattern match: + +.. code-block:: text + + backend=::1,8080;/bar/ + +The request path ``/bar/buzz`` matches the ``/bar/``. + +You can use ``*`` at the end of the path pattern to make it wildcard +pattern. ``*`` must match at least one character: + +.. code-block:: text + + backend=::1,8080;/sample* + +The request path ``/sample1/foo`` matches the ``/sample*`` pattern. + +Of course, you can specify both host and request path at the same +time: + +.. code-block:: text + + backend=192.168.0.10,8080;example.com/foo + +We can use ``*`` in the left most position of host to achieve wildcard +suffix match. If ``*`` is the left most character, then the remaining +string should match the request host suffix. ``*`` must match at +least one character. For example, ``*.example.com`` matches +``www.example.com`` and ``dev.example.com``, and does not match +``example.com`` and ``nghttp2.org``. The exact match (without ``*``) +always takes precedence over wildcard match. + +One important thing you have to remember is that we have to specify +default routing pattern for so called "catch all" pattern. To write +"catch all" pattern, just specify backend server address, without +pattern. + +Usually, host is the value of ``Host`` header field. In HTTP/2, the +value of ``:authority`` pseudo header field is used. + +When you write multiple backend addresses sharing the same routing +pattern, they are used as load balancing. For example, to use 2 +servers ``serv1:3000`` and ``serv2:3000`` for request host +``example.com`` and path ``/myservice``, you can write like so: + +.. code-block:: text + + backend=serv1,3000;example.com/myservice + backend=serv2,3000;example.com/myservice + +You can also specify backend application protocol in +:option:`--backend` option using ``proto`` keyword after pattern. +Utilizing this allows ngttpx to route certain request to HTTP/2, other +requests to HTTP/1. For example, to route requests to ``/ws/`` in +backend HTTP/1.1 connection, and use backend HTTP/2 for other +requests, do this: + +.. code-block:: text + + backend=serv1,3000;/;proto=h2 + backend=serv1,3000;/ws/;proto=http/1.1 + +The default backend protocol is HTTP/1.1. + +TLS can be enabled per pattern basis: + +.. code-block:: text + + backend=serv1,8443;/;proto=h2;tls + backend=serv2,8080;/ws/;proto=http/1.1 + +In the above case, connection to serv1 will be encrypted by TLS. On +the other hand, connection to serv2 will not be encrypted by TLS. + +Dynamic hostname lookup +----------------------- + +By default, nghttpx performs backend hostname lookup at start up, or +configuration reload, and keeps using them in its entire session. To +make nghttpx perform hostname lookup dynamically, use ``dns`` +parameter in :option:`--backend` option, like so: + +.. code-block:: text + + backend=foo.example.com,80;;dns + +nghttpx will cache resolved addresses for certain period of time. To +change this cache period, use :option:`--dns-cache-timeout`. + +Enable PROXY protocol +--------------------- + +PROXY protocol can be enabled per frontend. In order to enable PROXY +protocol, use ``proxyproto`` parameter in :option:`--frontend` option, +like so: + +.. code-block:: text + + frontend=*,443;proxyproto + +nghttpx supports both PROXY protocol v1 and v2. AF_UNIX in PROXY +protocol version 2 is ignored. + +Session affinity +---------------- + +Two kinds of session affinity are available: client IP, and HTTP +Cookie. + +To enable client IP based affinity, specify ``affinity=ip`` parameter +in :option:`--backend` option. If PROXY protocol is enabled, then an +address obtained from PROXY protocol is taken into consideration. + +To enable HTTP Cookie based affinity, specify ``affinity=cookie`` +parameter, and specify a name of cookie in ``affinity-cookie-name`` +parameter. Optionally, a Path attribute can be specified in +``affinity-cookie-path`` parameter: + +.. code-block:: text + + backend=127.0.0.1,3000;;affinity=cookie;affinity-cookie-name=nghttpxlb;affinity-cookie-path=/ + +Secure attribute of cookie is set if client connection is protected by +TLS. ``affinity-cookie-stickiness`` specifies the stickiness of this +affinity. If ``loose`` is given, which is the default, removing or +adding a backend server might break affinity. While ``strict`` is +given, removing the designated backend server breaks affinity, but +adding new backend server does not cause breakage. + +PSK cipher suites +----------------- + +nghttpx supports pre-shared key (PSK) cipher suites for both frontend +and backend TLS connections. For frontend connection, use +:option:`--psk-secrets` option to specify a file which contains PSK +identity and secrets. The format of the file is +``:``, where ```` is PSK identity, and +```` is PSK secret in hex, like so: + +.. code-block:: text + + client1:9567800e065e078085c241d54a01c6c3f24b3bab71a606600f4c6ad2c134f3b9 + client2:b1376c3f8f6dcf7c886c5bdcceecd1e6f1d708622b6ddd21bda26ebd0c0bca99 + +nghttpx server accepts any of the identity and secret pairs in the +file. The default cipher suite list does not contain PSK cipher +suites. In order to use PSK, PSK cipher suite must be enabled by +using :option:`--ciphers` option. The desired PSK cipher suite may be +listed in `HTTP/2 cipher block list +`_. In order to use +such PSK cipher suite with HTTP/2, disable HTTP/2 cipher block list by +using :option:`--no-http2-cipher-block-list` option. But you should +understand its implications. + +At the time of writing, even if only PSK cipher suites are specified +in :option:`--ciphers` option, certificate and private key are still +required. + +For backend connection, use :option:`--client-psk-secrets` option to +specify a file which contains single PSK identity and secret. The +format is the same as the file used by :option:`--psk-secrets` +described above, but only first identity and secret pair is solely +used, like so: + +.. code-block:: text + + client2:b1376c3f8f6dcf7c886c5bdcceecd1e6f1d708622b6ddd21bda26ebd0c0bca99 + +The default cipher suite list does not contain PSK cipher suites. In +order to use PSK, PSK cipher suite must be enabled by using +:option:`--client-ciphers` option. The desired PSK cipher suite may +be listed in `HTTP/2 cipher block list +`_. In order to use +such PSK cipher suite with HTTP/2, disable HTTP/2 cipher block list by +using :option:`--client-no-http2-cipher-block-list` option. But you +should understand its implications. + +TLSv1.3 +------- + +As of nghttpx v1.34.0, if it is built with OpenSSL 1.1.1 or later, it +supports TLSv1.3. 0-RTT data is supported, but by default its +processing is postponed until TLS handshake completes to mitigate +replay attack. This costs extra round trip and reduces effectiveness +of 0-RTT data. :option:`--tls-no-postpone-early-data` makes nghttpx +not wait for handshake to complete before forwarding request included +in 0-RTT to get full potential of 0-RTT data. In this case, nghttpx +adds ``Early-Data: 1`` header field when forwarding a request to a +backend server. All backend servers should recognize this header +field and understand that there is a risk for replay attack. See `RFC +8470 `_ for ``Early-Data`` header +field. + +nghttpx disables anti replay protection provided by OpenSSL. The anti +replay protection of OpenSSL requires that a resumed request must hit +the same server which generates the session ticket. Therefore it +might not work nicely in a deployment where there are multiple nghttpx +instances sharing ticket encryption keys via memcached. + +Because TLSv1.3 completely changes the semantics of cipher suite +naming scheme and structure, nghttpx provides the new option +:option:`--tls13-ciphers` and :option:`--tls13-client-ciphers` to +change preferred cipher list for TLSv1.3. + +WebSockets over HTTP/2 +---------------------- + +nghttpx supports `RFC 8441 `_ +Bootstrapping WebSockets with HTTP/2 for both frontend and backend +connections. This feature is enabled by default and no configuration +is required. + +WebSockets over HTTP/3 is also supported. + +HTTP/3 +------ + +nghttpx supports HTTP/3 if it is built with HTTP/3 support enabled. +HTTP/3 support is experimental. + +In order to listen UDP port to receive HTTP/3 traffic, +:option:`--frontend` option must have ``quic`` parameter: + +.. code-block:: text + + frontend=*,443;quic + +The above example makes nghttpx receive HTTP/3 traffic on UDP +port 443. + +nghttpx does not support HTTP/3 on backend connection. + +Hot swapping (SIGUSR2) or configuration reload (SIGHUP) require eBPF +program. Without eBPF, old worker processes keep getting HTTP/3 +traffic and do not work as intended. The QUIC keying material to +encrypt Connection ID must be set with +:option:`--frontend-quic-secret-file` and must provide the existing +keys in order to keep the existing connections alive during reload. + +The construction of Connection ID closely follows Block Cipher CID +Algorithm described in `QUIC-LB draft +`_. +A Connection ID that nghttpx generates is always 20 bytes long. It +uses first 2 bits as a configuration ID. The remaining bits in the +first byte are reserved and random. The next 4 bytes are server ID. +The next 4 bytes are used to route UDP datagram to a correct +``SO_REUSEPORT`` socket. The remaining bytes are randomly generated. +The server ID and the next 12 bytes are encrypted with AES-ECB. The +key is derived from the keying materials stored in a file specified by +:option:`--frontend-quic-secret-file`. The first 2 bits of keying +material in the file is used as a configuration ID. The remaining +bits and following 3 bytes are reserved and unused. The next 32 bytes +are used as an initial secret. The remaining 32 bytes are used as a +salt. The encryption key is generated by `HKDF +`_ with SHA256 and +these keying materials and ``connection id encryption key`` as info. + +In order announce that HTTP/3 endpoint is available, you should +specify alt-svc header field. For example, the following options send +alt-svc header field in HTTP/1.1 and HTTP/2 response: + +.. code-block:: text + + altsvc=h3,443,,,ma=3600 + http2-altsvc=h3,443,,,ma=3600 + +Migration from nghttpx v1.18.x or earlier +----------------------------------------- + +As of nghttpx v1.19.0, :option:`--ciphers` option only changes cipher +list for frontend TLS connection. In order to change cipher list for +backend connection, use :option:`--client-ciphers` option. + +Similarly, :option:`--no-http2-cipher-block-list` option only disables +HTTP/2 cipher block list for frontend connection. In order to disable +HTTP/2 cipher block list for backend connection, use +:option:`--client-no-http2-cipher-block-list` option. + +``--accept-proxy-protocol`` option was deprecated. Instead, use +``proxyproto`` parameter in :option:`--frontend` option to enable +PROXY protocol support per frontend. + +Migration from nghttpx v1.8.0 or earlier +---------------------------------------- + +As of nghttpx 1.9.0, ``--frontend-no-tls`` and ``--backend-no-tls`` +have been removed. + +To disable encryption on frontend connection, use ``no-tls`` keyword +in :option:`--frontend` potion: + +.. code-block:: text + + frontend=*,3000;no-tls + +The TLS encryption is now disabled on backend connection in all modes +by default. To enable encryption on backend connection, use ``tls`` +keyword in :option:`--backend` option: + +.. code-block:: text + + backend=127.0.0.1,8080;tls + +As of nghttpx 1.9.0, ``--http2-bridge``, ``--client`` and +``--client-proxy`` options have been removed. These functionality can +be used using combinations of options. + +Use following option instead of ``--http2-bridge``: + +.. code-block:: text + + backend=,;;proto=h2;tls + +Use following options instead of ``--client``: + +.. code-block:: text + + frontend=,;no-tls + backend=,;;proto=h2;tls + +Use following options instead of ``--client-proxy``: + +.. code-block:: text + + http2-proxy=yes + frontend=,;no-tls + backend=,;;proto=h2;tls + +We also removed ``--backend-http2-connections-per-worker`` option. It +was present because previously the number of backend h2 connection was +statically configured, and defaulted to 1. Now the number of backend +h2 connection is increased on demand. We know the maximum number of +concurrent streams per connection. When we push as many request as +the maximum concurrency to the one connection, we create another new +connection so that we can distribute load and avoid delay the request +processing. This is done automatically without any configuration. diff --git a/doc/sources/security.rst b/doc/sources/security.rst new file mode 100644 index 0000000..5a8fcd0 --- /dev/null +++ b/doc/sources/security.rst @@ -0,0 +1,33 @@ +Security Process +================ + +If you find a vulnerability in our software, please send the email to +"tatsuhiro.t at gmail dot com" about its details instead of submitting +issues on github issue page. It is a standard practice not to +disclose vulnerability information publicly until a fixed version is +released, or mitigation is worked out. In the future, we may setup a +dedicated mail address for this purpose. + +If we identify that the reported issue is really a vulnerability, we +open a new security advisory draft using `GitHub security feature +`_ 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/doc/sources/tutorial-client.rst b/doc/sources/tutorial-client.rst new file mode 100644 index 0000000..95a6230 --- /dev/null +++ b/doc/sources/tutorial-client.rst @@ -0,0 +1,464 @@ +Tutorial: HTTP/2 client +========================= + +In this tutorial, we are going to write a very primitive HTTP/2 +client. The complete source code, `libevent-client.c`_, is attached at +the end of this page. It also resides in the examples directory in +the archive or repository. + +This simple client takes a single HTTPS URI and retrieves the resource +at the URI. The synopsis is: + +.. code-block:: text + + $ libevent-client HTTPS_URI + +We use libevent in this tutorial to handle networking I/O. Please +note that nghttp2 itself does not depend on libevent. + +The client starts with some libevent and OpenSSL setup in the +``main()`` and ``run()`` functions. This setup isn't specific to +nghttp2, but one thing you should look at is setup of ALPN. Client +tells application protocols that it supports to server via ALPN:: + + static SSL_CTX *create_ssl_ctx(void) { + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); + + return ssl_ctx; + } + +Here we see ``SSL_CTX_get_alpn_protos()`` function call. We instructs +OpenSSL to notify the server that we support h2, ALPN identifier for +HTTP/2. + +The example client defines a couple of structs: + +We define and use a ``http2_session_data`` structure to store data +related to the HTTP/2 session:: + + typedef struct { + nghttp2_session *session; + struct evdns_base *dnsbase; + struct bufferevent *bev; + http2_stream_data *stream_data; + } http2_session_data; + +Since this program only handles one URI, it uses only one stream. We +store the single stream's data in a ``http2_stream_data`` structure +and the ``stream_data`` points to it. The ``http2_stream_data`` +structure is defined as follows:: + + typedef struct { + /* The NULL-terminated URI string to retrieve. */ + const char *uri; + /* Parsed result of the |uri| */ + struct http_parser_url *u; + /* The authority portion of the |uri|, not NULL-terminated */ + char *authority; + /* The path portion of the |uri|, including query, not + NULL-terminated */ + char *path; + /* The length of the |authority| */ + size_t authoritylen; + /* The length of the |path| */ + size_t pathlen; + /* The stream ID of this stream */ + int32_t stream_id; + } http2_stream_data; + +We create and initialize these structures in +``create_http2_session_data()`` and ``create_http2_stream_data()`` +respectively. + +``initiate_connection()`` is called to start the connection to the +remote server. It's defined as:: + + static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx, + const char *host, uint16_t port, + http2_session_data *session_data) { + int rv; + struct bufferevent *bev; + SSL *ssl; + + ssl = create_ssl(ssl_ctx); + bev = bufferevent_openssl_socket_new( + evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE); + bufferevent_enable(bev, EV_READ | EV_WRITE); + bufferevent_setcb(bev, readcb, writecb, eventcb, session_data); + rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase, + AF_UNSPEC, host, port); + + if (rv != 0) { + errx(1, "Could not connect to the remote host %s", host); + } + session_data->bev = bev; + } + +``initiate_connection()`` creates a bufferevent for the connection and +sets up three callbacks: ``readcb``, ``writecb``, and ``eventcb``. + +The ``eventcb()`` is invoked by the libevent event loop when an event +(e.g. connection has been established, timeout, etc.) occurs on the +underlying network socket:: + + static void eventcb(struct bufferevent *bev, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + int fd = bufferevent_getfd(bev); + int val = 1; + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + + fprintf(stderr, "Connected\n"); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "h2 is not negotiated\n"); + delete_http2_session_data(session_data); + return; + } + + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + initialize_nghttp2_session(session_data); + send_client_connection_header(session_data); + submit_request(session_data); + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + } + return; + } + if (events & BEV_EVENT_EOF) { + warnx("Disconnected from the remote host"); + } else if (events & BEV_EVENT_ERROR) { + warnx("Network error"); + } else if (events & BEV_EVENT_TIMEOUT) { + warnx("Timeout"); + } + delete_http2_session_data(session_data); + } + +Here we validate that HTTP/2 is negotiated, and if not, drop +connection. + +For ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and ``BEV_EVENT_TIMEOUT`` +events, we just simply tear down the connection. + +The ``BEV_EVENT_CONNECTED`` event is invoked when the SSL/TLS +handshake has completed successfully. After this we're ready to begin +communicating via HTTP/2. + +The ``initialize_nghttp2_session()`` function initializes the nghttp2 +session object and several callbacks:: + + static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_client_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); + } + +Since we are creating a client, we use `nghttp2_session_client_new()` +to initialize the nghttp2 session object. The callbacks setup are +explained later. + +The `delete_http2_session_data()` function destroys ``session_data`` +and frees its bufferevent, so the underlying connection is closed. It +also calls `nghttp2_session_del()` to delete the nghttp2 session +object. + +A HTTP/2 connection begins by sending the client connection preface, +which is a 24 byte magic byte string (:macro:`NGHTTP2_CLIENT_MAGIC`), +followed by a SETTINGS frame. The 24 byte magic string is sent +automatically by nghttp2. We send the SETTINGS frame in +``send_client_connection_header()``:: + + static void send_client_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); + } + } + +Here we specify SETTINGS_MAX_CONCURRENT_STREAMS as 100. This is not +needed for this tiny example program, it just demonstrates use of the +SETTINGS frame. To queue the SETTINGS frame for transmission, we call +`nghttp2_submit_settings()`. Note that `nghttp2_submit_settings()` +only queues the frame for transmission, and doesn't actually send it. +All ``nghttp2_submit_*()`` family functions have this property. To +actually send the frame, `nghttp2_session_send()` has to be called, +which is described (and called) later. + +After the transmission of the client connection header, we enqueue the +HTTP request in the ``submit_request()`` function:: + + static void submit_request(http2_session_data *session_data) { + int32_t stream_id; + http2_stream_data *stream_data = session_data->stream_data; + const char *uri = stream_data->uri; + const struct http_parser_url *u = stream_data->u; + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off], + u->field_data[UF_SCHEMA].len), + MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen), + MAKE_NV(":path", stream_data->path, stream_data->pathlen)}; + fprintf(stderr, "Request headers:\n"); + print_headers(stderr, hdrs, ARRLEN(hdrs)); + stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs, + ARRLEN(hdrs), NULL, stream_data); + if (stream_id < 0) { + errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id)); + } + + stream_data->stream_id = stream_id; + } + +We build the HTTP request header fields in ``hdrs``, which is an array +of :type:`nghttp2_nv`. There are four header fields to be sent: +``:method``, ``:scheme``, ``:authority``, and ``:path``. To queue the +HTTP request, we call `nghttp2_submit_request()`. The ``stream_data`` +is passed via the *stream_user_data* parameter, which is helpfully +later passed back to callback functions. + +`nghttp2_submit_request()` returns the newly assigned stream ID for +the request. + +The next bufferevent callback is ``readcb()``, which is invoked when +data is available to read from the bufferevent input buffer:: + + static void readcb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + delete_http2_session_data(session_data); + return; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + } + +In this function we feed all unprocessed, received data to the nghttp2 +session object using the `nghttp2_session_mem_recv()` function. +`nghttp2_session_mem_recv()` processes the received data and may +invoke nghttp2 callbacks and queue frames for transmission. Since +there may be pending frames for transmission, we call immediately +``session_send()`` to send them. ``session_send()`` is defined as +follows:: + + static int session_send(http2_session_data *session_data) { + int rv; + + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + +The `nghttp2_session_send()` function serializes pending frames into +wire format and calls the ``send_callback()`` function to send them. +``send_callback()`` has type :type:`nghttp2_send_callback` and is +defined as:: + + static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data, + size_t length, int flags _U_, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + bufferevent_write(bev, data, length); + return (ssize_t)length; + } + +Since we use bufferevent to abstract network I/O, we just write the +data to the bufferevent object. Note that `nghttp2_session_send()` +continues to write all frames queued so far. If we were writing the +data to the non-blocking socket directly using the ``write()`` system +call, we'd soon receive an ``EAGAIN`` or ``EWOULDBLOCK`` error, since +sockets have a limited send buffer. If that happens, it's possible to +return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the nghttp2 library +to stop sending further data. When writing to a bufferevent, you +should regulate the amount of data written, to avoid possible huge +memory consumption. In this example client however we don't implement +a limit. To see how to regulate the amount of buffered data, see the +``send_callback()`` in the server tutorial. + +The third bufferevent callback is ``writecb()``, which is invoked when +all data written in the bufferevent output buffer has been sent:: + + static void writecb(struct bufferevent *bev _U_, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0 && + evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) { + delete_http2_session_data(session_data); + } + } + +As described earlier, we just write off all data in `send_callback()`, +so there is no data to write in this function. All we have to do is +check if the connection should be dropped or not. The nghttp2 session +object keeps track of reception and transmission of GOAWAY frames and +other error conditions. Using this information, the nghttp2 session +object can state whether the connection should be dropped or not. +More specifically, when both `nghttp2_session_want_read()` and +`nghttp2_session_want_write()` return 0, the connection is no-longer +required and can be closed. Since we're using bufferevent and its +deferred callback option, the bufferevent output buffer may still +contain pending data when the ``writecb()`` is called. To handle this +situation, we also check whether the output buffer is empty or not. If +all of these conditions are met, then we drop the connection. + +Now let's look at the remaining nghttp2 callbacks setup in the +``initialize_nghttp2_setup()`` function. + +A server responds to the request by first sending a HEADERS frame. +The HEADERS frame consists of response header name/value pairs, and +the ``on_header_callback()`` is called for each name/value pair:: + + static int on_header_callback(nghttp2_session *session _U_, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags _U_, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + /* Print response headers for the initiated request. */ + print_header(stderr, name, namelen, value, valuelen); + break; + } + } + return 0; + } + +In this tutorial, we just print the name/value pairs on stderr. + +After the HEADERS frame has been fully received (and thus all response +header name/value pairs have been received), the +``on_frame_recv_callback()`` function is called:: + + static int on_frame_recv_callback(nghttp2_session *session _U_, + const nghttp2_frame *frame, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "All headers received\n"); + } + break; + } + return 0; + } + +``on_frame_recv_callback()`` is called for other frame types too. + +In this tutorial, we are just interested in the HTTP response HEADERS +frame. We check the frame type and its category (it should be +:macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). We also +check its stream ID. + +Next, zero or more DATA frames can be received. The +``on_data_chunk_recv_callback()`` function is invoked when a chunk of +data is received from the remote peer:: + + static int on_data_chunk_recv_callback(nghttp2_session *session _U_, + uint8_t flags _U_, int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + if (session_data->stream_data->stream_id == stream_id) { + fwrite(data, len, 1, stdout); + } + return 0; + } + +In our case, a chunk of data is HTTP response body. After checking the +stream ID, we just write the received data to stdout. Note the output +in the terminal may be corrupted if the response body contains some +binary data. + +The ``on_stream_close_callback()`` function is invoked when the stream +is about to close:: + + static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + nghttp2_error_code error_code, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + int rv; + + if (session_data->stream_data->stream_id == stream_id) { + fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id, + error_code); + rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; + } + +If the stream ID matches the one we initiated, it means that its +stream is going to be closed. Since we have finished receiving +resource we wanted (or the stream was reset by RST_STREAM from the +remote peer), we call `nghttp2_session_terminate_session()` to +commence closure of the HTTP/2 session gracefully. If you have +some data associated for the stream to be closed, you may delete it +here. diff --git a/doc/sources/tutorial-hpack.rst b/doc/sources/tutorial-hpack.rst new file mode 100644 index 0000000..36e82d9 --- /dev/null +++ b/doc/sources/tutorial-hpack.rst @@ -0,0 +1,126 @@ +Tutorial: HPACK API +=================== + +In this tutorial, we describe basic use of nghttp2's HPACK API. We +briefly describe the APIs for deflating and inflating header fields. +The full example of using these APIs, `deflate.c`_, is attached at the +end of this page. It also resides in the examples directory in the +archive or repository. + +Deflating (encoding) headers +---------------------------- + +First we need to initialize a :type:`nghttp2_hd_deflater` object using +the `nghttp2_hd_deflate_new()` function:: + + int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t deflate_hd_table_bufsize_max); + +This function allocates a :type:`nghttp2_hd_deflater` object, +initializes it, and assigns its pointer to ``*deflater_ptr``. The +*deflate_hd_table_bufsize_max* is the upper bound of header table size +the deflater will use. This will limit the memory usage by the +deflater object for the dynamic header table. If in doubt, just +specify 4096 here, which is the default upper bound of dynamic header +table buffer size. + +To encode header fields, use the `nghttp2_hd_deflate_hd()` function:: + + ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, + uint8_t *buf, size_t buflen, + const nghttp2_nv *nva, size_t nvlen); + +The *deflater* is the deflater object initialized by +`nghttp2_hd_deflate_new()` described above. The encoded byte string is +written to the buffer *buf*, which has length *buflen*. The *nva* is +a pointer to an array of headers fields, each of type +:type:`nghttp2_nv`. *nvlen* is the number of header fields which +*nva* contains. + +It is important to initialize and assign all members of +:type:`nghttp2_nv`. For security sensitive header fields (such as +cookies), set the :macro:`NGHTTP2_NV_FLAG_NO_INDEX` flag in +:member:`nghttp2_nv.flags`. Setting this flag prevents recovery of +sensitive header fields by compression based attacks: This is achieved +by not inserting the header field into the dynamic header table. + +`nghttp2_hd_deflate_hd()` processes all headers given in *nva*. The +*nva* must include all request or response header fields to be sent in +one HEADERS (or optionally following (multiple) CONTINUATION +frame(s)). The *buf* must have enough space to store the encoded +result, otherwise the function will fail. To estimate the upper bound +of the encoded result length, use `nghttp2_hd_deflate_bound()`:: + + size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nva, size_t nvlen); + +Pass this function the same parameters (*deflater*, *nva*, and +*nvlen*) which will be passed to `nghttp2_hd_deflate_hd()`. + +Subsequent calls to `nghttp2_hd_deflate_hd()` will use the current +encoder state and perform differential encoding, which yields HPAC's +fundamental compression gain. + +If `nghttp2_hd_deflate_hd()` fails, the failure is fatal and any +further calls with the same deflater object will fail. Thus it's very +important to use `nghttp2_hd_deflate_bound()` to determine the +required size of the output buffer. + +To delete a :type:`nghttp2_hd_deflater` object, use the +`nghttp2_hd_deflate_del()` function. + +Inflating (decoding) headers +---------------------------- + +A :type:`nghttp2_hd_inflater` object is used to inflate compressed +header data. To initialize the object, use +`nghttp2_hd_inflate_new()`:: + + int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr); + +To inflate header data, use `nghttp2_hd_inflate_hd2()`:: + + ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, + int in_final); + +`nghttp2_hd_inflate_hd2()` reads a stream of bytes and outputs a +single header field at a time. Multiple calls are normally required to +read a full stream of bytes and output all of the header fields. + +The *inflater* is the inflater object initialized above. The *nv_out* +is a pointer to a :type:`nghttp2_nv` into which one header field may +be stored. The *in* is a pointer to input data, and *inlen* is its +length. The caller is not required to specify the whole deflated +header data via *in* at once: Instead it can call this function +multiple times as additional data bytes become available. If +*in_final* is nonzero, it tells the function that the passed data is +the final sequence of deflated header data. + +The *inflate_flags* is an output parameter; on success the function +sets it to a bitset of flags. It will be described later. + +This function returns when each header field is inflated. When this +happens, the function sets the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag +in *inflate_flags*, and a header field is stored in *nv_out*. The +return value indicates the number of bytes read from *in* processed so +far, which may be less than *inlen*. The caller should call the +function repeatedly until all bytes are processed. Processed bytes +should be removed from *in*, and *inlen* should be adjusted +appropriately. + +If *in_final* is nonzero and all given data was processed, the +function sets the :macro:`NGHTTP2_HD_INFLATE_FINAL` flag in +*inflate_flags*. When you see this flag set, call the +`nghttp2_hd_inflate_end_headers()` function. + +If *in_final* is zero and the :macro:`NGHTTP2_HD_INFLATE_EMIT` flag is +not set, it indicates that all given data was processed. The caller +is required to pass additional data. + +Example usage of `nghttp2_hd_inflate_hd2()` is shown in the +`inflate_header_block()` function in `deflate.c`_. + +Finally, to delete a :type:`nghttp2_hd_inflater` object, use +`nghttp2_hd_inflate_del()`. diff --git a/doc/sources/tutorial-server.rst b/doc/sources/tutorial-server.rst new file mode 100644 index 0000000..41680bd --- /dev/null +++ b/doc/sources/tutorial-server.rst @@ -0,0 +1,577 @@ +Tutorial: HTTP/2 server +========================= + +In this tutorial, we are going to write a single-threaded, event-based +HTTP/2 web server, which supports HTTPS only. It can handle concurrent +multiple requests, but only the GET method is supported. The complete +source code, `libevent-server.c`_, is attached at the end of this +page. The source also resides in the examples directory in the +archive or repository. + +This simple server takes 3 arguments: The port number to listen on, +the path to your SSL/TLS private key file, and the path to your +certificate file. The synopsis is: + +.. code-block:: text + + $ libevent-server PORT /path/to/server.key /path/to/server.crt + +We use libevent in this tutorial to handle networking I/O. Please +note that nghttp2 itself does not depend on libevent. + +The server starts with some libevent and OpenSSL setup in the +``main()`` and ``run()`` functions. This setup isn't specific to +nghttp2, but one thing you should look at is setup of ALPN callback. +The ALPN callback is used by the server to select application +protocols offered by client. In ALPN, client sends the list of +supported application protocols, and server selects one of them. We +provide the callback for it:: + + static int alpn_select_proto_cb(SSL *ssl _U_, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg _U_) { + int rv; + + rv = nghttp2_select_alpn(out, outlen, in, inlen); + + if (rv != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; + } + + static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { + SSL_CTX *ssl_ctx; + EC_KEY *ecdh; + + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + + ... + + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); + + return ssl_ctx; + } + +In ``alpn_select_proto_cb()``, we use `nghttp2_select_alpn()` to +select application protocol. The `nghttp2_select_alpn()` returns 1 +only if it selected h2 (ALPN identifier for HTTP/2), and out +parameters were assigned accordingly. + +Next, let's take a look at the main structures used by the example +application: + +We use the ``app_context`` structure to store application-wide data:: + + struct app_context { + SSL_CTX *ssl_ctx; + struct event_base *evbase; + }; + +We use the ``http2_session_data`` structure to store session-level +(which corresponds to one HTTP/2 connection) data:: + + typedef struct http2_session_data { + struct http2_stream_data root; + struct bufferevent *bev; + app_context *app_ctx; + nghttp2_session *session; + char *client_addr; + } http2_session_data; + +We use the ``http2_stream_data`` structure to store stream-level data:: + + typedef struct http2_stream_data { + struct http2_stream_data *prev, *next; + char *request_path; + int32_t stream_id; + int fd; + } http2_stream_data; + +A single HTTP/2 session can have multiple streams. To manage them, we +use a doubly linked list: The first element of this list is pointed +to by the ``root->next`` in ``http2_session_data``. Initially, +``root->next`` is ``NULL``. + +libevent's bufferevent structure is used to perform network I/O, with +the pointer to the bufferevent stored in the ``http2_session_data`` +structure. Note that the bufferevent object is kept in +``http2_session_data`` and not in ``http2_stream_data``. This is +because ``http2_stream_data`` is just a logical stream multiplexed +over the single connection managed by the bufferevent in +``http2_session_data``. + +We first create a listener object to accept incoming connections. +libevent's ``struct evconnlistener`` is used for this purpose:: + + static void start_listen(struct event_base *evbase, const char *service, + app_context *app_ctx) { + int rv; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + #ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; + #endif /* AI_ADDRCONFIG */ + + rv = getaddrinfo(NULL, service, &hints, &res); + if (rv != 0) { + errx(1, NULL); + } + for (rp = res; rp; rp = rp->ai_next) { + struct evconnlistener *listener; + listener = evconnlistener_new_bind( + evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 16, rp->ai_addr, (int)rp->ai_addrlen); + if (listener) { + freeaddrinfo(res); + + return; + } + } + errx(1, "Could not start listener"); + } + +We specify the ``acceptcb`` callback, which is called when a new connection is +accepted:: + + static void acceptcb(struct evconnlistener *listener _U_, int fd, + struct sockaddr *addr, int addrlen, void *arg) { + app_context *app_ctx = (app_context *)arg; + http2_session_data *session_data; + + session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); + + bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); + } + +Here we create the ``http2_session_data`` object. The connection's +bufferevent is initialized at the same time. We specify three +callbacks for the bufferevent: ``readcb``, ``writecb``, and +``eventcb``. + +The ``eventcb()`` callback is invoked by the libevent event loop when an event +(e.g. connection has been established, timeout, etc.) occurs on the +underlying network socket:: + + static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + + fprintf(stderr, "%s connected\n", session_data->client_addr); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + delete_http2_session_data(session_data); + return; + } + + initialize_nghttp2_session(session_data); + + if (send_server_connection_header(session_data) != 0 || + session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + + return; + } + if (events & BEV_EVENT_EOF) { + fprintf(stderr, "%s EOF\n", session_data->client_addr); + } else if (events & BEV_EVENT_ERROR) { + fprintf(stderr, "%s network error\n", session_data->client_addr); + } else if (events & BEV_EVENT_TIMEOUT) { + fprintf(stderr, "%s timeout\n", session_data->client_addr); + } + delete_http2_session_data(session_data); + } + +Here we validate that HTTP/2 is negotiated, and if not, drop +connection. + +For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR``, and +``BEV_EVENT_TIMEOUT`` events, we just simply tear down the connection. +The ``delete_http2_session_data()`` function destroys the +``http2_session_data`` object and its associated bufferevent member. +As a result, the underlying connection is closed. + +The +``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake has +completed successfully. After this we are ready to begin communicating +via HTTP/2. + +The ``initialize_nghttp2_session()`` function initializes the nghttp2 +session object and several callbacks:: + + static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_server_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); + } + +Since we are creating a server, we use `nghttp2_session_server_new()` +to initialize the nghttp2 session object. We also setup 5 callbacks +for the nghttp2 session, these are explained later. + +The server now begins by sending the server connection preface, which +always consists of a SETTINGS frame. +``send_server_connection_header()`` configures and submits it:: + + static int send_server_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + +In the example SETTINGS frame we've set +SETTINGS_MAX_CONCURRENT_STREAMS to 100. `nghttp2_submit_settings()` +is used to queue the frame for transmission, but note it only queues +the frame for transmission, and doesn't actually send it. All +functions in the ``nghttp2_submit_*()`` family have this property. To +actually send the frame, `nghttp2_session_send()` should be used, as +described later. + +Since bufferevent may buffer more than the first 24 bytes from the client, we +have to process them here since libevent won't invoke callback functions for +this pending data. To process the received data, we call the +``session_recv()`` function:: + + static int session_recv(http2_session_data *session_data) { + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(session_data->bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + return -1; + } + if (session_send(session_data) != 0) { + return -1; + } + return 0; + } + +In this function, we feed all unprocessed but already received data to +the nghttp2 session object using the `nghttp2_session_mem_recv()` +function. The `nghttp2_session_mem_recv()` function processes the data +and may both invoke the previously setup callbacks and also queue +outgoing frames. To send any pending outgoing frames, we immediately +call ``session_send()``. + +The ``session_send()`` function is defined as follows:: + + static int session_send(http2_session_data *session_data) { + int rv; + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + +The `nghttp2_session_send()` function serializes the frame into wire +format and calls the ``send_callback()``, which is of type +:type:`nghttp2_send_callback`. The ``send_callback()`` is defined as +follows:: + + static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data, + size_t length, int flags _U_, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + /* Avoid excessive buffering in server side. */ + if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= + OUTPUT_WOULDBLOCK_THRESHOLD) { + return NGHTTP2_ERR_WOULDBLOCK; + } + bufferevent_write(bev, data, length); + return (ssize_t)length; + } + +Since we use bufferevent to abstract network I/O, we just write the +data to the bufferevent object. Note that `nghttp2_session_send()` +continues to write all frames queued so far. If we were writing the +data to a non-blocking socket directly using the ``write()`` system +call in the ``send_callback()``, we'd soon receive an ``EAGAIN`` or +``EWOULDBLOCK`` error since sockets have a limited send buffer. If +that happens, it's possible to return :macro:`NGHTTP2_ERR_WOULDBLOCK` +to signal the nghttp2 library to stop sending further data. But here, +when writing to the bufferevent, we have to regulate the amount data +to buffered ourselves to avoid using huge amounts of memory. To +achieve this, we check the size of the output buffer and if it reaches +more than or equal to ``OUTPUT_WOULDBLOCK_THRESHOLD`` bytes, we stop +writing data and return :macro:`NGHTTP2_ERR_WOULDBLOCK`. + +The next bufferevent callback is ``readcb()``, which is invoked when +data is available to read in the bufferevent input buffer:: + + static void readcb(struct bufferevent *bev _U_, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (session_recv(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + } + +In this function, we just call ``session_recv()`` to process incoming +data. + +The third bufferevent callback is ``writecb()``, which is invoked when all +data in the bufferevent output buffer has been sent:: + + static void writecb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { + return; + } + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0) { + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + } + +First we check whether we should drop the connection or not. The +nghttp2 session object keeps track of reception and transmission of +GOAWAY frames and other error conditions as well. Using this +information, the nghttp2 session object can state whether the +connection should be dropped or not. More specifically, if both +`nghttp2_session_want_read()` and `nghttp2_session_want_write()` +return 0, the connection is no-longer required and can be closed. +Since we are using bufferevent and its deferred callback option, the +bufferevent output buffer may still contain pending data when the +``writecb()`` is called. To handle this, we check whether the output +buffer is empty or not. If all of these conditions are met, we drop +connection. + +Otherwise, we call ``session_send()`` to process the pending output +data. Remember that in ``send_callback()``, we must not write all data to +bufferevent to avoid excessive buffering. We continue processing pending data +when the output buffer becomes empty. + +We have already described the nghttp2 callback ``send_callback()``. Let's +learn about the remaining nghttp2 callbacks setup in +``initialize_nghttp2_setup()`` function. + +The ``on_begin_headers_callback()`` function is invoked when the reception of +a header block in HEADERS or PUSH_PROMISE frame is started:: + + static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data); + return 0; + } + +We are only interested in the HEADERS frame in this function. Since +the HEADERS frame has several roles in the HTTP/2 protocol, we check +that it is a request HEADERS, which opens new stream. If the frame is +a request HEADERS, we create a ``http2_stream_data`` object to store +the stream related data. We associate the created +``http2_stream_data`` object with the stream in the nghttp2 session +object using `nghttp2_set_stream_user_data()`. The +``http2_stream_data`` object can later be easily retrieved from the +stream, without searching through the doubly linked list. + +In this example server, we want to serve files relative to the current working +directory in which the program was invoked. Each header name/value pair is +emitted via ``on_header_callback`` function, which is called after +``on_begin_headers_callback()``:: + + static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags _U_, + void *user_data _U_) { + http2_stream_data *stream_data; + const char PATH[] = ":path"; + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (!stream_data || stream_data->request_path) { + break; + } + if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for (j = 0; j < valuelen && value[j] != '?'; ++j) + ; + stream_data->request_path = percent_decode(value, j); + } + break; + } + return 0; + } + +We search for the ``:path`` header field among the request headers and +store the requested path in the ``http2_stream_data`` object. In this +example program, we ignore the ``:method`` header field and always +treat the request as a GET request. + +The ``on_frame_recv_callback()`` function is invoked when a frame is +fully received:: + + static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if (!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); + } + break; + default: + break; + } + return 0; + } + +First we retrieve the ``http2_stream_data`` object associated with the +stream in ``on_begin_headers_callback()`` using +`nghttp2_session_get_stream_user_data()`. If the requested path +cannot be served for some reason (e.g. file is not found), we send a +404 response using ``error_reply()``. Otherwise, we open +the requested file and send its content. We send the header field +``:status`` as a single response header. + +Sending the file content is performed by the ``send_response()`` function:: + + static int send_response(nghttp2_session *session, int32_t stream_id, + nghttp2_nv *nva, size_t nvlen, int fd) { + int rv; + nghttp2_data_provider data_prd; + data_prd.source.fd = fd; + data_prd.read_callback = file_read_callback; + + rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + +nghttp2 uses the :type:`nghttp2_data_provider` structure to send the +entity body to the remote peer. The ``source`` member of this +structure is a union, which can be either a void pointer or an int +(which is intended to be used as file descriptor). In this example +server, we use it as a file descriptor. We also set the +``file_read_callback()`` callback function to read the contents of the +file:: + + static ssize_t file_read_callback(nghttp2_session *session _U_, + int32_t stream_id _U_, uint8_t *buf, + size_t length, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data _U_) { + int fd = source->fd; + ssize_t r; + while ((r = read(fd, buf, length)) == -1 && errno == EINTR) + ; + if (r == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (r == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return r; + } + +If an error occurs while reading the file, we return +:macro:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. This tells the +library to send RST_STREAM to the stream. When all data has been +read, the :macro:`NGHTTP2_DATA_FLAG_EOF` flag is set to signal nghttp2 +that we have finished reading the file. + +The `nghttp2_submit_response()` function is used to send the response to the +remote peer. + +The ``on_stream_close_callback()`` function is invoked when the stream +is about to close:: + + static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code _U_, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + stream_data = nghttp2_session_get_stream_user_data(session, stream_id); + if (!stream_data) { + return 0; + } + remove_stream(session_data, stream_data); + delete_http2_stream_data(stream_data); + return 0; + } + +Lastly, we destroy the ``http2_stream_data`` object in this function, +since the stream is about to close and we no longer need the object. diff --git a/doc/tutorial-client.rst.in b/doc/tutorial-client.rst.in new file mode 100644 index 0000000..4f7fcfc --- /dev/null +++ b/doc/tutorial-client.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-client.rst + +libevent-client.c +----------------- + +.. literalinclude:: @top_srcdir@/examples/libevent-client.c diff --git a/doc/tutorial-hpack.rst.in b/doc/tutorial-hpack.rst.in new file mode 100644 index 0000000..832dedf --- /dev/null +++ b/doc/tutorial-hpack.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-hpack.rst + +deflate.c +--------- + +.. literalinclude:: @top_srcdir@/examples/deflate.c diff --git a/doc/tutorial-server.rst.in b/doc/tutorial-server.rst.in new file mode 100644 index 0000000..1972266 --- /dev/null +++ b/doc/tutorial-server.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-server.rst + +libevent-server.c +----------------- + +.. literalinclude:: @top_srcdir@/examples/libevent-server.c diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..25a7261 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,78 @@ +FROM debian:12 as build + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + git clang make binutils autoconf automake autotools-dev libtool \ + pkg-config \ + zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ + libelf-dev + +RUN git clone --depth 1 -b OpenSSL_1_1_1w+quic https://github.com/quictls/openssl && \ + cd openssl && \ + ./config --openssldir=/etc/ssl && \ + make -j$(nproc) && \ + make install_sw && \ + cd .. && \ + rm -rf openssl + +RUN git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && \ + autoreconf -i && \ + ./configure --enable-lib-only && \ + make -j$(nproc) && \ + make install-strip && \ + cd .. && \ + rm -rf nghttp3 + +RUN git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && \ + autoreconf -i && \ + ./configure --enable-lib-only \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" \ + PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ + make -j$(nproc) && \ + make install-strip && \ + cd .. && \ + rm -rf ngtcp2 + +RUN git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf && \ + cd libbpf && \ + PREFIX=/usr/local make -C src install && \ + cd .. && \ + rm -rf libbpf + +RUN git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \ + cd nghttp2 && \ + git submodule update --init && \ + autoreconf -i && \ + ./configure --disable-examples --disable-hpack-tools \ + --with-mruby --with-neverbleed \ + --enable-http3 --with-libbpf \ + CC=clang CXX=clang++ \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \ + LIBEV_LIBS="-l:libev.a" \ + JEMALLOC_LIBS="-l:libjemalloc.a" \ + LIBCARES_LIBS="-l:libcares.a" \ + ZLIB_LIBS="-l:libz.a" \ + LIBBPF_LIBS="-L/usr/local/lib64 -l:libbpf.a -l:libelf.a" \ + LDFLAGS="-static-libgcc -static-libstdc++" \ + PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ + make -j$(nproc) install-strip && \ + cd .. && \ + rm -rf nghttp2 + +FROM gcr.io/distroless/base-debian12 + +COPY --from=build \ + /usr/local/share/nghttp2/ \ + /usr/local/share/nghttp2/ +COPY --from=build \ + /usr/local/bin/h2load \ + /usr/local/bin/nghttpx \ + /usr/local/bin/nghttp \ + /usr/local/bin/nghttpd \ + /usr/local/bin/ +COPY --from=build /usr/local/lib/nghttp2/reuseport_kern.o \ + /usr/local/lib/nghttp2/ diff --git a/docker/README.rst b/docker/README.rst new file mode 100644 index 0000000..e08af23 --- /dev/null +++ b/docker/README.rst @@ -0,0 +1,25 @@ +Dockerfile +========== + +Dockerfile creates the applications bundled with nghttp2. +These applications are: + +- nghttp +- nghttpd +- nghttpx +- h2load + +HTTP/3 and eBPF features are enabled. + +In order to run nghttpx with HTTP/3 endpoint, you need to run the +image with the escalated privilege. Here is the example command-line +to run nghttpx to listen to HTTP/3 on port 443, assuming that the +current directory contains a private key and a certificate in +server.key and server.crt respectively: + +.. code-block:: text + + $ docker run --rm -it -v /path/to/certs:/shared --net=host --privileged \ + nghttp2 nghttpx \ + /shared/server.key /shared/server.crt \ + -f'*,443;quic' diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..16335a5 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,5 @@ +client +libevent-client +libevent-server +deflate +tiny-nghttpd diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..7a57bbf --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,37 @@ +if(ENABLE_EXAMPLES) + file(GLOB c_sources *.c) + set_source_files_properties(${c_sources} PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}") + file(GLOB cxx_sources *.cc) + set_source_files_properties(${cxx_sources} PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}") + + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include" + + ${LIBEVENT_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ) + + link_libraries( + nghttp2 + ${LIBEVENT_OPENSSL_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${APP_LIBRARIES} + ) + + add_executable(client client.c $ + $ + ) + add_executable(libevent-client libevent-client.c $ + $ + ) + add_executable(libevent-server libevent-server.c $ + $ + ) + add_executable(deflate deflate.c $ + $ + ) +endif() diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..7b682ed --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,54 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +EXTRA_DIST = CMakeLists.txt + +if ENABLE_EXAMPLES + +AM_CFLAGS = $(WARNCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/third-party \ + @LIBEVENT_OPENSSL_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ +LDADD = $(top_builddir)/lib/libnghttp2.la \ + $(top_builddir)/third-party/liburl-parser.la \ + @LIBEVENT_OPENSSL_LIBS@ \ + @OPENSSL_LIBS@ \ + @APPLDFLAGS@ + +noinst_PROGRAMS = client libevent-client libevent-server deflate + +client_SOURCES = client.c + +libevent_client_SOURCES = libevent-client.c + +libevent_server_SOURCES = libevent-server.c + +deflate_SOURCES = deflate.c + +endif # ENABLE_EXAMPLES diff --git a/examples/client.c b/examples/client.c new file mode 100644 index 0000000..ce8d1d0 --- /dev/null +++ b/examples/client.c @@ -0,0 +1,699 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +/* + * This program is written to show how to use nghttp2 API in C and + * intentionally made simple. + */ +#ifdef HAVE_CONFIG_H +# include +#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); +} + +/* + * Setup SSL/TLS context. + */ +static void init_ssl_ctx(SSL_CTX *ssl_ctx) { + /* Disable SSLv2 and enable all workarounds for buggy servers */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); +} + +static void ssl_handshake(SSL *ssl, int fd) { + int rv; + if (SSL_set_fd(ssl, fd) == 0) { + dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL)); + } + ERR_clear_error(); + rv = SSL_connect(ssl); + if (rv <= 0) { + dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL)); + } +} + +/* + * Connects to the host |host| and port |port|. This function returns + * the file descriptor of the client socket. + */ +static int connect_to(const char *host, uint16_t port) { + struct addrinfo hints; + int fd = -1; + int rv; + char service[NI_MAXSERV]; + struct addrinfo *res, *rp; + snprintf(service, sizeof(service), "%u", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + rv = getaddrinfo(host, service, &hints, &res); + if (rv != 0) { + dief("getaddrinfo", gai_strerror(rv)); + } + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + while ((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && + errno == EINTR) + ; + if (rv == 0) { + break; + } + close(fd); + fd = -1; + } + freeaddrinfo(res); + return fd; +} + +static void make_non_block(int fd) { + int flags, rv; + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + if (flags == -1) { + dief("fcntl", strerror(errno)); + } + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; + if (rv == -1) { + dief("fcntl", strerror(errno)); + } +} + +static void set_tcp_nodelay(int fd) { + int val = 1; + int rv; + rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if (rv == -1) { + dief("setsockopt", strerror(errno)); + } +} + +/* + * Update |pollfd| based on the state of |connection|. + */ +static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) { + pollfd->events = 0; + if (nghttp2_session_want_read(connection->session) || + connection->want_io == WANT_READ) { + pollfd->events |= POLLIN; + } + if (nghttp2_session_want_write(connection->session) || + connection->want_io == WANT_WRITE) { + pollfd->events |= POLLOUT; + } +} + +/* + * Submits the request |req| to the connection |connection|. This + * function does not send packets; just append the request to the + * internal queue in |connection->session|. + */ +static void submit_request(struct Connection *connection, struct Request *req) { + int32_t stream_id; + /* Make sure that the last item is NULL */ + const nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), + MAKE_NV_CS(":path", req->path), + MAKE_NV(":scheme", "https"), + MAKE_NV_CS(":authority", req->hostport), + MAKE_NV("accept", "*/*"), + MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)}; + + stream_id = nghttp2_submit_request(connection->session, NULL, nva, + sizeof(nva) / sizeof(nva[0]), NULL, req); + + if (stream_id < 0) { + diec("nghttp2_submit_request", stream_id); + } + + req->stream_id = stream_id; + printf("[INFO] Stream ID = %d\n", stream_id); +} + +/* + * Performs the network I/O. + */ +static void exec_io(struct Connection *connection) { + int rv; + rv = nghttp2_session_recv(connection->session); + if (rv != 0) { + diec("nghttp2_session_recv", rv); + } + rv = nghttp2_session_send(connection->session); + if (rv != 0) { + diec("nghttp2_session_send", rv); + } +} + +static void request_init(struct Request *req, const struct URI *uri) { + req->host = strcopy(uri->host, uri->hostlen); + req->port = uri->port; + req->path = strcopy(uri->path, uri->pathlen); + req->hostport = strcopy(uri->hostport, uri->hostportlen); + req->stream_id = -1; +} + +static void request_free(struct Request *req) { + free(req->host); + free(req->path); + free(req->hostport); +} + +/* + * Fetches the resource denoted by |uri|. + */ +static void fetch_uri(const struct URI *uri) { + nghttp2_session_callbacks *callbacks; + int fd; + SSL_CTX *ssl_ctx; + SSL *ssl; + struct Request req; + struct Connection connection; + int rv; + nfds_t npollfds = 1; + struct pollfd pollfds[1]; + + request_init(&req, uri); + + /* Establish connection and setup SSL */ + fd = connect_to(req.host, req.port); + if (fd == -1) { + die("Could not open file descriptor"); + } + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (ssl_ctx == NULL) { + dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL)); + } + init_ssl_ctx(ssl_ctx); + ssl = SSL_new(ssl_ctx); + if (ssl == NULL) { + dief("SSL_new", ERR_error_string(ERR_get_error(), NULL)); + } + /* To simplify the program, we perform SSL/TLS handshake in blocking + I/O. */ + ssl_handshake(ssl, fd); + + connection.ssl = ssl; + connection.want_io = IO_NONE; + + /* Here make file descriptor non-block */ + make_non_block(fd); + set_tcp_nodelay(fd); + + printf("[INFO] SSL/TLS handshake completed\n"); + + rv = nghttp2_session_callbacks_new(&callbacks); + + if (rv != 0) { + diec("nghttp2_session_callbacks_new", rv); + } + + setup_nghttp2_callbacks(callbacks); + + rv = nghttp2_session_client_new(&connection.session, callbacks, &connection); + + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + diec("nghttp2_session_client_new", rv); + } + + rv = nghttp2_submit_settings(connection.session, NGHTTP2_FLAG_NONE, NULL, 0); + + if (rv != 0) { + diec("nghttp2_submit_settings", rv); + } + + /* Submit the HTTP request to the outbound queue. */ + submit_request(&connection, &req); + + pollfds[0].fd = fd; + ctl_poll(pollfds, &connection); + + /* Event loop */ + while (nghttp2_session_want_read(connection.session) || + nghttp2_session_want_write(connection.session)) { + int nfds = poll(pollfds, npollfds, -1); + if (nfds == -1) { + dief("poll", strerror(errno)); + } + if (pollfds[0].revents & (POLLIN | POLLOUT)) { + exec_io(&connection); + } + if ((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) { + die("Connection error"); + } + ctl_poll(pollfds, &connection); + } + + /* Resource cleanup */ + nghttp2_session_del(connection.session); + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + shutdown(fd, SHUT_WR); + close(fd); + request_free(&req); +} + +static int parse_uri(struct URI *res, const char *uri) { + /* We only interested in https */ + size_t len, i, offset; + int ipv6addr = 0; + memset(res, 0, sizeof(struct URI)); + len = strlen(uri); + if (len < 9 || memcmp("https://", uri, 8) != 0) { + return -1; + } + offset = 8; + res->host = res->hostport = &uri[offset]; + res->hostlen = 0; + if (uri[offset] == '[') { + /* IPv6 literal address */ + ++offset; + ++res->host; + ipv6addr = 1; + for (i = offset; i < len; ++i) { + if (uri[i] == ']') { + res->hostlen = i - offset; + offset = i + 1; + break; + } + } + } else { + const char delims[] = ":/?#"; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + } + res->hostlen = i - offset; + offset = i; + } + if (res->hostlen == 0) { + return -1; + } + /* Assuming https */ + res->port = 443; + if (offset < len) { + if (uri[offset] == ':') { + /* port */ + const char delims[] = "/?#"; + int port = 0; + ++offset; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + if ('0' <= uri[i] && uri[i] <= '9') { + port *= 10; + port += uri[i] - '0'; + if (port > 65535) { + return -1; + } + } else { + return -1; + } + } + if (port == 0) { + return -1; + } + offset = i; + res->port = (uint16_t)port; + } + } + res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host); + for (i = offset; i < len; ++i) { + if (uri[i] == '#') { + break; + } + } + if (i - offset == 0) { + res->path = "/"; + res->pathlen = 1; + } else { + res->path = &uri[offset]; + res->pathlen = i - offset; + } + return 0; +} + +int main(int argc, char **argv) { + struct URI uri; + struct sigaction act; + int rv; + + if (argc < 2) { + die("Specify a https URI"); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + + rv = parse_uri(&uri, argv[1]); + if (rv != 0) { + die("parse_uri failed"); + } + fetch_uri(&uri); + return EXIT_SUCCESS; +} diff --git a/examples/deflate.c b/examples/deflate.c new file mode 100644 index 0000000..df1cb92 --- /dev/null +++ b/examples/deflate.c @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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/examples/libevent-client.c b/examples/libevent-client.c new file mode 100644 index 0000000..0e1c14a --- /dev/null +++ b/examples/libevent-client.c @@ -0,0 +1,595 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef __sgi +# include +# 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; +} + +/* Create SSL_CTX. */ +static SSL_CTX *create_ssl_ctx(void) { + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; +} + +static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_client_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); +} + +static void send_client_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); + } +} + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +/* Send HTTP request to the remote peer */ +static void submit_request(http2_session_data *session_data) { + int32_t stream_id; + http2_stream_data *stream_data = session_data->stream_data; + const char *uri = stream_data->uri; + const struct http_parser_url *u = stream_data->u; + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off], + u->field_data[UF_SCHEMA].len), + MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen), + MAKE_NV(":path", stream_data->path, stream_data->pathlen)}; + fprintf(stderr, "Request headers:\n"); + print_headers(stderr, hdrs, ARRLEN(hdrs)); + stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs, + ARRLEN(hdrs), NULL, stream_data); + if (stream_id < 0) { + errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id)); + } + + stream_data->stream_id = stream_id; +} + +/* Serialize the frame and send (or buffer) the data to + bufferevent. */ +static int session_send(http2_session_data *session_data) { + int rv; + + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* readcb for bufferevent. Here we get the data from the input buffer + of bufferevent and feed them to nghttp2 library. This may invoke + nghttp2 callbacks. It may also queues the frame in nghttp2 session + context. To send them, we call session_send() in the end. */ +static void readcb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + delete_http2_session_data(session_data); + return; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* writecb for bufferevent. To greaceful shutdown after sending or + receiving GOAWAY, we check the some conditions on the nghttp2 + library and output buffer of bufferevent. If it indicates we have + no business to this session, tear down the connection. */ +static void writecb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + (void)bev; + + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0 && + evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) { + delete_http2_session_data(session_data); + } +} + +/* eventcb for bufferevent. For the purpose of simplicity and + readability of the example program, we omitted the certificate and + peer verification. After SSL/TLS handshake is over, initialize + nghttp2 library session, and send client connection header. Then + send HTTP request. */ +static void eventcb(struct bufferevent *bev, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + int fd = bufferevent_getfd(bev); + int val = 1; + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + + fprintf(stderr, "Connected\n"); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "h2 is not negotiated\n"); + delete_http2_session_data(session_data); + return; + } + + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + initialize_nghttp2_session(session_data); + send_client_connection_header(session_data); + submit_request(session_data); + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + } + return; + } + if (events & BEV_EVENT_EOF) { + warnx("Disconnected from the remote host"); + } else if (events & BEV_EVENT_ERROR) { + warnx("Network error"); + } else if (events & BEV_EVENT_TIMEOUT) { + warnx("Timeout"); + } + delete_http2_session_data(session_data); +} + +/* Start connecting to the remote peer |host:port| */ +static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx, + const char *host, uint16_t port, + http2_session_data *session_data) { + int rv; + struct bufferevent *bev; + SSL *ssl; + + ssl = create_ssl(ssl_ctx); + bev = bufferevent_openssl_socket_new( + evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE); + bufferevent_enable(bev, EV_READ | EV_WRITE); + bufferevent_setcb(bev, readcb, writecb, eventcb, session_data); + rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase, + AF_UNSPEC, host, port); + + if (rv != 0) { + errx(1, "Could not connect to the remote host %s", host); + } + session_data->bev = bev; +} + +/* Get resource denoted by the |uri|. The debug and error messages are + printed in stderr, while the response body is printed in stdout. */ +static void run(const char *uri) { + struct http_parser_url u; + char *host; + uint16_t port; + int rv; + SSL_CTX *ssl_ctx; + struct event_base *evbase; + http2_session_data *session_data; + + /* Parse the |uri| and stores its components in |u| */ + rv = http_parser_parse_url(uri, strlen(uri), 0, &u); + if (rv != 0) { + errx(1, "Could not parse URI %s", uri); + } + host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len); + if (!(u.field_set & (1 << UF_PORT))) { + port = 443; + } else { + port = u.port; + } + + ssl_ctx = create_ssl_ctx(); + + evbase = event_base_new(); + + session_data = create_http2_session_data(evbase); + session_data->stream_data = create_http2_stream_data(uri, &u); + + initiate_connection(evbase, ssl_ctx, host, port, session_data); + free(host); + host = NULL; + + event_base_loop(evbase, 0); + + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); +} + +int main(int argc, char **argv) { + struct sigaction act; + + if (argc < 2) { + fprintf(stderr, "Usage: libevent-client HTTPS_URI\n"); + exit(EXIT_FAILURE); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + + run(argv[1]); + return 0; +} diff --git a/examples/libevent-server.c b/examples/libevent-server.c new file mode 100644 index 0000000..fa45d37 --- /dev/null +++ b/examples/libevent-server.c @@ -0,0 +1,787 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef __sgi +# define errx(exitcode, format, args...) \ + { \ + warnx(format, ##args); \ + exit(exitcode); \ + } +# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno)) +# define warnx(format, args...) fprintf(stderr, format "\n", ##args) +#endif + +#ifdef HAVE_CONFIG_H +# include +#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 int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + int rv; + (void)ssl; + (void)arg; + + rv = nghttp2_select_alpn(out, outlen, in, inlen); + + if (rv != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} + +/* Create SSL_CTX. */ +static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { + SSL_CTX *ssl_ctx; + + ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + errx(1, "SSL_CTX_set1_curves_list failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + { + EC_KEY *ecdh; + ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ecdh) { + errx(1, "EC_KEY_new_by_curv_name failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); + } +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { + errx(1, "Could not read private key file %s", key_file); + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + errx(1, "Could not read certificate file %s", cert_file); + } + + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; +} + +static void add_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + stream_data->next = session_data->root.next; + session_data->root.next = stream_data; + stream_data->prev = &session_data->root; + if (stream_data->next) { + stream_data->next->prev = stream_data; + } +} + +static void remove_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + (void)session_data; + + stream_data->prev->next = stream_data->next; + if (stream_data->next) { + stream_data->next->prev = stream_data->prev; + } +} + +static http2_stream_data * +create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { + http2_stream_data *stream_data; + stream_data = malloc(sizeof(http2_stream_data)); + memset(stream_data, 0, sizeof(http2_stream_data)); + stream_data->stream_id = stream_id; + stream_data->fd = -1; + + add_stream(session_data, stream_data); + return stream_data; +} + +static void delete_http2_stream_data(http2_stream_data *stream_data) { + if (stream_data->fd != -1) { + close(stream_data->fd); + } + free(stream_data->request_path); + free(stream_data); +} + +static http2_session_data *create_http2_session_data(app_context *app_ctx, + int fd, + struct sockaddr *addr, + int addrlen) { + int rv; + http2_session_data *session_data; + SSL *ssl; + char host[NI_MAXHOST]; + int val = 1; + + ssl = create_ssl(app_ctx->ssl_ctx); + session_data = malloc(sizeof(http2_session_data)); + memset(session_data, 0, sizeof(http2_session_data)); + session_data->app_ctx = app_ctx; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + session_data->bev = bufferevent_openssl_socket_new( + app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); + rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, + NI_NUMERICHOST); + if (rv != 0) { + session_data->client_addr = strdup("(unknown)"); + } else { + session_data->client_addr = strdup(host); + } + + return session_data; +} + +static void delete_http2_session_data(http2_session_data *session_data) { + http2_stream_data *stream_data; + SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + fprintf(stderr, "%s disconnected\n", session_data->client_addr); + if (ssl) { + SSL_shutdown(ssl); + } + bufferevent_free(session_data->bev); + nghttp2_session_del(session_data->session); + for (stream_data = session_data->root.next; stream_data;) { + http2_stream_data *next = stream_data->next; + delete_http2_stream_data(stream_data); + stream_data = next; + } + free(session_data->client_addr); + free(session_data); +} + +/* Serialize the frame and send (or buffer) the data to + bufferevent. */ +static int session_send(http2_session_data *session_data) { + int rv; + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* Read the data in the bufferevent and feed them into nghttp2 library + function. Invocation of nghttp2_session_mem_recv() may make + additional pending frames, so call session_send() at the end of the + function. */ +static int session_recv(http2_session_data *session_data) { + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(session_data->bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + return -1; + } + if (session_send(session_data) != 0) { + return -1; + } + return 0; +} + +static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, + size_t length, int flags, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + (void)session; + (void)flags; + + /* Avoid excessive buffering in server side. */ + if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= + OUTPUT_WOULDBLOCK_THRESHOLD) { + return NGHTTP2_ERR_WOULDBLOCK; + } + bufferevent_write(bev, data, length); + return (ssize_t)length; +} + +/* Returns nonzero if the string |s| ends with the substring |sub| */ +static int ends_with(const char *s, const char *sub) { + size_t slen = strlen(s); + size_t sublen = strlen(sub); + if (slen < sublen) { + return 0; + } + return memcmp(s + slen - sublen, sub, sublen) == 0; +} + +/* Returns int value of hex string character |c| */ +static uint8_t hex_to_uint(uint8_t c) { + if ('0' <= c && c <= '9') { + return (uint8_t)(c - '0'); + } + if ('A' <= c && c <= 'F') { + return (uint8_t)(c - 'A' + 10); + } + if ('a' <= c && c <= 'f') { + return (uint8_t)(c - 'a' + 10); + } + return 0; +} + +/* Decodes percent-encoded byte string |value| with length |valuelen| + and returns the decoded byte string in allocated buffer. The return + value is NULL terminated. The caller must free the returned + string. */ +static char *percent_decode(const uint8_t *value, size_t valuelen) { + char *res; + + res = malloc(valuelen + 1); + if (valuelen > 3) { + size_t i, j; + for (i = 0, j = 0; i < valuelen - 2;) { + if (value[i] != '%' || !isxdigit(value[i + 1]) || + !isxdigit(value[i + 2])) { + res[j++] = (char)value[i++]; + continue; + } + res[j++] = + (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2])); + i += 3; + } + memcpy(&res[j], &value[i], 2); + res[j + 2] = '\0'; + } else { + memcpy(res, value, valuelen); + res[valuelen] = '\0'; + } + return res; +} + +static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + int fd = source->fd; + ssize_t r; + (void)session; + (void)stream_id; + (void)user_data; + + while ((r = read(fd, buf, length)) == -1 && errno == EINTR) + ; + if (r == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (r == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return r; +} + +static int send_response(nghttp2_session *session, int32_t stream_id, + nghttp2_nv *nva, size_t nvlen, int fd) { + int rv; + nghttp2_data_provider data_prd; + data_prd.source.fd = fd; + data_prd.read_callback = file_read_callback; + + rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +static const char ERROR_HTML[] = "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); + + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + delete_http2_session_data(session_data); + return; + } + + initialize_nghttp2_session(session_data); + + if (send_server_connection_header(session_data) != 0 || + session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + + return; + } + if (events & BEV_EVENT_EOF) { + fprintf(stderr, "%s EOF\n", session_data->client_addr); + } else if (events & BEV_EVENT_ERROR) { + fprintf(stderr, "%s network error\n", session_data->client_addr); + } else if (events & BEV_EVENT_TIMEOUT) { + fprintf(stderr, "%s timeout\n", session_data->client_addr); + } + delete_http2_session_data(session_data); +} + +/* callback for evconnlistener */ +static void acceptcb(struct evconnlistener *listener, int fd, + struct sockaddr *addr, int addrlen, void *arg) { + app_context *app_ctx = (app_context *)arg; + http2_session_data *session_data; + (void)listener; + + session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); + + bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); +} + +static void start_listen(struct event_base *evbase, const char *service, + app_context *app_ctx) { + int rv; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif /* AI_ADDRCONFIG */ + + rv = getaddrinfo(NULL, service, &hints, &res); + if (rv != 0) { + errx(1, "Could not resolve server address"); + } + for (rp = res; rp; rp = rp->ai_next) { + struct evconnlistener *listener; + listener = evconnlistener_new_bind( + evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 16, rp->ai_addr, (int)rp->ai_addrlen); + if (listener) { + freeaddrinfo(res); + + return; + } + } + errx(1, "Could not start listener"); +} + +static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, + struct event_base *evbase) { + memset(app_ctx, 0, sizeof(app_context)); + app_ctx->ssl_ctx = ssl_ctx; + app_ctx->evbase = evbase; +} + +static void run(const char *service, const char *key_file, + const char *cert_file) { + SSL_CTX *ssl_ctx; + app_context app_ctx; + struct event_base *evbase; + + ssl_ctx = create_ssl_ctx(key_file, cert_file); + evbase = event_base_new(); + initialize_app_context(&app_ctx, ssl_ctx, evbase); + start_listen(evbase, service, &app_ctx); + + event_base_loop(evbase, 0); + + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); +} + +int main(int argc, char **argv) { + struct sigaction act; + + if (argc < 4) { + fprintf(stderr, "Usage: libevent-server PORT KEY_FILE CERT_FILE\n"); + exit(EXIT_FAILURE); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + + run(argv[1], argv[2], argv[3]); + return 0; +} diff --git a/fedora/spdylay.spec b/fedora/spdylay.spec new file mode 100644 index 0000000..967d908 --- /dev/null +++ b/fedora/spdylay.spec @@ -0,0 +1,75 @@ +Prefix: %{_usr} +Name: spdylay +Version: 0.3.7 +Release: 1%{?dist} +Summary: The experimental SPDY protocol version 2 and 3 implementation in C + +Group: System Environment/Libraries +License: MIT +URL: http://sourceforge.net/projects/spdylay/ +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: pkgconfig >= 0.20, zlib >= 1.2.3, gcc, gcc-c++, make +BuildRequires: openssl-devel, CUnit-devel + +#Requires: + +%description +This is an experimental implementation of Google's SPDY protocol in C. +This library provides SPDY version 2 and 3 framing layer implementation. It does not +perform any I/O operations. When the library needs them, it calls the callback functions +provided by the application. It also does not include any event polling mechanism, +so the application can freely choose the way of handling events. This library code does +not depend on any particular SSL library (except for example programs which depend on +OpenSSL 1.0.1 or later). + +%package devel +Summary: Development files for %{name} +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + +%prep +%setup -q + +%build +autoreconf -i +%{__automake} +%{__autoconf} +%configure --disable-static --enable-examples --disable-xmltest +%{__make} %{?_smp_mflags} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%defattr(-,root,root,-) +%doc +%{_libdir}/*.so.* +%exclude %{_libdir}/*.la +%{_bindir}/shrpx +%{_bindir}/spdycat +%{_bindir}/spdyd + +%files devel +%defattr(-,root,root,-) +%doc %{_docdir}/%{name} +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc + +%changelog +* Sat Oct 27 2012 Raul Gutierrez Segales 0.3.7-DEV +- Initial RPM release. diff --git a/fuzz/README.rst b/fuzz/README.rst new file mode 100644 index 0000000..54ae832 --- /dev/null +++ b/fuzz/README.rst @@ -0,0 +1,33 @@ +Fuzzer +====== + +This directory contains fuzzer target mainly written to integrate +nghttp2 into `oss-fuzz `_. + +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/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 b/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 new file mode 100644 index 0000000..f2bc466 Binary files /dev/null and b/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 differ diff --git a/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a b/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a new file mode 100644 index 0000000..629ce58 Binary files /dev/null and b/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a differ diff --git a/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 b/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 new file mode 100644 index 0000000..61c6383 Binary files /dev/null and b/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 differ diff --git a/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a b/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a new file mode 100644 index 0000000..f8651f1 Binary files /dev/null and b/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a differ diff --git a/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd b/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd new file mode 100644 index 0000000..15c71aa Binary files /dev/null and b/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd differ diff --git a/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f b/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f new file mode 100644 index 0000000..31a5ddc --- /dev/null +++ b/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f @@ -0,0 +1,2 @@ +INVALID CONNECTION PREFACE + diff --git a/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 b/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 new file mode 100644 index 0000000..cf66978 Binary files /dev/null and b/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 differ diff --git a/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 b/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 new file mode 100644 index 0000000..981e66a Binary files /dev/null and b/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 differ diff --git a/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 b/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 new file mode 100644 index 0000000..fb65c24 Binary files /dev/null and b/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 differ diff --git a/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 b/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 new file mode 100644 index 0000000..62d5887 Binary files /dev/null and b/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 differ diff --git a/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a b/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a new file mode 100644 index 0000000..e528508 Binary files /dev/null and b/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a differ diff --git a/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc b/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc new file mode 100644 index 0000000..4cd627b Binary files /dev/null and b/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc differ diff --git a/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 b/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 new file mode 100644 index 0000000..d5c77de Binary files /dev/null and b/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 differ diff --git a/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 b/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 new file mode 100644 index 0000000..e8ac9ee Binary files /dev/null and b/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 differ diff --git a/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 b/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 new file mode 100644 index 0000000..da1612a Binary files /dev/null and b/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 differ diff --git a/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab b/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab new file mode 100644 index 0000000..ab72d09 Binary files /dev/null and b/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab differ diff --git a/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 b/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 new file mode 100644 index 0000000..a9123c4 Binary files /dev/null and b/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 differ diff --git a/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 b/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 new file mode 100644 index 0000000..b6b54dc Binary files /dev/null and b/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 differ diff --git a/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 b/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 new file mode 100644 index 0000000..9338c99 Binary files /dev/null and b/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 differ diff --git a/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 b/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 new file mode 100644 index 0000000..aa67cbc Binary files /dev/null and b/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 differ diff --git a/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 b/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 new file mode 100644 index 0000000..ad14518 Binary files /dev/null and b/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 differ diff --git a/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df b/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df new file mode 100644 index 0000000..e36ad42 Binary files /dev/null and b/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df differ diff --git a/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f b/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f new file mode 100644 index 0000000..a0c5dc3 Binary files /dev/null and b/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f differ diff --git a/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 b/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 new file mode 100644 index 0000000..9176566 Binary files /dev/null and b/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 differ diff --git a/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce b/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce new file mode 100644 index 0000000..1eb839a Binary files /dev/null and b/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce differ diff --git a/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 b/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 new file mode 100644 index 0000000..5e6fd53 Binary files /dev/null and b/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 differ diff --git a/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 b/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 new file mode 100644 index 0000000..e8e09a8 Binary files /dev/null and b/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 differ diff --git a/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 b/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 new file mode 100644 index 0000000..f631036 Binary files /dev/null and b/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 differ diff --git a/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 b/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 new file mode 100644 index 0000000..c99b1d8 Binary files /dev/null and b/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 differ diff --git a/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a b/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a new file mode 100644 index 0000000..d168ca6 Binary files /dev/null and b/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a differ diff --git a/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c b/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c new file mode 100644 index 0000000..cf176eb Binary files /dev/null and b/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c differ diff --git a/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d b/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d new file mode 100644 index 0000000..07a4f67 Binary files /dev/null and b/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d differ diff --git a/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f b/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f new file mode 100644 index 0000000..73f52d1 Binary files /dev/null and b/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f differ diff --git a/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca b/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca new file mode 100644 index 0000000..a27ca46 Binary files /dev/null and b/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca differ diff --git a/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c b/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c new file mode 100644 index 0000000..f5a2f2f Binary files /dev/null and b/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c differ diff --git a/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf b/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf new file mode 100644 index 0000000..bdb70ef Binary files /dev/null and b/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf differ diff --git a/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 b/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 new file mode 100644 index 0000000..b5a05b6 Binary files /dev/null and b/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 differ diff --git a/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a b/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a new file mode 100644 index 0000000..fa18eff Binary files /dev/null and b/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a differ diff --git a/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 b/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 new file mode 100644 index 0000000..b8e0b33 Binary files /dev/null and b/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 differ diff --git a/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 b/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 new file mode 100644 index 0000000..2be33f5 Binary files /dev/null and b/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 differ diff --git a/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 b/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 new file mode 100644 index 0000000..a7d696a Binary files /dev/null and b/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 differ diff --git a/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab b/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab new file mode 100644 index 0000000..2f00755 Binary files /dev/null and b/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab differ diff --git a/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 b/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 new file mode 100644 index 0000000..d212c23 Binary files /dev/null and b/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 differ diff --git a/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 b/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 new file mode 100644 index 0000000..43a8423 Binary files /dev/null and b/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 differ diff --git a/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 b/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 new file mode 100644 index 0000000..43cf972 Binary files /dev/null and b/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 differ diff --git a/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 b/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 new file mode 100644 index 0000000..e41d637 Binary files /dev/null and b/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 differ diff --git a/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a b/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a new file mode 100644 index 0000000..cc92edc Binary files /dev/null and b/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a differ diff --git a/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 b/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 new file mode 100644 index 0000000..e1597d9 Binary files /dev/null and b/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 differ diff --git a/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e b/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e new file mode 100644 index 0000000..11175d8 Binary files /dev/null and b/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e differ diff --git a/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa b/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa new file mode 100644 index 0000000..4d233a0 Binary files /dev/null and b/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa differ diff --git a/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a b/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a new file mode 100644 index 0000000..71ab2d8 Binary files /dev/null and b/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a differ diff --git a/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 b/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 new file mode 100644 index 0000000..979e96a Binary files /dev/null and b/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 differ diff --git a/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 b/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 new file mode 100644 index 0000000..5b47f4f Binary files /dev/null and b/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 differ diff --git a/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 b/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 new file mode 100644 index 0000000..1ebf432 Binary files /dev/null and b/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 differ diff --git a/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 b/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 new file mode 100644 index 0000000..f2e3ff2 Binary files /dev/null and b/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 differ diff --git a/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e b/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e new file mode 100644 index 0000000..22137b3 Binary files /dev/null and b/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e differ diff --git a/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c b/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c new file mode 100644 index 0000000..570e98a Binary files /dev/null and b/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c differ diff --git a/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d b/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d new file mode 100644 index 0000000..c0f50b7 Binary files /dev/null and b/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d differ diff --git a/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 b/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 new file mode 100644 index 0000000..5a4c52a Binary files /dev/null and b/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 differ diff --git a/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 b/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 new file mode 100644 index 0000000..a7fa65d Binary files /dev/null and b/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 differ diff --git a/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 b/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 new file mode 100644 index 0000000..c89a9a7 Binary files /dev/null and b/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 differ diff --git a/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e b/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e new file mode 100644 index 0000000..e42f5f4 Binary files /dev/null and b/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e differ diff --git a/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea b/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea new file mode 100644 index 0000000..3200027 Binary files /dev/null and b/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea differ diff --git a/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f b/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f new file mode 100644 index 0000000..28709d4 Binary files /dev/null and b/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f differ diff --git a/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 b/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 new file mode 100644 index 0000000..46316c1 Binary files /dev/null and b/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 differ diff --git a/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 b/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 new file mode 100644 index 0000000..68a831a Binary files /dev/null and b/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 differ diff --git a/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a b/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a new file mode 100644 index 0000000..582b979 Binary files /dev/null and b/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a differ diff --git a/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 b/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 new file mode 100644 index 0000000..ea39871 Binary files /dev/null and b/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 differ diff --git a/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 b/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 new file mode 100644 index 0000000..38e5774 Binary files /dev/null and b/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 differ diff --git a/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 b/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 new file mode 100644 index 0000000..7a8ad5d Binary files /dev/null and b/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 differ diff --git a/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada b/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada new file mode 100644 index 0000000..063bdab Binary files /dev/null and b/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada differ diff --git a/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 b/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 new file mode 100644 index 0000000..7eed615 Binary files /dev/null and b/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 differ diff --git a/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a b/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a new file mode 100644 index 0000000..aa862db Binary files /dev/null and b/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a differ diff --git a/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 b/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 new file mode 100644 index 0000000..1c2503e Binary files /dev/null and b/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 differ diff --git a/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 b/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 new file mode 100644 index 0000000..4f6a2eb Binary files /dev/null and b/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 differ diff --git a/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 b/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 new file mode 100644 index 0000000..0c10194 Binary files /dev/null and b/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 differ diff --git a/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb b/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb new file mode 100644 index 0000000..b92e520 Binary files /dev/null and b/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb differ diff --git a/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef b/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef new file mode 100644 index 0000000..cd87803 Binary files /dev/null and b/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef differ diff --git a/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 b/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 new file mode 100644 index 0000000..63ca53a Binary files /dev/null and b/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 differ diff --git a/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 b/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 new file mode 100644 index 0000000..08dd2bd Binary files /dev/null and b/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 differ diff --git a/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 b/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 new file mode 100644 index 0000000..51c931c Binary files /dev/null and b/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 differ diff --git a/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 b/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 new file mode 100644 index 0000000..3513471 Binary files /dev/null and b/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 differ diff --git a/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 b/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 new file mode 100644 index 0000000..3f897bd Binary files /dev/null and b/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 differ diff --git a/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 b/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 new file mode 100644 index 0000000..a8b06a9 Binary files /dev/null and b/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 differ diff --git a/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 b/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 new file mode 100644 index 0000000..dfa9766 Binary files /dev/null and b/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 differ diff --git a/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 b/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 new file mode 100644 index 0000000..1b8b19e Binary files /dev/null and b/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 differ diff --git a/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 b/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 new file mode 100644 index 0000000..e8d7b2d Binary files /dev/null and b/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 differ diff --git a/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f b/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f new file mode 100644 index 0000000..1b4ae59 Binary files /dev/null and b/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f differ diff --git a/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 b/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 new file mode 100644 index 0000000..7d81f74 Binary files /dev/null and b/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 differ diff --git a/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 b/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 new file mode 100644 index 0000000..eae40c4 Binary files /dev/null and b/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 differ diff --git a/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b b/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b new file mode 100644 index 0000000..875250c Binary files /dev/null and b/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b differ diff --git a/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 b/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 new file mode 100644 index 0000000..4ca7ddb Binary files /dev/null and b/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 differ diff --git a/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb b/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb new file mode 100644 index 0000000..8c90cf3 Binary files /dev/null and b/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb differ diff --git a/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 b/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 new file mode 100644 index 0000000..3aff723 Binary files /dev/null and b/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 differ diff --git a/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 b/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 new file mode 100644 index 0000000..20ed145 Binary files /dev/null and b/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 differ diff --git a/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a b/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a new file mode 100644 index 0000000..adf60df Binary files /dev/null and b/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a differ diff --git a/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 b/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 new file mode 100644 index 0000000..28b6df1 Binary files /dev/null and b/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 differ diff --git a/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 b/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 new file mode 100644 index 0000000..6c1c4bc Binary files /dev/null and b/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 differ diff --git a/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d b/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d new file mode 100644 index 0000000..e290f86 Binary files /dev/null and b/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d differ diff --git a/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e b/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e new file mode 100644 index 0000000..4b92a83 Binary files /dev/null and b/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e differ diff --git a/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 b/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 new file mode 100644 index 0000000..5759f66 Binary files /dev/null and b/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 differ diff --git a/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc b/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc new file mode 100644 index 0000000..abfa58f Binary files /dev/null and b/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc differ diff --git a/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad b/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad new file mode 100644 index 0000000..e3ef3dc Binary files /dev/null and b/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad differ diff --git a/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b b/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b new file mode 100644 index 0000000..da91c8d Binary files /dev/null and b/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b differ diff --git a/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 b/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 new file mode 100644 index 0000000..a418c41 Binary files /dev/null and b/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 differ diff --git a/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 b/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 new file mode 100644 index 0000000..40659b5 Binary files /dev/null and b/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 differ diff --git a/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 b/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 new file mode 100644 index 0000000..98b11ed Binary files /dev/null and b/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 differ diff --git a/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 b/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 new file mode 100644 index 0000000..a88435f Binary files /dev/null and b/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 differ diff --git a/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 b/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 new file mode 100644 index 0000000..783b6a3 Binary files /dev/null and b/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 differ diff --git a/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 b/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 new file mode 100644 index 0000000..3ec3fbb Binary files /dev/null and b/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 differ diff --git a/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d b/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d new file mode 100644 index 0000000..348471d Binary files /dev/null and b/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d differ diff --git a/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf b/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf new file mode 100644 index 0000000..004ea71 Binary files /dev/null and b/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf differ diff --git a/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a b/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a new file mode 100644 index 0000000..11e84b4 Binary files /dev/null and b/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a differ diff --git a/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 b/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 new file mode 100644 index 0000000..cf972d1 Binary files /dev/null and b/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 differ diff --git a/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 b/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 new file mode 100644 index 0000000..d894be7 Binary files /dev/null and b/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 differ diff --git a/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 b/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 new file mode 100644 index 0000000..303b68a Binary files /dev/null and b/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 differ diff --git a/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 b/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 new file mode 100644 index 0000000..b19f4f6 Binary files /dev/null and b/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 differ diff --git a/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df b/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df new file mode 100644 index 0000000..e5c39cc Binary files /dev/null and b/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df differ diff --git a/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 b/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 new file mode 100644 index 0000000..81e30d7 Binary files /dev/null and b/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 differ diff --git a/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 b/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 new file mode 100644 index 0000000..0af0a7d Binary files /dev/null and b/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 differ diff --git a/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 b/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 new file mode 100644 index 0000000..27dec29 Binary files /dev/null and b/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 differ diff --git a/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 b/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 new file mode 100644 index 0000000..d528c3f Binary files /dev/null and b/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 differ diff --git a/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee b/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee new file mode 100644 index 0000000..ac58d53 Binary files /dev/null and b/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee differ diff --git a/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 b/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 new file mode 100644 index 0000000..317ead9 Binary files /dev/null and b/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 differ diff --git a/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a b/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a new file mode 100644 index 0000000..ff4af29 Binary files /dev/null and b/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a differ diff --git a/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 b/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 new file mode 100644 index 0000000..2019289 Binary files /dev/null and b/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 differ diff --git a/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc b/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc new file mode 100644 index 0000000..1514db5 Binary files /dev/null and b/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc differ diff --git a/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 b/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 new file mode 100644 index 0000000..45db5f0 Binary files /dev/null and b/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 differ diff --git a/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e b/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e new file mode 100644 index 0000000..76f3f05 Binary files /dev/null and b/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e differ diff --git a/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 b/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 new file mode 100644 index 0000000..f7cf1d7 Binary files /dev/null and b/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 differ diff --git a/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 b/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 new file mode 100644 index 0000000..14891ed Binary files /dev/null and b/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 differ diff --git a/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 b/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 new file mode 100644 index 0000000..95f5a2e Binary files /dev/null and b/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 differ diff --git a/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 b/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 new file mode 100644 index 0000000..8b31bfd Binary files /dev/null and b/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 differ diff --git a/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb b/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb new file mode 100644 index 0000000..a2142c6 Binary files /dev/null and b/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb differ diff --git a/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 b/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 new file mode 100644 index 0000000..6f9f9ac Binary files /dev/null and b/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 differ diff --git a/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa b/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa new file mode 100644 index 0000000..b3e2459 Binary files /dev/null and b/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa differ diff --git a/fuzz/fuzz_frames.cc b/fuzz/fuzz_frames.cc new file mode 100644 index 0000000..2a20c42 --- /dev/null +++ b/fuzz/fuzz_frames.cc @@ -0,0 +1,160 @@ +#include +#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/fuzz/fuzz_target.cc b/fuzz/fuzz_target.cc new file mode 100644 index 0000000..4adc5ed --- /dev/null +++ b/fuzz/fuzz_target.cc @@ -0,0 +1,79 @@ +#include + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + return 0; +} +} // namespace + +namespace { +int before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +void send_pending(nghttp2_session *session) { + for (;;) { + const uint8_t *data; + auto n = nghttp2_session_mem_send(session, &data); + if (n == 0) { + return; + } + } +} +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + nghttp2_session *session; + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + nghttp2_session_callbacks_set_before_frame_send_callback( + callbacks, before_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_server_new(&session, callbacks, nullptr); + nghttp2_session_callbacks_del(callbacks); + + nghttp2_settings_entry iv{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}; + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + send_pending(session); + nghttp2_session_mem_recv(session, data, size); + send_pending(session); + + nghttp2_session_del(session); + + return 0; +} diff --git a/fuzz/fuzz_target_fdp.cc b/fuzz/fuzz_target_fdp.cc new file mode 100644 index 0000000..f94b964 --- /dev/null +++ b/fuzz/fuzz_target_fdp.cc @@ -0,0 +1,99 @@ +#include +#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/genauthoritychartbl.py b/genauthoritychartbl.py new file mode 100755 index 0000000..1b10028 --- /dev/null +++ b/genauthoritychartbl.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in [ + "-", ".", "_", "~", + "!", "$", "&", "'", "(", ")", + "*", "+", ",", ";", "=", + "%", "@", ":", "[", "]"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('A' <= chr(i) and chr(i) <= 'Z') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/gendowncasetbl.py b/gendowncasetbl.py new file mode 100755 index 0000000..04aeda7 --- /dev/null +++ b/gendowncasetbl.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x20: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) == ' ': + sys.stdout.write('{} /* SPC */, '.format(i)) + elif chr(i) == '\t': + sys.stdout.write('{} /* HT */, '.format(i)) + elif 'A' <= chr(i) and chr(i) <= 'Z': + sys.stdout.write('{} /* {} */, '.format(i - ord('A') + ord('a'), chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('{} /* {} */, '.format(i, chr(i))) + elif 0x80 <= i: + sys.stdout.write('{} /* {} */, '.format(i, hex(i))) + elif 0 == i: + sys.stdout.write('{} /* NUL */, '.format(i)) + else: + sys.stdout.write('{} /* {} */, '.format(i, name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/genheaderfunc.py b/genheaderfunc.py new file mode 100755 index 0000000..2ac3c37 --- /dev/null +++ b/genheaderfunc.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +from gentokenlookup import gentokenlookup + +HEADERS = [ + ':authority', + ':method', + ':path', + ':scheme', + ':status', + ':host', # for spdy + ':protocol', + 'expect', + 'host', + 'if-modified-since', + "te", + "cookie", + "http2-settings", + "server", + "via", + "forwarded", + "x-forwarded-for", + "x-forwarded-proto", + "alt-svc", + "content-length", + "location", + "trailer", + "link", + "accept-encoding", + "accept-language", + "cache-control", + "user-agent", + "date", + "content-type", + "early-data", + "sec-websocket-accept", + "sec-websocket-key", + "priority", + # disallowed h1 headers + 'connection', + 'keep-alive', + 'proxy-connection', + 'transfer-encoding', + 'upgrade' +] + +if __name__ == '__main__': + gentokenlookup(HEADERS, 'HD_') diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py new file mode 100755 index 0000000..d15d0b5 --- /dev/null +++ b/genlibtokenlookup.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +HEADERS = [ + (':authority', 0), + (':method', 1), + (':method', 2), + (':path', 3), + (':path', 4), + (':scheme', 5), + (':scheme', 6), + (':status', 7), + (':status', 8), + (':status', 9), + (':status', 10), + (':status', 11), + (':status', 12), + (':status', 13), + ('accept-charset', 14), + ('accept-encoding', 15), + ('accept-language', 16), + ('accept-ranges', 17), + ('accept', 18), + ('access-control-allow-origin', 19), + ('age', 20), + ('allow', 21), + ('authorization', 22), + ('cache-control', 23), + ('content-disposition', 24), + ('content-encoding', 25), + ('content-language', 26), + ('content-length', 27), + ('content-location', 28), + ('content-range', 29), + ('content-type', 30), + ('cookie', 31), + ('date', 32), + ('etag', 33), + ('expect', 34), + ('expires', 35), + ('from', 36), + ('host', 37), + ('if-match', 38), + ('if-modified-since', 39), + ('if-none-match', 40), + ('if-range', 41), + ('if-unmodified-since', 42), + ('last-modified', 43), + ('link', 44), + ('location', 45), + ('max-forwards', 46), + ('proxy-authenticate', 47), + ('proxy-authorization', 48), + ('range', 49), + ('referer', 50), + ('refresh', 51), + ('retry-after', 52), + ('server', 53), + ('set-cookie', 54), + ('strict-transport-security', 55), + ('transfer-encoding', 56), + ('user-agent', 57), + ('vary', 58), + ('via', 59), + ('www-authenticate', 60), + ('te', None), + ('connection', None), + ('keep-alive',None), + ('proxy-connection', None), + ('upgrade', None), + (':protocol', None), + ('priority', None), +] + +def to_enum_hd(k): + res = 'NGHTTP2_TOKEN_' + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k, _ in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + if k not in ent[c]: + ent[c].append(k) + + return res + +def gen_enum(): + name = '' + print('typedef enum {') + for k, token in HEADERS: + if token is None: + print(' {},'.format(to_enum_hd(k))) + else: + if name != k: + name = k + print(' {} = {},'.format(to_enum_hd(k), token)) + print('} nghttp2_token;') + +def gen_index_header(): + print('''\ +static int32_t lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) {''') + b = build_header(HEADERS) + for size in sorted(b.keys()): + ents = b[size] + print('''\ + case {}:'''.format(size)) + print('''\ + switch (name[{}]) {{'''.format(size - 1)) + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print('''\ + case '{}':'''.format(c)) + for k in headers: + print('''\ + if (memeq("{}", name, {})) {{ + return {}; + }}'''.format(k[:-1], size - 1, to_enum_hd(k))) + print('''\ + break;''') + print('''\ + } + break;''') + print('''\ + } + return -1; +}''') + +if __name__ == '__main__': + gen_enum() + print() + gen_index_header() diff --git a/genmethodchartbl.py b/genmethodchartbl.py new file mode 100755 index 0000000..a0c3a37 --- /dev/null +++ b/genmethodchartbl.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in ["!" , "#" , "$" , "%" , "&" , "'" , "*", + "+" , "-" , "." , "^" , "_" , "`" , "|" , "~"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('A' <= chr(i) and chr(i) <= 'Z') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/genmethodfunc.py b/genmethodfunc.py new file mode 100755 index 0000000..436a77a --- /dev/null +++ b/genmethodfunc.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +from io import StringIO + +from gentokenlookup import gentokenlookup + +# copied from llhttp.h, and stripped trailing spaces and backslashes. +SRC = ''' + XX(0, DELETE, DELETE) + XX(1, GET, GET) + XX(2, HEAD, HEAD) + XX(3, POST, POST) + XX(4, PUT, PUT) + XX(5, CONNECT, CONNECT) + XX(6, OPTIONS, OPTIONS) + XX(7, TRACE, TRACE) + XX(8, COPY, COPY) + XX(9, LOCK, LOCK) + XX(10, MKCOL, MKCOL) + XX(11, MOVE, MOVE) + XX(12, PROPFIND, PROPFIND) + XX(13, PROPPATCH, PROPPATCH) + XX(14, SEARCH, SEARCH) + XX(15, UNLOCK, UNLOCK) + XX(16, BIND, BIND) + XX(17, REBIND, REBIND) + XX(18, UNBIND, UNBIND) + XX(19, ACL, ACL) + XX(20, REPORT, REPORT) + XX(21, MKACTIVITY, MKACTIVITY) + XX(22, CHECKOUT, CHECKOUT) + XX(23, MERGE, MERGE) + XX(24, MSEARCH, M-SEARCH) + XX(25, NOTIFY, NOTIFY) + XX(26, SUBSCRIBE, SUBSCRIBE) + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) + XX(28, PATCH, PATCH) + XX(29, PURGE, PURGE) + XX(30, MKCALENDAR, MKCALENDAR) + XX(31, LINK, LINK) + XX(32, UNLINK, UNLINK) + XX(33, SOURCE, SOURCE) +''' + +if __name__ == '__main__': + methods = [] + for line in StringIO(SRC): + line = line.strip() + if not line.startswith('XX'): + continue + _, m, _ = line.split(',', 2) + methods.append(m.strip()) + gentokenlookup(methods, 'HTTP_') diff --git a/gennghttpxfun.py b/gennghttpxfun.py new file mode 100755 index 0000000..2977407 --- /dev/null +++ b/gennghttpxfun.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 + +from gentokenlookup import gentokenlookup + +OPTIONS = [ + "private-key-file", + "private-key-passwd-file", + "certificate-file", + "dh-param-file", + "subcert", + "backend", + "frontend", + "workers", + "http2-max-concurrent-streams", + "log-level", + "daemon", + "http2-proxy", + "http2-bridge", + "client-proxy", + "add-x-forwarded-for", + "strip-incoming-x-forwarded-for", + "no-via", + "frontend-http2-read-timeout", + "frontend-read-timeout", + "frontend-write-timeout", + "backend-read-timeout", + "backend-write-timeout", + "stream-read-timeout", + "stream-write-timeout", + "accesslog-file", + "accesslog-syslog", + "accesslog-format", + "errorlog-file", + "errorlog-syslog", + "backend-keep-alive-timeout", + "frontend-http2-window-bits", + "backend-http2-window-bits", + "frontend-http2-connection-window-bits", + "backend-http2-connection-window-bits", + "frontend-no-tls", + "backend-no-tls", + "backend-tls-sni-field", + "pid-file", + "user", + "syslog-facility", + "backlog", + "ciphers", + "client", + "insecure", + "cacert", + "backend-ipv4", + "backend-ipv6", + "backend-http-proxy-uri", + "read-rate", + "read-burst", + "write-rate", + "write-burst", + "worker-read-rate", + "worker-read-burst", + "worker-write-rate", + "worker-write-burst", + "npn-list", + "tls-proto-list", + "verify-client", + "verify-client-cacert", + "client-private-key-file", + "client-cert-file", + "frontend-http2-dump-request-header", + "frontend-http2-dump-response-header", + "http2-no-cookie-crumbling", + "frontend-frame-debug", + "padding", + "altsvc", + "add-request-header", + "add-response-header", + "worker-frontend-connections", + "no-location-rewrite", + "no-host-rewrite", + "backend-http1-connections-per-host", + "backend-http1-connections-per-frontend", + "listener-disable-timeout", + "tls-ticket-key-file", + "rlimit-nofile", + "backend-request-buffer", + "backend-response-buffer", + "no-server-push", + "backend-http2-connections-per-worker", + "fetch-ocsp-response-file", + "ocsp-update-interval", + "no-ocsp", + "include", + "tls-ticket-key-cipher", + "host-rewrite", + "tls-session-cache-memcached", + "tls-session-cache-memcached-tls", + "tls-ticket-key-memcached", + "tls-ticket-key-memcached-interval", + "tls-ticket-key-memcached-max-retry", + "tls-ticket-key-memcached-max-fail", + "mruby-file", + "accept-proxy-protocol", + "conf", + "fastopen", + "tls-dyn-rec-warmup-threshold", + "tls-dyn-rec-idle-timeout", + "add-forwarded", + "strip-incoming-forwarded", + "forwarded-by", + "forwarded-for", + "response-header-field-buffer", + "max-response-header-fields", + "request-header-field-buffer", + "max-request-header-fields", + "header-field-buffer", + "max-header-fields", + "no-http2-cipher-block-list", + "no-http2-cipher-black-list", + "backend-http1-tls", + "tls-session-cache-memcached-cert-file", + "tls-session-cache-memcached-private-key-file", + "tls-session-cache-memcached-address-family", + "tls-ticket-key-memcached-tls", + "tls-ticket-key-memcached-cert-file", + "tls-ticket-key-memcached-private-key-file", + "tls-ticket-key-memcached-address-family", + "backend-address-family", + "frontend-http2-max-concurrent-streams", + "backend-http2-max-concurrent-streams", + "backend-connections-per-frontend", + "backend-tls", + "backend-connections-per-host", + "error-page", + "no-kqueue", + "frontend-http2-settings-timeout", + "backend-http2-settings-timeout", + "api-max-request-body", + "backend-max-backoff", + "server-name", + "no-server-rewrite", + "frontend-http2-optimize-write-buffer-size", + "frontend-http2-optimize-window-size", + "frontend-http2-window-size", + "frontend-http2-connection-window-size", + "backend-http2-window-size", + "backend-http2-connection-window-size", + "frontend-http2-encoder-dynamic-table-size", + "frontend-http2-decoder-dynamic-table-size", + "backend-http2-encoder-dynamic-table-size", + "backend-http2-decoder-dynamic-table-size", + "ecdh-curves", + "tls-sct-dir", + "backend-connect-timeout", + "dns-cache-timeout", + "dns-lookup-timeout", + "dns-max-try", + "frontend-keep-alive-timeout", + "psk-secrets", + "client-psk-secrets", + "client-no-http2-cipher-block-list", + "client-no-http2-cipher-black-list", + "client-ciphers", + "accesslog-write-early", + "tls-min-proto-version", + "tls-max-proto-version", + "redirect-https-port", + "frontend-max-requests", + "single-thread", + "single-process", + "no-add-x-forwarded-proto", + "no-strip-incoming-x-forwarded-proto", + "ocsp-startup", + "no-verify-ocsp", + "verify-client-tolerate-expired", + "ignore-per-pattern-mruby-error", + "tls-no-postpone-early-data", + "tls-max-early-data", + "tls13-ciphers", + "tls13-client-ciphers", + "no-strip-incoming-early-data", + "quic-bpf-program-file", + "no-quic-bpf", + "http2-altsvc", + "frontend-http3-read-timeout", + "frontend-quic-idle-timeout", + "frontend-quic-debug-log", + "frontend-http3-window-size", + "frontend-http3-connection-window-size", + "frontend-http3-max-window-size", + "frontend-http3-max-connection-window-size", + "frontend-http3-max-concurrent-streams", + "frontend-quic-early-data", + "frontend-quic-qlog-dir", + "frontend-quic-require-token", + "frontend-quic-congestion-controller", + "quic-server-id", + "frontend-quic-secret-file", + "rlimit-memlock", + "max-worker-processes", + "worker-process-grace-shutdown-period", + "frontend-quic-initial-rtt", + "require-http-scheme", + "tls-ktls", + "alpn-list", +] + +LOGVARS = [ + "remote_addr", + "time_local", + "time_iso8601", + "request", + "status", + "body_bytes_sent", + "remote_port", + "server_port", + "request_time", + "pid", + "alpn", + "ssl_cipher", + "ssl_protocol", + "ssl_session_id", + "ssl_session_reused", + "tls_cipher", + "tls_protocol", + "tls_session_id", + "tls_session_reused", + "tls_sni", + "tls_client_fingerprint_sha256", + "tls_client_fingerprint_sha1", + "tls_client_subject_name", + "tls_client_issuer_name", + "tls_client_serial", + "backend_host", + "backend_port", + "method", + "path", + "path_without_query", + "protocol_version", +] + +if __name__ == '__main__': + gentokenlookup(OPTIONS, 'SHRPX_OPTID_', value_type='char', comp_fun='util::strieq_l') + gentokenlookup(LOGVARS, 'LogFragmentType::', value_type='char', comp_fun='util::strieq_l', return_type='LogFragmentType', fail_value='LogFragmentType::NONE') diff --git a/gennmchartbl.py b/gennmchartbl.py new file mode 100755 index 0000000..b75b9a6 --- /dev/null +++ b/gennmchartbl.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in ["!" , "#" , "$" , "%" , "&" , "'" , "*", + "+" , "-" , "." , "^" , "_" , "`" , "|" , "~"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/genpathchartbl.py b/genpathchartbl.py new file mode 100755 index 0000000..cd8c714 --- /dev/null +++ b/genpathchartbl.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if (0x21 <= i and i < 0x7f): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('1 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/gentokenlookup.py b/gentokenlookup.py new file mode 100644 index 0000000..8277858 --- /dev/null +++ b/gentokenlookup.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +def to_enum_hd(k, prefix): + res = prefix + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + ent[c].append(k) + + return res + +def gen_enum(tokens, prefix): + print('''\ +enum {''') + for k in sorted(tokens): + print('''\ + {},'''.format(to_enum_hd(k, prefix))) + print('''\ + {}MAXIDX, +}};'''.format(prefix)) + +def gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value): + print('''\ +{} lookup_token(const {} *name, size_t namelen) {{ + switch (namelen) {{'''.format(return_type, value_type)) + b = build_header(tokens) + for size in sorted(b.keys()): + ents = b[size] + print('''\ + case {}:'''.format(size)) + print('''\ + switch (name[{}]) {{'''.format(size - 1)) + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print('''\ + case '{}':'''.format(c)) + for k in headers: + print('''\ + if ({}("{}", name, {})) {{ + return {}; + }}'''.format(comp_fun, k[:-1], size - 1, to_enum_hd(k, prefix))) + print('''\ + break;''') + print('''\ + } + break;''') + print('''\ + }} + return {}; +}}'''.format(fail_value)) + +def gentokenlookup(tokens, prefix, value_type='uint8_t', comp_fun='util::streq_l', return_type='int', fail_value='-1'): + gen_enum(tokens, prefix) + print() + gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value) diff --git a/genvchartbl.py b/genvchartbl.py new file mode 100755 index 0000000..a4ff8d1 --- /dev/null +++ b/genvchartbl.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x20: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) == ' ': + sys.stdout.write('1 /* SPC */, ') + elif chr(i) == '\t': + sys.stdout.write('1 /* HT */, ') + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('1 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/git-clang-format b/git-clang-format new file mode 100755 index 0000000..6a0db27 --- /dev/null +++ b/git-clang-format @@ -0,0 +1,484 @@ +#!/usr/bin/env python +# +#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +r""" +clang-format git integration +============================ + +This file provides a clang-format integration for git. Put it somewhere in your +path and ensure that it is executable. Then, "git clang-format" will invoke +clang-format on the changes in current files or a specific commit. + +For further details, run: +git clang-format -h + +Requires Python 2.7 +""" + +import argparse +import collections +import contextlib +import errno +import os +import re +import subprocess +import sys + +usage = 'git clang-format [OPTIONS] [] [--] [...]' + +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/go.mod b/go.mod new file mode 100644 index 0000000..4469aec --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module github.com/nghttp2/nghttp2 + +go 1.21.1 + +require ( + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 + github.com/quic-go/quic-go v0.41.0 + github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8 + golang.org/x/net v0.20.0 +) + +require ( + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + go.uber.org/mock v0.3.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.9.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bb79e47 --- /dev/null +++ b/go.sum @@ -0,0 +1,57 @@ +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= +github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8 h1:zKJxuRe+a0O34V81GAZWOrotuU6mveT30QLjJ7OPMMg= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20240121064059-46ccb0a462a8/go.mod h1:gTqc3Q4boc+cKRlSFywTYdX9t6VGRcsThlNIWwaL3Dc= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/help2rst.py b/help2rst.py new file mode 100755 index 0000000..245f669 --- /dev/null +++ b/help2rst.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# script to produce rst file from program's help output. + +import sys +import re +import argparse + +arg_indent = ' ' * 14 + +def help2man(infile): + # We assume that first line is usage line like this: + # + # Usage: nghttp [OPTIONS]... URI... + # + # The second line is description of the command. Multiple lines + # are permitted. The blank line signals the end of this section. + # After that, we parses positional and optional arguments. + # + # The positional argument is enclosed with < and >: + # + # + # + # 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/integration-tests/.gitignore b/integration-tests/.gitignore new file mode 100644 index 0000000..f40c109 --- /dev/null +++ b/integration-tests/.gitignore @@ -0,0 +1,3 @@ +# generated files +config.go +setenv diff --git a/integration-tests/CMakeLists.txt b/integration-tests/CMakeLists.txt new file mode 100644 index 0000000..cc92e9f --- /dev/null +++ b/integration-tests/CMakeLists.txt @@ -0,0 +1,45 @@ +set(GO_FILES + nghttpx_http1_test.go + nghttpx_http2_test.go + server_tester.go + server_tester_http3.go +) + +# XXX unused +set(EXTRA_DIST + ${GO_FILES} + server.key + server.crt + alt-server.key + alt-server.crt + setenv + req-set-header.rb + resp-set-header.rb + req-return.rb + resp-return.rb +) + +# 'go test' requires both config.go and the test files in the same directory. +# For out-of-tree builds, config.go is normally not placed next to the source +# files, so copy the tests to the build directory as a workaround. +set(GO_BUILD_FILES) +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + foreach(gofile IN LISTS GO_FILES) + set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${gofile}") + add_custom_command(OUTPUT "${outfile}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}" "${outfile}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}" + ) + list(APPEND GO_BUILD_FILES "${outfile}") + endforeach() +endif() + +if(ENABLE_HTTP3) + set(GO_TEST_TAGS quic) +endif() + +add_custom_target(it + COMMAND sh setenv go test -v --tags=${GO_TEST_TAGS} + DEPENDS ${GO_BUILD_FILES} +) diff --git a/integration-tests/Makefile.am b/integration-tests/Makefile.am new file mode 100644 index 0000000..21166ed --- /dev/null +++ b/integration-tests/Makefile.am @@ -0,0 +1,52 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +GO_FILES = \ + nghttpx_http1_test.go \ + nghttpx_http2_test.go \ + nghttpx_http3_test.go \ + server_tester.go \ + server_tester_http3.go + +EXTRA_DIST = \ + CMakeLists.txt \ + $(GO_FILES) \ + server.key \ + server.crt \ + alt-server.key \ + alt-server.crt \ + setenv \ + req-set-header.rb \ + resp-set-header.rb \ + req-return.rb \ + resp-return.rb + +GO_TEST_TAGS = + +if ENABLE_HTTP3 +GO_TEST_TAGS += quic +endif # ENABLE_HTTP3 + +it: + for i in $(GO_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done + sh setenv go test -v --tags=${GO_TEST_TAGS} diff --git a/integration-tests/alt-server.crt b/integration-tests/alt-server.crt new file mode 100644 index 0000000..f003eb1 --- /dev/null +++ b/integration-tests/alt-server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhzCCAm+gAwIBAgIJANfuEldiquMNMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMCmFsdC1kb21haW4wHhcNMTUwMTI1MDYy +NTQxWhcNMjUwMTIyMDYyNTQxWjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29t +ZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYD +VQQDDAphbHQtZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +0IwhDOGDipGrJQ9IoRSzPdkU/Ii4aJgGKHlXminym42X0VI3IW61RLvOHRlHVmVH +JQjFuDo2x+y81t9NlDg3HGUbSpzOzpm6StiutB7c4hreT5G4r0YKya1ugiemN0+p +qjIPJWm2jVnf448eZvUKRKEQ9W0MLZjiNjVGKrKlwo7fIlXg4N3+YixLYffAT1NV +d1T6V5jzlbruj15gK2nGjMQ9D1h1t9vTbTxY+mtk72aX0Y64IE6pPBWLFSSH8ozU +idDoL3AZwz2Jker+ALKK8CM4uho/RPpyW1C06HH+HLdH2MqEjDOROde/Nzxm668O +gK/JWGIEyUqYiUXx0yhFxwIDAQABo1AwTjAdBgNVHQ4EFgQU/Y0GDN2uPjbyePcu +95ZvYEK/gHIwHwYDVR0jBBgwFoAU/Y0GDN2uPjbyePcu95ZvYEK/gHIwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAodD6LVCzL3wfsZ6TxTzf9TfgIdbj +ilL3SEMT/xnfTXT3SLYScTRqQIAI29Y7dOLMq89p4hY2wmeUEhBUAz+y9G2JVr8o +6EbxXrQpWgNJogELqoNnMdrDxB5RsmDDKEJ/rLjDfSkjWbK7B2PZsqVTDgjekCFw +u6FqTIjn/O1O/L5tjwxwxjHmQod/maFCvXoDOVBuwdHnkp298tqlvsHfHO8m++Wj ++XYB8plMIjpeTh9v4w9Jc4QZ59lK/3Tt4qaENeQrMEubKSY/Zen7L2bzhk+cChWT +GSGz9uNXieoZaH79D0wnyZaSZ5Ds4ActMevnGg3iYXuzuFqx8Pungn74Vg== +-----END CERTIFICATE----- diff --git a/integration-tests/alt-server.key b/integration-tests/alt-server.key new file mode 100644 index 0000000..a977663 --- /dev/null +++ b/integration-tests/alt-server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDQjCEM4YOKkasl +D0ihFLM92RT8iLhomAYoeVeaKfKbjZfRUjchbrVEu84dGUdWZUclCMW4OjbH7LzW +302UODccZRtKnM7OmbpK2K60HtziGt5PkbivRgrJrW6CJ6Y3T6mqMg8labaNWd/j +jx5m9QpEoRD1bQwtmOI2NUYqsqXCjt8iVeDg3f5iLEth98BPU1V3VPpXmPOVuu6P +XmAracaMxD0PWHW329NtPFj6a2TvZpfRjrggTqk8FYsVJIfyjNSJ0OgvcBnDPYmR +6v4AsorwIzi6Gj9E+nJbULTocf4ct0fYyoSMM5E51783PGbrrw6Ar8lYYgTJSpiJ +RfHTKEXHAgMBAAECggEBALTrjFSXY72YB+h7rN+JjMIwDIPUvF6I3HbKZhQpJf6K +xNVkRM2tNHavku0tm/S4ohLf3F+pqRKiL2Udjjjy1+S7VgTRqpwTQ0lhV5aNW8SP +2KMg4R61XfB+k+s4KHu9kYxEJ12mqydPe+r3o0FgfYryTDsOYk1AX6b1aqzqFOGF +7GaqLALSbKU59tcJJ1SZNBbpIKFUrAT9nZt9dW02/foqP5bzUk43Yjw48xmLwegc +bMXXcpZhNZSktltvwRw7Q4Foc9kuRlMdTAnAD9PnMCcZwicS/YeVVF6Rz4fGviKv +7/kPHQ7g4YpFktVDzuZ5xw6GDVFeJ6uGMVUX8+EePvkCgYEA+/nrcn82nFHCxm8Q +0iiUhi/AoXjZg+O5Ytaje9O/YNoX+c4ywe13h0+TXKH79O0KfTwXeJyDgPZbAIFV +9oURellRYUzKDafnBHis2f+Ywn6GqHL5e2X30ZxIp1GK46pcvne1YuvJhgGmiVay +vd7sRx09OKU124dG22rIFCis6asCgYEA0+CsA6LrEwQ/aPJYASY3VHNO/WoAOnPg +Cwsg+02XWsPEwP//lNmpanz8TUm2URS063ZK8bx7t3ejvDgBdsRwwjiMlDp7XTUU +3Zk+mhCV2qkMi02aKemvz29bDhmh5JoH7W3IwsXtJYO0yZDYrDR3ioiKRccioPoE +b/Nq781sEFUCgYEA4xqx9xRpaCLY5nicNI6WrwrDF8YQZisNn+PMnYKP7v8itOgA +H4GkRbSXINpueKZc2dsbXH3UmJtyEdaAYBw3UIrIKmZHhl9afFE3mZQhXssjGxfl +fC6/WZD+eq+n+uJFjPXf6jSSAdHjA828dB1D4CSeVTuyexZF6uUnR+QRVNkCgYEA +i+pb7XLSpZYygY03zFp+Q0h6KyKqz+7hTqmkuA8/GfMZpRHop1UtaWLsAeXhfZ2c +87kEOKptUHSzLYIWhWWnyLorK1+LQ7vf8Y5XJso5C1KDNCKk4XSuYt94U9FddWa6 +QXI0F1s5BYL6Cfma++0R2+va08Vy+rbf40XtojoXWJkCgYEA0hMQSCvok7is27nQ +G80KXfmghU2eEB7zif3T00/fwJycxEbmnNeof+SKmhdY4ZgqTscfOxlQPflV/eqB +xs4GnFDDeM0F8KH0BimOXxr7sJPFCg22PCCQQcRtM/KoU+ip/kNmTfwrsC0xMFPU +HD8M1JCZF2eLMekXXP3cB0U4sUs= +-----END PRIVATE KEY----- diff --git a/integration-tests/config.go.in b/integration-tests/config.go.in new file mode 100644 index 0000000..3d79297 --- /dev/null +++ b/integration-tests/config.go.in @@ -0,0 +1,6 @@ +package nghttp2 + +const ( + buildDir = "@top_builddir@" + sourceDir = "@top_srcdir@" +) diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go new file mode 100644 index 0000000..740396d --- /dev/null +++ b/integration-tests/nghttpx_http1_test.go @@ -0,0 +1,1631 @@ +package nghttp2 + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "syscall" + "testing" + "time" + + "golang.org/x/net/http2/hpack" + "golang.org/x/net/websocket" +) + +// TestH1H1PlainGET tests whether simple HTTP/1 GET request works. +func TestH1H1PlainGET(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with +// Connection: close request header field works. +func TestH1H1PlainGETClose(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1PlainGETClose", + header: []hpack.HeaderField{ + pair("Connection", "close"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1InvalidMethod tests that server rejects invalid method with +// 501 status code +func TestH1H1InvalidMethod(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1InvalidMethod", + method: "get", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotImplemented; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1MultipleRequestCL tests that server rejects request which +// contains multiple Content-Length header fields. +func TestH1H1MultipleRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1MultipleRequestCL\r\nContent-Length: 0\r\nContent-Length: 0\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// // TestH1H1ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/1 backend failed. +// func TestH1H1ConnectFailure(t *testing.T) { +// st := newServerTester(t, options{}) +// defer st.Close() + +// // shutdown backend server to simulate backend connect failure +// st.ts.Close() + +// res, err := st.http1(requestParam{ +// name: "TestH1H1ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http1() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH1H1AffinityCookie tests that affinity cookie is sent back in +// cleartext http. +func TestH1H1AffinityCookie(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1AffinityCookie", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH1H1AffinityCookieTLS tests that affinity cookie is sent back +// in https. +func TestH1H1AffinityCookieTLS(t *testing.T) { + opts := options{ + args: []string{"--alpn-h1", "--affinity-cookie"}, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1AffinityCookieTLS", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH1H1GracefulShutdown tests graceful shutdown. +func TestH1H1GracefulShutdown(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1GracefulShutdown-1", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil { + t.Fatalf("Error st.cmd.Process.Signal() = %v", err) + } + + time.Sleep(150 * time.Millisecond) + + res, err = st.http1(requestParam{ + name: "TestH1H1GracefulShutdown-2", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if got, want := res.connClose, true; got != want { + t.Errorf("res.connClose: %v; want %v", got, want) + } + + want := io.EOF + b := make([]byte, 256) + if _, err := st.conn.Read(b); err == nil || err != want { + t.Errorf("st.conn.Read(): %v; want %v", err, want) + } +} + +// TestH1H1HostRewrite tests that server rewrites Host header field +func TestH1H1HostRewrite(t *testing.T) { + opts := options{ + args: []string{"--host-rewrite"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1BadHost tests that server rejects request including bad +// characters in host header field. +func TestH1H1BadHost(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H1HBadHost\r\nHost: foo\"bar\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1BadAuthority tests that server rejects request including +// bad characters in authority component of requset URI. +func TestH1H1BadAuthority(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET http://foo\"bar/ HTTP/1.1\r\nTest-Case: TestH1H1HBadAuthority\r\nHost: foobar\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1BadScheme tests that server rejects request including +// bad characters in scheme component of requset URI. +func TestH1H1BadScheme(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET http*://example.com/ HTTP/1.1\r\nTest-Case: TestH1H1HBadScheme\r\nHost: example.com\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request +// without Host header field +func TestH1H1HTTP10(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1HTTP10NoHostRewrite tests that server generates host header +// field using actual backend server even if --no-http-rewrite is +// used. +func TestH1H1HTTP10NoHostRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10NoHostRewrite\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1RequestTrailer tests request trailer part is forwarded to +// backend. +func TestH1H1RequestTrailer(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 4096) + for { + _, err := r.Body.Read(buf) + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("r.Body.Read() = %v", err) + } + } + if got, want := r.Trailer.Get("foo"), "bar"; got != want { + t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RequestTrailer", + body: []byte("1"), + trailer: []hpack.HeaderField{ + pair("foo", "bar"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFieldBufferPath tests that request with request path +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBufferPath(t *testing.T) { + // The value 100 is chosen so that sum of header fields bytes + // does not exceed it. We use > 100 bytes URI to exceed this + // limit. + opts := options{ + args: []string{"--request-header-field-buffer=100"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBufferPath", + path: "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBuffer(t *testing.T) { + opts := options{ + args: []string{"--request-header-field-buffer=10"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestH1H1HeaderFields(t *testing.T) { + opts := options{ + args: []string{"--max-request-header-fields=1"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFields", + header: []hpack.HeaderField{ + // Add extra header field to ensure that + // header field limit exceeds + pair("Connection", "close"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1Websocket tests that HTTP Upgrade to WebSocket works. +func TestH1H1Websocket(t *testing.T) { + opts := options{ + handler: websocket.Handler(func(ws *websocket.Conn) { + if _, err := io.Copy(ws, ws); err != nil { + t.Fatalf("Error io.Copy() = %v", err) + } + }).ServeHTTP, + } + st := newServerTester(t, opts) + defer st.Close() + + content := []byte("hello world") + res := st.websocket(requestParam{ + name: "TestH1H1Websocket", + body: content, + }) + if got, want := res.body, content; !bytes.Equal(got, want) { + t.Errorf("echo: %q; want %q", got, want) + } +} + +// TestH1H1ReqPhaseSetHeader tests mruby request phase hook +// modifies request header fields. +func TestH1H1ReqPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-set-header.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("User-Agent"), "mruby"; got != want { + t.Errorf("User-Agent = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ReqPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH1H1ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H1ReqPhaseReturnCONNECTMethod tests that mruby request phase +// hook resets llhttp HPE_PAUSED_UPGRADE. +func TestH1H1ReqPhaseReturnCONNECTMethod(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + hdCheck := func() { + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + + for _, tt := range hdtests { + if got, want := resp.Header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } + } + + hdCheck() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err = http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + hdCheck() + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } +} + +// TestH1H1RespPhaseSetHeader tests mruby response phase hook modifies +// response header fields. +func TestH1H1RespPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-set-header.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RespPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + if got, want := res.header.Get("alpha"), "bravo"; got != want { + t.Errorf("alpha = %v; want %v", got, want) + } +} + +// TestH1H1RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH1H1RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-return.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H1HTTPSRedirect tests that the request to the backend which +// requires TLS is redirected to https URI. +func TestH1H1HTTPSRedirect(t *testing.T) { + opts := options{ + args: []string{"--redirect-if-not-tls"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HTTPSRedirect", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH1H1HTTPSRedirectPort tests that the request to the backend +// which requires TLS is redirected to https URI with given port. +func TestH1H1HTTPSRedirectPort(t *testing.T) { + opts := options{ + args: []string{ + "--redirect-if-not-tls", + "--redirect-https-port=8443", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + path: "/foo?bar", + name: "TestH1H1HTTPSRedirectPort", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH1H1POSTRequests tests that server can handle 2 requests with +// request body. +func TestH1H1POSTRequests(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1POSTRequestsNo1", + body: make([]byte, 1), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + res, err = st.http1(requestParam{ + name: "TestH1H1POSTRequestsNo2", + body: make([]byte, 65536), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1CONNECTMethodFailure tests that CONNECT method failure +// resets llhttp HPE_PAUSED_UPGRADE. +func TestH1H1CONNECTMethodFailure(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("required-header") == "" { + w.WriteHeader(http.StatusNotFound) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\r\nrequired-header: foo\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err = http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// // TestH1H2ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/2 backend failed. +// func TestH1H2ConnectFailure(t *testing.T) { +// opts := options{ +// args: []string{"--http2-bridge"}, +// } +// st := newServerTester(t, opts) +// defer st.Close() + +// // simulate backend connect attempt failure +// st.ts.Close() + +// res, err := st.http1(requestParam{ +// name: "TestH1H2ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http1() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH1H2NoHost tests that server rejects request without Host +// header field for HTTP/2 backend. +func TestH1H2NoHost(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + // without Host header field, we expect 400 response + if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H2NoHost\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H2HTTP10 tests that server can accept HTTP/1.0 request +// without Host header field +func TestH1H2HTTP10(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H2HTTP10NoHostRewrite tests that server generates host header +// field using actual backend server even if --no-http-rewrite is +// used. +func TestH1H2HTTP10NoHostRewrite(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10NoHostRewrite\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H2CrumbleCookie tests that Cookies are crumbled and assembled +// when forwarding to HTTP/2 backend link. go-nghttp2 server +// concatenates crumbled Cookies automatically, so this test is not +// much effective now. +func TestH1H2CrumbleCookie(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want { + t.Errorf("Cookie: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2CrumbleCookie", + header: []hpack.HeaderField{ + pair("Cookie", "alpha; bravo; charlie"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H2GenerateVia tests that server generates Via header field to and +// from backend server. +func TestH1H2GenerateVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH1H2AppendVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH1H2NoVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH1H2ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/req-return.rb", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H2RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH1H2RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/resp-return.rb", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H2TE tests that "te: trailers" header is forwarded to HTTP/2 +// backend server by stripping other encodings. +func TestH1H2TE(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("te"), "trailers"; got != want { + t.Errorf("te: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2TE", + header: []hpack.HeaderField{ + pair("te", "foo,trailers,bar"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfig exercise backendconfig API endpoint routine +// for successful case. +func TestH1APIBackendconfig(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfig", + path: "/api/v1beta1/backendconfig", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfigQuery exercise backendconfig API endpoint +// routine with query. +func TestH1APIBackendconfigQuery(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfigQuery", + path: "/api/v1beta1/backendconfig?foo=bar", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfigBadMethod exercise backendconfig API endpoint +// routine with bad method. +func TestH1APIBackendconfigBadMethod(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfigBadMethod", + path: "/api/v1beta1/backendconfig", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusMethodNotAllowed; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 405; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIConfigrevision tests configrevision API. +func TestH1APIConfigrevision(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIConfigrevision", + path: "/api/v1beta1/configrevision", + method: "GET", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want = %v", got, want) + } + + var apiResp APIResponse + d := json.NewDecoder(bytes.NewBuffer(res.body)) + d.UseNumber() + err = d.Decode(&apiResp) + if err != nil { + t.Fatalf("Error unmarshalling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Data["configRevision"], json.Number("0"); got != want { + t.Errorf(`apiResp.Data["configRevision"]: %v %t; want %v`, got, got, want) + } +} + +// TestH1APINotFound exercise backendconfig API endpoint routine when +// API endpoint is not found. +func TestH1APINotFound(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APINotFound", + path: "/api/notfound", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 404; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1Healthmon tests health monitor endpoint. +func TestH1Healthmon(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3011;healthmon;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3011, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1Healthmon", + path: "/alpha/bravo", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1ResponseBeforeRequestEnd tests the situation where response +// ends before request body finishes. +func TestH1ResponseBeforeRequestEnd(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("POST / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1ResponseBeforeRequestEnd\r\nContent-Length: 1000000\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1ChunkedEndsPrematurely tests that an HTTP/1.1 request fails +// if the backend chunked encoded response ends prematurely. +func TestH1H1ChunkedEndsPrematurely(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http1(requestParam{ + name: "TestH1H1ChunkedEndsPrematurely", + }) + if err == nil { + t.Fatal("st.http1() should fail") + } +} + +// TestH1H1RequestMalformedTransferEncoding tests that server rejects +// request which contains malformed transfer-encoding. +func TestH1H1RequestMalformedTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1RequestMalformedTransferEncoding\r\nTransfer-Encoding: ,chunked\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1ResponseMalformedTransferEncoding tests a request fails if +// its response contains malformed transfer-encoding. +func TestH1H1ResponseMalformedTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: ,chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ResponseMalformedTransferEncoding", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusBadGateway; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1ResponseUnknownTransferEncoding tests a request succeeds if +// its response contains unknown transfer-encoding. +func TestH1H1ResponseUnknownTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: foo\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1ResponseUnknownTransferEncoding\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error: io.WriteString() = %v", err) + } + + r := bufio.NewReader(st.conn) + + resp := make([]byte, 4096) + + resplen, err := r.Read(resp) + if err != nil { + t.Fatalf("Error: r.Read() = %v", err) + } + + resp = resp[:resplen] + + const expect = "HTTP/1.1 200 OK\r\nTransfer-Encoding: foo\r\nConnection: close\r\nServer: nghttpx\r\nVia: 1.1 nghttpx\r\n\r\n" + + if got, want := string(resp), expect; got != want { + t.Errorf("resp = %v, want %v", got, want) + } +} + +// TestH1H1RequestHTTP10TransferEncoding tests that server rejects +// HTTP/1.0 request which contains transfer-encoding. +func TestH1H1RequestHTTP10TransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1RequestHTTP10TransferEncoding\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go new file mode 100644 index 0000000..5324a18 --- /dev/null +++ b/integration-tests/nghttpx_http2_test.go @@ -0,0 +1,3740 @@ +package nghttp2 + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "regexp" + "strings" + "syscall" + "testing" + "time" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +// TestH2H1PlainGET tests whether simple HTTP/2 GET request works. +func TestH2H1PlainGET(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXfp tests that server appends :scheme to the existing +// x-forwarded-proto header field. +func TestH2H1AddXfp(t *testing.T) { + opts := options{ + args: []string{"--no-strip-incoming-x-forwarded-proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo, http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1NoAddXfp tests that server does not append :scheme to the +// existing x-forwarded-proto header field. +func TestH2H1NoAddXfp(t *testing.T) { + opts := options{ + args: []string{ + "--no-add-x-forwarded-proto", + "--no-strip-incoming-x-forwarded-proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripXfp tests that server strips incoming +// x-forwarded-proto header field. +func TestH2H1StripXfp(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripNoAddXfp tests that server strips incoming +// x-forwarded-proto header field, and does not add another. +func TestH2H1StripNoAddXfp(t *testing.T) { + opts := options{ + args: []string{"--no-add-x-forwarded-proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["X-Forwarded-Proto"]; found { + t.Errorf("X-Forwarded-Proto = %q; want nothing", got) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripNoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXff tests that server generates X-Forwarded-For header +// field when forwarding request to backend. +func TestH2H1AddXff(t *testing.T) { + opts := options{ + args: []string{"--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXff", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXff2 tests that server appends X-Forwarded-For header +// field to existing one when forwarding request to backend. +func TestH2H1AddXff2(t *testing.T) { + opts := options{ + args: []string{"--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "host, 127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXff2", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for +// option. +func TestH2H1StripXff(t *testing.T) { + opts := options{ + args: []string{"--strip-incoming-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if xff, found := r.Header["X-Forwarded-For"]; found { + t.Errorf("X-Forwarded-For = %v; want nothing", xff) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and +// --add-x-forwarded-for options. +func TestH2H1StripAddXff(t *testing.T) { + opts := options{ + args: []string{ + "--strip-incoming-x-forwarded-for", + "--add-x-forwarded-for", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripAddXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedObfuscated tests that server generates +// Forwarded header field with obfuscated "by" and "for" parameters. +func TestH2H1AddForwardedObfuscated(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=by,for,host,proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := fmt.Sprintf(`by=_[^;]+;for=_[^;]+;host="127\.0\.0\.1:%v";proto=http`, serverPort) + validFwd := regexp.MustCompile(pattern) + got := r.Header.Get("Forwarded") + + if !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedObfuscated", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedByIP tests that server generates Forwarded header +// field with IP address in "by" parameter. +func TestH2H1AddForwardedByIP(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=by,for", "--forwarded-by=ip"}, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := fmt.Sprintf(`by="127\.0\.0\.1:%v";for=_[^;]+`, serverPort) + validFwd := regexp.MustCompile(pattern) + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedByIP", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedForIP tests that server generates Forwarded header +// field with IP address in "for" parameters. +func TestH2H1AddForwardedForIP(t *testing.T) { + opts := options{ + args: []string{ + "--add-forwarded=by,for,host,proto", + "--forwarded-by=_alpha", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + want := fmt.Sprintf(`by=_alpha;for=127.0.0.1;host="127.0.0.1:%v";proto=http`, serverPort) + if got := r.Header.Get("Forwarded"); got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedForIP", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedMerge tests that server generates Forwarded +// header field with IP address in "by" and "for" parameters. The +// generated values must be appended to the existing value. +func TestH2H1AddForwardedMerge(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Forwarded"), `host=foo, proto=http`; got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedMerge", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedStrip tests that server generates Forwarded +// header field with IP address in "by" and "for" parameters. The +// generated values must not include the existing value. +func TestH2H1AddForwardedStrip(t *testing.T) { + opts := options{ + args: []string{ + "--strip-incoming-forwarded", + "--add-forwarded=proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Forwarded"), `proto=http`; got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedStrip", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1StripForwarded tests that server strips incoming Forwarded +// header field. +func TestH2H1StripForwarded(t *testing.T) { + opts := options{ + args: []string{"--strip-incoming-forwarded"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["Forwarded"]; found { + t.Errorf("Forwarded = %v; want nothing", got) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripForwarded", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedStatic tests that server generates Forwarded +// header field with the given static obfuscated string for "by" +// parameter. +func TestH2H1AddForwardedStatic(t *testing.T) { + opts := options{ + args: []string{ + "--add-forwarded=by,for", + "--forwarded-by=_alpha", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := `by=_alpha;for=_[^;]+` + validFwd := regexp.MustCompile(pattern) + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedStatic", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1GenerateVia tests that server generates Via header field to and +// from backend server. +func TestH2H1GenerateVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH2H1AppendVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH2H1NoVia(t *testing.T) { + opts := options{ + args: []string{"--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1HostRewrite tests that server rewrites host header field +func TestH2H1HostRewrite(t *testing.T) { + opts := options{ + args: []string{"--host-rewrite"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H1NoHostRewrite tests that server does not rewrite host +// header field +func TestH2H1NoHostRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoHostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.frontendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H1BadRequestCL tests that server rejects request whose +// content-length header field value does not match its request body +// size. +func TestH2H1BadRequestCL(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + // we set content-length: 1024, but the actual request body is + // 3 bytes. + res, err := st.http2(requestParam{ + name: "TestH2H1BadRequestCL", + method: "POST", + header: []hpack.HeaderField{ + pair("content-length", "1024"), + }, + body: []byte("foo"), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := http2.ErrCodeProtocol + if res.errCode != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1BadResponseCL tests that server returns error when +// content-length response header field value does not match its +// response body size. +func TestH2H1BadResponseCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // we set content-length: 1024, but only send 3 bytes. + w.Header().Add("Content-Length", "1024") + if _, err := w.Write([]byte("foo")); err != nil { + t.Fatalf("Error w.Write() = %v", err) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadResponseCL", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := http2.ErrCodeInternal + if res.errCode != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1LocationRewrite tests location header field rewriting +// works. +func TestH2H1LocationRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // TODO we cannot get st.ts's port number + // here.. 8443 is just a place holder. We + // ignore it on rewrite. + w.Header().Add("Location", "http://127.0.0.1:8443/p/q?a=b#fragment") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1LocationRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := fmt.Sprintf("http://127.0.0.1:%v/p/q?a=b#fragment", serverPort) + if got := res.header.Get("Location"); got != want { + t.Errorf("Location: %v; want %v", got, want) + } +} + +// TestH2H1ChunkedRequestBody tests that chunked request body works. +func TestH2H1ChunkedRequestBody(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + want := "[chunked]" + if got := fmt.Sprint(r.TransferEncoding); got != want { + t.Errorf("Transfer-Encoding: %v; want %v", got, want) + } + body, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("Error reading r.body: %v", err) + } + want = "foo" + if got := string(body); got != want { + t.Errorf("body: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ChunkedRequestBody", + method: "POST", + body: []byte("foo"), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1MultipleRequestCL tests that server rejects request with +// multiple Content-Length request header fields. +func TestH2H1MultipleRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1MultipleRequestCL", + header: []hpack.HeaderField{ + pair("content-length", "1"), + pair("content-length", "1"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1InvalidRequestCL tests that server rejects request with +// Content-Length which cannot be parsed as a number. +func TestH2H1InvalidRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1InvalidRequestCL", + header: []hpack.HeaderField{ + pair("content-length", ""), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// // TestH2H1ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/1 backend failed. +// func TestH2H1ConnectFailure(t *testing.T) { +// st := newServerTester(t, options{}) +// defer st.Close() + +// // shutdown backend server to simulate backend connect failure +// st.ts.Close() + +// res, err := st.http2(requestParam{ +// name: "TestH2H1ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http2() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH2H1InvalidMethod tests that server rejects invalid method with +// 501. +func TestH2H1InvalidMethod(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1InvalidMethod", + method: "get", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusNotImplemented; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1BadAuthority tests that server rejects request including +// bad characters in :authority header field. +func TestH2H1BadAuthority(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadAuthority", + authority: `foo\bar`, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1BadScheme tests that server rejects request including +// bad characters in :scheme header field. +func TestH2H1BadScheme(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadScheme", + scheme: "http*", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2 +// request is assembled into 1 when forwarding to HTTP/1 backend link. +func TestH2H1AssembleCookies(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want { + t.Errorf("Cookie: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AssembleCookies", + header: []hpack.HeaderField{ + pair("cookie", "alpha"), + pair("cookie", "bravo"), + pair("cookie", "charlie"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1TETrailers tests that server accepts TE request header +// field if it has trailers only. +func TestH2H1TETrailers(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TETrailers", + header: []hpack.HeaderField{ + pair("te", "trailers"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1TEGzip tests that server resets stream if TE request header +// field contains gzip. +func TestH2H1TEGzip(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Error("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TEGzip", + header: []hpack.HeaderField{ + pair("te", "gzip"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1SNI tests server's TLS SNI extension feature. It must +// choose appropriate certificate depending on the indicated +// server_name from client. +func TestH2H1SNI(t *testing.T) { + opts := options{ + args: []string{"--subcert=" + testDir + "/alt-server.key:" + testDir + "/alt-server.crt"}, + tls: true, + tlsConfig: &tls.Config{ + ServerName: "alt-domain", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + tlsConn := st.conn.(*tls.Conn) + connState := tlsConn.ConnectionState() + cert := connState.PeerCertificates[0] + + if got, want := cert.Subject.CommonName, "alt-domain"; got != want { + t.Errorf("CommonName: %v; want %v", got, want) + } +} + +// TestH2H1TLSXfp tests nghttpx sends x-forwarded-proto header field +// with http value since :scheme is http, even if the frontend +// connection is encrypted. +func TestH2H1TLSXfp(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("x-forwarded-proto"), "http"; got != want { + t.Errorf("x-forwarded-proto: want %v; got %v", want, got) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TLSXfp", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ServerPush tests server push using Link header field from +// backend server. +func TestH2H1ServerPush(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // only resources marked as rel=preload are pushed + if !strings.HasPrefix(r.URL.Path, "/css/") { + w.Header().Add("Link", "; 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/integration-tests/nghttpx_http3_test.go b/integration-tests/nghttpx_http3_test.go new file mode 100644 index 0000000..9ea85d7 --- /dev/null +++ b/integration-tests/nghttpx_http3_test.go @@ -0,0 +1,393 @@ +//go:build quic + +package nghttp2 + +import ( + "bytes" + "crypto/rand" + "io" + "net/http" + "regexp" + "testing" + + "golang.org/x/net/http2/hpack" +) + +// TestH3H1PlainGET tests whether simple HTTP/3 GET request works. +func TestH3H1PlainGET(t *testing.T) { + st := newServerTester(t, options{ + quic: true, + }) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH3H1RequestBody tests HTTP/3 request with body works. +func TestH3H1RequestBody(t *testing.T) { + body := make([]byte, 3333) + _, err := rand.Read(body) + if err != nil { + t.Fatalf("Unable to create request body: %v", err) + } + + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 4096) + buflen := 0 + p := buf + + for { + if len(p) == 0 { + t.Fatal("Request body is too large") + } + + n, err := r.Body.Read(p) + + p = p[n:] + buflen += n + + if err != nil { + if err == io.EOF { + break + } + + t.Fatalf("r.Body.Read() = %v", err) + } + } + + buf = buf[:buflen] + + if got, want := buf, body; !bytes.Equal(got, want) { + t.Fatalf("buf = %v; want %v", got, want) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1RequestBody", + body: body, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH3H1GenerateVia tests that server generates Via header field to +// and from backend server. +func TestH3H1GenerateVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "3 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH3H1AppendVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 3 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH3H1NoVia(t *testing.T) { + opts := options{ + args: []string{"--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1BadResponseCL tests that server returns error when +// content-length response header field value does not match its +// response body size. +func TestH3H1BadResponseCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // we set content-length: 1024, but only send 3 bytes. + w.Header().Add("Content-Length", "1024") + if _, err := w.Write([]byte("foo")); err != nil { + t.Fatalf("Error w.Write() = %v", err) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http3(requestParam{ + name: "TestH3H1BadResponseCL", + }) + if err == nil { + t.Fatal("st.http3() should fail") + } +} + +// TestH3H1HTTPSRedirect tests that HTTPS redirect should not happen +// with HTTP/3. +func TestH3H1HTTPSRedirect(t *testing.T) { + opts := options{ + args: []string{"--redirect-if-not-tls"}, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1HTTPSRedirect", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH3H1AffinityCookieTLS tests that affinity cookie is sent back +// in https. +func TestH3H1AffinityCookieTLS(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1AffinityCookieTLS", + scheme: "https", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH3H2ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH3H2ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/req-return.rb", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H2ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH3H2RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH3H2RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/resp-return.rb", + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H2RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH3ResponseBeforeRequestEnd tests the situation where response +// ends before request body finishes. +func TestH3ResponseBeforeRequestEnd(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be forwarded") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3ResponseBeforeRequestEnd", + noEndStream: true, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH3H1ChunkedEndsPrematurely tests that a stream is reset if the +// backend chunked encoded response ends prematurely. +func TestH3H1ChunkedEndsPrematurely(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http3(requestParam{ + name: "TestH3H1ChunkedEndsPrematurely", + }) + if err == nil { + t.Fatal("st.http3() should fail") + } +} diff --git a/integration-tests/req-return.rb b/integration-tests/req-return.rb new file mode 100644 index 0000000..51d315e --- /dev/null +++ b/integration-tests/req-return.rb @@ -0,0 +1,12 @@ +class App + def on_req(env) + resp = env.resp + + resp.clear_headers + resp.status = 404 + resp.add_header "from", "mruby" + resp.return "Hello World from req" + end +end + +App.new diff --git a/integration-tests/req-set-header.rb b/integration-tests/req-set-header.rb new file mode 100644 index 0000000..986f128 --- /dev/null +++ b/integration-tests/req-set-header.rb @@ -0,0 +1,7 @@ +class App + def on_req(env) + env.req.set_header "User-Agent", "mruby" + end +end + +App.new diff --git a/integration-tests/resp-return.rb b/integration-tests/resp-return.rb new file mode 100644 index 0000000..fbbd775 --- /dev/null +++ b/integration-tests/resp-return.rb @@ -0,0 +1,12 @@ +class App + def on_resp(env) + resp = env.resp + + resp.clear_headers + resp.status = 404 + resp.add_header "from", "mruby" + resp.return "Hello World from resp" + end +end + +App.new diff --git a/integration-tests/resp-set-header.rb b/integration-tests/resp-set-header.rb new file mode 100644 index 0000000..228837a --- /dev/null +++ b/integration-tests/resp-set-header.rb @@ -0,0 +1,7 @@ +class App + def on_resp(env) + env.resp.set_header "Alpha", "bravo" + end +end + +App.new diff --git a/integration-tests/server.crt b/integration-tests/server.crt new file mode 100644 index 0000000..c50fdaa --- /dev/null +++ b/integration-tests/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAOvIx8xIxgyOMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xNTAxMjMxMjI0 +MjdaFw0yNTAxMjAxMjI0MjdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMuI +QZRI/iBaxPTjTWGemt8tCEfzZWxuIW3hY/gIhwJDfH2SbourBh1s9vqcqhBq5vmo +kdfVQXAnNLjIG1uhWmcHuNnKrE5hU82N6i9RsmuM5TQRvhsamHri4G+EXJMu9GqF +Mso8g7MWpRSGKf+8gfjAVNwfCHFiu8oBcMmy3l54MFHgRLSveAMhiPB0e3Xlnpr5 +2bS/oGTx5ynwPgBpEn2FrpT4Z/aLCLzJ/ysgNH8BXEh7n/v7xM3vd5grqB039rd5 +JoxlWvp+4XpzKp5upaqmOcVUq4pDSFUQ3w6C+v33Z3OK6Qaon7GMxLv3Us3b7PZ3 +1CLoWJR2o3OSnUfO/gUCAwEAAaNQME4wHQYDVR0OBBYEFLc5JWPUUVx4GJesogMV +w2Rz0L3yMB8GA1UdIwQYMBaAFLc5JWPUUVx4GJesogMVw2Rz0L3yMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAP/cJWpM+GEjmVYHFacKTdbXBMox2Xn +QY2NLm00WPOGvKnO7czMFfX/pEmiq71kD45rLLfbaJP205QpxqiAIvhFhuq50Co7 +sTDtwcDTPLX9H7Ugjt4sTMPiwC14uVXFfoT/J46zMjXwP00qKyfszc2tkIgHfrTl +h4M1hkdfmMximir/Ii7TdYYJ3oGS8tdcYb6D4DZwAljKmxF6iUOwFCUgpTmqDBT5 +irXY8D27DzuNN5Pg07rwAlwXLCzrJE10UtO4MmRVXwpzmoaRQD4/tna6bZzdetvs +gPdGP6W1o0q85gullieMJWeKyQA/wasoE7fypn4pHAdTZm/vH+v7GHg= +-----END CERTIFICATE----- diff --git a/integration-tests/server.key b/integration-tests/server.key new file mode 100644 index 0000000..0fc02bb --- /dev/null +++ b/integration-tests/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLiEGUSP4gWsT0 +401hnprfLQhH82VsbiFt4WP4CIcCQ3x9km6LqwYdbPb6nKoQaub5qJHX1UFwJzS4 +yBtboVpnB7jZyqxOYVPNjeovUbJrjOU0Eb4bGph64uBvhFyTLvRqhTLKPIOzFqUU +hin/vIH4wFTcHwhxYrvKAXDJst5eeDBR4ES0r3gDIYjwdHt15Z6a+dm0v6Bk8ecp +8D4AaRJ9ha6U+Gf2iwi8yf8rIDR/AVxIe5/7+8TN73eYK6gdN/a3eSaMZVr6fuF6 +cyqebqWqpjnFVKuKQ0hVEN8Ogvr992dziukGqJ+xjMS791LN2+z2d9Qi6FiUdqNz +kp1Hzv4FAgMBAAECggEACG26GYP0Ui6wHVwUZkiFLVzWDPS9bIIbDEvbMfhYbvWQ +gDrCLTKF7E4I5FP8jvV+XzRl5cRFE3nsKwLObzr9XWrqcsp73DsXl1mbKx58/ws0 +qrVZZBHz4pLmrHeUxduZ75dYhRuAcLgtWe48awTJdR2x5fO7C8cE89afbxrjLpJE +tVyiw6vVB0GfWTZodxtAFMTX1KVm4bTngXfg0NF1FBNHAX3Cm6t4YCE41hKSc0IQ +Jr3C4e9uj8poze1B17k79bGB8HNMbbc8Ws0sdbxi5xnY+HUA/mYQrmGXo8sdqiYC +EYCMqPm3iJrCmmpHukGf2Vt9k1aLlJ+lxOclSwFO+QKBgQDoRmoprfdmU20LyxYH +eVeVqggqmhNohwnuhIvOAyrWGUkbDsssqx2Vv82z0WHAAkwEvQ984UzaYWCCL3m3 ++JzpF2dz6aKhXIaYnXBlk3STMGUCDT5ysPvsin9z/unzkffh3vrbDBARGFYWG18x +eUyTDOVVeTZNHUJXGjRyiftCkwKBgQDgUkR6dHU4ciSt7Y0UkyAgtZ7POR41T05L +bcxbjJeqm6qlj+oP9WUk7JxeSEFUbrMiROABLPPqTwmGo4xrDRx/e7WrqN6QBKC+ +Y8CfalrKRb0np60x7Mxx0kbmHp5cwv9QDKznKViOYSgKxFrOFZyMAEXQdZ3FvjXF +OQWrw86kBwKBgQDXuxa9MWO3uUJtkqkaNfw/+FVvY/0kt09lJdxHci+l/IQmyl2w +Vhm7TRK7sXvtfvSl7gblgMgFiC2/nGKbmR/7ag5e3R98aVhlhMywuvyp/GfEORLI +KVNChfwMezVFUUx+j8BEFHcTuZuzGqcWZ0fUyER0V4k0pDlKdv9BZqBkWwKBgCdP +o3qGQCilMDJex/OMGPxCd9M+4kFbZZAobMC6cbXPU+dxwgYL7i67XGfVZ8WBJNlj +kpICK7irIzM6JBh6krzwlBTCIkbA2N6kopQNUl3SPOTfKKXwJp/nxs77HKuK7K09 +m2tjPoatFhRU9sjY1rdeMN3oTr7hp5CpfonsZaEvAoGAEPsZcDd4N9ap5bgaeDy9 +NOfLsIyaxT5k6moRIiy83QPihvCuECP16+r6M5tiSfgt/PtCimdjhRiqXzIHNRhh +Nfsv13vUtZgt8cYXuTdI4a8feKI7Q4876ME8Qp3WM5/UNZWq6/sWCuZFqbXUhqM0 +mwNEi5Zddzf8VsSL2gCraQg= +-----END PRIVATE KEY----- diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go new file mode 100644 index 0000000..a1bccad --- /dev/null +++ b/integration-tests/server_tester.go @@ -0,0 +1,819 @@ +package nghttp2 + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/tatsuhiro-t/go-nghttp2" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" + "golang.org/x/net/websocket" +) + +const ( + serverBin = buildDir + "/src/nghttpx" + serverPort = 3009 + testDir = sourceDir + "/integration-tests" + logDir = buildDir + "/integration-tests" +) + +func pair(name, value string) hpack.HeaderField { + return hpack.HeaderField{ + Name: name, + Value: value, + } +} + +type serverTester struct { + cmd *exec.Cmd // test frontend server process, which is test subject + url string // test frontend server URL + t *testing.T + ts *httptest.Server // backend server + frontendHost string // frontend server host + backendHost string // backend server host + conn net.Conn // connection to frontend server + h2PrefaceSent bool // HTTP/2 preface was sent in conn + nextStreamID uint32 // next stream ID + fr *http2.Framer // HTTP/2 framer + headerBlkBuf bytes.Buffer // buffer to store encoded header block + enc *hpack.Encoder // HTTP/2 HPACK encoder + header http.Header // received header fields + dec *hpack.Decoder // HTTP/2 HPACK decoder + authority string // server's host:port + frCh chan http2.Frame // used for incoming HTTP/2 frame + errCh chan error +} + +type options struct { + // args is the additional arguments to nghttpx. + args []string + // handler is the handler to handle the request. It defaults + // to noopHandler. + handler http.HandlerFunc + // connectPort is the server side port where client connection + // is made. It defaults to serverPort. + connectPort int + // tls, if set to true, sets up TLS frontend connection. + tls bool + // tlsConfig is the client side TLS configuration that is used + // when tls is true. + tlsConfig *tls.Config + // tcpData is additional data that are written to connection + // before TLS handshake starts. This field is ignored if tls + // is false. + tcpData []byte + // quic, if set to true, sets up QUIC frontend connection. + // quic implies tls = true. + quic bool +} + +// newServerTester creates test context. +func newServerTester(t *testing.T, opts options) *serverTester { + if opts.quic { + opts.tls = true + } + + if opts.handler == nil { + opts.handler = noopHandler + } + if opts.connectPort == 0 { + opts.connectPort = serverPort + } + + ts := httptest.NewUnstartedServer(opts.handler) + + var args []string + var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS, affinityCookie, alpnH1 bool + + for _, k := range opts.args { + switch k { + case "--http2-bridge": + backendTLS = true + case "--dns": + dns = true + case "--external-dns": + dns = true + externalDNS = true + case "--accept-proxy-protocol": + acceptProxyProtocol = true + case "--redirect-if-not-tls": + redirectIfNotTLS = true + case "--affinity-cookie": + affinityCookie = true + case "--alpn-h1": + alpnH1 = true + default: + args = append(args, k) + } + } + if backendTLS { + nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) + // According to httptest/server.go, we have to set + // NextProtos separately for ts.TLS. NextProtos set + // in nghttp2.ConfigureServer is effectively ignored. + ts.TLS = new(tls.Config) + ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2") + ts.StartTLS() + args = append(args, "-k") + } else { + ts.Start() + } + scheme := "http" + if opts.tls { + scheme = "https" + args = append(args, testDir+"/server.key", testDir+"/server.crt") + } + + backendURL, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("Error parsing URL from httptest.Server: %v", err) + } + + // URL.Host looks like "127.0.0.1:8080", but we want + // "127.0.0.1,8080" + b := "-b" + if !externalDNS { + b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1)) + } else { + sep := strings.LastIndex(backendURL.Host, ":") + if sep == -1 { + t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host) + } + // We use awesome service nip.io. + b += fmt.Sprintf("%v.nip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:]) + } + + if backendTLS { + b += ";proto=h2;tls" + } + if dns { + b += ";dns" + } + + if redirectIfNotTLS { + b += ";redirect-if-not-tls" + } + + if affinityCookie { + b += ";affinity=cookie;affinity-cookie-name=affinity;affinity-cookie-path=/foo/bar" + } + + noTLS := ";no-tls" + if opts.tls { + noTLS = "" + } + + var proxyProto string + if acceptProxyProtocol { + proxyProto = ";proxyproto" + } + + args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b, + "--errorlog-file="+logDir+"/log.txt", "-LINFO") + + if opts.quic { + args = append(args, + fmt.Sprintf("-f127.0.0.1,%v;quic", serverPort), + "--no-quic-bpf") + } + + authority := fmt.Sprintf("127.0.0.1:%v", opts.connectPort) + + st := &serverTester{ + cmd: exec.Command(serverBin, args...), + t: t, + ts: ts, + url: fmt.Sprintf("%v://%v", scheme, authority), + frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), + backendHost: backendURL.Host, + nextStreamID: 1, + authority: authority, + frCh: make(chan http2.Frame), + errCh: make(chan error), + } + + st.cmd.Stdout = os.Stdout + st.cmd.Stderr = os.Stderr + + if err := st.cmd.Start(); err != nil { + st.t.Fatalf("Error starting %v: %v", serverBin, err) + } + + retry := 0 + for { + time.Sleep(50 * time.Millisecond) + + conn, err := net.Dial("tcp", authority) + if err == nil && opts.tls { + if len(opts.tcpData) > 0 { + if _, err := conn.Write(opts.tcpData); err != nil { + st.Close() + st.t.Fatal("Error writing TCP data") + } + } + + var tlsConfig *tls.Config + if opts.tlsConfig == nil { + tlsConfig = new(tls.Config) + } else { + tlsConfig = opts.tlsConfig.Clone() + } + tlsConfig.InsecureSkipVerify = true + if alpnH1 { + tlsConfig.NextProtos = []string{"http/1.1"} + } else { + tlsConfig.NextProtos = []string{"h2"} + } + tlsConn := tls.Client(conn, tlsConfig) + err = tlsConn.Handshake() + if err == nil { + conn = tlsConn + } + } + if err != nil { + retry++ + if retry >= 100 { + st.Close() + st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") + } + continue + } + st.conn = conn + break + } + + st.fr = http2.NewFramer(st.conn, st.conn) + st.enc = hpack.NewEncoder(&st.headerBlkBuf) + st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { + st.header.Add(f.Name, f.Value) + }) + + return st +} + +func (st *serverTester) Close() { + if st.conn != nil { + st.conn.Close() + } + if st.cmd != nil { + done := make(chan struct{}) + go func() { + if err := st.cmd.Wait(); err != nil { + st.t.Errorf("Error st.cmd.Wait() = %v", err) + } + close(done) + }() + + if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil { + st.t.Errorf("Error st.cmd.Process.Signal() = %v", err) + } + + select { + case <-done: + case <-time.After(10 * time.Second): + if err := st.cmd.Process.Kill(); err != nil { + st.t.Errorf("Error st.cmd.Process.Kill() = %v", err) + } + <-done + } + } + if st.ts != nil { + st.ts.Close() + } +} + +func (st *serverTester) readFrame() (http2.Frame, error) { + go func() { + f, err := st.fr.ReadFrame() + if err != nil { + st.errCh <- err + return + } + st.frCh <- f + }() + + select { + case f := <-st.frCh: + return f, nil + case err := <-st.errCh: + return nil, err + case <-time.After(5 * time.Second): + return nil, errors.New("timeout waiting for frame") + } +} + +type requestParam struct { + name string // name for this request to identify the request in log easily + streamID uint32 // stream ID, automatically assigned if 0 + method string // method, defaults to GET + scheme string // scheme, defaults to http + authority string // authority, defaults to backend server address + path string // path, defaults to / + header []hpack.HeaderField // additional request header fields + body []byte // request body + trailer []hpack.HeaderField // trailer part + httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade + noEndStream bool // true if END_STREAM should not be sent +} + +// wrapper for request body to set trailer part +type chunkedBodyReader struct { + trailer []hpack.HeaderField + trailerWritten bool + body io.Reader + req *http.Request +} + +func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) { + // document says that we have to set http.Request.Trailer + // after request was sent and before body returns EOF. + if !cbr.trailerWritten { + cbr.trailerWritten = true + for _, h := range cbr.trailer { + cbr.req.Trailer.Set(h.Name, h.Value) + } + } + return cbr.body.Read(p) +} + +func (st *serverTester) websocket(rp requestParam) *serverResponse { + urlstring := st.url + "/echo" + + config, err := websocket.NewConfig(urlstring, st.url) + if err != nil { + st.t.Fatalf("websocket.NewConfig(%q, %q) returned error: %v", urlstring, st.url, err) + } + + config.Header.Add("Test-Case", rp.name) + for _, h := range rp.header { + config.Header.Add(h.Name, h.Value) + } + + ws, err := websocket.NewClient(config, st.conn) + if err != nil { + st.t.Fatalf("Error creating websocket client: %v", err) + } + + if _, err := ws.Write(rp.body); err != nil { + st.t.Fatalf("ws.Write() returned error: %v", err) + } + + msg := make([]byte, 1024) + var n int + if n, err = ws.Read(msg); err != nil { + st.t.Fatalf("ws.Read() returned error: %v", err) + } + + res := &serverResponse{ + body: msg[:n], + } + + return res +} + +func (st *serverTester) http1(rp requestParam) (*serverResponse, error) { + method := "GET" + if rp.method != "" { + method = rp.method + } + + var body io.Reader + var cbr *chunkedBodyReader + if rp.body != nil { + body = bytes.NewBuffer(rp.body) + if len(rp.trailer) != 0 { + cbr = &chunkedBodyReader{ + trailer: rp.trailer, + body: body, + } + body = cbr + } + } + + reqURL := st.url + + if rp.path != "" { + u, err := url.Parse(st.url) + if err != nil { + st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) + } + u.Path = "" + u.RawQuery = "" + reqURL = u.String() + rp.path + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, reqURL, body) + if err != nil { + return nil, err + } + for _, h := range rp.header { + req.Header.Add(h.Name, h.Value) + } + req.Header.Add("Test-Case", rp.name) + if cbr != nil { + cbr.req = req + // this makes request use chunked encoding + req.ContentLength = -1 + req.Trailer = make(http.Header) + for _, h := range cbr.trailer { + req.Trailer.Set(h.Name, "") + } + } + if err := req.Write(st.conn); err != nil { + return nil, err + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), req) + if err != nil { + return nil, err + } + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body.Close() + + res := &serverResponse{ + status: resp.StatusCode, + header: resp.Header, + body: respBody, + connClose: resp.Close, + } + + return res, nil +} + +func (st *serverTester) http2(rp requestParam) (*serverResponse, error) { + st.headerBlkBuf.Reset() + st.header = make(http.Header) + + var id uint32 + if rp.streamID != 0 { + id = rp.streamID + if id >= st.nextStreamID && id%2 == 1 { + st.nextStreamID = id + 2 + } + } else { + id = st.nextStreamID + st.nextStreamID += 2 + } + + if !st.h2PrefaceSent { + st.h2PrefaceSent = true + fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") + if err := st.fr.WriteSettings(); err != nil { + return nil, err + } + } + + res := &serverResponse{ + streamID: id, + } + + streams := make(map[uint32]*serverResponse) + streams[id] = res + + if !rp.httpUpgrade { + method := "GET" + if rp.method != "" { + method = rp.method + } + _ = st.enc.WriteField(pair(":method", method)) + + scheme := "http" + if rp.scheme != "" { + scheme = rp.scheme + } + _ = st.enc.WriteField(pair(":scheme", scheme)) + + authority := st.authority + if rp.authority != "" { + authority = rp.authority + } + _ = st.enc.WriteField(pair(":authority", authority)) + + path := "/" + if rp.path != "" { + path = rp.path + } + _ = st.enc.WriteField(pair(":path", path)) + + _ = st.enc.WriteField(pair("test-case", rp.name)) + + for _, h := range rp.header { + _ = st.enc.WriteField(h) + } + + err := st.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + EndStream: len(rp.body) == 0 && len(rp.trailer) == 0 && !rp.noEndStream, + EndHeaders: true, + BlockFragment: st.headerBlkBuf.Bytes(), + }) + if err != nil { + return nil, err + } + + if len(rp.body) != 0 { + // TODO we assume rp.body fits in 1 frame + if err := st.fr.WriteData(id, len(rp.trailer) == 0 && !rp.noEndStream, rp.body); err != nil { + return nil, err + } + } + + if len(rp.trailer) != 0 { + st.headerBlkBuf.Reset() + for _, h := range rp.trailer { + _ = st.enc.WriteField(h) + } + err := st.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + EndStream: true, + EndHeaders: true, + BlockFragment: st.headerBlkBuf.Bytes(), + }) + if err != nil { + return nil, err + } + } + } +loop: + for { + fr, err := st.readFrame() + if err != nil { + return res, err + } + switch f := fr.(type) { + case *http2.HeadersFrame: + _, err := st.dec.Write(f.HeaderBlockFragment()) + if err != nil { + return res, err + } + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + st.header = make(http.Header) + break + } + sr.header = cloneHeader(st.header) + var status int + status, err = strconv.Atoi(sr.header.Get(":status")) + if err != nil { + return res, fmt.Errorf("Error parsing status code: %w", err) + } + sr.status = status + if f.StreamEnded() { + if streamEnded(res, streams, sr) { + break loop + } + } + case *http2.PushPromiseFrame: + _, err := st.dec.Write(f.HeaderBlockFragment()) + if err != nil { + return res, err + } + sr := &serverResponse{ + streamID: f.PromiseID, + reqHeader: cloneHeader(st.header), + } + streams[sr.streamID] = sr + case *http2.DataFrame: + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + break + } + sr.body = append(sr.body, f.Data()...) + if f.StreamEnded() { + if streamEnded(res, streams, sr) { + break loop + } + } + case *http2.RSTStreamFrame: + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + break + } + sr.errCode = f.ErrCode + if streamEnded(res, streams, sr) { + break loop + } + case *http2.GoAwayFrame: + if f.ErrCode == http2.ErrCodeNo { + break + } + res.errCode = f.ErrCode + res.connErr = true + break loop + case *http2.SettingsFrame: + if f.IsAck() { + break + } + if err := st.fr.WriteSettingsAck(); err != nil { + return res, err + } + } + } + sort.Sort(ByStreamID(res.pushResponse)) + return res, nil +} + +func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool { + delete(streams, sr.streamID) + if mainSr.streamID != sr.streamID { + mainSr.pushResponse = append(mainSr.pushResponse, sr) + } + return len(streams) == 0 +} + +type serverResponse struct { + status int // HTTP status code + header http.Header // response header fields + body []byte // response body + streamID uint32 // stream ID in HTTP/2 + errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY + connErr bool // true if HTTP/2 connection error + connClose bool // Connection: close is included in response header in HTTP/1 test + reqHeader http.Header // http request header, currently only stores pushed request header + pushResponse []*serverResponse // pushed response +} + +type ByStreamID []*serverResponse + +func (b ByStreamID) Len() int { + return len(b) +} + +func (b ByStreamID) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b ByStreamID) Less(i, j int) bool { + return b[i].streamID < b[j].streamID +} + +func cloneHeader(h http.Header) http.Header { + h2 := make(http.Header, len(h)) + for k, vv := range h { + vv2 := make([]string, len(vv)) + copy(vv2, vv) + h2[k] = vv2 + } + return h2 +} + +func noopHandler(w http.ResponseWriter, r *http.Request) { + if _, err := io.ReadAll(r.Body); err != nil { + http.Error(w, fmt.Sprintf("Error io.ReadAll() = %v", err), http.StatusInternalServerError) + } +} + +type APIResponse struct { + Status string `json:"status,omitempty"` + Code int `json:"code,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` +} + +type proxyProtocolV2 struct { + command proxyProtocolV2Command + sourceAddress net.Addr + destinationAddress net.Addr + additionalData []byte +} + +type proxyProtocolV2Command int + +const ( + proxyProtocolV2CommandLocal proxyProtocolV2Command = 0x0 + proxyProtocolV2CommandProxy proxyProtocolV2Command = 0x1 +) + +type proxyProtocolV2Family int + +const ( + proxyProtocolV2FamilyUnspec proxyProtocolV2Family = 0x0 + proxyProtocolV2FamilyInet proxyProtocolV2Family = 0x1 + proxyProtocolV2FamilyInet6 proxyProtocolV2Family = 0x2 + proxyProtocolV2FamilyUnix proxyProtocolV2Family = 0x3 +) + +type proxyProtocolV2Protocol int + +const ( + proxyProtocolV2ProtocolUnspec proxyProtocolV2Protocol = 0x0 + proxyProtocolV2ProtocolStream proxyProtocolV2Protocol = 0x1 + proxyProtocolV2ProtocolDgram proxyProtocolV2Protocol = 0x2 +) + +func writeProxyProtocolV2(w io.Writer, hdr proxyProtocolV2) error { + if _, err := w.Write([]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}); err != nil { + return err + } + if _, err := w.Write([]byte{byte(0x20 | hdr.command)}); err != nil { + return err + } + + switch srcAddr := hdr.sourceAddress.(type) { + case *net.TCPAddr: + dstAddr := hdr.destinationAddress.(*net.TCPAddr) + if len(srcAddr.IP) != len(dstAddr.IP) { + panic("len(srcAddr.IP) != len(dstAddr.IP)") + } + var fam byte + if len(srcAddr.IP) == 4 { + fam = byte(proxyProtocolV2FamilyInet << 4) + } else { + fam = byte(proxyProtocolV2FamilyInet6 << 4) + } + fam |= byte(proxyProtocolV2ProtocolStream) + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(len(srcAddr.IP)*2 + 4 + len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + if _, err := w.Write(srcAddr.IP); err != nil { + return err + } + if _, err := w.Write(dstAddr.IP); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, uint16(srcAddr.Port)); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, uint16(dstAddr.Port)); err != nil { + return err + } + case *net.UnixAddr: + dstAddr := hdr.destinationAddress.(*net.UnixAddr) + if len(srcAddr.Name) > 108 { + panic("too long Unix source address") + } + if len(dstAddr.Name) > 108 { + panic("too long Unix destination address") + } + fam := byte(proxyProtocolV2FamilyUnix << 4) + switch srcAddr.Net { + case "unix": + fam |= byte(proxyProtocolV2ProtocolStream) + case "unixdgram": + fam |= byte(proxyProtocolV2ProtocolDgram) + default: + fam |= byte(proxyProtocolV2ProtocolUnspec) + } + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(216 + len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + zeros := make([]byte, 108) + if _, err := w.Write([]byte(srcAddr.Name)); err != nil { + return err + } + if _, err := w.Write(zeros[:108-len(srcAddr.Name)]); err != nil { + return err + } + if _, err := w.Write([]byte(dstAddr.Name)); err != nil { + return err + } + if _, err := w.Write(zeros[:108-len(dstAddr.Name)]); err != nil { + return err + } + default: + fam := byte(proxyProtocolV2FamilyUnspec<<4) | byte(proxyProtocolV2ProtocolUnspec) + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + } + + if _, err := w.Write(hdr.additionalData); err != nil { + return err + } + + return nil +} diff --git a/integration-tests/server_tester_http3.go b/integration-tests/server_tester_http3.go new file mode 100644 index 0000000..7bbc4e7 --- /dev/null +++ b/integration-tests/server_tester_http3.go @@ -0,0 +1,90 @@ +//go:build quic + +package nghttp2 + +import ( + "bytes" + "context" + "crypto/tls" + "io" + "net/http" + "net/url" + "time" + + "github.com/quic-go/quic-go/http3" +) + +func (st *serverTester) http3(rp requestParam) (*serverResponse, error) { + rt := &http3.RoundTripper{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + defer rt.Close() + + c := &http.Client{ + Transport: rt, + } + + method := "GET" + if rp.method != "" { + method = rp.method + } + + var body io.Reader + + if rp.body != nil { + body = bytes.NewBuffer(rp.body) + } + + reqURL := st.url + + if rp.path != "" { + u, err := url.Parse(st.url) + if err != nil { + st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) + } + u.Path = "" + u.RawQuery = "" + reqURL = u.String() + rp.path + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, reqURL, body) + if err != nil { + return nil, err + } + + for _, h := range rp.header { + req.Header.Add(h.Name, h.Value) + } + + req.Header.Add("Test-Case", rp.name) + + // TODO http3 package does not support trailer at the time of + // this writing. + + resp, err := c.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + res := &serverResponse{ + status: resp.StatusCode, + header: resp.Header, + body: respBody, + connClose: resp.Close, + } + + return res, nil +} diff --git a/integration-tests/setenv.in b/integration-tests/setenv.in new file mode 100644 index 0000000..7177200 --- /dev/null +++ b/integration-tests/setenv.in @@ -0,0 +1,13 @@ +#!/bin/sh -e + +libdir="@abs_top_builddir@/lib" +if [ -d "$libdir/.libs" ]; then + libdir="$libdir/.libs" +fi + +export CGO_CFLAGS="-I@abs_top_srcdir@/lib/includes -I@abs_top_builddir@/lib/includes @CFLAGS@" +export CGO_CPPFLAGS="@CPPFLAGS@" +export CGO_LDFLAGS="-L$libdir @LDFLAGS@" +export LD_LIBRARY_PATH="$libdir" +export GODEBUG=cgocheck=0 +"$@" diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..f64f46b --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,3 @@ +# generated files +includes/nghttp2/nghttp2ver.h +libnghttp2.pc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..4180748 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,80 @@ +add_subdirectory(includes) + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_BINARY_DIR}/includes" +) + +add_definitions(-DBUILDING_NGHTTP2) + +set(NGHTTP2_SOURCES + nghttp2_pq.c nghttp2_map.c nghttp2_queue.c + nghttp2_frame.c + nghttp2_buf.c + nghttp2_stream.c nghttp2_outbound_item.c + nghttp2_session.c nghttp2_submit.c + nghttp2_helper.c + nghttp2_alpn.c + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c + nghttp2_version.c + nghttp2_priority_spec.c + nghttp2_option.c + nghttp2_callbacks.c + nghttp2_mem.c + nghttp2_http.c + nghttp2_rcbuf.c + nghttp2_extpri.c + nghttp2_ratelim.c + nghttp2_time.c + nghttp2_debug.c + sfparse.c +) + +set(NGHTTP2_RES "") + +if(WIN32) + configure_file( + version.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set(NGHTTP2_RES ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(nghttp2 SHARED ${NGHTTP2_SOURCES} ${NGHTTP2_RES}) + set_target_properties(nghttp2 PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + target_include_directories(nghttp2 INTERFACE + "${CMAKE_CURRENT_BINARY_DIR}/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + ) + + install(TARGETS nghttp2 + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(HAVE_CUNIT OR ENABLE_STATIC_LIB) + # Static library (for unittests because of symbol visibility) + add_library(nghttp2_static STATIC ${NGHTTP2_SOURCES}) + set_target_properties(nghttp2_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + ARCHIVE_OUTPUT_NAME nghttp2${STATIC_LIB_SUFFIX} + ) + target_compile_definitions(nghttp2_static PUBLIC "-DNGHTTP2_STATICLIB") + if(ENABLE_STATIC_LIB) + install(TARGETS nghttp2_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() +endif() + + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp2.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..1168c1e --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,81 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = includes + +EXTRA_DIST = Makefile.msvc CMakeLists.txt version.rc.in + +AM_CFLAGS = $(WARNCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGHTTP2 \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libnghttp2.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libnghttp2.la + +OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ + nghttp2_frame.c \ + nghttp2_buf.c \ + nghttp2_stream.c nghttp2_outbound_item.c \ + nghttp2_session.c nghttp2_submit.c \ + nghttp2_helper.c \ + nghttp2_alpn.c \ + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + nghttp2_http.c \ + nghttp2_rcbuf.c \ + nghttp2_extpri.c \ + nghttp2_ratelim.c \ + nghttp2_time.c \ + nghttp2_debug.c \ + sfparse.c + +HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ + nghttp2_frame.h \ + nghttp2_buf.h \ + nghttp2_session.h nghttp2_helper.h nghttp2_stream.h nghttp2_int.h \ + nghttp2_alpn.h \ + nghttp2_submit.h nghttp2_outbound_item.h \ + nghttp2_net.h \ + nghttp2_hd.h nghttp2_hd_huffman.h \ + nghttp2_priority_spec.h \ + nghttp2_option.h \ + nghttp2_callbacks.h \ + nghttp2_mem.h \ + nghttp2_http.h \ + nghttp2_rcbuf.h \ + nghttp2_extpri.h \ + nghttp2_ratelim.h \ + nghttp2_time.h \ + nghttp2_debug.h \ + sfparse.h + +libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) +libnghttp2_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) diff --git a/lib/Makefile.msvc b/lib/Makefile.msvc new file mode 100644 index 0000000..752389e --- /dev/null +++ b/lib/Makefile.msvc @@ -0,0 +1,254 @@ +# +# GNU Makefile for nghttp2 / MSVC. +# +# By G. Vanem 2013 +# Updated 3/2015 by Remo Eichenberger @remoe +# The MIT License apply. +# + +THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) + +_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV//g' -e 's/], //g') +_VERSION := $(subst ., ,$(_VERSION)) +VER_MAJOR := $(word 1,$(_VERSION)) +VER_MINOR := $(word 2,$(_VERSION)) +VER_MICRO := $(word 3,$(_VERSION)) +VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO) +VERSION_NUM := (($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO)) + +GENERATED := 'Generated by $(realpath Makefile.MSVC)' + +OBJ_DIR := MSVC_obj +#SUFFIX :=-vc90-mt-x86 + +# +# Where to copy nghttp2.dll + lib + headers to. +# Note: 'make install' is not in default targets. Do it explicitly. +# +TARGET_DIR ?= ../_VC_ROOT +VC_ROOT := $(abspath $(TARGET_DIR)) +INSTALL_BIN := $(VC_ROOT)/bin +INSTALL_LIB := $(VC_ROOT)/lib +INSTALL_HDR := $(VC_ROOT)/include +DLL_R := $(OBJ_DIR)/nghttp2$(SUFFIX).dll +DLL_D := $(OBJ_DIR)/nghttp2d$(SUFFIX).dll +LIB_R := $(OBJ_DIR)/nghttp2-static.lib +LIB_D := $(OBJ_DIR)/nghttp2d-static.lib +IMP_R := $(OBJ_DIR)/nghttp2.lib +IMP_D := $(OBJ_DIR)/nghttp2d.lib + +# +# Build for DEBUG-model and RELEASE at the same time. +# +TARGETS := $(LIB_R) $(DLL_R) $(IMP_R) \ + $(LIB_D) $(DLL_D) $(IMP_D) + +EXT_LIBS = + +NGHTTP2_PDB_R := $(OBJ_DIR)/nghttp2.pdb +NGHTTP2_PDB_D := $(OBJ_DIR)/nghttp2d.pdb + +CC = cl +LD := link +AR := lib +#CC := icl +#LD := xilink +#AR := xilib +RC := rc +CFLAGS := -I./includes -Dssize_t=long + +CFLAGS_R := -nologo -MD -W3 -Z7 -DBUILDING_NGHTTP2 +CFLAGS_D := -nologo -MDd -W3 -Z7 -DBUILDING_NGHTTP2 \ + -Ot -D_DEBUG -GF -RTCs -RTCu # -RTCc -GS + +LDFLAGS := -nologo -MAP -debug -incremental:no -opt:ref,icf -MANIFEST # -verbose + + +NGHTTP2_SRC := nghttp2_pq.c \ + nghttp2_map.c \ + nghttp2_queue.c \ + nghttp2_frame.c \ + nghttp2_buf.c \ + nghttp2_stream.c \ + nghttp2_outbound_item.c \ + nghttp2_session.c \ + nghttp2_submit.c \ + nghttp2_helper.c \ + nghttp2_alpn.c \ + nghttp2_hd.c \ + nghttp2_hd_huffman.c \ + nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + nghttp2_http.c \ + nghttp2_rcbuf.c + +NGHTTP2_OBJ_R := $(addprefix $(OBJ_DIR)/r_, $(notdir $(NGHTTP2_SRC:.c=.obj))) +NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj))) + +.PHONY: all intro test_ver install copy_headers_and_libs \ + install_nghttp2_pyd_0 install_nghttp2_pyd_1 \ + build_nghttp2_pyd_0 build_nghttp2_pyd_1 \ + clean_nghttp2_pyd_0 clean_nghttp2_pyd_1 + + +all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS) + @echo 'Welcome to NgHTTP2 (release + debug).' + @echo 'Do a "make -f Makefile.MSVC install" at own risk!' + +intro: + @echo 'Building NgHTTP (MSVC) ver. "$(VERSION)".' + +test_ver: + @echo '$$(VERSION): "$(VERSION)".' + @echo '$$(_VERSION): "$(_VERSION)".' + @echo '$$(VER_MAJOR): "$(VER_MAJOR)".' + @echo '$$(VER_MINOR): "$(VER_MINOR)".' + @echo '$$(VER_MICRO): "$(VER_MICRO)".' + +$(OBJ_DIR): + - mkdir $(OBJ_DIR) + +install: includes/nghttp2/nghttp2.h includes/nghttp2/nghttp2ver.h \ + $(TARGETS) \ + copy_headers_and_libs + +# +# This MUST be done before using the 'install_nghttp2_pyd_1' rule. +# +copy_headers_and_libs: + - mkdir -p $(INSTALL_HDR)/nghttp2 $(INSTALL_BIN) $(INSTALL_LIB) + cp --update $(addprefix includes/nghttp2/, nghttp2.h nghttp2ver.h) $(INSTALL_HDR)/nghttp2 + cp --update $(DLL_R) $(DLL_D) $(NGHTTP2_PDB_R) $(NGHTTP2_PDB_D) $(INSTALL_BIN) + cp --update $(IMP_R) $(IMP_D) $(LIB_R) $(LIB_D) $(INSTALL_LIB) + @echo + +$(LIB_R): $(NGHTTP2_OBJ_R) + $(AR) -nologo -out:$@ $^ + @echo + +$(LIB_D): $(NGHTTP2_OBJ_D) + $(AR) -nologo -out:$@ $^ + @echo + + +$(IMP_R): $(DLL_R) + +$(DLL_R): $(NGHTTP2_OBJ_R) $(OBJ_DIR)/r_nghttp2.res + $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_R) $(NGHTTP2_OBJ_R) -PDB:$(NGHTTP2_PDB_R) $(OBJ_DIR)/r_nghttp2.res $(EXT_LIBS) + mt -nologo -manifest $@.manifest -outputresource:$@\;2 + @echo + +$(IMP_D): $(DLL_D) + +$(DLL_D): $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res + $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_D) $(NGHTTP2_OBJ_D) -PDB:$(NGHTTP2_PDB_D) $(OBJ_DIR)/d_nghttp2.res $(EXT_LIBS) + mt -nologo -manifest $@.manifest -outputresource:$@\;2 + @echo + + +WIN_OBJDIR:=$(shell cygpath -w $(abspath $(OBJ_DIR))) +WIN_OBJDIR:=$(subst \,/,$(WIN_OBJDIR)) + +$(OBJ_DIR)/r_%.obj: %.c $(THIS_MAKEFILE) + $(CC) $(CFLAGS_R) $(CFLAGS) -Fo$@ -c $< + @echo + +$(OBJ_DIR)/d_%.obj: %.c $(THIS_MAKEFILE) + $(CC) $(CFLAGS_D) $(CFLAGS) -Fo$@ -c $< + @echo + +$(OBJ_DIR)/r_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE) + $(RC) -D_RELEASE -Fo $@ $< + @echo + +$(OBJ_DIR)/d_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE) + $(RC) -D_DEBUG -Fo $@ $< + @echo + +includes/nghttp2/nghttp2ver.h: includes/nghttp2/nghttp2ver.h.in $(THIS_MAKEFILE) + sed < includes/nghttp2/nghttp2ver.h.in \ + -e 's/@PACKAGE_VERSION@/$(VERSION)/g' \ + -e 's/@PACKAGE_VERSION_NUM@/$(VERSION_NUM)/g' > $@ + touch --reference=includes/nghttp2/nghttp2ver.h.in $@ + + +define RES_FILE + #include + + VS_VERSION_INFO VERSIONINFO + FILEVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0 + PRODUCTVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0 + FILEFLAGSMASK 0x3fL + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L + #ifdef _DEBUG + #define VER_STR "$(VERSION).0 (MSVC debug)" + #define DBG "d" + FILEFLAGS 0x1L + #else + #define VER_STR "$(VERSION).0 (MSVC release)" + #define DBG "" + FILEFLAGS 0x0L + #endif + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "http://tatsuhiro-t.github.io/nghttp2/" + VALUE "FileDescription", "nghttp2; HTTP/2 C library" + VALUE "FileVersion", VER_STR + VALUE "InternalName", "nghttp2" DBG + VALUE "LegalCopyright", "The MIT License" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "nghttp2" DBG ".dll" + VALUE "ProductName", "NGHTTP2." + VALUE "ProductVersion", VER_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END + END +endef + +export RES_FILE + +$(OBJ_DIR)/nghttp2.rc: Makefile.MSVC + @echo 'Generating $@...' + @echo ' /* $(GENERATED). DO NOT EDIT.' > $@ + @echo ' */' >> $@ + @echo "$$RES_FILE" >> $@ + +clean: + rm -f $(OBJ_DIR)/* includes/nghttp2/nghttp2ver.h + @echo + +vclean realclean: clean + - rm -rf $(OBJ_DIR) + - rm -f .depend.MSVC + +# +# Use gcc to generated the dependencies. No MSVC specific args please! +# +REPLACE_R = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/r_\1.obj: /' +REPLACE_D = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/d_\1.obj: /' + +depend: includes/nghttp2/nghttp2ver.h + @echo '# $(GENERATED). DO NOT EDIT.' > .depend.MSVC + gcc -MM $(CFLAGS) $(NGHTTP2_SRC) >> .depend.tmp + @echo '#' >> .depend.MSVC + @echo '# Release lib objects:' >> .depend.MSVC + sed -e $(REPLACE_R) .depend.tmp >> .depend.MSVC + @echo '#' >> .depend.MSVC + @echo '# Debug lib objects:' >> .depend.MSVC + sed -e $(REPLACE_D) .depend.tmp >> .depend.MSVC + rm -f .depend.tmp + +-include .depend.MSVC diff --git a/lib/includes/CMakeLists.txt b/lib/includes/CMakeLists.txt new file mode 100644 index 0000000..17de2ec --- /dev/null +++ b/lib/includes/CMakeLists.txt @@ -0,0 +1,4 @@ +install(FILES + nghttp2/nghttp2.h + "${CMAKE_CURRENT_BINARY_DIR}/nghttp2/nghttp2ver.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/nghttp2") diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am new file mode 100644 index 0000000..c07cb4d --- /dev/null +++ b/lib/includes/Makefile.am @@ -0,0 +1,26 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +EXTRA_DIST = CMakeLists.txt + +nobase_include_HEADERS = nghttp2/nghttp2.h nghttp2/nghttp2ver.h diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h new file mode 100644 index 0000000..7910db2 --- /dev/null +++ b/lib/includes/nghttp2/nghttp2.h @@ -0,0 +1,5941 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013, 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_H +#define NGHTTP2_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +/* Compatibility for non-Clang compilers */ +#ifndef __has_declspec_attribute +# define __has_declspec_attribute(x) 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#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 to use + * |*payload|, and do its own mechanism to process extension frames. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library asks the application to pack + * extension payload in its wire format. The frame header will be + * packed by library. Application must pack payload only. + * ``frame->ext.payload`` is the object passed to + * `nghttp2_submit_extension()` as payload parameter. Application + * must pack extension payload to the |buf| of its capacity |len| + * bytes. The |len| is at least 16KiB. + * + * The implementation of this function should return the number of + * bytes written into |buf| when it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and + * :type:`nghttp2_on_frame_not_send_callback` will be invoked. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return + * value is strictly larger than |len|, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, + uint8_t *buf, size_t len, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library provides the error message + * intended for human consumption. This callback is solely for + * debugging purpose. The |msg| is typically NULL-terminated string + * of length |len|. |len| does not include the sentinel NULL + * character. + * + * This function is deprecated. The new application should use + * :type:`nghttp2_error_callback2`. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library provides the error code, and + * message. This callback is solely for debugging purpose. + * |lib_error_code| is one of error code defined in + * :enum:`nghttp2_error`. The |msg| is typically NULL-terminated + * string of length |len|, and intended for human consumption. |len| + * does not include the sentinel NULL character. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback2)(nghttp2_session *session, + int lib_error_code, const char *msg, + size_t len, void *user_data); + +struct nghttp2_session_callbacks; + +/** + * @struct + * + * Callback functions for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_session_callbacks nghttp2_session_callbacks; + +/** + * @function + * + * Initializes |*callbacks_ptr| with NULL values. + * + * The initialized object can be used when initializing multiple + * :type:`nghttp2_session` objects. + * + * When the application finished using this object, it can use + * `nghttp2_session_callbacks_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr); + +/** + * @function + * + * Frees any resources allocated for |callbacks|. If |callbacks| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks); + +/** + * @function + * + * Sets callback function invoked when a session wants to send data to + * the remote peer. This callback is not necessary if the application + * uses solely `nghttp2_session_mem_send()` to serialize data to + * transmit. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback( + nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback); + +/** + * @function + * + * Sets callback function invoked when the a session wants to receive + * data from the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_recv()` to process + * received data. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when a frame is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_recv_callback on_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when an invalid non-DATA frame is + * received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked when a chunk of data in DATA frame + * is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback); + +/** + * @function + * + * Sets callback function invoked before a non-DATA frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_before_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_before_frame_send_callback before_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked after a frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_send_callback on_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked when a non-DATA frame is not sent + * because of an error. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_not_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_not_send_callback on_frame_not_send_callback); + +/** + * @function + * + * Sets callback function invoked when the stream is closed. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_stream_close_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_stream_close_callback on_stream_close_callback); + +/** + * @function + * + * Sets callback function invoked when the reception of header block + * in HEADERS or PUSH_PROMISE is started. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_headers_callback on_begin_headers_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. If both + * `nghttp2_session_callbacks_set_on_header_callback()` and + * `nghttp2_session_callbacks_set_on_header_callback2()` are used to + * set callbacks, the latter has the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback on_header_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback2 on_header_callback2); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. If both + * `nghttp2_session_callbacks_set_on_invalid_header_callback()` and + * `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are + * used to set callbacks, the latter takes the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback on_invalid_header_callback); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2); + +/** + * @function + * + * Sets callback function invoked when the library asks application + * how many padding bytes are required for the transmission of the + * given frame. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback select_padding_callback); + +/** + * @function + * + * Sets callback function determine the length allowed in + * :type:`nghttp2_data_source_read_callback`. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_data_source_read_length_callback( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback data_source_read_length_callback); + +/** + * @function + * + * Sets callback function invoked when a frame header is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_frame_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback); + +/** + * @function + * + * Sets callback function invoked when + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in + * :type:`nghttp2_data_source_read_callback` to avoid data copy. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback( + nghttp2_session_callbacks *cbs, + nghttp2_send_data_callback send_data_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to pack extension frame payload in wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to unpack extension frame payload from wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when chunk of extension frame + * payload is received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback); + +/** + * @function + * + * Sets callback function invoked when library tells error message to + * the application. + * + * This function is deprecated. The new application should use + * `nghttp2_session_callbacks_set_error_callback2()`. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback); + +/** + * @function + * + * Sets callback function invoked when library tells error code, and + * message to the application. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback2( + nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2); + +/** + * @functypedef + * + * Custom memory allocator to replace malloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace free(). The |mem_user_data| is + * the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void (*nghttp2_free)(void *ptr, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace calloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace realloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data); + +/** + * @struct + * + * Custom memory allocator functions and user defined pointer. The + * |mem_user_data| member is passed to each allocator function. This + * can be used, for example, to achieve per-session memory pool. + * + * In the following example code, ``my_malloc``, ``my_free``, + * ``my_calloc`` and ``my_realloc`` are the replacement of the + * standard allocators ``malloc``, ``free``, ``calloc`` and + * ``realloc`` respectively:: + * + * void *my_malloc_cb(size_t size, void *mem_user_data) { + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *mem_user_data) { my_free(ptr); } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *mem_user_data) { + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *mem_user_data) { + * return my_realloc(ptr, size); + * } + * + * void session_new() { + * nghttp2_session *session; + * nghttp2_session_callbacks *callbacks; + * nghttp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * + * nghttp2_session_client_new3(&session, callbacks, NULL, NULL, &mem); + * + * ... + * } + */ +typedef struct { + /** + * An arbitrary user supplied data. This is passed to each + * allocator function. + */ + void *mem_user_data; + /** + * Custom allocator function to replace malloc(). + */ + nghttp2_malloc malloc; + /** + * Custom allocator function to replace free(). + */ + nghttp2_free free; + /** + * Custom allocator function to replace calloc(). + */ + nghttp2_calloc calloc; + /** + * Custom allocator function to replace realloc(). + */ + nghttp2_realloc realloc; +} nghttp2_mem; + +struct nghttp2_option; + +/** + * @struct + * + * Configuration options for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_option nghttp2_option; + +/** + * @function + * + * Initializes |*option_ptr| with default values. + * + * When the application finished using this object, it can use + * `nghttp2_option_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_option_new(nghttp2_option **option_ptr); + +/** + * @function + * + * Frees any resources allocated for |option|. If |option| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void nghttp2_option_del(nghttp2_option *option); + +/** + * @function + * + * This option prevents the library from sending WINDOW_UPDATE for a + * connection automatically. If this option is set to nonzero, the + * library won't send WINDOW_UPDATE for DATA until application calls + * `nghttp2_session_consume()` to indicate the consumed amount of + * data. Don't use `nghttp2_submit_window_update()` for this purpose. + * By default, this option is set to zero. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val); + +/** + * @function + * + * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of + * remote endpoint as if it is received in SETTINGS frame. Without + * specifying this option, the maximum number of outgoing concurrent + * streams is initially limited to 100 to avoid issues when the local + * endpoint submits lots of requests before receiving initial SETTINGS + * frame from the remote endpoint, since sending them at once to the + * remote endpoint could lead to rejection of some of the requests. + * This value will be overwritten when the local endpoint receives + * initial SETTINGS frame from the remote endpoint, either to the + * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the + * default value (unlimited) if none was advertised. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, + uint32_t val); + +/** + * @function + * + * By default, nghttp2 library, if configured as server, requires + * first 24 bytes of client magic byte string (MAGIC). In most cases, + * this will simplify the implementation of server. But sometimes + * server may want to detect the application protocol based on first + * few bytes on clear text communication. + * + * If this option is used with nonzero |val|, nghttp2 library does not + * handle MAGIC. It still checks following SETTINGS frame. This + * means that applications should deal with MAGIC by themselves. + * + * If this option is not used or used with zero value, if MAGIC does + * not match :macro:`NGHTTP2_CLIENT_MAGIC`, `nghttp2_session_recv()` + * and `nghttp2_session_mem_recv()` will return error + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`, which is fatal + * error. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val); + +/** + * @function + * + * By default, nghttp2 library enforces subset of HTTP Messaging rules + * described in `HTTP/2 specification, section 8 + * `_. 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 + * + * .. warning:: + * + * Deprecated. Use `nghttp2_select_alpn` instead. + * + * A helper function for dealing with ALPN in server side. The |in| + * contains peer's protocol list in preferable order. The format of + * |in| is length-prefixed and not null-terminated. For example, + * ``h2`` and ``http/1.1`` stored in |in| like this:: + * + * in[0] = 2 + * in[1..2] = "h2" + * in[3] = 8 + * in[4..11] = "http/1.1" + * inlen = 12 + * + * The selection algorithm is as follows: + * + * 1. If peer's list contains HTTP/2 protocol the library supports, + * it is selected and returns 1. The following step is not taken. + * + * 2. If peer's list contains ``http/1.1``, this function selects + * ``http/1.1`` and returns 0. The following step is not taken. + * + * 3. This function selects nothing and returns -1 (So called + * non-overlap case). In this case, |out| and |outlen| are left + * untouched. + * + * Selecting ``h2`` means that ``h2`` is written into |*out| and its + * length (which is 2) is assigned to |*outlen|. + * + * For ALPN, refer to https://tools.ietf.org/html/rfc7301 + * + * To use this method you should do something like:: + * + * static int alpn_select_proto_cb(SSL* ssl, + * const unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_next_protocol((unsigned char**)out, outlen, + * in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * A helper function for dealing with ALPN in server side. The |in| + * contains peer's protocol list in preferable order. The format of + * |in| is length-prefixed and not null-terminated. For example, + * ``h2`` and ``http/1.1`` stored in |in| like this:: + * + * in[0] = 2 + * in[1..2] = "h2" + * in[3] = 8 + * in[4..11] = "http/1.1" + * inlen = 12 + * + * The selection algorithm is as follows: + * + * 1. If peer's list contains HTTP/2 protocol the library supports, + * it is selected and returns 1. The following step is not taken. + * + * 2. If peer's list contains ``http/1.1``, this function selects + * ``http/1.1`` and returns 0. The following step is not taken. + * + * 3. This function selects nothing and returns -1 (So called + * non-overlap case). In this case, |out| and |outlen| are left + * untouched. + * + * Selecting ``h2`` means that ``h2`` is written into |*out| and its + * length (which is 2) is assigned to |*outlen|. + * + * For ALPN, refer to https://tools.ietf.org/html/rfc7301 + * + * To use this method you should do something like:: + * + * static int alpn_select_proto_cb(SSL* ssl, + * const unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_alpn(out, outlen, in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_alpn(const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * Returns a pointer to a nghttp2_info struct with version information + * about the run-time library in use. The |least_version| argument + * can be set to a 24 bit numerical value for the least accepted + * version number and if the condition is not met, this function will + * return a ``NULL``. Pass in 0 to skip the version checking. + */ +NGHTTP2_EXTERN nghttp2_info *nghttp2_version(int least_version); + +/** + * @function + * + * Returns nonzero if the :type:`nghttp2_error` library error code + * |lib_error| is fatal. + */ +NGHTTP2_EXTERN int nghttp2_is_fatal(int lib_error_code); + +/** + * @function + * + * Returns nonzero if HTTP header field name |name| of length |len| is + * valid according to http://tools.ietf.org/html/rfc7230#section-3.2 + * + * Because this is a header field name in HTTP2, the upper cased alphabet + * is treated as error. + */ +NGHTTP2_EXTERN int nghttp2_check_header_name(const uint8_t *name, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2 + * + * This function is considered obsolete, and application should + * consider to use `nghttp2_check_header_value_rfc9113()` instead. + */ +NGHTTP2_EXTERN int nghttp2_check_header_value(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2, plus + * https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1 + */ +NGHTTP2_EXTERN int nghttp2_check_header_value_rfc9113(const uint8_t *value, + size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :method header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7231#section-4 and + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + */ +NGHTTP2_EXTERN int nghttp2_check_method(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :path header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3 + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of path. The allowed characters are all characters valid by + * `nghttp2_check_header_value` minus SPC and HT. + */ +NGHTTP2_EXTERN int nghttp2_check_path(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of the + * :authority or host header field is valid according to + * https://tools.ietf.org/html/rfc3986#section-3.2 + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of authority. + */ +NGHTTP2_EXTERN int nghttp2_check_authority(const uint8_t *value, size_t len); + +/* HPACK API */ + +struct nghttp2_hd_deflater; + +/** + * @struct + * + * HPACK deflater object. + */ +typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; + +/** + * @function + * + * Initializes |*deflater_ptr| for deflating name/values pairs. + * + * The |max_deflate_dynamic_table_size| is the upper bound of header + * table size the deflater will use. + * + * If this function fails, |*deflater_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size); + +/** + * @function + * + * Like `nghttp2_hd_deflate_new()`, but with additional custom memory + * allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_hd_deflate_new()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem); + +/** + * @function + * + * Deallocates any resources allocated for |deflater|. + */ +NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Changes header table size of the |deflater| to + * |settings_max_dynamic_table_size| bytes. This may trigger eviction + * in the dynamic table. + * + * The |settings_max_dynamic_table_size| should be the value received + * in SETTINGS_HEADER_TABLE_SIZE. + * + * The deflater never uses more memory than + * ``max_deflate_dynamic_table_size`` bytes specified in + * `nghttp2_hd_deflate_new()`. Therefore, if + * |settings_max_dynamic_table_size| > + * ``max_deflate_dynamic_table_size``, resulting maximum table size + * becomes ``max_deflate_dynamic_table_size``. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, + size_t settings_max_dynamic_table_size); + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |buf| of length |buflen|. + * + * If |buf| is not large enough to store the deflated header block, + * this function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |buf| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, + uint8_t *buf, size_t buflen, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |veclen| size of buf vector |vec|. The each size of buffer + * must be set in len field of :type:`nghttp2_vec`. If and only if + * one chunk is filled up completely, next chunk will be used. If + * |vec| is not large enough to store the deflated header block, this + * function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |vec| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, + const nghttp2_vec *vec, + size_t veclen, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Returns an upper bound on the compressed size after deflation of + * |nva| of length |nvlen|. + */ +NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Returns the number of entries that header table of |deflater| + * contains. This is the sum of the number of static table and + * dynamic table, so the return value is at least 61. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Returns the table entry denoted by |idx| from header table of + * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of + * static table. idx=62 returns first entry of dynamic table if it + * exists. Specifying idx=0 is error, and this function returns NULL. + * If |idx| is strictly greater than the number of entries the tables + * contain, this function returns NULL. + */ +NGHTTP2_EXTERN +const nghttp2_nv * +nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx); + +/** + * @function + * + * Returns the used dynamic table size, including the overhead 32 + * bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Returns the maximum dynamic table size. + */ +NGHTTP2_EXTERN +size_t +nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater); + +struct nghttp2_hd_inflater; + +/** + * @struct + * + * HPACK inflater object. + */ +typedef struct nghttp2_hd_inflater nghttp2_hd_inflater; + +/** + * @function + * + * Initializes |*inflater_ptr| for inflating name/values pairs. + * + * If this function fails, |*inflater_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr); + +/** + * @function + * + * Like `nghttp2_hd_inflate_new()`, but with additional custom memory + * allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_hd_inflate_new()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + */ +NGHTTP2_EXTERN int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr, + nghttp2_mem *mem); + +/** + * @function + * + * Deallocates any resources allocated for |inflater|. + */ +NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Changes header table size in the |inflater|. This may trigger + * eviction in the dynamic table. + * + * The |settings_max_dynamic_table_size| should be the value + * transmitted in SETTINGS_HEADER_TABLE_SIZE. + * + * This function must not be called while header block is being + * inflated. In other words, this function must be called after + * initialization of |inflater|, but before calling + * `nghttp2_hd_inflate_hd2()`, or after + * `nghttp2_hd_inflate_end_headers()`. Otherwise, + * `NGHTTP2_ERR_INVALID_STATE` was returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called while header block is being inflated. + * Probably, application missed to call + * `nghttp2_hd_inflate_end_headers()`. + */ +NGHTTP2_EXTERN int +nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, + size_t settings_max_dynamic_table_size); + +/** + * @enum + * + * The flags for header inflation. + */ +typedef enum { + /** + * No flag set. + */ + NGHTTP2_HD_INFLATE_NONE = 0, + /** + * Indicates all headers were inflated. + */ + NGHTTP2_HD_INFLATE_FINAL = 0x01, + /** + * Indicates a header was emitted. + */ + NGHTTP2_HD_INFLATE_EMIT = 0x02 +} nghttp2_hd_inflate_flag; + +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_hd_inflate_hd2()` instead. + * + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. For each successful emission of + * header name/value pair, + * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. + * + * The |nv_out| may include pointers to the memory region in the |in|. + * The caller must retain the |in| while the |nv_out| is used. + * + * The application should call this function repeatedly until the + * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and + * return value is non-negative. This means the all input values are + * processed successfully. Then the application must call + * `nghttp2_hd_inflate_end_headers()` to prepare for the next header + * block input. + * + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Inflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` + * The header field name or value is too large. + * + * Example follows:: + * + * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, + * uint8_t *in, size_t inlen, int final) + * { + * ssize_t rv; + * + * for(;;) { + * nghttp2_nv nv; + * int inflate_flags = 0; + * + * rv = nghttp2_hd_inflate_hd(hd_inflater, &nv, &inflate_flags, + * in, inlen, final); + * + * if(rv < 0) { + * fprintf(stderr, "inflate failed with error code %zd", rv); + * return -1; + * } + * + * in += rv; + * inlen -= rv; + * + * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + * fwrite(nv.name, nv.namelen, 1, stderr); + * fprintf(stderr, ": "); + * fwrite(nv.value, nv.valuelen, 1, stderr); + * fprintf(stderr, "\n"); + * } + * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + * nghttp2_hd_inflate_end_headers(hd_inflater); + * break; + * } + * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + * inlen == 0) { + * break; + * } + * } + * + * return 0; + * } + * + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, + int *inflate_flags, uint8_t *in, + size_t inlen, int in_final); + +/** + * @function + * + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. For each successful emission of + * header name/value pair, + * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. + * + * The |nv_out| may include pointers to the memory region in the |in|. + * The caller must retain the |in| while the |nv_out| is used. + * + * The application should call this function repeatedly until the + * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and + * return value is non-negative. If that happens, all given input + * data (|inlen| bytes) are processed successfully. Then the + * application must call `nghttp2_hd_inflate_end_headers()` to prepare + * for the next header block input. + * + * In other words, if |in_final| is nonzero, and this function returns + * |inlen|, you can assert that + * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in + * |*inflate_flags|. + * + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Inflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` + * The header field name or value is too large. + * + * Example follows:: + * + * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, + * uint8_t *in, size_t inlen, int final) + * { + * ssize_t rv; + * + * for(;;) { + * nghttp2_nv nv; + * int inflate_flags = 0; + * + * rv = nghttp2_hd_inflate_hd2(hd_inflater, &nv, &inflate_flags, + * in, inlen, final); + * + * if(rv < 0) { + * fprintf(stderr, "inflate failed with error code %zd", rv); + * return -1; + * } + * + * in += rv; + * inlen -= rv; + * + * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + * fwrite(nv.name, nv.namelen, 1, stderr); + * fprintf(stderr, ": "); + * fwrite(nv.value, nv.valuelen, 1, stderr); + * fprintf(stderr, "\n"); + * } + * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + * nghttp2_hd_inflate_end_headers(hd_inflater); + * break; + * } + * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + * inlen == 0) { + * break; + * } + * } + * + * return 0; + * } + * + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, + int *inflate_flags, + const uint8_t *in, size_t inlen, + int in_final); + +/** + * @function + * + * Signals the end of decompression for one header block. + * + * This function returns 0 if it succeeds. Currently this function + * always succeeds. + */ +NGHTTP2_EXTERN int +nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the number of entries that header table of |inflater| + * contains. This is the sum of the number of static table and + * dynamic table, so the return value is at least 61. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the table entry denoted by |idx| from header table of + * |inflater|. The |idx| is 1-based, and idx=1 returns first entry of + * static table. idx=62 returns first entry of dynamic table if it + * exists. Specifying idx=0 is error, and this function returns NULL. + * If |idx| is strictly greater than the number of entries the tables + * contain, this function returns NULL. + */ +NGHTTP2_EXTERN +const nghttp2_nv * +nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx); + +/** + * @function + * + * Returns the used dynamic table size, including the overhead 32 + * bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the maximum dynamic table size. + */ +NGHTTP2_EXTERN +size_t +nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater); + +struct nghttp2_stream; + +/** + * @struct + * + * The structure to represent HTTP/2 stream. The details of this + * structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_stream nghttp2_stream; + +/** + * @function + * + * Returns pointer to :type:`nghttp2_stream` object denoted by + * |stream_id|. If stream was not found, returns NULL. + * + * Returns imaginary root stream (see + * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|. + * + * Unless |stream_id| == 0, the returned pointer is valid until next + * call of `nghttp2_session_send()`, `nghttp2_session_mem_send()`, + * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_find_stream(nghttp2_session *session, int32_t stream_id); + +/** + * @enum + * + * State of stream as described in RFC 7540. + */ +typedef enum { + /** + * idle state. + */ + NGHTTP2_STREAM_STATE_IDLE = 1, + /** + * open state. + */ + NGHTTP2_STREAM_STATE_OPEN, + /** + * reserved (local) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_LOCAL, + /** + * reserved (remote) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_REMOTE, + /** + * half closed (local) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, + /** + * half closed (remote) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, + /** + * closed state. + */ + NGHTTP2_STREAM_STATE_CLOSED +} nghttp2_stream_proto_state; + +/** + * @function + * + * Returns state of |stream|. The root stream retrieved by + * `nghttp2_session_get_root_stream()` will have stream state + * :enum:`nghttp2_stream_proto_state.NGHTTP2_STREAM_STATE_IDLE`. + */ +NGHTTP2_EXTERN nghttp2_stream_proto_state +nghttp2_stream_get_state(nghttp2_stream *stream); + +/** + * @function + * + * Returns root of dependency tree, which is imaginary stream with + * stream ID 0. The returned pointer is valid until |session| is + * freed by `nghttp2_session_del()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_get_root_stream(nghttp2_session *session); + +/** + * @function + * + * Returns the parent stream of |stream| in dependency tree. Returns + * NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_parent(nghttp2_stream *stream); + +NGHTTP2_EXTERN int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream); + +/** + * @function + * + * Returns the next sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_next_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the previous sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_previous_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the first child stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_first_child(nghttp2_stream *stream); + +/** + * @function + * + * Returns dependency weight to the parent stream of |stream|. + */ +NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream); + +/** + * @function + * + * Returns the sum of the weight for |stream|'s children. + */ +NGHTTP2_EXTERN int32_t +nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); + +/** + * @functypedef + * + * Callback function invoked when the library outputs debug logging. + * The function is called with arguments suitable for ``vfprintf(3)`` + * + * The debug output is only enabled if the library is built with + * ``DEBUGBUILD`` macro defined. + */ +typedef void (*nghttp2_debug_vprintf_callback)(const char *format, + va_list args); + +/** + * @function + * + * Sets a debug output callback called by the library when built with + * ``DEBUGBUILD`` macro defined. If this option is not used, debug + * log is written into standard error output. + * + * For builds without ``DEBUGBUILD`` macro defined, this function is + * noop. + * + * Note that building with ``DEBUGBUILD`` may cause significant + * performance penalty to libnghttp2 because of extra processing. It + * should be used for debugging purpose only. + * + * .. Warning:: + * + * Building with ``DEBUGBUILD`` may cause significant performance + * penalty to libnghttp2 because of extra processing. It should be + * used for debugging purpose only. We write this two times because + * this is important. + */ +NGHTTP2_EXTERN void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback); + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP2_H */ diff --git a/lib/includes/nghttp2/nghttp2ver.h.in b/lib/includes/nghttp2/nghttp2ver.h.in new file mode 100644 index 0000000..7717a64 --- /dev/null +++ b/lib/includes/nghttp2/nghttp2ver.h.in @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2VER_H +#define NGHTTP2VER_H + +/** + * @macro + * Version number of the nghttp2 library release + */ +#define NGHTTP2_VERSION "@PACKAGE_VERSION@" + +/** + * @macro + * Numerical representation of the version number of the nghttp2 library + * release. This is a 24 bit number with 8 bits for major number, 8 bits + * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. + */ +#define NGHTTP2_VERSION_NUM @PACKAGE_VERSION_NUM@ + +#endif /* NGHTTP2VER_H */ diff --git a/lib/libnghttp2.pc.in b/lib/libnghttp2.pc.in new file mode 100644 index 0000000..da6938f --- /dev/null +++ b/lib/libnghttp2.pc.in @@ -0,0 +1,33 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libnghttp2 +Description: HTTP/2 C library +URL: https://github.com/tatsuhiro-t/nghttp2 +Version: @VERSION@ +Libs: -L${libdir} -lnghttp2 +Cflags: -I${includedir} diff --git a/lib/nghttp2_alpn.c b/lib/nghttp2_alpn.c new file mode 100644 index 0000000..33c5885 --- /dev/null +++ b/lib/nghttp2_alpn.c @@ -0,0 +1,70 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_alpn.h" + +#include + +static int select_alpn(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const char *key, unsigned int keylen) { + unsigned int i; + for (i = 0; i + keylen <= inlen; i += (unsigned int)(in[i] + 1)) { + if (memcmp(&in[i], key, keylen) == 0) { + *out = (unsigned char *)&in[i + 1]; + *outlen = in[i]; + return 0; + } + } + return -1; +} + +#define NGHTTP2_HTTP_1_1_ALPN "\x8http/1.1" +#define NGHTTP2_HTTP_1_1_ALPN_LEN (sizeof(NGHTTP2_HTTP_1_1_ALPN) - 1) + +int nghttp2_select_next_protocol(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen) { + if (select_alpn((const unsigned char **)out, outlen, in, inlen, + NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN) == 0) { + return 1; + } + if (select_alpn((const unsigned char **)out, outlen, in, inlen, + NGHTTP2_HTTP_1_1_ALPN, NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) { + return 0; + } + return -1; +} + +int nghttp2_select_alpn(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen) { + if (select_alpn(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN, + NGHTTP2_PROTO_ALPN_LEN) == 0) { + return 1; + } + if (select_alpn(out, outlen, in, inlen, NGHTTP2_HTTP_1_1_ALPN, + NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) { + return 0; + } + return -1; +} diff --git a/lib/nghttp2_alpn.h b/lib/nghttp2_alpn.h new file mode 100644 index 0000000..09810fd --- /dev/null +++ b/lib/nghttp2_alpn.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_ALPN_H +#define NGHTTP2_ALPN_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_ALPN_H */ diff --git a/lib/nghttp2_buf.c b/lib/nghttp2_buf.c new file mode 100644 index 0000000..a328447 --- /dev/null +++ b/lib/nghttp2_buf.c @@ -0,0 +1,527 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_buf.h" + +#include + +#include "nghttp2_helper.h" +#include "nghttp2_debug.h" + +void nghttp2_buf_init(nghttp2_buf *buf) { + buf->begin = NULL; + buf->end = NULL; + buf->pos = NULL; + buf->last = NULL; + buf->mark = NULL; +} + +int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem) { + nghttp2_buf_init(buf); + return nghttp2_buf_reserve(buf, initial, mem); +} + +void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem) { + if (buf == NULL) { + return; + } + + nghttp2_mem_free(mem, buf->begin); + buf->begin = NULL; +} + +int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) { + uint8_t *ptr; + size_t cap; + + cap = nghttp2_buf_cap(buf); + + if (cap >= new_cap) { + return 0; + } + + new_cap = nghttp2_max(new_cap, cap * 2); + + ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap); + if (ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + buf->pos = ptr + (buf->pos - buf->begin); + buf->last = ptr + (buf->last - buf->begin); + buf->mark = ptr + (buf->mark - buf->begin); + buf->begin = ptr; + buf->end = ptr + new_cap; + + return 0; +} + +void nghttp2_buf_reset(nghttp2_buf *buf) { + buf->pos = buf->last = buf->mark = buf->begin; +} + +void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len) { + buf->begin = buf->pos = buf->last = buf->mark = buf->end = begin; + if (len) { + buf->end += len; + } +} + +static int buf_chain_new(nghttp2_buf_chain **chain, size_t chunk_length, + nghttp2_mem *mem) { + int rv; + + *chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); + if (*chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + (*chain)->next = NULL; + + rv = nghttp2_buf_init2(&(*chain)->buf, chunk_length, mem); + if (rv != 0) { + nghttp2_mem_free(mem, *chain); + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +static void buf_chain_del(nghttp2_buf_chain *chain, nghttp2_mem *mem) { + nghttp2_buf_free(&chain->buf, mem); + nghttp2_mem_free(mem, chain); +} + +int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, + nghttp2_mem *mem) { + return nghttp2_bufs_init2(bufs, chunk_length, max_chunk, 0, mem); +} + +int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t offset, nghttp2_mem *mem) { + return nghttp2_bufs_init3(bufs, chunk_length, max_chunk, max_chunk, offset, + mem); +} + +int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t chunk_keep, size_t offset, + nghttp2_mem *mem) { + int rv; + nghttp2_buf_chain *chain; + + if (chunk_keep == 0 || max_chunk < chunk_keep || chunk_length < offset) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = buf_chain_new(&chain, chunk_length, mem); + if (rv != 0) { + return rv; + } + + bufs->mem = mem; + bufs->offset = offset; + + bufs->head = chain; + bufs->cur = bufs->head; + + nghttp2_buf_shift_right(&bufs->cur->buf, offset); + + bufs->chunk_length = chunk_length; + bufs->chunk_used = 1; + bufs->max_chunk = max_chunk; + bufs->chunk_keep = chunk_keep; + + return 0; +} + +int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length) { + int rv; + nghttp2_buf_chain *chain; + + if (chunk_length < bufs->offset) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = buf_chain_new(&chain, chunk_length, bufs->mem); + if (rv != 0) { + return rv; + } + + nghttp2_bufs_free(bufs); + + bufs->head = chain; + bufs->cur = bufs->head; + + nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); + + bufs->chunk_length = chunk_length; + bufs->chunk_used = 1; + + return 0; +} + +void nghttp2_bufs_free(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain, *next_chain; + + if (bufs == NULL) { + return; + } + + for (chain = bufs->head; chain;) { + next_chain = chain->next; + + buf_chain_del(chain, bufs->mem); + + chain = next_chain; + } + + bufs->head = NULL; +} + +int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len, + nghttp2_mem *mem) { + nghttp2_buf_chain *chain; + + chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); + if (chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + chain->next = NULL; + + nghttp2_buf_wrap_init(&chain->buf, begin, len); + + bufs->mem = mem; + bufs->offset = 0; + + bufs->head = chain; + bufs->cur = bufs->head; + + bufs->chunk_length = len; + bufs->chunk_used = 1; + bufs->max_chunk = 1; + bufs->chunk_keep = 1; + + return 0; +} + +int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec, + size_t veclen, nghttp2_mem *mem) { + size_t i = 0; + nghttp2_buf_chain *cur_chain; + nghttp2_buf_chain *head_chain; + nghttp2_buf_chain **dst_chain = &head_chain; + + if (veclen == 0) { + return nghttp2_bufs_wrap_init(bufs, NULL, 0, mem); + } + + head_chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain) * veclen); + if (head_chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + for (i = 0; i < veclen; ++i) { + cur_chain = &head_chain[i]; + cur_chain->next = NULL; + nghttp2_buf_wrap_init(&cur_chain->buf, vec[i].base, vec[i].len); + + *dst_chain = cur_chain; + dst_chain = &cur_chain->next; + } + + bufs->mem = mem; + bufs->offset = 0; + + bufs->head = head_chain; + bufs->cur = bufs->head; + + /* We don't use chunk_length since no allocation is expected. */ + bufs->chunk_length = 0; + bufs->chunk_used = veclen; + bufs->max_chunk = veclen; + bufs->chunk_keep = veclen; + + return 0; +} + +void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) { + if (bufs == NULL) { + return; + } + + if (bufs->head) { + nghttp2_mem_free(bufs->mem, bufs->head); + } +} + +void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + + for (ci = bufs->cur; ci; ci = ci->next) { + if (nghttp2_buf_len(&ci->buf) == 0) { + return; + } else { + bufs->cur = ci; + } + } +} + +size_t nghttp2_bufs_len(nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + size_t len; + + len = 0; + for (ci = bufs->head; ci; ci = ci->next) { + len += nghttp2_buf_len(&ci->buf); + } + + return len; +} + +static int bufs_alloc_chain(nghttp2_bufs *bufs) { + int rv; + nghttp2_buf_chain *chain; + + if (bufs->cur->next) { + bufs->cur = bufs->cur->next; + + return 0; + } + + if (bufs->max_chunk == bufs->chunk_used) { + return NGHTTP2_ERR_BUFFER_ERROR; + } + + rv = buf_chain_new(&chain, bufs->chunk_length, bufs->mem); + if (rv != 0) { + return rv; + } + + DEBUGF("new buffer %zu bytes allocated for bufs %p, used %zu\n", + bufs->chunk_length, bufs, bufs->chunk_used); + + ++bufs->chunk_used; + + bufs->cur->next = chain; + bufs->cur = chain; + + nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); + + return 0; +} + +int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) { + int rv; + size_t nwrite; + nghttp2_buf *buf; + const uint8_t *p; + + p = data; + + while (len) { + buf = &bufs->cur->buf; + + nwrite = nghttp2_min(nghttp2_buf_avail(buf), len); + if (nwrite == 0) { + rv = bufs_alloc_chain(bufs); + if (rv != 0) { + return rv; + } + continue; + } + + buf->last = nghttp2_cpymem(buf->last, p, nwrite); + p += nwrite; + len -= nwrite; + } + + return 0; +} + +static int bufs_ensure_addb(nghttp2_bufs *bufs) { + int rv; + nghttp2_buf *buf; + + buf = &bufs->cur->buf; + + if (nghttp2_buf_avail(buf) > 0) { + return 0; + } + + rv = bufs_alloc_chain(bufs); + if (rv != 0) { + return rv; + } + + return 0; +} + +int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last++ = b; + + return 0; +} + +int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last = b; + + return 0; +} + +int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last++ |= b; + + return 0; +} + +int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last |= b; + + return 0; +} + +ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { + size_t len; + nghttp2_buf_chain *chain; + nghttp2_buf *buf; + uint8_t *res; + nghttp2_buf resbuf; + + len = 0; + + for (chain = bufs->head; chain; chain = chain->next) { + len += nghttp2_buf_len(&chain->buf); + } + + if (len == 0) { + res = NULL; + return 0; + } + + res = nghttp2_mem_malloc(bufs->mem, len); + if (res == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&resbuf, res, len); + + for (chain = bufs->head; chain; chain = chain->next) { + buf = &chain->buf; + resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); + } + + *out = res; + + return (ssize_t)len; +} + +size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) { + size_t len; + nghttp2_buf_chain *chain; + nghttp2_buf *buf; + nghttp2_buf resbuf; + + len = nghttp2_bufs_len(bufs); + + nghttp2_buf_wrap_init(&resbuf, out, len); + + for (chain = bufs->head; chain; chain = chain->next) { + buf = &chain->buf; + resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); + } + + return len; +} + +void nghttp2_bufs_reset(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain, *ci; + size_t k; + + k = bufs->chunk_keep; + + for (ci = bufs->head; ci; ci = ci->next) { + nghttp2_buf_reset(&ci->buf); + nghttp2_buf_shift_right(&ci->buf, bufs->offset); + + if (--k == 0) { + break; + } + } + + if (ci) { + chain = ci->next; + ci->next = NULL; + + for (ci = chain; ci;) { + chain = ci->next; + + buf_chain_del(ci, bufs->mem); + + ci = chain; + } + + bufs->chunk_used = bufs->chunk_keep; + } + + bufs->cur = bufs->head; +} + +int nghttp2_bufs_advance(nghttp2_bufs *bufs) { return bufs_alloc_chain(bufs); } + +int nghttp2_bufs_next_present(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain; + + chain = bufs->cur->next; + + return chain && nghttp2_buf_len(&chain->buf); +} diff --git a/lib/nghttp2_buf.h b/lib/nghttp2_buf.h new file mode 100644 index 0000000..45f62f1 --- /dev/null +++ b/lib/nghttp2_buf.h @@ -0,0 +1,412 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_BUF_H +#define NGHTTP2_BUF_H + +#ifdef HAVE_CONFIG_H +# include +#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_callbacks.c b/lib/nghttp2_callbacks.c new file mode 100644 index 0000000..3c38214 --- /dev/null +++ b/lib/nghttp2_callbacks.c @@ -0,0 +1,175 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_callbacks.h" + +#include + +int nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr) { + *callbacks_ptr = calloc(1, sizeof(nghttp2_session_callbacks)); + + if (*callbacks_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks) { + free(callbacks); +} + +void nghttp2_session_callbacks_set_send_callback( + nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback) { + cbs->send_callback = send_callback; +} + +void nghttp2_session_callbacks_set_recv_callback( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback) { + cbs->recv_callback = recv_callback; +} + +void nghttp2_session_callbacks_set_on_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_recv_callback on_frame_recv_callback) { + cbs->on_frame_recv_callback = on_frame_recv_callback; +} + +void nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback) { + cbs->on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; +} + +void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback) { + cbs->on_data_chunk_recv_callback = on_data_chunk_recv_callback; +} + +void nghttp2_session_callbacks_set_before_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_before_frame_send_callback before_frame_send_callback) { + cbs->before_frame_send_callback = before_frame_send_callback; +} + +void nghttp2_session_callbacks_set_on_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_send_callback on_frame_send_callback) { + cbs->on_frame_send_callback = on_frame_send_callback; +} + +void nghttp2_session_callbacks_set_on_frame_not_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_not_send_callback on_frame_not_send_callback) { + cbs->on_frame_not_send_callback = on_frame_not_send_callback; +} + +void nghttp2_session_callbacks_set_on_stream_close_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_stream_close_callback on_stream_close_callback) { + cbs->on_stream_close_callback = on_stream_close_callback; +} + +void nghttp2_session_callbacks_set_on_begin_headers_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_headers_callback on_begin_headers_callback) { + cbs->on_begin_headers_callback = on_begin_headers_callback; +} + +void nghttp2_session_callbacks_set_on_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback on_header_callback) { + cbs->on_header_callback = on_header_callback; +} + +void nghttp2_session_callbacks_set_on_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback2 on_header_callback2) { + cbs->on_header_callback2 = on_header_callback2; +} + +void nghttp2_session_callbacks_set_on_invalid_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback on_invalid_header_callback) { + cbs->on_invalid_header_callback = on_invalid_header_callback; +} + +void nghttp2_session_callbacks_set_on_invalid_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2) { + cbs->on_invalid_header_callback2 = on_invalid_header_callback2; +} + +void nghttp2_session_callbacks_set_select_padding_callback( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback select_padding_callback) { + cbs->select_padding_callback = select_padding_callback; +} + +void nghttp2_session_callbacks_set_data_source_read_length_callback( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback data_source_read_length_callback) { + cbs->read_length_callback = data_source_read_length_callback; +} + +void nghttp2_session_callbacks_set_on_begin_frame_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback) { + cbs->on_begin_frame_callback = on_begin_frame_callback; +} + +void nghttp2_session_callbacks_set_send_data_callback( + nghttp2_session_callbacks *cbs, + nghttp2_send_data_callback send_data_callback) { + cbs->send_data_callback = send_data_callback; +} + +void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback) { + cbs->pack_extension_callback = pack_extension_callback; +} + +void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback) { + cbs->unpack_extension_callback = unpack_extension_callback; +} + +void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback) { + cbs->on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; +} + +void nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback) { + cbs->error_callback = error_callback; +} + +void nghttp2_session_callbacks_set_error_callback2( + nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2) { + cbs->error_callback2 = error_callback2; +} diff --git a/lib/nghttp2_callbacks.h b/lib/nghttp2_callbacks.h new file mode 100644 index 0000000..61e51fa --- /dev/null +++ b/lib/nghttp2_callbacks.h @@ -0,0 +1,125 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_CALLBACKS_H +#define NGHTTP2_CALLBACKS_H + +#ifdef HAVE_CONFIG_H +# include +#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_debug.c b/lib/nghttp2_debug.c new file mode 100644 index 0000000..cb27797 --- /dev/null +++ b/lib/nghttp2_debug.c @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_debug.h" + +#include + +#ifdef DEBUGBUILD + +static void nghttp2_default_debug_vfprintf_callback(const char *fmt, + va_list args) { + vfprintf(stderr, fmt, args); +} + +static nghttp2_debug_vprintf_callback static_debug_vprintf_callback = + nghttp2_default_debug_vfprintf_callback; + +void nghttp2_debug_vprintf(const char *format, ...) { + if (static_debug_vprintf_callback) { + va_list args; + va_start(args, format); + static_debug_vprintf_callback(format, args); + va_end(args); + } +} + +void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback) { + static_debug_vprintf_callback = debug_vprintf_callback; +} + +#else /* !DEBUGBUILD */ + +void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback) { + (void)debug_vprintf_callback; +} + +#endif /* !DEBUGBUILD */ diff --git a/lib/nghttp2_debug.h b/lib/nghttp2_debug.h new file mode 100644 index 0000000..cbb4dd5 --- /dev/null +++ b/lib/nghttp2_debug.h @@ -0,0 +1,43 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_DEBUG_H +#define NGHTTP2_DEBUG_H + +#ifdef HAVE_CONFIG_H +# include +#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_extpri.c b/lib/nghttp2_extpri.c new file mode 100644 index 0000000..ba0263e --- /dev/null +++ b/lib/nghttp2_extpri.c @@ -0,0 +1,41 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_extpri.h" +#include "nghttp2_http.h" + +uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri) { + return (uint8_t)((uint32_t)extpri->inc << 7 | extpri->urgency); +} + +void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri) { + extpri->urgency = nghttp2_extpri_uint8_urgency(u8extpri); + extpri->inc = nghttp2_extpri_uint8_inc(u8extpri); +} + +int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, const uint8_t *value, + size_t len) { + return nghttp2_http_parse_priority(extpri, value, len); +} diff --git a/lib/nghttp2_extpri.h b/lib/nghttp2_extpri.h new file mode 100644 index 0000000..23c6ddc --- /dev/null +++ b/lib/nghttp2_extpri.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_EXTPRI_H +#define NGHTTP2_EXTPRI_H + +#ifdef HAVE_CONFIG_H +# include +#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_frame.c b/lib/nghttp2_frame.c new file mode 100644 index 0000000..77cb463 --- /dev/null +++ b/lib/nghttp2_frame.c @@ -0,0 +1,1214 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_frame.h" + +#include +#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_frame.h b/lib/nghttp2_frame.h new file mode 100644 index 0000000..d586688 --- /dev/null +++ b/lib/nghttp2_frame.h @@ -0,0 +1,637 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_FRAME_H +#define NGHTTP2_FRAME_H + +#ifdef HAVE_CONFIG_H +# include +#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_hd.c b/lib/nghttp2_hd.c new file mode 100644 index 0000000..8a2bda6 --- /dev/null +++ b/lib/nghttp2_hd.c @@ -0,0 +1,2357 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_hd.h" + +#include +#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_hd.h b/lib/nghttp2_hd.h new file mode 100644 index 0000000..6de0052 --- /dev/null +++ b/lib/nghttp2_hd.h @@ -0,0 +1,440 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HD_H +#define NGHTTP2_HD_H + +#ifdef HAVE_CONFIG_H +# include +#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_hd_huffman.c b/lib/nghttp2_hd_huffman.c new file mode 100644 index 0000000..ac90f49 --- /dev/null +++ b/lib/nghttp2_hd_huffman.c @@ -0,0 +1,144 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_hd_huffman.h" + +#include +#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_hd_huffman.h b/lib/nghttp2_hd_huffman.h new file mode 100644 index 0000000..2bfd531 --- /dev/null +++ b/lib/nghttp2_hd_huffman.h @@ -0,0 +1,72 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HD_HUFFMAN_H +#define NGHTTP2_HD_HUFFMAN_H + +#ifdef HAVE_CONFIG_H +# include +#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_hd_huffman_data.c b/lib/nghttp2_hd_huffman_data.c new file mode 100644 index 0000000..2e2e13f --- /dev/null +++ b/lib/nghttp2_hd_huffman_data.c @@ -0,0 +1,4980 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_hd_huffman.h" + +/* Generated by mkhufftbl.py */ + +const nghttp2_huff_sym huff_sym_table[] = { + {13, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u}, + {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u}, + {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u}, + {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u}, + {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u}, + {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u}, + {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u}, + {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u}, + {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u}, + {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u}, + {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u}, + {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u}, + {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u}, + {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u}, + {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u}, + {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u}, + {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u}, + {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u}, + {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u}, + {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u}, + {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u}, + {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u}, + {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u}, + {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u}, + {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u}, + {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u}, + {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u}, + {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u}, + {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u}, + {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u}, + {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u}, + {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u}, + {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u}, + {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u}, + {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u}, + {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u}, + {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u}, + {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u}, + {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u}, + {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u}, + {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u}, + {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u}, + {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u}, + {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u}, + {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u}, + {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u}, + {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u}, + {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u}, + {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u}, + {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u}, + {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u}, + {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u}, + {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u}, + {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u}, + {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u}, + {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u}, + {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u}, + {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u}, + {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u}, + {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u}, + {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u}, + {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u}, + {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u}, + {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u}, + {30, 0xfffffffcu}}; + +const nghttp2_huff_decode huff_decode_table[][16] = { + /* 0 */ + { + {0x04, 0}, + {0x05, 0}, + {0x07, 0}, + {0x08, 0}, + {0x0b, 0}, + {0x0c, 0}, + {0x10, 0}, + {0x13, 0}, + {0x19, 0}, + {0x1c, 0}, + {0x20, 0}, + {0x23, 0}, + {0x2a, 0}, + {0x31, 0}, + {0x39, 0}, + {0x4040, 0}, + }, + /* 1 */ + { + {0xc000, 48}, + {0xc000, 49}, + {0xc000, 50}, + {0xc000, 97}, + {0xc000, 99}, + {0xc000, 101}, + {0xc000, 105}, + {0xc000, 111}, + {0xc000, 115}, + {0xc000, 116}, + {0x0d, 0}, + {0x0e, 0}, + {0x11, 0}, + {0x12, 0}, + {0x14, 0}, + {0x15, 0}, + }, + /* 2 */ + { + {0x8001, 48}, + {0xc016, 48}, + {0x8001, 49}, + {0xc016, 49}, + {0x8001, 50}, + {0xc016, 50}, + {0x8001, 97}, + {0xc016, 97}, + {0x8001, 99}, + {0xc016, 99}, + {0x8001, 101}, + {0xc016, 101}, + {0x8001, 105}, + {0xc016, 105}, + {0x8001, 111}, + {0xc016, 111}, + }, + /* 3 */ + { + {0x8002, 48}, + {0x8009, 48}, + {0x8017, 48}, + {0xc028, 48}, + {0x8002, 49}, + {0x8009, 49}, + {0x8017, 49}, + {0xc028, 49}, + {0x8002, 50}, + {0x8009, 50}, + {0x8017, 50}, + {0xc028, 50}, + {0x8002, 97}, + {0x8009, 97}, + {0x8017, 97}, + {0xc028, 97}, + }, + /* 4 */ + { + {0x8003, 48}, + {0x8006, 48}, + {0x800a, 48}, + {0x800f, 48}, + {0x8018, 48}, + {0x801f, 48}, + {0x8029, 48}, + {0xc038, 48}, + {0x8003, 49}, + {0x8006, 49}, + {0x800a, 49}, + {0x800f, 49}, + {0x8018, 49}, + {0x801f, 49}, + {0x8029, 49}, + {0xc038, 49}, + }, + /* 5 */ + { + {0x8003, 50}, + {0x8006, 50}, + {0x800a, 50}, + {0x800f, 50}, + {0x8018, 50}, + {0x801f, 50}, + {0x8029, 50}, + {0xc038, 50}, + {0x8003, 97}, + {0x8006, 97}, + {0x800a, 97}, + {0x800f, 97}, + {0x8018, 97}, + {0x801f, 97}, + {0x8029, 97}, + {0xc038, 97}, + }, + /* 6 */ + { + {0x8002, 99}, + {0x8009, 99}, + {0x8017, 99}, + {0xc028, 99}, + {0x8002, 101}, + {0x8009, 101}, + {0x8017, 101}, + {0xc028, 101}, + {0x8002, 105}, + {0x8009, 105}, + {0x8017, 105}, + {0xc028, 105}, + {0x8002, 111}, + {0x8009, 111}, + {0x8017, 111}, + {0xc028, 111}, + }, + /* 7 */ + { + {0x8003, 99}, + {0x8006, 99}, + {0x800a, 99}, + {0x800f, 99}, + {0x8018, 99}, + {0x801f, 99}, + {0x8029, 99}, + {0xc038, 99}, + {0x8003, 101}, + {0x8006, 101}, + {0x800a, 101}, + {0x800f, 101}, + {0x8018, 101}, + {0x801f, 101}, + {0x8029, 101}, + {0xc038, 101}, + }, + /* 8 */ + { + {0x8003, 105}, + {0x8006, 105}, + {0x800a, 105}, + {0x800f, 105}, + {0x8018, 105}, + {0x801f, 105}, + {0x8029, 105}, + {0xc038, 105}, + {0x8003, 111}, + {0x8006, 111}, + {0x800a, 111}, + {0x800f, 111}, + {0x8018, 111}, + {0x801f, 111}, + {0x8029, 111}, + {0xc038, 111}, + }, + /* 9 */ + { + {0x8001, 115}, + {0xc016, 115}, + {0x8001, 116}, + {0xc016, 116}, + {0xc000, 32}, + {0xc000, 37}, + {0xc000, 45}, + {0xc000, 46}, + {0xc000, 47}, + {0xc000, 51}, + {0xc000, 52}, + {0xc000, 53}, + {0xc000, 54}, + {0xc000, 55}, + {0xc000, 56}, + {0xc000, 57}, + }, + /* 10 */ + { + {0x8002, 115}, + {0x8009, 115}, + {0x8017, 115}, + {0xc028, 115}, + {0x8002, 116}, + {0x8009, 116}, + {0x8017, 116}, + {0xc028, 116}, + {0x8001, 32}, + {0xc016, 32}, + {0x8001, 37}, + {0xc016, 37}, + {0x8001, 45}, + {0xc016, 45}, + {0x8001, 46}, + {0xc016, 46}, + }, + /* 11 */ + { + {0x8003, 115}, + {0x8006, 115}, + {0x800a, 115}, + {0x800f, 115}, + {0x8018, 115}, + {0x801f, 115}, + {0x8029, 115}, + {0xc038, 115}, + {0x8003, 116}, + {0x8006, 116}, + {0x800a, 116}, + {0x800f, 116}, + {0x8018, 116}, + {0x801f, 116}, + {0x8029, 116}, + {0xc038, 116}, + }, + /* 12 */ + { + {0x8002, 32}, + {0x8009, 32}, + {0x8017, 32}, + {0xc028, 32}, + {0x8002, 37}, + {0x8009, 37}, + {0x8017, 37}, + {0xc028, 37}, + {0x8002, 45}, + {0x8009, 45}, + {0x8017, 45}, + {0xc028, 45}, + {0x8002, 46}, + {0x8009, 46}, + {0x8017, 46}, + {0xc028, 46}, + }, + /* 13 */ + { + {0x8003, 32}, + {0x8006, 32}, + {0x800a, 32}, + {0x800f, 32}, + {0x8018, 32}, + {0x801f, 32}, + {0x8029, 32}, + {0xc038, 32}, + {0x8003, 37}, + {0x8006, 37}, + {0x800a, 37}, + {0x800f, 37}, + {0x8018, 37}, + {0x801f, 37}, + {0x8029, 37}, + {0xc038, 37}, + }, + /* 14 */ + { + {0x8003, 45}, + {0x8006, 45}, + {0x800a, 45}, + {0x800f, 45}, + {0x8018, 45}, + {0x801f, 45}, + {0x8029, 45}, + {0xc038, 45}, + {0x8003, 46}, + {0x8006, 46}, + {0x800a, 46}, + {0x800f, 46}, + {0x8018, 46}, + {0x801f, 46}, + {0x8029, 46}, + {0xc038, 46}, + }, + /* 15 */ + { + {0x8001, 47}, + {0xc016, 47}, + {0x8001, 51}, + {0xc016, 51}, + {0x8001, 52}, + {0xc016, 52}, + {0x8001, 53}, + {0xc016, 53}, + {0x8001, 54}, + {0xc016, 54}, + {0x8001, 55}, + {0xc016, 55}, + {0x8001, 56}, + {0xc016, 56}, + {0x8001, 57}, + {0xc016, 57}, + }, + /* 16 */ + { + {0x8002, 47}, + {0x8009, 47}, + {0x8017, 47}, + {0xc028, 47}, + {0x8002, 51}, + {0x8009, 51}, + {0x8017, 51}, + {0xc028, 51}, + {0x8002, 52}, + {0x8009, 52}, + {0x8017, 52}, + {0xc028, 52}, + {0x8002, 53}, + {0x8009, 53}, + {0x8017, 53}, + {0xc028, 53}, + }, + /* 17 */ + { + {0x8003, 47}, + {0x8006, 47}, + {0x800a, 47}, + {0x800f, 47}, + {0x8018, 47}, + {0x801f, 47}, + {0x8029, 47}, + {0xc038, 47}, + {0x8003, 51}, + {0x8006, 51}, + {0x800a, 51}, + {0x800f, 51}, + {0x8018, 51}, + {0x801f, 51}, + {0x8029, 51}, + {0xc038, 51}, + }, + /* 18 */ + { + {0x8003, 52}, + {0x8006, 52}, + {0x800a, 52}, + {0x800f, 52}, + {0x8018, 52}, + {0x801f, 52}, + {0x8029, 52}, + {0xc038, 52}, + {0x8003, 53}, + {0x8006, 53}, + {0x800a, 53}, + {0x800f, 53}, + {0x8018, 53}, + {0x801f, 53}, + {0x8029, 53}, + {0xc038, 53}, + }, + /* 19 */ + { + {0x8002, 54}, + {0x8009, 54}, + {0x8017, 54}, + {0xc028, 54}, + {0x8002, 55}, + {0x8009, 55}, + {0x8017, 55}, + {0xc028, 55}, + {0x8002, 56}, + {0x8009, 56}, + {0x8017, 56}, + {0xc028, 56}, + {0x8002, 57}, + {0x8009, 57}, + {0x8017, 57}, + {0xc028, 57}, + }, + /* 20 */ + { + {0x8003, 54}, + {0x8006, 54}, + {0x800a, 54}, + {0x800f, 54}, + {0x8018, 54}, + {0x801f, 54}, + {0x8029, 54}, + {0xc038, 54}, + {0x8003, 55}, + {0x8006, 55}, + {0x800a, 55}, + {0x800f, 55}, + {0x8018, 55}, + {0x801f, 55}, + {0x8029, 55}, + {0xc038, 55}, + }, + /* 21 */ + { + {0x8003, 56}, + {0x8006, 56}, + {0x800a, 56}, + {0x800f, 56}, + {0x8018, 56}, + {0x801f, 56}, + {0x8029, 56}, + {0xc038, 56}, + {0x8003, 57}, + {0x8006, 57}, + {0x800a, 57}, + {0x800f, 57}, + {0x8018, 57}, + {0x801f, 57}, + {0x8029, 57}, + {0xc038, 57}, + }, + /* 22 */ + { + {0x1a, 0}, + {0x1b, 0}, + {0x1d, 0}, + {0x1e, 0}, + {0x21, 0}, + {0x22, 0}, + {0x24, 0}, + {0x25, 0}, + {0x2b, 0}, + {0x2e, 0}, + {0x32, 0}, + {0x35, 0}, + {0x3a, 0}, + {0x3d, 0}, + {0x41, 0}, + {0x4044, 0}, + }, + /* 23 */ + { + {0xc000, 61}, + {0xc000, 65}, + {0xc000, 95}, + {0xc000, 98}, + {0xc000, 100}, + {0xc000, 102}, + {0xc000, 103}, + {0xc000, 104}, + {0xc000, 108}, + {0xc000, 109}, + {0xc000, 110}, + {0xc000, 112}, + {0xc000, 114}, + {0xc000, 117}, + {0x26, 0}, + {0x27, 0}, + }, + /* 24 */ + { + {0x8001, 61}, + {0xc016, 61}, + {0x8001, 65}, + {0xc016, 65}, + {0x8001, 95}, + {0xc016, 95}, + {0x8001, 98}, + {0xc016, 98}, + {0x8001, 100}, + {0xc016, 100}, + {0x8001, 102}, + {0xc016, 102}, + {0x8001, 103}, + {0xc016, 103}, + {0x8001, 104}, + {0xc016, 104}, + }, + /* 25 */ + { + {0x8002, 61}, + {0x8009, 61}, + {0x8017, 61}, + {0xc028, 61}, + {0x8002, 65}, + {0x8009, 65}, + {0x8017, 65}, + {0xc028, 65}, + {0x8002, 95}, + {0x8009, 95}, + {0x8017, 95}, + {0xc028, 95}, + {0x8002, 98}, + {0x8009, 98}, + {0x8017, 98}, + {0xc028, 98}, + }, + /* 26 */ + { + {0x8003, 61}, + {0x8006, 61}, + {0x800a, 61}, + {0x800f, 61}, + {0x8018, 61}, + {0x801f, 61}, + {0x8029, 61}, + {0xc038, 61}, + {0x8003, 65}, + {0x8006, 65}, + {0x800a, 65}, + {0x800f, 65}, + {0x8018, 65}, + {0x801f, 65}, + {0x8029, 65}, + {0xc038, 65}, + }, + /* 27 */ + { + {0x8003, 95}, + {0x8006, 95}, + {0x800a, 95}, + {0x800f, 95}, + {0x8018, 95}, + {0x801f, 95}, + {0x8029, 95}, + {0xc038, 95}, + {0x8003, 98}, + {0x8006, 98}, + {0x800a, 98}, + {0x800f, 98}, + {0x8018, 98}, + {0x801f, 98}, + {0x8029, 98}, + {0xc038, 98}, + }, + /* 28 */ + { + {0x8002, 100}, + {0x8009, 100}, + {0x8017, 100}, + {0xc028, 100}, + {0x8002, 102}, + {0x8009, 102}, + {0x8017, 102}, + {0xc028, 102}, + {0x8002, 103}, + {0x8009, 103}, + {0x8017, 103}, + {0xc028, 103}, + {0x8002, 104}, + {0x8009, 104}, + {0x8017, 104}, + {0xc028, 104}, + }, + /* 29 */ + { + {0x8003, 100}, + {0x8006, 100}, + {0x800a, 100}, + {0x800f, 100}, + {0x8018, 100}, + {0x801f, 100}, + {0x8029, 100}, + {0xc038, 100}, + {0x8003, 102}, + {0x8006, 102}, + {0x800a, 102}, + {0x800f, 102}, + {0x8018, 102}, + {0x801f, 102}, + {0x8029, 102}, + {0xc038, 102}, + }, + /* 30 */ + { + {0x8003, 103}, + {0x8006, 103}, + {0x800a, 103}, + {0x800f, 103}, + {0x8018, 103}, + {0x801f, 103}, + {0x8029, 103}, + {0xc038, 103}, + {0x8003, 104}, + {0x8006, 104}, + {0x800a, 104}, + {0x800f, 104}, + {0x8018, 104}, + {0x801f, 104}, + {0x8029, 104}, + {0xc038, 104}, + }, + /* 31 */ + { + {0x8001, 108}, + {0xc016, 108}, + {0x8001, 109}, + {0xc016, 109}, + {0x8001, 110}, + {0xc016, 110}, + {0x8001, 112}, + {0xc016, 112}, + {0x8001, 114}, + {0xc016, 114}, + {0x8001, 117}, + {0xc016, 117}, + {0xc000, 58}, + {0xc000, 66}, + {0xc000, 67}, + {0xc000, 68}, + }, + /* 32 */ + { + {0x8002, 108}, + {0x8009, 108}, + {0x8017, 108}, + {0xc028, 108}, + {0x8002, 109}, + {0x8009, 109}, + {0x8017, 109}, + {0xc028, 109}, + {0x8002, 110}, + {0x8009, 110}, + {0x8017, 110}, + {0xc028, 110}, + {0x8002, 112}, + {0x8009, 112}, + {0x8017, 112}, + {0xc028, 112}, + }, + /* 33 */ + { + {0x8003, 108}, + {0x8006, 108}, + {0x800a, 108}, + {0x800f, 108}, + {0x8018, 108}, + {0x801f, 108}, + {0x8029, 108}, + {0xc038, 108}, + {0x8003, 109}, + {0x8006, 109}, + {0x800a, 109}, + {0x800f, 109}, + {0x8018, 109}, + {0x801f, 109}, + {0x8029, 109}, + {0xc038, 109}, + }, + /* 34 */ + { + {0x8003, 110}, + {0x8006, 110}, + {0x800a, 110}, + {0x800f, 110}, + {0x8018, 110}, + {0x801f, 110}, + {0x8029, 110}, + {0xc038, 110}, + {0x8003, 112}, + {0x8006, 112}, + {0x800a, 112}, + {0x800f, 112}, + {0x8018, 112}, + {0x801f, 112}, + {0x8029, 112}, + {0xc038, 112}, + }, + /* 35 */ + { + {0x8002, 114}, + {0x8009, 114}, + {0x8017, 114}, + {0xc028, 114}, + {0x8002, 117}, + {0x8009, 117}, + {0x8017, 117}, + {0xc028, 117}, + {0x8001, 58}, + {0xc016, 58}, + {0x8001, 66}, + {0xc016, 66}, + {0x8001, 67}, + {0xc016, 67}, + {0x8001, 68}, + {0xc016, 68}, + }, + /* 36 */ + { + {0x8003, 114}, + {0x8006, 114}, + {0x800a, 114}, + {0x800f, 114}, + {0x8018, 114}, + {0x801f, 114}, + {0x8029, 114}, + {0xc038, 114}, + {0x8003, 117}, + {0x8006, 117}, + {0x800a, 117}, + {0x800f, 117}, + {0x8018, 117}, + {0x801f, 117}, + {0x8029, 117}, + {0xc038, 117}, + }, + /* 37 */ + { + {0x8002, 58}, + {0x8009, 58}, + {0x8017, 58}, + {0xc028, 58}, + {0x8002, 66}, + {0x8009, 66}, + {0x8017, 66}, + {0xc028, 66}, + {0x8002, 67}, + {0x8009, 67}, + {0x8017, 67}, + {0xc028, 67}, + {0x8002, 68}, + {0x8009, 68}, + {0x8017, 68}, + {0xc028, 68}, + }, + /* 38 */ + { + {0x8003, 58}, + {0x8006, 58}, + {0x800a, 58}, + {0x800f, 58}, + {0x8018, 58}, + {0x801f, 58}, + {0x8029, 58}, + {0xc038, 58}, + {0x8003, 66}, + {0x8006, 66}, + {0x800a, 66}, + {0x800f, 66}, + {0x8018, 66}, + {0x801f, 66}, + {0x8029, 66}, + {0xc038, 66}, + }, + /* 39 */ + { + {0x8003, 67}, + {0x8006, 67}, + {0x800a, 67}, + {0x800f, 67}, + {0x8018, 67}, + {0x801f, 67}, + {0x8029, 67}, + {0xc038, 67}, + {0x8003, 68}, + {0x8006, 68}, + {0x800a, 68}, + {0x800f, 68}, + {0x8018, 68}, + {0x801f, 68}, + {0x8029, 68}, + {0xc038, 68}, + }, + /* 40 */ + { + {0x2c, 0}, + {0x2d, 0}, + {0x2f, 0}, + {0x30, 0}, + {0x33, 0}, + {0x34, 0}, + {0x36, 0}, + {0x37, 0}, + {0x3b, 0}, + {0x3c, 0}, + {0x3e, 0}, + {0x3f, 0}, + {0x42, 0}, + {0x43, 0}, + {0x45, 0}, + {0x4048, 0}, + }, + /* 41 */ + { + {0xc000, 69}, + {0xc000, 70}, + {0xc000, 71}, + {0xc000, 72}, + {0xc000, 73}, + {0xc000, 74}, + {0xc000, 75}, + {0xc000, 76}, + {0xc000, 77}, + {0xc000, 78}, + {0xc000, 79}, + {0xc000, 80}, + {0xc000, 81}, + {0xc000, 82}, + {0xc000, 83}, + {0xc000, 84}, + }, + /* 42 */ + { + {0x8001, 69}, + {0xc016, 69}, + {0x8001, 70}, + {0xc016, 70}, + {0x8001, 71}, + {0xc016, 71}, + {0x8001, 72}, + {0xc016, 72}, + {0x8001, 73}, + {0xc016, 73}, + {0x8001, 74}, + {0xc016, 74}, + {0x8001, 75}, + {0xc016, 75}, + {0x8001, 76}, + {0xc016, 76}, + }, + /* 43 */ + { + {0x8002, 69}, + {0x8009, 69}, + {0x8017, 69}, + {0xc028, 69}, + {0x8002, 70}, + {0x8009, 70}, + {0x8017, 70}, + {0xc028, 70}, + {0x8002, 71}, + {0x8009, 71}, + {0x8017, 71}, + {0xc028, 71}, + {0x8002, 72}, + {0x8009, 72}, + {0x8017, 72}, + {0xc028, 72}, + }, + /* 44 */ + { + {0x8003, 69}, + {0x8006, 69}, + {0x800a, 69}, + {0x800f, 69}, + {0x8018, 69}, + {0x801f, 69}, + {0x8029, 69}, + {0xc038, 69}, + {0x8003, 70}, + {0x8006, 70}, + {0x800a, 70}, + {0x800f, 70}, + {0x8018, 70}, + {0x801f, 70}, + {0x8029, 70}, + {0xc038, 70}, + }, + /* 45 */ + { + {0x8003, 71}, + {0x8006, 71}, + {0x800a, 71}, + {0x800f, 71}, + {0x8018, 71}, + {0x801f, 71}, + {0x8029, 71}, + {0xc038, 71}, + {0x8003, 72}, + {0x8006, 72}, + {0x800a, 72}, + {0x800f, 72}, + {0x8018, 72}, + {0x801f, 72}, + {0x8029, 72}, + {0xc038, 72}, + }, + /* 46 */ + { + {0x8002, 73}, + {0x8009, 73}, + {0x8017, 73}, + {0xc028, 73}, + {0x8002, 74}, + {0x8009, 74}, + {0x8017, 74}, + {0xc028, 74}, + {0x8002, 75}, + {0x8009, 75}, + {0x8017, 75}, + {0xc028, 75}, + {0x8002, 76}, + {0x8009, 76}, + {0x8017, 76}, + {0xc028, 76}, + }, + /* 47 */ + { + {0x8003, 73}, + {0x8006, 73}, + {0x800a, 73}, + {0x800f, 73}, + {0x8018, 73}, + {0x801f, 73}, + {0x8029, 73}, + {0xc038, 73}, + {0x8003, 74}, + {0x8006, 74}, + {0x800a, 74}, + {0x800f, 74}, + {0x8018, 74}, + {0x801f, 74}, + {0x8029, 74}, + {0xc038, 74}, + }, + /* 48 */ + { + {0x8003, 75}, + {0x8006, 75}, + {0x800a, 75}, + {0x800f, 75}, + {0x8018, 75}, + {0x801f, 75}, + {0x8029, 75}, + {0xc038, 75}, + {0x8003, 76}, + {0x8006, 76}, + {0x800a, 76}, + {0x800f, 76}, + {0x8018, 76}, + {0x801f, 76}, + {0x8029, 76}, + {0xc038, 76}, + }, + /* 49 */ + { + {0x8001, 77}, + {0xc016, 77}, + {0x8001, 78}, + {0xc016, 78}, + {0x8001, 79}, + {0xc016, 79}, + {0x8001, 80}, + {0xc016, 80}, + {0x8001, 81}, + {0xc016, 81}, + {0x8001, 82}, + {0xc016, 82}, + {0x8001, 83}, + {0xc016, 83}, + {0x8001, 84}, + {0xc016, 84}, + }, + /* 50 */ + { + {0x8002, 77}, + {0x8009, 77}, + {0x8017, 77}, + {0xc028, 77}, + {0x8002, 78}, + {0x8009, 78}, + {0x8017, 78}, + {0xc028, 78}, + {0x8002, 79}, + {0x8009, 79}, + {0x8017, 79}, + {0xc028, 79}, + {0x8002, 80}, + {0x8009, 80}, + {0x8017, 80}, + {0xc028, 80}, + }, + /* 51 */ + { + {0x8003, 77}, + {0x8006, 77}, + {0x800a, 77}, + {0x800f, 77}, + {0x8018, 77}, + {0x801f, 77}, + {0x8029, 77}, + {0xc038, 77}, + {0x8003, 78}, + {0x8006, 78}, + {0x800a, 78}, + {0x800f, 78}, + {0x8018, 78}, + {0x801f, 78}, + {0x8029, 78}, + {0xc038, 78}, + }, + /* 52 */ + { + {0x8003, 79}, + {0x8006, 79}, + {0x800a, 79}, + {0x800f, 79}, + {0x8018, 79}, + {0x801f, 79}, + {0x8029, 79}, + {0xc038, 79}, + {0x8003, 80}, + {0x8006, 80}, + {0x800a, 80}, + {0x800f, 80}, + {0x8018, 80}, + {0x801f, 80}, + {0x8029, 80}, + {0xc038, 80}, + }, + /* 53 */ + { + {0x8002, 81}, + {0x8009, 81}, + {0x8017, 81}, + {0xc028, 81}, + {0x8002, 82}, + {0x8009, 82}, + {0x8017, 82}, + {0xc028, 82}, + {0x8002, 83}, + {0x8009, 83}, + {0x8017, 83}, + {0xc028, 83}, + {0x8002, 84}, + {0x8009, 84}, + {0x8017, 84}, + {0xc028, 84}, + }, + /* 54 */ + { + {0x8003, 81}, + {0x8006, 81}, + {0x800a, 81}, + {0x800f, 81}, + {0x8018, 81}, + {0x801f, 81}, + {0x8029, 81}, + {0xc038, 81}, + {0x8003, 82}, + {0x8006, 82}, + {0x800a, 82}, + {0x800f, 82}, + {0x8018, 82}, + {0x801f, 82}, + {0x8029, 82}, + {0xc038, 82}, + }, + /* 55 */ + { + {0x8003, 83}, + {0x8006, 83}, + {0x800a, 83}, + {0x800f, 83}, + {0x8018, 83}, + {0x801f, 83}, + {0x8029, 83}, + {0xc038, 83}, + {0x8003, 84}, + {0x8006, 84}, + {0x800a, 84}, + {0x800f, 84}, + {0x8018, 84}, + {0x801f, 84}, + {0x8029, 84}, + {0xc038, 84}, + }, + /* 56 */ + { + {0xc000, 85}, + {0xc000, 86}, + {0xc000, 87}, + {0xc000, 89}, + {0xc000, 106}, + {0xc000, 107}, + {0xc000, 113}, + {0xc000, 118}, + {0xc000, 119}, + {0xc000, 120}, + {0xc000, 121}, + {0xc000, 122}, + {0x46, 0}, + {0x47, 0}, + {0x49, 0}, + {0x404a, 0}, + }, + /* 57 */ + { + {0x8001, 85}, + {0xc016, 85}, + {0x8001, 86}, + {0xc016, 86}, + {0x8001, 87}, + {0xc016, 87}, + {0x8001, 89}, + {0xc016, 89}, + {0x8001, 106}, + {0xc016, 106}, + {0x8001, 107}, + {0xc016, 107}, + {0x8001, 113}, + {0xc016, 113}, + {0x8001, 118}, + {0xc016, 118}, + }, + /* 58 */ + { + {0x8002, 85}, + {0x8009, 85}, + {0x8017, 85}, + {0xc028, 85}, + {0x8002, 86}, + {0x8009, 86}, + {0x8017, 86}, + {0xc028, 86}, + {0x8002, 87}, + {0x8009, 87}, + {0x8017, 87}, + {0xc028, 87}, + {0x8002, 89}, + {0x8009, 89}, + {0x8017, 89}, + {0xc028, 89}, + }, + /* 59 */ + { + {0x8003, 85}, + {0x8006, 85}, + {0x800a, 85}, + {0x800f, 85}, + {0x8018, 85}, + {0x801f, 85}, + {0x8029, 85}, + {0xc038, 85}, + {0x8003, 86}, + {0x8006, 86}, + {0x800a, 86}, + {0x800f, 86}, + {0x8018, 86}, + {0x801f, 86}, + {0x8029, 86}, + {0xc038, 86}, + }, + /* 60 */ + { + {0x8003, 87}, + {0x8006, 87}, + {0x800a, 87}, + {0x800f, 87}, + {0x8018, 87}, + {0x801f, 87}, + {0x8029, 87}, + {0xc038, 87}, + {0x8003, 89}, + {0x8006, 89}, + {0x800a, 89}, + {0x800f, 89}, + {0x8018, 89}, + {0x801f, 89}, + {0x8029, 89}, + {0xc038, 89}, + }, + /* 61 */ + { + {0x8002, 106}, + {0x8009, 106}, + {0x8017, 106}, + {0xc028, 106}, + {0x8002, 107}, + {0x8009, 107}, + {0x8017, 107}, + {0xc028, 107}, + {0x8002, 113}, + {0x8009, 113}, + {0x8017, 113}, + {0xc028, 113}, + {0x8002, 118}, + {0x8009, 118}, + {0x8017, 118}, + {0xc028, 118}, + }, + /* 62 */ + { + {0x8003, 106}, + {0x8006, 106}, + {0x800a, 106}, + {0x800f, 106}, + {0x8018, 106}, + {0x801f, 106}, + {0x8029, 106}, + {0xc038, 106}, + {0x8003, 107}, + {0x8006, 107}, + {0x800a, 107}, + {0x800f, 107}, + {0x8018, 107}, + {0x801f, 107}, + {0x8029, 107}, + {0xc038, 107}, + }, + /* 63 */ + { + {0x8003, 113}, + {0x8006, 113}, + {0x800a, 113}, + {0x800f, 113}, + {0x8018, 113}, + {0x801f, 113}, + {0x8029, 113}, + {0xc038, 113}, + {0x8003, 118}, + {0x8006, 118}, + {0x800a, 118}, + {0x800f, 118}, + {0x8018, 118}, + {0x801f, 118}, + {0x8029, 118}, + {0xc038, 118}, + }, + /* 64 */ + { + {0x8001, 119}, + {0xc016, 119}, + {0x8001, 120}, + {0xc016, 120}, + {0x8001, 121}, + {0xc016, 121}, + {0x8001, 122}, + {0xc016, 122}, + {0xc000, 38}, + {0xc000, 42}, + {0xc000, 44}, + {0xc000, 59}, + {0xc000, 88}, + {0xc000, 90}, + {0x4b, 0}, + {0x4e, 0}, + }, + /* 65 */ + { + {0x8002, 119}, + {0x8009, 119}, + {0x8017, 119}, + {0xc028, 119}, + {0x8002, 120}, + {0x8009, 120}, + {0x8017, 120}, + {0xc028, 120}, + {0x8002, 121}, + {0x8009, 121}, + {0x8017, 121}, + {0xc028, 121}, + {0x8002, 122}, + {0x8009, 122}, + {0x8017, 122}, + {0xc028, 122}, + }, + /* 66 */ + { + {0x8003, 119}, + {0x8006, 119}, + {0x800a, 119}, + {0x800f, 119}, + {0x8018, 119}, + {0x801f, 119}, + {0x8029, 119}, + {0xc038, 119}, + {0x8003, 120}, + {0x8006, 120}, + {0x800a, 120}, + {0x800f, 120}, + {0x8018, 120}, + {0x801f, 120}, + {0x8029, 120}, + {0xc038, 120}, + }, + /* 67 */ + { + {0x8003, 121}, + {0x8006, 121}, + {0x800a, 121}, + {0x800f, 121}, + {0x8018, 121}, + {0x801f, 121}, + {0x8029, 121}, + {0xc038, 121}, + {0x8003, 122}, + {0x8006, 122}, + {0x800a, 122}, + {0x800f, 122}, + {0x8018, 122}, + {0x801f, 122}, + {0x8029, 122}, + {0xc038, 122}, + }, + /* 68 */ + { + {0x8001, 38}, + {0xc016, 38}, + {0x8001, 42}, + {0xc016, 42}, + {0x8001, 44}, + {0xc016, 44}, + {0x8001, 59}, + {0xc016, 59}, + {0x8001, 88}, + {0xc016, 88}, + {0x8001, 90}, + {0xc016, 90}, + {0x4c, 0}, + {0x4d, 0}, + {0x4f, 0}, + {0x51, 0}, + }, + /* 69 */ + { + {0x8002, 38}, + {0x8009, 38}, + {0x8017, 38}, + {0xc028, 38}, + {0x8002, 42}, + {0x8009, 42}, + {0x8017, 42}, + {0xc028, 42}, + {0x8002, 44}, + {0x8009, 44}, + {0x8017, 44}, + {0xc028, 44}, + {0x8002, 59}, + {0x8009, 59}, + {0x8017, 59}, + {0xc028, 59}, + }, + /* 70 */ + { + {0x8003, 38}, + {0x8006, 38}, + {0x800a, 38}, + {0x800f, 38}, + {0x8018, 38}, + {0x801f, 38}, + {0x8029, 38}, + {0xc038, 38}, + {0x8003, 42}, + {0x8006, 42}, + {0x800a, 42}, + {0x800f, 42}, + {0x8018, 42}, + {0x801f, 42}, + {0x8029, 42}, + {0xc038, 42}, + }, + /* 71 */ + { + {0x8003, 44}, + {0x8006, 44}, + {0x800a, 44}, + {0x800f, 44}, + {0x8018, 44}, + {0x801f, 44}, + {0x8029, 44}, + {0xc038, 44}, + {0x8003, 59}, + {0x8006, 59}, + {0x800a, 59}, + {0x800f, 59}, + {0x8018, 59}, + {0x801f, 59}, + {0x8029, 59}, + {0xc038, 59}, + }, + /* 72 */ + { + {0x8002, 88}, + {0x8009, 88}, + {0x8017, 88}, + {0xc028, 88}, + {0x8002, 90}, + {0x8009, 90}, + {0x8017, 90}, + {0xc028, 90}, + {0xc000, 33}, + {0xc000, 34}, + {0xc000, 40}, + {0xc000, 41}, + {0xc000, 63}, + {0x50, 0}, + {0x52, 0}, + {0x54, 0}, + }, + /* 73 */ + { + {0x8003, 88}, + {0x8006, 88}, + {0x800a, 88}, + {0x800f, 88}, + {0x8018, 88}, + {0x801f, 88}, + {0x8029, 88}, + {0xc038, 88}, + {0x8003, 90}, + {0x8006, 90}, + {0x800a, 90}, + {0x800f, 90}, + {0x8018, 90}, + {0x801f, 90}, + {0x8029, 90}, + {0xc038, 90}, + }, + /* 74 */ + { + {0x8001, 33}, + {0xc016, 33}, + {0x8001, 34}, + {0xc016, 34}, + {0x8001, 40}, + {0xc016, 40}, + {0x8001, 41}, + {0xc016, 41}, + {0x8001, 63}, + {0xc016, 63}, + {0xc000, 39}, + {0xc000, 43}, + {0xc000, 124}, + {0x53, 0}, + {0x55, 0}, + {0x58, 0}, + }, + /* 75 */ + { + {0x8002, 33}, + {0x8009, 33}, + {0x8017, 33}, + {0xc028, 33}, + {0x8002, 34}, + {0x8009, 34}, + {0x8017, 34}, + {0xc028, 34}, + {0x8002, 40}, + {0x8009, 40}, + {0x8017, 40}, + {0xc028, 40}, + {0x8002, 41}, + {0x8009, 41}, + {0x8017, 41}, + {0xc028, 41}, + }, + /* 76 */ + { + {0x8003, 33}, + {0x8006, 33}, + {0x800a, 33}, + {0x800f, 33}, + {0x8018, 33}, + {0x801f, 33}, + {0x8029, 33}, + {0xc038, 33}, + {0x8003, 34}, + {0x8006, 34}, + {0x800a, 34}, + {0x800f, 34}, + {0x8018, 34}, + {0x801f, 34}, + {0x8029, 34}, + {0xc038, 34}, + }, + /* 77 */ + { + {0x8003, 40}, + {0x8006, 40}, + {0x800a, 40}, + {0x800f, 40}, + {0x8018, 40}, + {0x801f, 40}, + {0x8029, 40}, + {0xc038, 40}, + {0x8003, 41}, + {0x8006, 41}, + {0x800a, 41}, + {0x800f, 41}, + {0x8018, 41}, + {0x801f, 41}, + {0x8029, 41}, + {0xc038, 41}, + }, + /* 78 */ + { + {0x8002, 63}, + {0x8009, 63}, + {0x8017, 63}, + {0xc028, 63}, + {0x8001, 39}, + {0xc016, 39}, + {0x8001, 43}, + {0xc016, 43}, + {0x8001, 124}, + {0xc016, 124}, + {0xc000, 35}, + {0xc000, 62}, + {0x56, 0}, + {0x57, 0}, + {0x59, 0}, + {0x5a, 0}, + }, + /* 79 */ + { + {0x8003, 63}, + {0x8006, 63}, + {0x800a, 63}, + {0x800f, 63}, + {0x8018, 63}, + {0x801f, 63}, + {0x8029, 63}, + {0xc038, 63}, + {0x8002, 39}, + {0x8009, 39}, + {0x8017, 39}, + {0xc028, 39}, + {0x8002, 43}, + {0x8009, 43}, + {0x8017, 43}, + {0xc028, 43}, + }, + /* 80 */ + { + {0x8003, 39}, + {0x8006, 39}, + {0x800a, 39}, + {0x800f, 39}, + {0x8018, 39}, + {0x801f, 39}, + {0x8029, 39}, + {0xc038, 39}, + {0x8003, 43}, + {0x8006, 43}, + {0x800a, 43}, + {0x800f, 43}, + {0x8018, 43}, + {0x801f, 43}, + {0x8029, 43}, + {0xc038, 43}, + }, + /* 81 */ + { + {0x8002, 124}, + {0x8009, 124}, + {0x8017, 124}, + {0xc028, 124}, + {0x8001, 35}, + {0xc016, 35}, + {0x8001, 62}, + {0xc016, 62}, + {0xc000, 0}, + {0xc000, 36}, + {0xc000, 64}, + {0xc000, 91}, + {0xc000, 93}, + {0xc000, 126}, + {0x5b, 0}, + {0x5c, 0}, + }, + /* 82 */ + { + {0x8003, 124}, + {0x8006, 124}, + {0x800a, 124}, + {0x800f, 124}, + {0x8018, 124}, + {0x801f, 124}, + {0x8029, 124}, + {0xc038, 124}, + {0x8002, 35}, + {0x8009, 35}, + {0x8017, 35}, + {0xc028, 35}, + {0x8002, 62}, + {0x8009, 62}, + {0x8017, 62}, + {0xc028, 62}, + }, + /* 83 */ + { + {0x8003, 35}, + {0x8006, 35}, + {0x800a, 35}, + {0x800f, 35}, + {0x8018, 35}, + {0x801f, 35}, + {0x8029, 35}, + {0xc038, 35}, + {0x8003, 62}, + {0x8006, 62}, + {0x800a, 62}, + {0x800f, 62}, + {0x8018, 62}, + {0x801f, 62}, + {0x8029, 62}, + {0xc038, 62}, + }, + /* 84 */ + { + {0x8001, 0}, + {0xc016, 0}, + {0x8001, 36}, + {0xc016, 36}, + {0x8001, 64}, + {0xc016, 64}, + {0x8001, 91}, + {0xc016, 91}, + {0x8001, 93}, + {0xc016, 93}, + {0x8001, 126}, + {0xc016, 126}, + {0xc000, 94}, + {0xc000, 125}, + {0x5d, 0}, + {0x5e, 0}, + }, + /* 85 */ + { + {0x8002, 0}, + {0x8009, 0}, + {0x8017, 0}, + {0xc028, 0}, + {0x8002, 36}, + {0x8009, 36}, + {0x8017, 36}, + {0xc028, 36}, + {0x8002, 64}, + {0x8009, 64}, + {0x8017, 64}, + {0xc028, 64}, + {0x8002, 91}, + {0x8009, 91}, + {0x8017, 91}, + {0xc028, 91}, + }, + /* 86 */ + { + {0x8003, 0}, + {0x8006, 0}, + {0x800a, 0}, + {0x800f, 0}, + {0x8018, 0}, + {0x801f, 0}, + {0x8029, 0}, + {0xc038, 0}, + {0x8003, 36}, + {0x8006, 36}, + {0x800a, 36}, + {0x800f, 36}, + {0x8018, 36}, + {0x801f, 36}, + {0x8029, 36}, + {0xc038, 36}, + }, + /* 87 */ + { + {0x8003, 64}, + {0x8006, 64}, + {0x800a, 64}, + {0x800f, 64}, + {0x8018, 64}, + {0x801f, 64}, + {0x8029, 64}, + {0xc038, 64}, + {0x8003, 91}, + {0x8006, 91}, + {0x800a, 91}, + {0x800f, 91}, + {0x8018, 91}, + {0x801f, 91}, + {0x8029, 91}, + {0xc038, 91}, + }, + /* 88 */ + { + {0x8002, 93}, + {0x8009, 93}, + {0x8017, 93}, + {0xc028, 93}, + {0x8002, 126}, + {0x8009, 126}, + {0x8017, 126}, + {0xc028, 126}, + {0x8001, 94}, + {0xc016, 94}, + {0x8001, 125}, + {0xc016, 125}, + {0xc000, 60}, + {0xc000, 96}, + {0xc000, 123}, + {0x5f, 0}, + }, + /* 89 */ + { + {0x8003, 93}, + {0x8006, 93}, + {0x800a, 93}, + {0x800f, 93}, + {0x8018, 93}, + {0x801f, 93}, + {0x8029, 93}, + {0xc038, 93}, + {0x8003, 126}, + {0x8006, 126}, + {0x800a, 126}, + {0x800f, 126}, + {0x8018, 126}, + {0x801f, 126}, + {0x8029, 126}, + {0xc038, 126}, + }, + /* 90 */ + { + {0x8002, 94}, + {0x8009, 94}, + {0x8017, 94}, + {0xc028, 94}, + {0x8002, 125}, + {0x8009, 125}, + {0x8017, 125}, + {0xc028, 125}, + {0x8001, 60}, + {0xc016, 60}, + {0x8001, 96}, + {0xc016, 96}, + {0x8001, 123}, + {0xc016, 123}, + {0x60, 0}, + {0x6e, 0}, + }, + /* 91 */ + { + {0x8003, 94}, + {0x8006, 94}, + {0x800a, 94}, + {0x800f, 94}, + {0x8018, 94}, + {0x801f, 94}, + {0x8029, 94}, + {0xc038, 94}, + {0x8003, 125}, + {0x8006, 125}, + {0x800a, 125}, + {0x800f, 125}, + {0x8018, 125}, + {0x801f, 125}, + {0x8029, 125}, + {0xc038, 125}, + }, + /* 92 */ + { + {0x8002, 60}, + {0x8009, 60}, + {0x8017, 60}, + {0xc028, 60}, + {0x8002, 96}, + {0x8009, 96}, + {0x8017, 96}, + {0xc028, 96}, + {0x8002, 123}, + {0x8009, 123}, + {0x8017, 123}, + {0xc028, 123}, + {0x61, 0}, + {0x65, 0}, + {0x6f, 0}, + {0x85, 0}, + }, + /* 93 */ + { + {0x8003, 60}, + {0x8006, 60}, + {0x800a, 60}, + {0x800f, 60}, + {0x8018, 60}, + {0x801f, 60}, + {0x8029, 60}, + {0xc038, 60}, + {0x8003, 96}, + {0x8006, 96}, + {0x800a, 96}, + {0x800f, 96}, + {0x8018, 96}, + {0x801f, 96}, + {0x8029, 96}, + {0xc038, 96}, + }, + /* 94 */ + { + {0x8003, 123}, + {0x8006, 123}, + {0x800a, 123}, + {0x800f, 123}, + {0x8018, 123}, + {0x801f, 123}, + {0x8029, 123}, + {0xc038, 123}, + {0x62, 0}, + {0x63, 0}, + {0x66, 0}, + {0x69, 0}, + {0x70, 0}, + {0x77, 0}, + {0x86, 0}, + {0x99, 0}, + }, + /* 95 */ + { + {0xc000, 92}, + {0xc000, 195}, + {0xc000, 208}, + {0x64, 0}, + {0x67, 0}, + {0x68, 0}, + {0x6a, 0}, + {0x6b, 0}, + {0x71, 0}, + {0x74, 0}, + {0x78, 0}, + {0x7e, 0}, + {0x87, 0}, + {0x8e, 0}, + {0x9a, 0}, + {0xa9, 0}, + }, + /* 96 */ + { + {0x8001, 92}, + {0xc016, 92}, + {0x8001, 195}, + {0xc016, 195}, + {0x8001, 208}, + {0xc016, 208}, + {0xc000, 128}, + {0xc000, 130}, + {0xc000, 131}, + {0xc000, 162}, + {0xc000, 184}, + {0xc000, 194}, + {0xc000, 224}, + {0xc000, 226}, + {0x6c, 0}, + {0x6d, 0}, + }, + /* 97 */ + { + {0x8002, 92}, + {0x8009, 92}, + {0x8017, 92}, + {0xc028, 92}, + {0x8002, 195}, + {0x8009, 195}, + {0x8017, 195}, + {0xc028, 195}, + {0x8002, 208}, + {0x8009, 208}, + {0x8017, 208}, + {0xc028, 208}, + {0x8001, 128}, + {0xc016, 128}, + {0x8001, 130}, + {0xc016, 130}, + }, + /* 98 */ + { + {0x8003, 92}, + {0x8006, 92}, + {0x800a, 92}, + {0x800f, 92}, + {0x8018, 92}, + {0x801f, 92}, + {0x8029, 92}, + {0xc038, 92}, + {0x8003, 195}, + {0x8006, 195}, + {0x800a, 195}, + {0x800f, 195}, + {0x8018, 195}, + {0x801f, 195}, + {0x8029, 195}, + {0xc038, 195}, + }, + /* 99 */ + { + {0x8003, 208}, + {0x8006, 208}, + {0x800a, 208}, + {0x800f, 208}, + {0x8018, 208}, + {0x801f, 208}, + {0x8029, 208}, + {0xc038, 208}, + {0x8002, 128}, + {0x8009, 128}, + {0x8017, 128}, + {0xc028, 128}, + {0x8002, 130}, + {0x8009, 130}, + {0x8017, 130}, + {0xc028, 130}, + }, + /* 100 */ + { + {0x8003, 128}, + {0x8006, 128}, + {0x800a, 128}, + {0x800f, 128}, + {0x8018, 128}, + {0x801f, 128}, + {0x8029, 128}, + {0xc038, 128}, + {0x8003, 130}, + {0x8006, 130}, + {0x800a, 130}, + {0x800f, 130}, + {0x8018, 130}, + {0x801f, 130}, + {0x8029, 130}, + {0xc038, 130}, + }, + /* 101 */ + { + {0x8001, 131}, + {0xc016, 131}, + {0x8001, 162}, + {0xc016, 162}, + {0x8001, 184}, + {0xc016, 184}, + {0x8001, 194}, + {0xc016, 194}, + {0x8001, 224}, + {0xc016, 224}, + {0x8001, 226}, + {0xc016, 226}, + {0xc000, 153}, + {0xc000, 161}, + {0xc000, 167}, + {0xc000, 172}, + }, + /* 102 */ + { + {0x8002, 131}, + {0x8009, 131}, + {0x8017, 131}, + {0xc028, 131}, + {0x8002, 162}, + {0x8009, 162}, + {0x8017, 162}, + {0xc028, 162}, + {0x8002, 184}, + {0x8009, 184}, + {0x8017, 184}, + {0xc028, 184}, + {0x8002, 194}, + {0x8009, 194}, + {0x8017, 194}, + {0xc028, 194}, + }, + /* 103 */ + { + {0x8003, 131}, + {0x8006, 131}, + {0x800a, 131}, + {0x800f, 131}, + {0x8018, 131}, + {0x801f, 131}, + {0x8029, 131}, + {0xc038, 131}, + {0x8003, 162}, + {0x8006, 162}, + {0x800a, 162}, + {0x800f, 162}, + {0x8018, 162}, + {0x801f, 162}, + {0x8029, 162}, + {0xc038, 162}, + }, + /* 104 */ + { + {0x8003, 184}, + {0x8006, 184}, + {0x800a, 184}, + {0x800f, 184}, + {0x8018, 184}, + {0x801f, 184}, + {0x8029, 184}, + {0xc038, 184}, + {0x8003, 194}, + {0x8006, 194}, + {0x800a, 194}, + {0x800f, 194}, + {0x8018, 194}, + {0x801f, 194}, + {0x8029, 194}, + {0xc038, 194}, + }, + /* 105 */ + { + {0x8002, 224}, + {0x8009, 224}, + {0x8017, 224}, + {0xc028, 224}, + {0x8002, 226}, + {0x8009, 226}, + {0x8017, 226}, + {0xc028, 226}, + {0x8001, 153}, + {0xc016, 153}, + {0x8001, 161}, + {0xc016, 161}, + {0x8001, 167}, + {0xc016, 167}, + {0x8001, 172}, + {0xc016, 172}, + }, + /* 106 */ + { + {0x8003, 224}, + {0x8006, 224}, + {0x800a, 224}, + {0x800f, 224}, + {0x8018, 224}, + {0x801f, 224}, + {0x8029, 224}, + {0xc038, 224}, + {0x8003, 226}, + {0x8006, 226}, + {0x800a, 226}, + {0x800f, 226}, + {0x8018, 226}, + {0x801f, 226}, + {0x8029, 226}, + {0xc038, 226}, + }, + /* 107 */ + { + {0x8002, 153}, + {0x8009, 153}, + {0x8017, 153}, + {0xc028, 153}, + {0x8002, 161}, + {0x8009, 161}, + {0x8017, 161}, + {0xc028, 161}, + {0x8002, 167}, + {0x8009, 167}, + {0x8017, 167}, + {0xc028, 167}, + {0x8002, 172}, + {0x8009, 172}, + {0x8017, 172}, + {0xc028, 172}, + }, + /* 108 */ + { + {0x8003, 153}, + {0x8006, 153}, + {0x800a, 153}, + {0x800f, 153}, + {0x8018, 153}, + {0x801f, 153}, + {0x8029, 153}, + {0xc038, 153}, + {0x8003, 161}, + {0x8006, 161}, + {0x800a, 161}, + {0x800f, 161}, + {0x8018, 161}, + {0x801f, 161}, + {0x8029, 161}, + {0xc038, 161}, + }, + /* 109 */ + { + {0x8003, 167}, + {0x8006, 167}, + {0x800a, 167}, + {0x800f, 167}, + {0x8018, 167}, + {0x801f, 167}, + {0x8029, 167}, + {0xc038, 167}, + {0x8003, 172}, + {0x8006, 172}, + {0x800a, 172}, + {0x800f, 172}, + {0x8018, 172}, + {0x801f, 172}, + {0x8029, 172}, + {0xc038, 172}, + }, + /* 110 */ + { + {0x72, 0}, + {0x73, 0}, + {0x75, 0}, + {0x76, 0}, + {0x79, 0}, + {0x7b, 0}, + {0x7f, 0}, + {0x82, 0}, + {0x88, 0}, + {0x8b, 0}, + {0x8f, 0}, + {0x92, 0}, + {0x9b, 0}, + {0xa2, 0}, + {0xaa, 0}, + {0xb4, 0}, + }, + /* 111 */ + { + {0xc000, 176}, + {0xc000, 177}, + {0xc000, 179}, + {0xc000, 209}, + {0xc000, 216}, + {0xc000, 217}, + {0xc000, 227}, + {0xc000, 229}, + {0xc000, 230}, + {0x7a, 0}, + {0x7c, 0}, + {0x7d, 0}, + {0x80, 0}, + {0x81, 0}, + {0x83, 0}, + {0x84, 0}, + }, + /* 112 */ + { + {0x8001, 176}, + {0xc016, 176}, + {0x8001, 177}, + {0xc016, 177}, + {0x8001, 179}, + {0xc016, 179}, + {0x8001, 209}, + {0xc016, 209}, + {0x8001, 216}, + {0xc016, 216}, + {0x8001, 217}, + {0xc016, 217}, + {0x8001, 227}, + {0xc016, 227}, + {0x8001, 229}, + {0xc016, 229}, + }, + /* 113 */ + { + {0x8002, 176}, + {0x8009, 176}, + {0x8017, 176}, + {0xc028, 176}, + {0x8002, 177}, + {0x8009, 177}, + {0x8017, 177}, + {0xc028, 177}, + {0x8002, 179}, + {0x8009, 179}, + {0x8017, 179}, + {0xc028, 179}, + {0x8002, 209}, + {0x8009, 209}, + {0x8017, 209}, + {0xc028, 209}, + }, + /* 114 */ + { + {0x8003, 176}, + {0x8006, 176}, + {0x800a, 176}, + {0x800f, 176}, + {0x8018, 176}, + {0x801f, 176}, + {0x8029, 176}, + {0xc038, 176}, + {0x8003, 177}, + {0x8006, 177}, + {0x800a, 177}, + {0x800f, 177}, + {0x8018, 177}, + {0x801f, 177}, + {0x8029, 177}, + {0xc038, 177}, + }, + /* 115 */ + { + {0x8003, 179}, + {0x8006, 179}, + {0x800a, 179}, + {0x800f, 179}, + {0x8018, 179}, + {0x801f, 179}, + {0x8029, 179}, + {0xc038, 179}, + {0x8003, 209}, + {0x8006, 209}, + {0x800a, 209}, + {0x800f, 209}, + {0x8018, 209}, + {0x801f, 209}, + {0x8029, 209}, + {0xc038, 209}, + }, + /* 116 */ + { + {0x8002, 216}, + {0x8009, 216}, + {0x8017, 216}, + {0xc028, 216}, + {0x8002, 217}, + {0x8009, 217}, + {0x8017, 217}, + {0xc028, 217}, + {0x8002, 227}, + {0x8009, 227}, + {0x8017, 227}, + {0xc028, 227}, + {0x8002, 229}, + {0x8009, 229}, + {0x8017, 229}, + {0xc028, 229}, + }, + /* 117 */ + { + {0x8003, 216}, + {0x8006, 216}, + {0x800a, 216}, + {0x800f, 216}, + {0x8018, 216}, + {0x801f, 216}, + {0x8029, 216}, + {0xc038, 216}, + {0x8003, 217}, + {0x8006, 217}, + {0x800a, 217}, + {0x800f, 217}, + {0x8018, 217}, + {0x801f, 217}, + {0x8029, 217}, + {0xc038, 217}, + }, + /* 118 */ + { + {0x8003, 227}, + {0x8006, 227}, + {0x800a, 227}, + {0x800f, 227}, + {0x8018, 227}, + {0x801f, 227}, + {0x8029, 227}, + {0xc038, 227}, + {0x8003, 229}, + {0x8006, 229}, + {0x800a, 229}, + {0x800f, 229}, + {0x8018, 229}, + {0x801f, 229}, + {0x8029, 229}, + {0xc038, 229}, + }, + /* 119 */ + { + {0x8001, 230}, + {0xc016, 230}, + {0xc000, 129}, + {0xc000, 132}, + {0xc000, 133}, + {0xc000, 134}, + {0xc000, 136}, + {0xc000, 146}, + {0xc000, 154}, + {0xc000, 156}, + {0xc000, 160}, + {0xc000, 163}, + {0xc000, 164}, + {0xc000, 169}, + {0xc000, 170}, + {0xc000, 173}, + }, + /* 120 */ + { + {0x8002, 230}, + {0x8009, 230}, + {0x8017, 230}, + {0xc028, 230}, + {0x8001, 129}, + {0xc016, 129}, + {0x8001, 132}, + {0xc016, 132}, + {0x8001, 133}, + {0xc016, 133}, + {0x8001, 134}, + {0xc016, 134}, + {0x8001, 136}, + {0xc016, 136}, + {0x8001, 146}, + {0xc016, 146}, + }, + /* 121 */ + { + {0x8003, 230}, + {0x8006, 230}, + {0x800a, 230}, + {0x800f, 230}, + {0x8018, 230}, + {0x801f, 230}, + {0x8029, 230}, + {0xc038, 230}, + {0x8002, 129}, + {0x8009, 129}, + {0x8017, 129}, + {0xc028, 129}, + {0x8002, 132}, + {0x8009, 132}, + {0x8017, 132}, + {0xc028, 132}, + }, + /* 122 */ + { + {0x8003, 129}, + {0x8006, 129}, + {0x800a, 129}, + {0x800f, 129}, + {0x8018, 129}, + {0x801f, 129}, + {0x8029, 129}, + {0xc038, 129}, + {0x8003, 132}, + {0x8006, 132}, + {0x800a, 132}, + {0x800f, 132}, + {0x8018, 132}, + {0x801f, 132}, + {0x8029, 132}, + {0xc038, 132}, + }, + /* 123 */ + { + {0x8002, 133}, + {0x8009, 133}, + {0x8017, 133}, + {0xc028, 133}, + {0x8002, 134}, + {0x8009, 134}, + {0x8017, 134}, + {0xc028, 134}, + {0x8002, 136}, + {0x8009, 136}, + {0x8017, 136}, + {0xc028, 136}, + {0x8002, 146}, + {0x8009, 146}, + {0x8017, 146}, + {0xc028, 146}, + }, + /* 124 */ + { + {0x8003, 133}, + {0x8006, 133}, + {0x800a, 133}, + {0x800f, 133}, + {0x8018, 133}, + {0x801f, 133}, + {0x8029, 133}, + {0xc038, 133}, + {0x8003, 134}, + {0x8006, 134}, + {0x800a, 134}, + {0x800f, 134}, + {0x8018, 134}, + {0x801f, 134}, + {0x8029, 134}, + {0xc038, 134}, + }, + /* 125 */ + { + {0x8003, 136}, + {0x8006, 136}, + {0x800a, 136}, + {0x800f, 136}, + {0x8018, 136}, + {0x801f, 136}, + {0x8029, 136}, + {0xc038, 136}, + {0x8003, 146}, + {0x8006, 146}, + {0x800a, 146}, + {0x800f, 146}, + {0x8018, 146}, + {0x801f, 146}, + {0x8029, 146}, + {0xc038, 146}, + }, + /* 126 */ + { + {0x8001, 154}, + {0xc016, 154}, + {0x8001, 156}, + {0xc016, 156}, + {0x8001, 160}, + {0xc016, 160}, + {0x8001, 163}, + {0xc016, 163}, + {0x8001, 164}, + {0xc016, 164}, + {0x8001, 169}, + {0xc016, 169}, + {0x8001, 170}, + {0xc016, 170}, + {0x8001, 173}, + {0xc016, 173}, + }, + /* 127 */ + { + {0x8002, 154}, + {0x8009, 154}, + {0x8017, 154}, + {0xc028, 154}, + {0x8002, 156}, + {0x8009, 156}, + {0x8017, 156}, + {0xc028, 156}, + {0x8002, 160}, + {0x8009, 160}, + {0x8017, 160}, + {0xc028, 160}, + {0x8002, 163}, + {0x8009, 163}, + {0x8017, 163}, + {0xc028, 163}, + }, + /* 128 */ + { + {0x8003, 154}, + {0x8006, 154}, + {0x800a, 154}, + {0x800f, 154}, + {0x8018, 154}, + {0x801f, 154}, + {0x8029, 154}, + {0xc038, 154}, + {0x8003, 156}, + {0x8006, 156}, + {0x800a, 156}, + {0x800f, 156}, + {0x8018, 156}, + {0x801f, 156}, + {0x8029, 156}, + {0xc038, 156}, + }, + /* 129 */ + { + {0x8003, 160}, + {0x8006, 160}, + {0x800a, 160}, + {0x800f, 160}, + {0x8018, 160}, + {0x801f, 160}, + {0x8029, 160}, + {0xc038, 160}, + {0x8003, 163}, + {0x8006, 163}, + {0x800a, 163}, + {0x800f, 163}, + {0x8018, 163}, + {0x801f, 163}, + {0x8029, 163}, + {0xc038, 163}, + }, + /* 130 */ + { + {0x8002, 164}, + {0x8009, 164}, + {0x8017, 164}, + {0xc028, 164}, + {0x8002, 169}, + {0x8009, 169}, + {0x8017, 169}, + {0xc028, 169}, + {0x8002, 170}, + {0x8009, 170}, + {0x8017, 170}, + {0xc028, 170}, + {0x8002, 173}, + {0x8009, 173}, + {0x8017, 173}, + {0xc028, 173}, + }, + /* 131 */ + { + {0x8003, 164}, + {0x8006, 164}, + {0x800a, 164}, + {0x800f, 164}, + {0x8018, 164}, + {0x801f, 164}, + {0x8029, 164}, + {0xc038, 164}, + {0x8003, 169}, + {0x8006, 169}, + {0x800a, 169}, + {0x800f, 169}, + {0x8018, 169}, + {0x801f, 169}, + {0x8029, 169}, + {0xc038, 169}, + }, + /* 132 */ + { + {0x8003, 170}, + {0x8006, 170}, + {0x800a, 170}, + {0x800f, 170}, + {0x8018, 170}, + {0x801f, 170}, + {0x8029, 170}, + {0xc038, 170}, + {0x8003, 173}, + {0x8006, 173}, + {0x800a, 173}, + {0x800f, 173}, + {0x8018, 173}, + {0x801f, 173}, + {0x8029, 173}, + {0xc038, 173}, + }, + /* 133 */ + { + {0x89, 0}, + {0x8a, 0}, + {0x8c, 0}, + {0x8d, 0}, + {0x90, 0}, + {0x91, 0}, + {0x93, 0}, + {0x96, 0}, + {0x9c, 0}, + {0x9f, 0}, + {0xa3, 0}, + {0xa6, 0}, + {0xab, 0}, + {0xae, 0}, + {0xb5, 0}, + {0xbe, 0}, + }, + /* 134 */ + { + {0xc000, 178}, + {0xc000, 181}, + {0xc000, 185}, + {0xc000, 186}, + {0xc000, 187}, + {0xc000, 189}, + {0xc000, 190}, + {0xc000, 196}, + {0xc000, 198}, + {0xc000, 228}, + {0xc000, 232}, + {0xc000, 233}, + {0x94, 0}, + {0x95, 0}, + {0x97, 0}, + {0x98, 0}, + }, + /* 135 */ + { + {0x8001, 178}, + {0xc016, 178}, + {0x8001, 181}, + {0xc016, 181}, + {0x8001, 185}, + {0xc016, 185}, + {0x8001, 186}, + {0xc016, 186}, + {0x8001, 187}, + {0xc016, 187}, + {0x8001, 189}, + {0xc016, 189}, + {0x8001, 190}, + {0xc016, 190}, + {0x8001, 196}, + {0xc016, 196}, + }, + /* 136 */ + { + {0x8002, 178}, + {0x8009, 178}, + {0x8017, 178}, + {0xc028, 178}, + {0x8002, 181}, + {0x8009, 181}, + {0x8017, 181}, + {0xc028, 181}, + {0x8002, 185}, + {0x8009, 185}, + {0x8017, 185}, + {0xc028, 185}, + {0x8002, 186}, + {0x8009, 186}, + {0x8017, 186}, + {0xc028, 186}, + }, + /* 137 */ + { + {0x8003, 178}, + {0x8006, 178}, + {0x800a, 178}, + {0x800f, 178}, + {0x8018, 178}, + {0x801f, 178}, + {0x8029, 178}, + {0xc038, 178}, + {0x8003, 181}, + {0x8006, 181}, + {0x800a, 181}, + {0x800f, 181}, + {0x8018, 181}, + {0x801f, 181}, + {0x8029, 181}, + {0xc038, 181}, + }, + /* 138 */ + { + {0x8003, 185}, + {0x8006, 185}, + {0x800a, 185}, + {0x800f, 185}, + {0x8018, 185}, + {0x801f, 185}, + {0x8029, 185}, + {0xc038, 185}, + {0x8003, 186}, + {0x8006, 186}, + {0x800a, 186}, + {0x800f, 186}, + {0x8018, 186}, + {0x801f, 186}, + {0x8029, 186}, + {0xc038, 186}, + }, + /* 139 */ + { + {0x8002, 187}, + {0x8009, 187}, + {0x8017, 187}, + {0xc028, 187}, + {0x8002, 189}, + {0x8009, 189}, + {0x8017, 189}, + {0xc028, 189}, + {0x8002, 190}, + {0x8009, 190}, + {0x8017, 190}, + {0xc028, 190}, + {0x8002, 196}, + {0x8009, 196}, + {0x8017, 196}, + {0xc028, 196}, + }, + /* 140 */ + { + {0x8003, 187}, + {0x8006, 187}, + {0x800a, 187}, + {0x800f, 187}, + {0x8018, 187}, + {0x801f, 187}, + {0x8029, 187}, + {0xc038, 187}, + {0x8003, 189}, + {0x8006, 189}, + {0x800a, 189}, + {0x800f, 189}, + {0x8018, 189}, + {0x801f, 189}, + {0x8029, 189}, + {0xc038, 189}, + }, + /* 141 */ + { + {0x8003, 190}, + {0x8006, 190}, + {0x800a, 190}, + {0x800f, 190}, + {0x8018, 190}, + {0x801f, 190}, + {0x8029, 190}, + {0xc038, 190}, + {0x8003, 196}, + {0x8006, 196}, + {0x800a, 196}, + {0x800f, 196}, + {0x8018, 196}, + {0x801f, 196}, + {0x8029, 196}, + {0xc038, 196}, + }, + /* 142 */ + { + {0x8001, 198}, + {0xc016, 198}, + {0x8001, 228}, + {0xc016, 228}, + {0x8001, 232}, + {0xc016, 232}, + {0x8001, 233}, + {0xc016, 233}, + {0xc000, 1}, + {0xc000, 135}, + {0xc000, 137}, + {0xc000, 138}, + {0xc000, 139}, + {0xc000, 140}, + {0xc000, 141}, + {0xc000, 143}, + }, + /* 143 */ + { + {0x8002, 198}, + {0x8009, 198}, + {0x8017, 198}, + {0xc028, 198}, + {0x8002, 228}, + {0x8009, 228}, + {0x8017, 228}, + {0xc028, 228}, + {0x8002, 232}, + {0x8009, 232}, + {0x8017, 232}, + {0xc028, 232}, + {0x8002, 233}, + {0x8009, 233}, + {0x8017, 233}, + {0xc028, 233}, + }, + /* 144 */ + { + {0x8003, 198}, + {0x8006, 198}, + {0x800a, 198}, + {0x800f, 198}, + {0x8018, 198}, + {0x801f, 198}, + {0x8029, 198}, + {0xc038, 198}, + {0x8003, 228}, + {0x8006, 228}, + {0x800a, 228}, + {0x800f, 228}, + {0x8018, 228}, + {0x801f, 228}, + {0x8029, 228}, + {0xc038, 228}, + }, + /* 145 */ + { + {0x8003, 232}, + {0x8006, 232}, + {0x800a, 232}, + {0x800f, 232}, + {0x8018, 232}, + {0x801f, 232}, + {0x8029, 232}, + {0xc038, 232}, + {0x8003, 233}, + {0x8006, 233}, + {0x800a, 233}, + {0x800f, 233}, + {0x8018, 233}, + {0x801f, 233}, + {0x8029, 233}, + {0xc038, 233}, + }, + /* 146 */ + { + {0x8001, 1}, + {0xc016, 1}, + {0x8001, 135}, + {0xc016, 135}, + {0x8001, 137}, + {0xc016, 137}, + {0x8001, 138}, + {0xc016, 138}, + {0x8001, 139}, + {0xc016, 139}, + {0x8001, 140}, + {0xc016, 140}, + {0x8001, 141}, + {0xc016, 141}, + {0x8001, 143}, + {0xc016, 143}, + }, + /* 147 */ + { + {0x8002, 1}, + {0x8009, 1}, + {0x8017, 1}, + {0xc028, 1}, + {0x8002, 135}, + {0x8009, 135}, + {0x8017, 135}, + {0xc028, 135}, + {0x8002, 137}, + {0x8009, 137}, + {0x8017, 137}, + {0xc028, 137}, + {0x8002, 138}, + {0x8009, 138}, + {0x8017, 138}, + {0xc028, 138}, + }, + /* 148 */ + { + {0x8003, 1}, + {0x8006, 1}, + {0x800a, 1}, + {0x800f, 1}, + {0x8018, 1}, + {0x801f, 1}, + {0x8029, 1}, + {0xc038, 1}, + {0x8003, 135}, + {0x8006, 135}, + {0x800a, 135}, + {0x800f, 135}, + {0x8018, 135}, + {0x801f, 135}, + {0x8029, 135}, + {0xc038, 135}, + }, + /* 149 */ + { + {0x8003, 137}, + {0x8006, 137}, + {0x800a, 137}, + {0x800f, 137}, + {0x8018, 137}, + {0x801f, 137}, + {0x8029, 137}, + {0xc038, 137}, + {0x8003, 138}, + {0x8006, 138}, + {0x800a, 138}, + {0x800f, 138}, + {0x8018, 138}, + {0x801f, 138}, + {0x8029, 138}, + {0xc038, 138}, + }, + /* 150 */ + { + {0x8002, 139}, + {0x8009, 139}, + {0x8017, 139}, + {0xc028, 139}, + {0x8002, 140}, + {0x8009, 140}, + {0x8017, 140}, + {0xc028, 140}, + {0x8002, 141}, + {0x8009, 141}, + {0x8017, 141}, + {0xc028, 141}, + {0x8002, 143}, + {0x8009, 143}, + {0x8017, 143}, + {0xc028, 143}, + }, + /* 151 */ + { + {0x8003, 139}, + {0x8006, 139}, + {0x800a, 139}, + {0x800f, 139}, + {0x8018, 139}, + {0x801f, 139}, + {0x8029, 139}, + {0xc038, 139}, + {0x8003, 140}, + {0x8006, 140}, + {0x800a, 140}, + {0x800f, 140}, + {0x8018, 140}, + {0x801f, 140}, + {0x8029, 140}, + {0xc038, 140}, + }, + /* 152 */ + { + {0x8003, 141}, + {0x8006, 141}, + {0x800a, 141}, + {0x800f, 141}, + {0x8018, 141}, + {0x801f, 141}, + {0x8029, 141}, + {0xc038, 141}, + {0x8003, 143}, + {0x8006, 143}, + {0x800a, 143}, + {0x800f, 143}, + {0x8018, 143}, + {0x801f, 143}, + {0x8029, 143}, + {0xc038, 143}, + }, + /* 153 */ + { + {0x9d, 0}, + {0x9e, 0}, + {0xa0, 0}, + {0xa1, 0}, + {0xa4, 0}, + {0xa5, 0}, + {0xa7, 0}, + {0xa8, 0}, + {0xac, 0}, + {0xad, 0}, + {0xaf, 0}, + {0xb1, 0}, + {0xb6, 0}, + {0xb9, 0}, + {0xbf, 0}, + {0xcf, 0}, + }, + /* 154 */ + { + {0xc000, 147}, + {0xc000, 149}, + {0xc000, 150}, + {0xc000, 151}, + {0xc000, 152}, + {0xc000, 155}, + {0xc000, 157}, + {0xc000, 158}, + {0xc000, 165}, + {0xc000, 166}, + {0xc000, 168}, + {0xc000, 174}, + {0xc000, 175}, + {0xc000, 180}, + {0xc000, 182}, + {0xc000, 183}, + }, + /* 155 */ + { + {0x8001, 147}, + {0xc016, 147}, + {0x8001, 149}, + {0xc016, 149}, + {0x8001, 150}, + {0xc016, 150}, + {0x8001, 151}, + {0xc016, 151}, + {0x8001, 152}, + {0xc016, 152}, + {0x8001, 155}, + {0xc016, 155}, + {0x8001, 157}, + {0xc016, 157}, + {0x8001, 158}, + {0xc016, 158}, + }, + /* 156 */ + { + {0x8002, 147}, + {0x8009, 147}, + {0x8017, 147}, + {0xc028, 147}, + {0x8002, 149}, + {0x8009, 149}, + {0x8017, 149}, + {0xc028, 149}, + {0x8002, 150}, + {0x8009, 150}, + {0x8017, 150}, + {0xc028, 150}, + {0x8002, 151}, + {0x8009, 151}, + {0x8017, 151}, + {0xc028, 151}, + }, + /* 157 */ + { + {0x8003, 147}, + {0x8006, 147}, + {0x800a, 147}, + {0x800f, 147}, + {0x8018, 147}, + {0x801f, 147}, + {0x8029, 147}, + {0xc038, 147}, + {0x8003, 149}, + {0x8006, 149}, + {0x800a, 149}, + {0x800f, 149}, + {0x8018, 149}, + {0x801f, 149}, + {0x8029, 149}, + {0xc038, 149}, + }, + /* 158 */ + { + {0x8003, 150}, + {0x8006, 150}, + {0x800a, 150}, + {0x800f, 150}, + {0x8018, 150}, + {0x801f, 150}, + {0x8029, 150}, + {0xc038, 150}, + {0x8003, 151}, + {0x8006, 151}, + {0x800a, 151}, + {0x800f, 151}, + {0x8018, 151}, + {0x801f, 151}, + {0x8029, 151}, + {0xc038, 151}, + }, + /* 159 */ + { + {0x8002, 152}, + {0x8009, 152}, + {0x8017, 152}, + {0xc028, 152}, + {0x8002, 155}, + {0x8009, 155}, + {0x8017, 155}, + {0xc028, 155}, + {0x8002, 157}, + {0x8009, 157}, + {0x8017, 157}, + {0xc028, 157}, + {0x8002, 158}, + {0x8009, 158}, + {0x8017, 158}, + {0xc028, 158}, + }, + /* 160 */ + { + {0x8003, 152}, + {0x8006, 152}, + {0x800a, 152}, + {0x800f, 152}, + {0x8018, 152}, + {0x801f, 152}, + {0x8029, 152}, + {0xc038, 152}, + {0x8003, 155}, + {0x8006, 155}, + {0x800a, 155}, + {0x800f, 155}, + {0x8018, 155}, + {0x801f, 155}, + {0x8029, 155}, + {0xc038, 155}, + }, + /* 161 */ + { + {0x8003, 157}, + {0x8006, 157}, + {0x800a, 157}, + {0x800f, 157}, + {0x8018, 157}, + {0x801f, 157}, + {0x8029, 157}, + {0xc038, 157}, + {0x8003, 158}, + {0x8006, 158}, + {0x800a, 158}, + {0x800f, 158}, + {0x8018, 158}, + {0x801f, 158}, + {0x8029, 158}, + {0xc038, 158}, + }, + /* 162 */ + { + {0x8001, 165}, + {0xc016, 165}, + {0x8001, 166}, + {0xc016, 166}, + {0x8001, 168}, + {0xc016, 168}, + {0x8001, 174}, + {0xc016, 174}, + {0x8001, 175}, + {0xc016, 175}, + {0x8001, 180}, + {0xc016, 180}, + {0x8001, 182}, + {0xc016, 182}, + {0x8001, 183}, + {0xc016, 183}, + }, + /* 163 */ + { + {0x8002, 165}, + {0x8009, 165}, + {0x8017, 165}, + {0xc028, 165}, + {0x8002, 166}, + {0x8009, 166}, + {0x8017, 166}, + {0xc028, 166}, + {0x8002, 168}, + {0x8009, 168}, + {0x8017, 168}, + {0xc028, 168}, + {0x8002, 174}, + {0x8009, 174}, + {0x8017, 174}, + {0xc028, 174}, + }, + /* 164 */ + { + {0x8003, 165}, + {0x8006, 165}, + {0x800a, 165}, + {0x800f, 165}, + {0x8018, 165}, + {0x801f, 165}, + {0x8029, 165}, + {0xc038, 165}, + {0x8003, 166}, + {0x8006, 166}, + {0x800a, 166}, + {0x800f, 166}, + {0x8018, 166}, + {0x801f, 166}, + {0x8029, 166}, + {0xc038, 166}, + }, + /* 165 */ + { + {0x8003, 168}, + {0x8006, 168}, + {0x800a, 168}, + {0x800f, 168}, + {0x8018, 168}, + {0x801f, 168}, + {0x8029, 168}, + {0xc038, 168}, + {0x8003, 174}, + {0x8006, 174}, + {0x800a, 174}, + {0x800f, 174}, + {0x8018, 174}, + {0x801f, 174}, + {0x8029, 174}, + {0xc038, 174}, + }, + /* 166 */ + { + {0x8002, 175}, + {0x8009, 175}, + {0x8017, 175}, + {0xc028, 175}, + {0x8002, 180}, + {0x8009, 180}, + {0x8017, 180}, + {0xc028, 180}, + {0x8002, 182}, + {0x8009, 182}, + {0x8017, 182}, + {0xc028, 182}, + {0x8002, 183}, + {0x8009, 183}, + {0x8017, 183}, + {0xc028, 183}, + }, + /* 167 */ + { + {0x8003, 175}, + {0x8006, 175}, + {0x800a, 175}, + {0x800f, 175}, + {0x8018, 175}, + {0x801f, 175}, + {0x8029, 175}, + {0xc038, 175}, + {0x8003, 180}, + {0x8006, 180}, + {0x800a, 180}, + {0x800f, 180}, + {0x8018, 180}, + {0x801f, 180}, + {0x8029, 180}, + {0xc038, 180}, + }, + /* 168 */ + { + {0x8003, 182}, + {0x8006, 182}, + {0x800a, 182}, + {0x800f, 182}, + {0x8018, 182}, + {0x801f, 182}, + {0x8029, 182}, + {0xc038, 182}, + {0x8003, 183}, + {0x8006, 183}, + {0x800a, 183}, + {0x800f, 183}, + {0x8018, 183}, + {0x801f, 183}, + {0x8029, 183}, + {0xc038, 183}, + }, + /* 169 */ + { + {0xc000, 188}, + {0xc000, 191}, + {0xc000, 197}, + {0xc000, 231}, + {0xc000, 239}, + {0xb0, 0}, + {0xb2, 0}, + {0xb3, 0}, + {0xb7, 0}, + {0xb8, 0}, + {0xba, 0}, + {0xbb, 0}, + {0xc0, 0}, + {0xc7, 0}, + {0xd0, 0}, + {0xdf, 0}, + }, + /* 170 */ + { + {0x8001, 188}, + {0xc016, 188}, + {0x8001, 191}, + {0xc016, 191}, + {0x8001, 197}, + {0xc016, 197}, + {0x8001, 231}, + {0xc016, 231}, + {0x8001, 239}, + {0xc016, 239}, + {0xc000, 9}, + {0xc000, 142}, + {0xc000, 144}, + {0xc000, 145}, + {0xc000, 148}, + {0xc000, 159}, + }, + /* 171 */ + { + {0x8002, 188}, + {0x8009, 188}, + {0x8017, 188}, + {0xc028, 188}, + {0x8002, 191}, + {0x8009, 191}, + {0x8017, 191}, + {0xc028, 191}, + {0x8002, 197}, + {0x8009, 197}, + {0x8017, 197}, + {0xc028, 197}, + {0x8002, 231}, + {0x8009, 231}, + {0x8017, 231}, + {0xc028, 231}, + }, + /* 172 */ + { + {0x8003, 188}, + {0x8006, 188}, + {0x800a, 188}, + {0x800f, 188}, + {0x8018, 188}, + {0x801f, 188}, + {0x8029, 188}, + {0xc038, 188}, + {0x8003, 191}, + {0x8006, 191}, + {0x800a, 191}, + {0x800f, 191}, + {0x8018, 191}, + {0x801f, 191}, + {0x8029, 191}, + {0xc038, 191}, + }, + /* 173 */ + { + {0x8003, 197}, + {0x8006, 197}, + {0x800a, 197}, + {0x800f, 197}, + {0x8018, 197}, + {0x801f, 197}, + {0x8029, 197}, + {0xc038, 197}, + {0x8003, 231}, + {0x8006, 231}, + {0x800a, 231}, + {0x800f, 231}, + {0x8018, 231}, + {0x801f, 231}, + {0x8029, 231}, + {0xc038, 231}, + }, + /* 174 */ + { + {0x8002, 239}, + {0x8009, 239}, + {0x8017, 239}, + {0xc028, 239}, + {0x8001, 9}, + {0xc016, 9}, + {0x8001, 142}, + {0xc016, 142}, + {0x8001, 144}, + {0xc016, 144}, + {0x8001, 145}, + {0xc016, 145}, + {0x8001, 148}, + {0xc016, 148}, + {0x8001, 159}, + {0xc016, 159}, + }, + /* 175 */ + { + {0x8003, 239}, + {0x8006, 239}, + {0x800a, 239}, + {0x800f, 239}, + {0x8018, 239}, + {0x801f, 239}, + {0x8029, 239}, + {0xc038, 239}, + {0x8002, 9}, + {0x8009, 9}, + {0x8017, 9}, + {0xc028, 9}, + {0x8002, 142}, + {0x8009, 142}, + {0x8017, 142}, + {0xc028, 142}, + }, + /* 176 */ + { + {0x8003, 9}, + {0x8006, 9}, + {0x800a, 9}, + {0x800f, 9}, + {0x8018, 9}, + {0x801f, 9}, + {0x8029, 9}, + {0xc038, 9}, + {0x8003, 142}, + {0x8006, 142}, + {0x800a, 142}, + {0x800f, 142}, + {0x8018, 142}, + {0x801f, 142}, + {0x8029, 142}, + {0xc038, 142}, + }, + /* 177 */ + { + {0x8002, 144}, + {0x8009, 144}, + {0x8017, 144}, + {0xc028, 144}, + {0x8002, 145}, + {0x8009, 145}, + {0x8017, 145}, + {0xc028, 145}, + {0x8002, 148}, + {0x8009, 148}, + {0x8017, 148}, + {0xc028, 148}, + {0x8002, 159}, + {0x8009, 159}, + {0x8017, 159}, + {0xc028, 159}, + }, + /* 178 */ + { + {0x8003, 144}, + {0x8006, 144}, + {0x800a, 144}, + {0x800f, 144}, + {0x8018, 144}, + {0x801f, 144}, + {0x8029, 144}, + {0xc038, 144}, + {0x8003, 145}, + {0x8006, 145}, + {0x800a, 145}, + {0x800f, 145}, + {0x8018, 145}, + {0x801f, 145}, + {0x8029, 145}, + {0xc038, 145}, + }, + /* 179 */ + { + {0x8003, 148}, + {0x8006, 148}, + {0x800a, 148}, + {0x800f, 148}, + {0x8018, 148}, + {0x801f, 148}, + {0x8029, 148}, + {0xc038, 148}, + {0x8003, 159}, + {0x8006, 159}, + {0x800a, 159}, + {0x800f, 159}, + {0x8018, 159}, + {0x801f, 159}, + {0x8029, 159}, + {0xc038, 159}, + }, + /* 180 */ + { + {0xc000, 171}, + {0xc000, 206}, + {0xc000, 215}, + {0xc000, 225}, + {0xc000, 236}, + {0xc000, 237}, + {0xbc, 0}, + {0xbd, 0}, + {0xc1, 0}, + {0xc4, 0}, + {0xc8, 0}, + {0xcb, 0}, + {0xd1, 0}, + {0xd8, 0}, + {0xe0, 0}, + {0xee, 0}, + }, + /* 181 */ + { + {0x8001, 171}, + {0xc016, 171}, + {0x8001, 206}, + {0xc016, 206}, + {0x8001, 215}, + {0xc016, 215}, + {0x8001, 225}, + {0xc016, 225}, + {0x8001, 236}, + {0xc016, 236}, + {0x8001, 237}, + {0xc016, 237}, + {0xc000, 199}, + {0xc000, 207}, + {0xc000, 234}, + {0xc000, 235}, + }, + /* 182 */ + { + {0x8002, 171}, + {0x8009, 171}, + {0x8017, 171}, + {0xc028, 171}, + {0x8002, 206}, + {0x8009, 206}, + {0x8017, 206}, + {0xc028, 206}, + {0x8002, 215}, + {0x8009, 215}, + {0x8017, 215}, + {0xc028, 215}, + {0x8002, 225}, + {0x8009, 225}, + {0x8017, 225}, + {0xc028, 225}, + }, + /* 183 */ + { + {0x8003, 171}, + {0x8006, 171}, + {0x800a, 171}, + {0x800f, 171}, + {0x8018, 171}, + {0x801f, 171}, + {0x8029, 171}, + {0xc038, 171}, + {0x8003, 206}, + {0x8006, 206}, + {0x800a, 206}, + {0x800f, 206}, + {0x8018, 206}, + {0x801f, 206}, + {0x8029, 206}, + {0xc038, 206}, + }, + /* 184 */ + { + {0x8003, 215}, + {0x8006, 215}, + {0x800a, 215}, + {0x800f, 215}, + {0x8018, 215}, + {0x801f, 215}, + {0x8029, 215}, + {0xc038, 215}, + {0x8003, 225}, + {0x8006, 225}, + {0x800a, 225}, + {0x800f, 225}, + {0x8018, 225}, + {0x801f, 225}, + {0x8029, 225}, + {0xc038, 225}, + }, + /* 185 */ + { + {0x8002, 236}, + {0x8009, 236}, + {0x8017, 236}, + {0xc028, 236}, + {0x8002, 237}, + {0x8009, 237}, + {0x8017, 237}, + {0xc028, 237}, + {0x8001, 199}, + {0xc016, 199}, + {0x8001, 207}, + {0xc016, 207}, + {0x8001, 234}, + {0xc016, 234}, + {0x8001, 235}, + {0xc016, 235}, + }, + /* 186 */ + { + {0x8003, 236}, + {0x8006, 236}, + {0x800a, 236}, + {0x800f, 236}, + {0x8018, 236}, + {0x801f, 236}, + {0x8029, 236}, + {0xc038, 236}, + {0x8003, 237}, + {0x8006, 237}, + {0x800a, 237}, + {0x800f, 237}, + {0x8018, 237}, + {0x801f, 237}, + {0x8029, 237}, + {0xc038, 237}, + }, + /* 187 */ + { + {0x8002, 199}, + {0x8009, 199}, + {0x8017, 199}, + {0xc028, 199}, + {0x8002, 207}, + {0x8009, 207}, + {0x8017, 207}, + {0xc028, 207}, + {0x8002, 234}, + {0x8009, 234}, + {0x8017, 234}, + {0xc028, 234}, + {0x8002, 235}, + {0x8009, 235}, + {0x8017, 235}, + {0xc028, 235}, + }, + /* 188 */ + { + {0x8003, 199}, + {0x8006, 199}, + {0x800a, 199}, + {0x800f, 199}, + {0x8018, 199}, + {0x801f, 199}, + {0x8029, 199}, + {0xc038, 199}, + {0x8003, 207}, + {0x8006, 207}, + {0x800a, 207}, + {0x800f, 207}, + {0x8018, 207}, + {0x801f, 207}, + {0x8029, 207}, + {0xc038, 207}, + }, + /* 189 */ + { + {0x8003, 234}, + {0x8006, 234}, + {0x800a, 234}, + {0x800f, 234}, + {0x8018, 234}, + {0x801f, 234}, + {0x8029, 234}, + {0xc038, 234}, + {0x8003, 235}, + {0x8006, 235}, + {0x800a, 235}, + {0x800f, 235}, + {0x8018, 235}, + {0x801f, 235}, + {0x8029, 235}, + {0xc038, 235}, + }, + /* 190 */ + { + {0xc2, 0}, + {0xc3, 0}, + {0xc5, 0}, + {0xc6, 0}, + {0xc9, 0}, + {0xca, 0}, + {0xcc, 0}, + {0xcd, 0}, + {0xd2, 0}, + {0xd5, 0}, + {0xd9, 0}, + {0xdc, 0}, + {0xe1, 0}, + {0xe7, 0}, + {0xef, 0}, + {0xf6, 0}, + }, + /* 191 */ + { + {0xc000, 192}, + {0xc000, 193}, + {0xc000, 200}, + {0xc000, 201}, + {0xc000, 202}, + {0xc000, 205}, + {0xc000, 210}, + {0xc000, 213}, + {0xc000, 218}, + {0xc000, 219}, + {0xc000, 238}, + {0xc000, 240}, + {0xc000, 242}, + {0xc000, 243}, + {0xc000, 255}, + {0xce, 0}, + }, + /* 192 */ + { + {0x8001, 192}, + {0xc016, 192}, + {0x8001, 193}, + {0xc016, 193}, + {0x8001, 200}, + {0xc016, 200}, + {0x8001, 201}, + {0xc016, 201}, + {0x8001, 202}, + {0xc016, 202}, + {0x8001, 205}, + {0xc016, 205}, + {0x8001, 210}, + {0xc016, 210}, + {0x8001, 213}, + {0xc016, 213}, + }, + /* 193 */ + { + {0x8002, 192}, + {0x8009, 192}, + {0x8017, 192}, + {0xc028, 192}, + {0x8002, 193}, + {0x8009, 193}, + {0x8017, 193}, + {0xc028, 193}, + {0x8002, 200}, + {0x8009, 200}, + {0x8017, 200}, + {0xc028, 200}, + {0x8002, 201}, + {0x8009, 201}, + {0x8017, 201}, + {0xc028, 201}, + }, + /* 194 */ + { + {0x8003, 192}, + {0x8006, 192}, + {0x800a, 192}, + {0x800f, 192}, + {0x8018, 192}, + {0x801f, 192}, + {0x8029, 192}, + {0xc038, 192}, + {0x8003, 193}, + {0x8006, 193}, + {0x800a, 193}, + {0x800f, 193}, + {0x8018, 193}, + {0x801f, 193}, + {0x8029, 193}, + {0xc038, 193}, + }, + /* 195 */ + { + {0x8003, 200}, + {0x8006, 200}, + {0x800a, 200}, + {0x800f, 200}, + {0x8018, 200}, + {0x801f, 200}, + {0x8029, 200}, + {0xc038, 200}, + {0x8003, 201}, + {0x8006, 201}, + {0x800a, 201}, + {0x800f, 201}, + {0x8018, 201}, + {0x801f, 201}, + {0x8029, 201}, + {0xc038, 201}, + }, + /* 196 */ + { + {0x8002, 202}, + {0x8009, 202}, + {0x8017, 202}, + {0xc028, 202}, + {0x8002, 205}, + {0x8009, 205}, + {0x8017, 205}, + {0xc028, 205}, + {0x8002, 210}, + {0x8009, 210}, + {0x8017, 210}, + {0xc028, 210}, + {0x8002, 213}, + {0x8009, 213}, + {0x8017, 213}, + {0xc028, 213}, + }, + /* 197 */ + { + {0x8003, 202}, + {0x8006, 202}, + {0x800a, 202}, + {0x800f, 202}, + {0x8018, 202}, + {0x801f, 202}, + {0x8029, 202}, + {0xc038, 202}, + {0x8003, 205}, + {0x8006, 205}, + {0x800a, 205}, + {0x800f, 205}, + {0x8018, 205}, + {0x801f, 205}, + {0x8029, 205}, + {0xc038, 205}, + }, + /* 198 */ + { + {0x8003, 210}, + {0x8006, 210}, + {0x800a, 210}, + {0x800f, 210}, + {0x8018, 210}, + {0x801f, 210}, + {0x8029, 210}, + {0xc038, 210}, + {0x8003, 213}, + {0x8006, 213}, + {0x800a, 213}, + {0x800f, 213}, + {0x8018, 213}, + {0x801f, 213}, + {0x8029, 213}, + {0xc038, 213}, + }, + /* 199 */ + { + {0x8001, 218}, + {0xc016, 218}, + {0x8001, 219}, + {0xc016, 219}, + {0x8001, 238}, + {0xc016, 238}, + {0x8001, 240}, + {0xc016, 240}, + {0x8001, 242}, + {0xc016, 242}, + {0x8001, 243}, + {0xc016, 243}, + {0x8001, 255}, + {0xc016, 255}, + {0xc000, 203}, + {0xc000, 204}, + }, + /* 200 */ + { + {0x8002, 218}, + {0x8009, 218}, + {0x8017, 218}, + {0xc028, 218}, + {0x8002, 219}, + {0x8009, 219}, + {0x8017, 219}, + {0xc028, 219}, + {0x8002, 238}, + {0x8009, 238}, + {0x8017, 238}, + {0xc028, 238}, + {0x8002, 240}, + {0x8009, 240}, + {0x8017, 240}, + {0xc028, 240}, + }, + /* 201 */ + { + {0x8003, 218}, + {0x8006, 218}, + {0x800a, 218}, + {0x800f, 218}, + {0x8018, 218}, + {0x801f, 218}, + {0x8029, 218}, + {0xc038, 218}, + {0x8003, 219}, + {0x8006, 219}, + {0x800a, 219}, + {0x800f, 219}, + {0x8018, 219}, + {0x801f, 219}, + {0x8029, 219}, + {0xc038, 219}, + }, + /* 202 */ + { + {0x8003, 238}, + {0x8006, 238}, + {0x800a, 238}, + {0x800f, 238}, + {0x8018, 238}, + {0x801f, 238}, + {0x8029, 238}, + {0xc038, 238}, + {0x8003, 240}, + {0x8006, 240}, + {0x800a, 240}, + {0x800f, 240}, + {0x8018, 240}, + {0x801f, 240}, + {0x8029, 240}, + {0xc038, 240}, + }, + /* 203 */ + { + {0x8002, 242}, + {0x8009, 242}, + {0x8017, 242}, + {0xc028, 242}, + {0x8002, 243}, + {0x8009, 243}, + {0x8017, 243}, + {0xc028, 243}, + {0x8002, 255}, + {0x8009, 255}, + {0x8017, 255}, + {0xc028, 255}, + {0x8001, 203}, + {0xc016, 203}, + {0x8001, 204}, + {0xc016, 204}, + }, + /* 204 */ + { + {0x8003, 242}, + {0x8006, 242}, + {0x800a, 242}, + {0x800f, 242}, + {0x8018, 242}, + {0x801f, 242}, + {0x8029, 242}, + {0xc038, 242}, + {0x8003, 243}, + {0x8006, 243}, + {0x800a, 243}, + {0x800f, 243}, + {0x8018, 243}, + {0x801f, 243}, + {0x8029, 243}, + {0xc038, 243}, + }, + /* 205 */ + { + {0x8003, 255}, + {0x8006, 255}, + {0x800a, 255}, + {0x800f, 255}, + {0x8018, 255}, + {0x801f, 255}, + {0x8029, 255}, + {0xc038, 255}, + {0x8002, 203}, + {0x8009, 203}, + {0x8017, 203}, + {0xc028, 203}, + {0x8002, 204}, + {0x8009, 204}, + {0x8017, 204}, + {0xc028, 204}, + }, + /* 206 */ + { + {0x8003, 203}, + {0x8006, 203}, + {0x800a, 203}, + {0x800f, 203}, + {0x8018, 203}, + {0x801f, 203}, + {0x8029, 203}, + {0xc038, 203}, + {0x8003, 204}, + {0x8006, 204}, + {0x800a, 204}, + {0x800f, 204}, + {0x8018, 204}, + {0x801f, 204}, + {0x8029, 204}, + {0xc038, 204}, + }, + /* 207 */ + { + {0xd3, 0}, + {0xd4, 0}, + {0xd6, 0}, + {0xd7, 0}, + {0xda, 0}, + {0xdb, 0}, + {0xdd, 0}, + {0xde, 0}, + {0xe2, 0}, + {0xe4, 0}, + {0xe8, 0}, + {0xeb, 0}, + {0xf0, 0}, + {0xf3, 0}, + {0xf7, 0}, + {0xfa, 0}, + }, + /* 208 */ + { + {0xc000, 211}, + {0xc000, 212}, + {0xc000, 214}, + {0xc000, 221}, + {0xc000, 222}, + {0xc000, 223}, + {0xc000, 241}, + {0xc000, 244}, + {0xc000, 245}, + {0xc000, 246}, + {0xc000, 247}, + {0xc000, 248}, + {0xc000, 250}, + {0xc000, 251}, + {0xc000, 252}, + {0xc000, 253}, + }, + /* 209 */ + { + {0x8001, 211}, + {0xc016, 211}, + {0x8001, 212}, + {0xc016, 212}, + {0x8001, 214}, + {0xc016, 214}, + {0x8001, 221}, + {0xc016, 221}, + {0x8001, 222}, + {0xc016, 222}, + {0x8001, 223}, + {0xc016, 223}, + {0x8001, 241}, + {0xc016, 241}, + {0x8001, 244}, + {0xc016, 244}, + }, + /* 210 */ + { + {0x8002, 211}, + {0x8009, 211}, + {0x8017, 211}, + {0xc028, 211}, + {0x8002, 212}, + {0x8009, 212}, + {0x8017, 212}, + {0xc028, 212}, + {0x8002, 214}, + {0x8009, 214}, + {0x8017, 214}, + {0xc028, 214}, + {0x8002, 221}, + {0x8009, 221}, + {0x8017, 221}, + {0xc028, 221}, + }, + /* 211 */ + { + {0x8003, 211}, + {0x8006, 211}, + {0x800a, 211}, + {0x800f, 211}, + {0x8018, 211}, + {0x801f, 211}, + {0x8029, 211}, + {0xc038, 211}, + {0x8003, 212}, + {0x8006, 212}, + {0x800a, 212}, + {0x800f, 212}, + {0x8018, 212}, + {0x801f, 212}, + {0x8029, 212}, + {0xc038, 212}, + }, + /* 212 */ + { + {0x8003, 214}, + {0x8006, 214}, + {0x800a, 214}, + {0x800f, 214}, + {0x8018, 214}, + {0x801f, 214}, + {0x8029, 214}, + {0xc038, 214}, + {0x8003, 221}, + {0x8006, 221}, + {0x800a, 221}, + {0x800f, 221}, + {0x8018, 221}, + {0x801f, 221}, + {0x8029, 221}, + {0xc038, 221}, + }, + /* 213 */ + { + {0x8002, 222}, + {0x8009, 222}, + {0x8017, 222}, + {0xc028, 222}, + {0x8002, 223}, + {0x8009, 223}, + {0x8017, 223}, + {0xc028, 223}, + {0x8002, 241}, + {0x8009, 241}, + {0x8017, 241}, + {0xc028, 241}, + {0x8002, 244}, + {0x8009, 244}, + {0x8017, 244}, + {0xc028, 244}, + }, + /* 214 */ + { + {0x8003, 222}, + {0x8006, 222}, + {0x800a, 222}, + {0x800f, 222}, + {0x8018, 222}, + {0x801f, 222}, + {0x8029, 222}, + {0xc038, 222}, + {0x8003, 223}, + {0x8006, 223}, + {0x800a, 223}, + {0x800f, 223}, + {0x8018, 223}, + {0x801f, 223}, + {0x8029, 223}, + {0xc038, 223}, + }, + /* 215 */ + { + {0x8003, 241}, + {0x8006, 241}, + {0x800a, 241}, + {0x800f, 241}, + {0x8018, 241}, + {0x801f, 241}, + {0x8029, 241}, + {0xc038, 241}, + {0x8003, 244}, + {0x8006, 244}, + {0x800a, 244}, + {0x800f, 244}, + {0x8018, 244}, + {0x801f, 244}, + {0x8029, 244}, + {0xc038, 244}, + }, + /* 216 */ + { + {0x8001, 245}, + {0xc016, 245}, + {0x8001, 246}, + {0xc016, 246}, + {0x8001, 247}, + {0xc016, 247}, + {0x8001, 248}, + {0xc016, 248}, + {0x8001, 250}, + {0xc016, 250}, + {0x8001, 251}, + {0xc016, 251}, + {0x8001, 252}, + {0xc016, 252}, + {0x8001, 253}, + {0xc016, 253}, + }, + /* 217 */ + { + {0x8002, 245}, + {0x8009, 245}, + {0x8017, 245}, + {0xc028, 245}, + {0x8002, 246}, + {0x8009, 246}, + {0x8017, 246}, + {0xc028, 246}, + {0x8002, 247}, + {0x8009, 247}, + {0x8017, 247}, + {0xc028, 247}, + {0x8002, 248}, + {0x8009, 248}, + {0x8017, 248}, + {0xc028, 248}, + }, + /* 218 */ + { + {0x8003, 245}, + {0x8006, 245}, + {0x800a, 245}, + {0x800f, 245}, + {0x8018, 245}, + {0x801f, 245}, + {0x8029, 245}, + {0xc038, 245}, + {0x8003, 246}, + {0x8006, 246}, + {0x800a, 246}, + {0x800f, 246}, + {0x8018, 246}, + {0x801f, 246}, + {0x8029, 246}, + {0xc038, 246}, + }, + /* 219 */ + { + {0x8003, 247}, + {0x8006, 247}, + {0x800a, 247}, + {0x800f, 247}, + {0x8018, 247}, + {0x801f, 247}, + {0x8029, 247}, + {0xc038, 247}, + {0x8003, 248}, + {0x8006, 248}, + {0x800a, 248}, + {0x800f, 248}, + {0x8018, 248}, + {0x801f, 248}, + {0x8029, 248}, + {0xc038, 248}, + }, + /* 220 */ + { + {0x8002, 250}, + {0x8009, 250}, + {0x8017, 250}, + {0xc028, 250}, + {0x8002, 251}, + {0x8009, 251}, + {0x8017, 251}, + {0xc028, 251}, + {0x8002, 252}, + {0x8009, 252}, + {0x8017, 252}, + {0xc028, 252}, + {0x8002, 253}, + {0x8009, 253}, + {0x8017, 253}, + {0xc028, 253}, + }, + /* 221 */ + { + {0x8003, 250}, + {0x8006, 250}, + {0x800a, 250}, + {0x800f, 250}, + {0x8018, 250}, + {0x801f, 250}, + {0x8029, 250}, + {0xc038, 250}, + {0x8003, 251}, + {0x8006, 251}, + {0x800a, 251}, + {0x800f, 251}, + {0x8018, 251}, + {0x801f, 251}, + {0x8029, 251}, + {0xc038, 251}, + }, + /* 222 */ + { + {0x8003, 252}, + {0x8006, 252}, + {0x800a, 252}, + {0x800f, 252}, + {0x8018, 252}, + {0x801f, 252}, + {0x8029, 252}, + {0xc038, 252}, + {0x8003, 253}, + {0x8006, 253}, + {0x800a, 253}, + {0x800f, 253}, + {0x8018, 253}, + {0x801f, 253}, + {0x8029, 253}, + {0xc038, 253}, + }, + /* 223 */ + { + {0xc000, 254}, + {0xe3, 0}, + {0xe5, 0}, + {0xe6, 0}, + {0xe9, 0}, + {0xea, 0}, + {0xec, 0}, + {0xed, 0}, + {0xf1, 0}, + {0xf2, 0}, + {0xf4, 0}, + {0xf5, 0}, + {0xf8, 0}, + {0xf9, 0}, + {0xfb, 0}, + {0xfc, 0}, + }, + /* 224 */ + { + {0x8001, 254}, + {0xc016, 254}, + {0xc000, 2}, + {0xc000, 3}, + {0xc000, 4}, + {0xc000, 5}, + {0xc000, 6}, + {0xc000, 7}, + {0xc000, 8}, + {0xc000, 11}, + {0xc000, 12}, + {0xc000, 14}, + {0xc000, 15}, + {0xc000, 16}, + {0xc000, 17}, + {0xc000, 18}, + }, + /* 225 */ + { + {0x8002, 254}, + {0x8009, 254}, + {0x8017, 254}, + {0xc028, 254}, + {0x8001, 2}, + {0xc016, 2}, + {0x8001, 3}, + {0xc016, 3}, + {0x8001, 4}, + {0xc016, 4}, + {0x8001, 5}, + {0xc016, 5}, + {0x8001, 6}, + {0xc016, 6}, + {0x8001, 7}, + {0xc016, 7}, + }, + /* 226 */ + { + {0x8003, 254}, + {0x8006, 254}, + {0x800a, 254}, + {0x800f, 254}, + {0x8018, 254}, + {0x801f, 254}, + {0x8029, 254}, + {0xc038, 254}, + {0x8002, 2}, + {0x8009, 2}, + {0x8017, 2}, + {0xc028, 2}, + {0x8002, 3}, + {0x8009, 3}, + {0x8017, 3}, + {0xc028, 3}, + }, + /* 227 */ + { + {0x8003, 2}, + {0x8006, 2}, + {0x800a, 2}, + {0x800f, 2}, + {0x8018, 2}, + {0x801f, 2}, + {0x8029, 2}, + {0xc038, 2}, + {0x8003, 3}, + {0x8006, 3}, + {0x800a, 3}, + {0x800f, 3}, + {0x8018, 3}, + {0x801f, 3}, + {0x8029, 3}, + {0xc038, 3}, + }, + /* 228 */ + { + {0x8002, 4}, + {0x8009, 4}, + {0x8017, 4}, + {0xc028, 4}, + {0x8002, 5}, + {0x8009, 5}, + {0x8017, 5}, + {0xc028, 5}, + {0x8002, 6}, + {0x8009, 6}, + {0x8017, 6}, + {0xc028, 6}, + {0x8002, 7}, + {0x8009, 7}, + {0x8017, 7}, + {0xc028, 7}, + }, + /* 229 */ + { + {0x8003, 4}, + {0x8006, 4}, + {0x800a, 4}, + {0x800f, 4}, + {0x8018, 4}, + {0x801f, 4}, + {0x8029, 4}, + {0xc038, 4}, + {0x8003, 5}, + {0x8006, 5}, + {0x800a, 5}, + {0x800f, 5}, + {0x8018, 5}, + {0x801f, 5}, + {0x8029, 5}, + {0xc038, 5}, + }, + /* 230 */ + { + {0x8003, 6}, + {0x8006, 6}, + {0x800a, 6}, + {0x800f, 6}, + {0x8018, 6}, + {0x801f, 6}, + {0x8029, 6}, + {0xc038, 6}, + {0x8003, 7}, + {0x8006, 7}, + {0x800a, 7}, + {0x800f, 7}, + {0x8018, 7}, + {0x801f, 7}, + {0x8029, 7}, + {0xc038, 7}, + }, + /* 231 */ + { + {0x8001, 8}, + {0xc016, 8}, + {0x8001, 11}, + {0xc016, 11}, + {0x8001, 12}, + {0xc016, 12}, + {0x8001, 14}, + {0xc016, 14}, + {0x8001, 15}, + {0xc016, 15}, + {0x8001, 16}, + {0xc016, 16}, + {0x8001, 17}, + {0xc016, 17}, + {0x8001, 18}, + {0xc016, 18}, + }, + /* 232 */ + { + {0x8002, 8}, + {0x8009, 8}, + {0x8017, 8}, + {0xc028, 8}, + {0x8002, 11}, + {0x8009, 11}, + {0x8017, 11}, + {0xc028, 11}, + {0x8002, 12}, + {0x8009, 12}, + {0x8017, 12}, + {0xc028, 12}, + {0x8002, 14}, + {0x8009, 14}, + {0x8017, 14}, + {0xc028, 14}, + }, + /* 233 */ + { + {0x8003, 8}, + {0x8006, 8}, + {0x800a, 8}, + {0x800f, 8}, + {0x8018, 8}, + {0x801f, 8}, + {0x8029, 8}, + {0xc038, 8}, + {0x8003, 11}, + {0x8006, 11}, + {0x800a, 11}, + {0x800f, 11}, + {0x8018, 11}, + {0x801f, 11}, + {0x8029, 11}, + {0xc038, 11}, + }, + /* 234 */ + { + {0x8003, 12}, + {0x8006, 12}, + {0x800a, 12}, + {0x800f, 12}, + {0x8018, 12}, + {0x801f, 12}, + {0x8029, 12}, + {0xc038, 12}, + {0x8003, 14}, + {0x8006, 14}, + {0x800a, 14}, + {0x800f, 14}, + {0x8018, 14}, + {0x801f, 14}, + {0x8029, 14}, + {0xc038, 14}, + }, + /* 235 */ + { + {0x8002, 15}, + {0x8009, 15}, + {0x8017, 15}, + {0xc028, 15}, + {0x8002, 16}, + {0x8009, 16}, + {0x8017, 16}, + {0xc028, 16}, + {0x8002, 17}, + {0x8009, 17}, + {0x8017, 17}, + {0xc028, 17}, + {0x8002, 18}, + {0x8009, 18}, + {0x8017, 18}, + {0xc028, 18}, + }, + /* 236 */ + { + {0x8003, 15}, + {0x8006, 15}, + {0x800a, 15}, + {0x800f, 15}, + {0x8018, 15}, + {0x801f, 15}, + {0x8029, 15}, + {0xc038, 15}, + {0x8003, 16}, + {0x8006, 16}, + {0x800a, 16}, + {0x800f, 16}, + {0x8018, 16}, + {0x801f, 16}, + {0x8029, 16}, + {0xc038, 16}, + }, + /* 237 */ + { + {0x8003, 17}, + {0x8006, 17}, + {0x800a, 17}, + {0x800f, 17}, + {0x8018, 17}, + {0x801f, 17}, + {0x8029, 17}, + {0xc038, 17}, + {0x8003, 18}, + {0x8006, 18}, + {0x800a, 18}, + {0x800f, 18}, + {0x8018, 18}, + {0x801f, 18}, + {0x8029, 18}, + {0xc038, 18}, + }, + /* 238 */ + { + {0xc000, 19}, + {0xc000, 20}, + {0xc000, 21}, + {0xc000, 23}, + {0xc000, 24}, + {0xc000, 25}, + {0xc000, 26}, + {0xc000, 27}, + {0xc000, 28}, + {0xc000, 29}, + {0xc000, 30}, + {0xc000, 31}, + {0xc000, 127}, + {0xc000, 220}, + {0xc000, 249}, + {0xfd, 0}, + }, + /* 239 */ + { + {0x8001, 19}, + {0xc016, 19}, + {0x8001, 20}, + {0xc016, 20}, + {0x8001, 21}, + {0xc016, 21}, + {0x8001, 23}, + {0xc016, 23}, + {0x8001, 24}, + {0xc016, 24}, + {0x8001, 25}, + {0xc016, 25}, + {0x8001, 26}, + {0xc016, 26}, + {0x8001, 27}, + {0xc016, 27}, + }, + /* 240 */ + { + {0x8002, 19}, + {0x8009, 19}, + {0x8017, 19}, + {0xc028, 19}, + {0x8002, 20}, + {0x8009, 20}, + {0x8017, 20}, + {0xc028, 20}, + {0x8002, 21}, + {0x8009, 21}, + {0x8017, 21}, + {0xc028, 21}, + {0x8002, 23}, + {0x8009, 23}, + {0x8017, 23}, + {0xc028, 23}, + }, + /* 241 */ + { + {0x8003, 19}, + {0x8006, 19}, + {0x800a, 19}, + {0x800f, 19}, + {0x8018, 19}, + {0x801f, 19}, + {0x8029, 19}, + {0xc038, 19}, + {0x8003, 20}, + {0x8006, 20}, + {0x800a, 20}, + {0x800f, 20}, + {0x8018, 20}, + {0x801f, 20}, + {0x8029, 20}, + {0xc038, 20}, + }, + /* 242 */ + { + {0x8003, 21}, + {0x8006, 21}, + {0x800a, 21}, + {0x800f, 21}, + {0x8018, 21}, + {0x801f, 21}, + {0x8029, 21}, + {0xc038, 21}, + {0x8003, 23}, + {0x8006, 23}, + {0x800a, 23}, + {0x800f, 23}, + {0x8018, 23}, + {0x801f, 23}, + {0x8029, 23}, + {0xc038, 23}, + }, + /* 243 */ + { + {0x8002, 24}, + {0x8009, 24}, + {0x8017, 24}, + {0xc028, 24}, + {0x8002, 25}, + {0x8009, 25}, + {0x8017, 25}, + {0xc028, 25}, + {0x8002, 26}, + {0x8009, 26}, + {0x8017, 26}, + {0xc028, 26}, + {0x8002, 27}, + {0x8009, 27}, + {0x8017, 27}, + {0xc028, 27}, + }, + /* 244 */ + { + {0x8003, 24}, + {0x8006, 24}, + {0x800a, 24}, + {0x800f, 24}, + {0x8018, 24}, + {0x801f, 24}, + {0x8029, 24}, + {0xc038, 24}, + {0x8003, 25}, + {0x8006, 25}, + {0x800a, 25}, + {0x800f, 25}, + {0x8018, 25}, + {0x801f, 25}, + {0x8029, 25}, + {0xc038, 25}, + }, + /* 245 */ + { + {0x8003, 26}, + {0x8006, 26}, + {0x800a, 26}, + {0x800f, 26}, + {0x8018, 26}, + {0x801f, 26}, + {0x8029, 26}, + {0xc038, 26}, + {0x8003, 27}, + {0x8006, 27}, + {0x800a, 27}, + {0x800f, 27}, + {0x8018, 27}, + {0x801f, 27}, + {0x8029, 27}, + {0xc038, 27}, + }, + /* 246 */ + { + {0x8001, 28}, + {0xc016, 28}, + {0x8001, 29}, + {0xc016, 29}, + {0x8001, 30}, + {0xc016, 30}, + {0x8001, 31}, + {0xc016, 31}, + {0x8001, 127}, + {0xc016, 127}, + {0x8001, 220}, + {0xc016, 220}, + {0x8001, 249}, + {0xc016, 249}, + {0xfe, 0}, + {0xff, 0}, + }, + /* 247 */ + { + {0x8002, 28}, + {0x8009, 28}, + {0x8017, 28}, + {0xc028, 28}, + {0x8002, 29}, + {0x8009, 29}, + {0x8017, 29}, + {0xc028, 29}, + {0x8002, 30}, + {0x8009, 30}, + {0x8017, 30}, + {0xc028, 30}, + {0x8002, 31}, + {0x8009, 31}, + {0x8017, 31}, + {0xc028, 31}, + }, + /* 248 */ + { + {0x8003, 28}, + {0x8006, 28}, + {0x800a, 28}, + {0x800f, 28}, + {0x8018, 28}, + {0x801f, 28}, + {0x8029, 28}, + {0xc038, 28}, + {0x8003, 29}, + {0x8006, 29}, + {0x800a, 29}, + {0x800f, 29}, + {0x8018, 29}, + {0x801f, 29}, + {0x8029, 29}, + {0xc038, 29}, + }, + /* 249 */ + { + {0x8003, 30}, + {0x8006, 30}, + {0x800a, 30}, + {0x800f, 30}, + {0x8018, 30}, + {0x801f, 30}, + {0x8029, 30}, + {0xc038, 30}, + {0x8003, 31}, + {0x8006, 31}, + {0x800a, 31}, + {0x800f, 31}, + {0x8018, 31}, + {0x801f, 31}, + {0x8029, 31}, + {0xc038, 31}, + }, + /* 250 */ + { + {0x8002, 127}, + {0x8009, 127}, + {0x8017, 127}, + {0xc028, 127}, + {0x8002, 220}, + {0x8009, 220}, + {0x8017, 220}, + {0xc028, 220}, + {0x8002, 249}, + {0x8009, 249}, + {0x8017, 249}, + {0xc028, 249}, + {0xc000, 10}, + {0xc000, 13}, + {0xc000, 22}, + {0x100, 0}, + }, + /* 251 */ + { + {0x8003, 127}, + {0x8006, 127}, + {0x800a, 127}, + {0x800f, 127}, + {0x8018, 127}, + {0x801f, 127}, + {0x8029, 127}, + {0xc038, 127}, + {0x8003, 220}, + {0x8006, 220}, + {0x800a, 220}, + {0x800f, 220}, + {0x8018, 220}, + {0x801f, 220}, + {0x8029, 220}, + {0xc038, 220}, + }, + /* 252 */ + { + {0x8003, 249}, + {0x8006, 249}, + {0x800a, 249}, + {0x800f, 249}, + {0x8018, 249}, + {0x801f, 249}, + {0x8029, 249}, + {0xc038, 249}, + {0x8001, 10}, + {0xc016, 10}, + {0x8001, 13}, + {0xc016, 13}, + {0x8001, 22}, + {0xc016, 22}, + {0x100, 0}, + {0x100, 0}, + }, + /* 253 */ + { + {0x8002, 10}, + {0x8009, 10}, + {0x8017, 10}, + {0xc028, 10}, + {0x8002, 13}, + {0x8009, 13}, + {0x8017, 13}, + {0xc028, 13}, + {0x8002, 22}, + {0x8009, 22}, + {0x8017, 22}, + {0xc028, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 254 */ + { + {0x8003, 10}, + {0x8006, 10}, + {0x800a, 10}, + {0x800f, 10}, + {0x8018, 10}, + {0x801f, 10}, + {0x8029, 10}, + {0xc038, 10}, + {0x8003, 13}, + {0x8006, 13}, + {0x800a, 13}, + {0x800f, 13}, + {0x8018, 13}, + {0x801f, 13}, + {0x8029, 13}, + {0xc038, 13}, + }, + /* 255 */ + { + {0x8003, 22}, + {0x8006, 22}, + {0x800a, 22}, + {0x800f, 22}, + {0x8018, 22}, + {0x801f, 22}, + {0x8029, 22}, + {0xc038, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 256 */ + { + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, +}; diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c new file mode 100644 index 0000000..93dd475 --- /dev/null +++ b/lib/nghttp2_helper.c @@ -0,0 +1,803 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_helper.h" + +#include +#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_helper.h b/lib/nghttp2_helper.h new file mode 100644 index 0000000..b1f18ce --- /dev/null +++ b/lib/nghttp2_helper.h @@ -0,0 +1,122 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HELPER_H +#define NGHTTP2_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#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_http.c b/lib/nghttp2_http.c new file mode 100644 index 0000000..ecdeb21 --- /dev/null +++ b/lib/nghttp2_http.c @@ -0,0 +1,631 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_http.h" + +#include +#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_http.h b/lib/nghttp2_http.h new file mode 100644 index 0000000..d9992fe --- /dev/null +++ b/lib/nghttp2_http.h @@ -0,0 +1,100 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HTTP_H +#define NGHTTP2_HTTP_H + +#ifdef HAVE_CONFIG_H +# include +#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_int.h b/lib/nghttp2_int.h new file mode 100644 index 0000000..b23585c --- /dev/null +++ b/lib/nghttp2_int.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_INT_H +#define NGHTTP2_INT_H + +#ifdef HAVE_CONFIG_H +# include +#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_map.c b/lib/nghttp2_map.c new file mode 100644 index 0000000..0aaaf29 --- /dev/null +++ b/lib/nghttp2_map.c @@ -0,0 +1,338 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_map.h" + +#include +#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_map.h b/lib/nghttp2_map.h new file mode 100644 index 0000000..236d282 --- /dev/null +++ b/lib/nghttp2_map.h @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_MAP_H +#define NGHTTP2_MAP_H + +#ifdef HAVE_CONFIG_H +# include +#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_mem.c b/lib/nghttp2_mem.c new file mode 100644 index 0000000..6a449cf --- /dev/null +++ b/lib/nghttp2_mem.c @@ -0,0 +1,74 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_mem.h" + +static void *default_malloc(size_t size, void *mem_user_data) { + (void)mem_user_data; + + return malloc(size); +} + +static void default_free(void *ptr, void *mem_user_data) { + (void)mem_user_data; + + free(ptr); +} + +static void *default_calloc(size_t nmemb, size_t size, void *mem_user_data) { + (void)mem_user_data; + + return calloc(nmemb, size); +} + +static void *default_realloc(void *ptr, size_t size, void *mem_user_data) { + (void)mem_user_data; + + return realloc(ptr, size); +} + +static nghttp2_mem mem_default = {NULL, default_malloc, default_free, + default_calloc, default_realloc}; + +nghttp2_mem *nghttp2_mem_default(void) { return &mem_default; } + +void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size) { + return mem->malloc(size, mem->mem_user_data); +} + +void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) { + mem->free(ptr, mem->mem_user_data); +} + +void nghttp2_mem_free2(nghttp2_free free_func, void *ptr, void *mem_user_data) { + free_func(ptr, mem_user_data); +} + +void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) { + return mem->calloc(nmemb, size, mem->mem_user_data); +} + +void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size) { + return mem->realloc(ptr, size, mem->mem_user_data); +} diff --git a/lib/nghttp2_mem.h b/lib/nghttp2_mem.h new file mode 100644 index 0000000..f83dbcb --- /dev/null +++ b/lib/nghttp2_mem.h @@ -0,0 +1,45 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_MEM_H +#define NGHTTP2_MEM_H + +#ifdef HAVE_CONFIG_H +# include +#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_net.h b/lib/nghttp2_net.h new file mode 100644 index 0000000..521f981 --- /dev/null +++ b/lib/nghttp2_net.h @@ -0,0 +1,91 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_NET_H +#define NGHTTP2_NET_H + +#ifdef HAVE_CONFIG_H +# include +#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_option.c b/lib/nghttp2_option.c new file mode 100644 index 0000000..43d4e95 --- /dev/null +++ b/lib/nghttp2_option.c @@ -0,0 +1,152 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_option.h" + +#include "nghttp2_session.h" + +int nghttp2_option_new(nghttp2_option **option_ptr) { + *option_ptr = calloc(1, sizeof(nghttp2_option)); + + if (*option_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +void nghttp2_option_del(nghttp2_option *option) { free(option); } + +void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE; + option->no_auto_window_update = val; +} + +void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, + uint32_t val) { + option->opt_set_mask |= NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS; + option->peer_max_concurrent_streams = val; +} + +void nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC; + option->no_recv_client_magic = val; +} + +void nghttp2_option_set_no_http_messaging(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_HTTP_MESSAGING; + option->no_http_messaging = val; +} + +void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, + uint32_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS; + option->max_reserved_remote_streams = val; +} + +static void set_ext_type(uint8_t *ext_types, uint8_t type) { + ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7))); +} + +void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type) { + if (type < 10) { + return; + } + + option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; + set_ext_type(option->user_recv_ext_types, type); +} + +void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type) { + switch (type) { + case NGHTTP2_ALTSVC: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ALTSVC; + return; + case NGHTTP2_ORIGIN: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ORIGIN; + return; + case NGHTTP2_PRIORITY_UPDATE: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_PRIORITY_UPDATE; + return; + default: + return; + } +} + +void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK; + option->no_auto_ping_ack = val; +} + +void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH; + option->max_send_header_block_length = val; +} + +void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE; + option->max_deflate_dynamic_table_size = val; +} + +void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS; + option->no_closed_streams = val; +} + +void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK; + option->max_outbound_ack = val; +} + +void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS; + option->max_settings = val; +} + +void nghttp2_option_set_server_fallback_rfc7540_priorities( + nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES; + option->server_fallback_rfc7540_priorities = val; +} + +void nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( + nghttp2_option *option, int val) { + option->opt_set_mask |= + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + option->no_rfc9113_leading_and_trailing_ws_validation = val; +} + +void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + uint64_t burst, uint64_t rate) { + option->opt_set_mask |= NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT; + option->stream_reset_burst = burst; + option->stream_reset_rate = rate; +} diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h new file mode 100644 index 0000000..2259e18 --- /dev/null +++ b/lib/nghttp2_option.h @@ -0,0 +1,152 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_OPTION_H +#define NGHTTP2_OPTION_H + +#ifdef HAVE_CONFIG_H +# include +#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_outbound_item.c b/lib/nghttp2_outbound_item.c new file mode 100644 index 0000000..2a3041d --- /dev/null +++ b/lib/nghttp2_outbound_item.c @@ -0,0 +1,130 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_outbound_item.h" + +#include +#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_outbound_item.h b/lib/nghttp2_outbound_item.h new file mode 100644 index 0000000..bd4611b --- /dev/null +++ b/lib/nghttp2_outbound_item.h @@ -0,0 +1,166 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_OUTBOUND_ITEM_H +#define NGHTTP2_OUTBOUND_ITEM_H + +#ifdef HAVE_CONFIG_H +# include +#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_pq.c b/lib/nghttp2_pq.c new file mode 100644 index 0000000..64353ac --- /dev/null +++ b/lib/nghttp2_pq.c @@ -0,0 +1,183 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_pq.h" + +#include +#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_pq.h b/lib/nghttp2_pq.h new file mode 100644 index 0000000..c8d90ef --- /dev/null +++ b/lib/nghttp2_pq.h @@ -0,0 +1,124 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_PQ_H +#define NGHTTP2_PQ_H + +#ifdef HAVE_CONFIG_H +# include +#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_priority_spec.c b/lib/nghttp2_priority_spec.c new file mode 100644 index 0000000..c2196e3 --- /dev/null +++ b/lib/nghttp2_priority_spec.c @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_priority_spec.h" + +void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, int32_t weight, + int exclusive) { + pri_spec->stream_id = stream_id; + pri_spec->weight = weight; + pri_spec->exclusive = exclusive != 0; +} + +void nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec) { + pri_spec->stream_id = 0; + pri_spec->weight = NGHTTP2_DEFAULT_WEIGHT; + pri_spec->exclusive = 0; +} + +int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec) { + return pri_spec->stream_id == 0 && + pri_spec->weight == NGHTTP2_DEFAULT_WEIGHT && pri_spec->exclusive == 0; +} + +void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec) { + if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) { + pri_spec->weight = NGHTTP2_MIN_WEIGHT; + } else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) { + pri_spec->weight = NGHTTP2_MAX_WEIGHT; + } +} diff --git a/lib/nghttp2_priority_spec.h b/lib/nghttp2_priority_spec.h new file mode 100644 index 0000000..92ece82 --- /dev/null +++ b/lib/nghttp2_priority_spec.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_PRIORITY_SPEC_H +#define NGHTTP2_PRIORITY_SPEC_H + +#ifdef HAVE_CONFIG_H +# include +#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_queue.c b/lib/nghttp2_queue.c new file mode 100644 index 0000000..055eb69 --- /dev/null +++ b/lib/nghttp2_queue.c @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_queue.h" + +#include +#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_queue.h b/lib/nghttp2_queue.h new file mode 100644 index 0000000..a06fa6c --- /dev/null +++ b/lib/nghttp2_queue.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_QUEUE_H +#define NGHTTP2_QUEUE_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include + +typedef struct nghttp2_queue_cell { + void *data; + struct nghttp2_queue_cell *next; +} nghttp2_queue_cell; + +typedef struct { + nghttp2_queue_cell *front, *back; +} nghttp2_queue; + +void nghttp2_queue_init(nghttp2_queue *queue); +void nghttp2_queue_free(nghttp2_queue *queue); +int nghttp2_queue_push(nghttp2_queue *queue, void *data); +void nghttp2_queue_pop(nghttp2_queue *queue); +void *nghttp2_queue_front(nghttp2_queue *queue); +void *nghttp2_queue_back(nghttp2_queue *queue); +int nghttp2_queue_empty(nghttp2_queue *queue); + +#endif /* NGHTTP2_QUEUE_H */ diff --git a/lib/nghttp2_ratelim.c b/lib/nghttp2_ratelim.c new file mode 100644 index 0000000..7011655 --- /dev/null +++ b/lib/nghttp2_ratelim.c @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_ratelim.h" +#include "nghttp2_helper.h" + +void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate) { + rl->val = rl->burst = burst; + rl->rate = rate; + rl->tstamp = 0; +} + +void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp) { + uint64_t d, gain; + + if (tstamp == rl->tstamp) { + return; + } + + if (tstamp > rl->tstamp) { + d = tstamp - rl->tstamp; + } else { + d = 1; + } + + rl->tstamp = tstamp; + + if (UINT64_MAX / d < rl->rate) { + rl->val = rl->burst; + + return; + } + + gain = rl->rate * d; + + if (UINT64_MAX - gain < rl->val) { + rl->val = rl->burst; + + return; + } + + rl->val += gain; + rl->val = nghttp2_min(rl->val, rl->burst); +} + +int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n) { + if (rl->val < n) { + return -1; + } + + rl->val -= n; + + return 0; +} diff --git a/lib/nghttp2_ratelim.h b/lib/nghttp2_ratelim.h new file mode 100644 index 0000000..866ed3f --- /dev/null +++ b/lib/nghttp2_ratelim.h @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_RATELIM_H +#define NGHTTP2_RATELIM_H + +#ifdef HAVE_CONFIG_H +# include +#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_rcbuf.c b/lib/nghttp2_rcbuf.c new file mode 100644 index 0000000..7e7814d --- /dev/null +++ b/lib/nghttp2_rcbuf.c @@ -0,0 +1,102 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_rcbuf.h" + +#include +#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_rcbuf.h b/lib/nghttp2_rcbuf.h new file mode 100644 index 0000000..6814e70 --- /dev/null +++ b/lib/nghttp2_rcbuf.h @@ -0,0 +1,80 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_RCBUF_H +#define NGHTTP2_RCBUF_H + +#ifdef HAVE_CONFIG_H +# include +#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_session.c b/lib/nghttp2_session.c new file mode 100644 index 0000000..ce21caf --- /dev/null +++ b/lib/nghttp2_session.c @@ -0,0 +1,8395 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_session.h" + +#include +#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 terminate + connection. */ + rv = nghttp2_session_terminate_session(session, + NGHTTP2_INTERNAL_ERROR); + } + if (nghttp2_is_fatal(rv)) { + return rv; + } + break; + } + + aob->item = item; + + nghttp2_bufs_rewind(framebufs); + + if (item->frame.hd.type != NGHTTP2_DATA) { + nghttp2_frame *frame; + + frame = &item->frame; + + DEBUGF("send: next frame: payloadlen=%zu, type=%u, flags=0x%02x, " + "stream_id=%d\n", + frame->hd.length, frame->hd.type, frame->hd.flags, + frame->hd.stream_id); + + rv = session_call_before_frame_send(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv == NGHTTP2_ERR_CANCEL) { + int32_t opened_stream_id = 0; + uint32_t error_code = NGHTTP2_INTERNAL_ERROR; + + if (session->callbacks.on_frame_not_send_callback) { + if (session->callbacks.on_frame_not_send_callback( + session, frame, rv, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + /* We have to close stream opened by canceled request + HEADERS or PUSH_PROMISE. */ + switch (item->frame.hd.type) { + case NGHTTP2_HEADERS: + if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) { + opened_stream_id = item->frame.hd.stream_id; + /* We don't have to check + item->aux_data.headers.canceled since it has already + been checked. */ + /* Set error_code to REFUSED_STREAM so that application + can send request again. */ + error_code = NGHTTP2_REFUSED_STREAM; + } + break; + case NGHTTP2_PUSH_PROMISE: + opened_stream_id = item->frame.push_promise.promised_stream_id; + break; + } + if (opened_stream_id) { + /* careful not to override rv */ + int rv2; + rv2 = nghttp2_session_close_stream(session, opened_stream_id, + error_code); + + if (nghttp2_is_fatal(rv2)) { + return rv2; + } + } + + active_outbound_item_reset(aob, mem); + + break; + } + } else { + DEBUGF("send: next frame: DATA\n"); + + if (item->aux_data.data.no_copy) { + aob->state = NGHTTP2_OB_SEND_NO_COPY; + break; + } + } + + DEBUGF("send: start transmitting frame type=%u, length=%zd\n", + framebufs->cur->buf.pos[3], + framebufs->cur->buf.last - framebufs->cur->buf.pos); + + aob->state = NGHTTP2_OB_SEND_DATA; + + break; + } + case NGHTTP2_OB_SEND_DATA: { + size_t datalen; + nghttp2_buf *buf; + + buf = &framebufs->cur->buf; + + if (buf->pos == buf->last) { + DEBUGF("send: end transmission of a frame\n"); + + /* Frame has completely sent */ + if (fast_cb) { + session_after_frame_sent2(session); + } else { + rv = session_after_frame_sent1(session); + if (rv < 0) { + /* FATAL */ + assert(nghttp2_is_fatal(rv)); + return rv; + } + session_after_frame_sent2(session); + } + /* We have already adjusted the next state */ + break; + } + + *data_ptr = buf->pos; + datalen = nghttp2_buf_len(buf); + + /* We increment the offset here. If send_callback does not send + everything, we will adjust it. */ + buf->pos += datalen; + + return (ssize_t)datalen; + } + case NGHTTP2_OB_SEND_NO_COPY: { + nghttp2_stream *stream; + nghttp2_frame *frame; + int pause; + + DEBUGF("send: no copy DATA\n"); + + frame = &aob->item->frame; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (stream == NULL) { + DEBUGF("send: no copy DATA cancelled because stream was closed\n"); + + active_outbound_item_reset(aob, mem); + + break; + } + + rv = session_call_send_data(session, aob->item, framebufs); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + session_detach_stream_item(session, stream); + + rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + active_outbound_item_reset(aob, mem); + + break; + } + + if (rv == NGHTTP2_ERR_WOULDBLOCK) { + return 0; + } + + pause = (rv == NGHTTP2_ERR_PAUSE); + + rv = session_after_frame_sent1(session); + if (rv < 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + session_after_frame_sent2(session); + + /* We have already adjusted the next state */ + + if (pause) { + return 0; + } + + break; + } + case NGHTTP2_OB_SEND_CLIENT_MAGIC: { + size_t datalen; + nghttp2_buf *buf; + + buf = &framebufs->cur->buf; + + if (buf->pos == buf->last) { + DEBUGF("send: end transmission of client magic\n"); + active_outbound_item_reset(aob, mem); + break; + } + + *data_ptr = buf->pos; + datalen = nghttp2_buf_len(buf); + + buf->pos += datalen; + + return (ssize_t)datalen; + } + } + } +} + +ssize_t nghttp2_session_mem_send(nghttp2_session *session, + const uint8_t **data_ptr) { + int rv; + ssize_t len; + + *data_ptr = NULL; + + len = nghttp2_session_mem_send_internal(session, data_ptr, 1); + if (len <= 0) { + return len; + } + + if (session->aob.item) { + /* We have to call session_after_frame_sent1 here to handle stream + closure upon transmission of frames. Otherwise, END_STREAM may + be reached to client before we call nghttp2_session_mem_send + again and we may get exceeding number of incoming streams. */ + rv = session_after_frame_sent1(session); + if (rv < 0) { + assert(nghttp2_is_fatal(rv)); + return (ssize_t)rv; + } + } + + return len; +} + +int nghttp2_session_send(nghttp2_session *session) { + const uint8_t *data = NULL; + ssize_t datalen; + ssize_t sentlen; + nghttp2_bufs *framebufs; + + framebufs = &session->aob.framebufs; + + for (;;) { + datalen = nghttp2_session_mem_send_internal(session, &data, 0); + if (datalen <= 0) { + return (int)datalen; + } + sentlen = session->callbacks.send_callback(session, data, (size_t)datalen, + 0, session->user_data); + if (sentlen < 0) { + if (sentlen == NGHTTP2_ERR_WOULDBLOCK) { + /* Transmission canceled. Rewind the offset */ + framebufs->cur->buf.pos -= datalen; + + return 0; + } + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + /* Rewind the offset to the amount of unsent bytes */ + framebufs->cur->buf.pos -= datalen - sentlen; + } +} + +static ssize_t session_recv(nghttp2_session *session, uint8_t *buf, + size_t len) { + ssize_t rv; + rv = session->callbacks.recv_callback(session, buf, len, 0, + session->user_data); + if (rv > 0) { + if ((size_t)rv > len) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK && rv != NGHTTP2_ERR_EOF) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return rv; +} + +static int session_call_on_begin_frame(nghttp2_session *session, + const nghttp2_frame_hd *hd) { + int rv; + + if (session->callbacks.on_begin_frame_callback) { + + rv = session->callbacks.on_begin_frame_callback(session, hd, + session->user_data); + + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int session_call_on_frame_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + if (session->callbacks.on_frame_recv_callback) { + rv = session->callbacks.on_frame_recv_callback(session, frame, + session->user_data); + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_call_on_begin_headers(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + DEBUGF("recv: call on_begin_headers callback stream_id=%d\n", + frame->hd.stream_id); + if (session->callbacks.on_begin_headers_callback) { + rv = session->callbacks.on_begin_headers_callback(session, frame, + session->user_data); + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_call_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const nghttp2_hd_nv *nv) { + int rv = 0; + if (session->callbacks.on_header_callback2) { + rv = session->callbacks.on_header_callback2( + session, frame, nv->name, nv->value, nv->flags, session->user_data); + } else if (session->callbacks.on_header_callback) { + rv = session->callbacks.on_header_callback( + session, frame, nv->name->base, nv->name->len, nv->value->base, + nv->value->len, nv->flags, session->user_data); + } + + if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int session_call_on_invalid_header(nghttp2_session *session, + const nghttp2_frame *frame, + const nghttp2_hd_nv *nv) { + int rv; + if (session->callbacks.on_invalid_header_callback2) { + rv = session->callbacks.on_invalid_header_callback2( + session, frame, nv->name, nv->value, nv->flags, session->user_data); + } else if (session->callbacks.on_invalid_header_callback) { + rv = session->callbacks.on_invalid_header_callback( + session, frame, nv->name->base, nv->name->len, nv->value->base, + nv->value->len, nv->flags, session->user_data); + } else { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +session_call_on_extension_chunk_recv_callback(nghttp2_session *session, + const uint8_t *data, size_t len) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + if (session->callbacks.on_extension_chunk_recv_callback) { + rv = session->callbacks.on_extension_chunk_recv_callback( + session, &frame->hd, data, len, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int session_call_unpack_extension_callback(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + void *payload = NULL; + + rv = session->callbacks.unpack_extension_callback( + session, &payload, &frame->hd, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + frame->ext.payload = payload; + + return 0; +} + +/* + * Handles frame size error. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_handle_frame_size_error(nghttp2_session *session) { + /* TODO Currently no callback is called for this error, because we + call this callback before reading any payload */ + return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR); +} + +static uint32_t get_error_code_from_lib_error_code(int lib_error_code) { + switch (lib_error_code) { + case NGHTTP2_ERR_STREAM_CLOSED: + return NGHTTP2_STREAM_CLOSED; + case NGHTTP2_ERR_HEADER_COMP: + return NGHTTP2_COMPRESSION_ERROR; + case NGHTTP2_ERR_FRAME_SIZE_ERROR: + return NGHTTP2_FRAME_SIZE_ERROR; + case NGHTTP2_ERR_FLOW_CONTROL: + return NGHTTP2_FLOW_CONTROL_ERROR; + case NGHTTP2_ERR_REFUSED_STREAM: + return NGHTTP2_REFUSED_STREAM; + case NGHTTP2_ERR_PROTO: + case NGHTTP2_ERR_HTTP_HEADER: + case NGHTTP2_ERR_HTTP_MESSAGING: + return NGHTTP2_PROTOCOL_ERROR; + default: + return NGHTTP2_INTERNAL_ERROR; + } +} + +/* + * Calls on_invalid_frame_recv_callback if it is set to |session|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * User defined callback function fails. + */ +static int session_call_on_invalid_frame_recv_callback(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_handle_invalid_stream2(nghttp2_session *session, + int32_t stream_id, + nghttp2_frame *frame, + int lib_error_code) { + int rv; + rv = nghttp2_session_add_rst_stream( + session, stream_id, get_error_code_from_lib_error_code(lib_error_code)); + if (rv != 0) { + return rv; + } + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_handle_invalid_stream(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + return session_handle_invalid_stream2(session, frame->hd.stream_id, frame, + lib_error_code); +} + +static int session_inflate_handle_invalid_stream(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + int rv; + rv = session_handle_invalid_stream(session, frame, lib_error_code); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +/* + * Handles invalid frame which causes connection error. + */ +static int session_handle_invalid_connection(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code, + const char *reason) { + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return nghttp2_session_terminate_session_with_reason( + session, get_error_code_from_lib_error_code(lib_error_code), reason); +} + +static int session_inflate_handle_invalid_connection(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code, + const char *reason) { + int rv; + rv = + session_handle_invalid_connection(session, frame, lib_error_code, reason); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +/* + * Inflates header block in the memory pointed by |in| with |inlen| + * bytes. If this function returns NGHTTP2_ERR_PAUSE, the caller must + * call this function again, until it returns 0 or one of negative + * error code. If |call_header_cb| is zero, the on_header_callback + * are not invoked and the function never return NGHTTP2_ERR_PAUSE. If + * the given |in| is the last chunk of header block, the |final| must + * be nonzero. If header block is successfully processed (which is + * indicated by the return value 0, NGHTTP2_ERR_PAUSE or + * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE), the number of processed + * input bytes is assigned to the |*readlen_ptr|. + * + * This function return 0 if it succeeds, or one of the negative error + * codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE + * The callback returns this error code, indicating that this + * stream should be RST_STREAMed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_PAUSE + * The callback function returned NGHTTP2_ERR_PAUSE + * NGHTTP2_ERR_HEADER_COMP + * Header decompression failed + */ +static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, + size_t *readlen_ptr, uint8_t *in, size_t inlen, + int final, int call_header_cb) { + ssize_t proclen; + int rv; + int inflate_flags; + nghttp2_hd_nv nv; + nghttp2_stream *stream; + nghttp2_stream *subject_stream; + int trailer = 0; + + *readlen_ptr = 0; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + } else { + subject_stream = stream; + trailer = session_trailer_headers(session, stream, frame); + } + + DEBUGF("recv: decoding header block %zu bytes\n", inlen); + for (;;) { + inflate_flags = 0; + proclen = nghttp2_hd_inflate_hd_nv(&session->hd_inflater, &nv, + &inflate_flags, in, inlen, final); + if (nghttp2_is_fatal((int)proclen)) { + return (int)proclen; + } + if (proclen < 0) { + if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) { + if (subject_stream && subject_stream->state != NGHTTP2_STREAM_CLOSING) { + /* Adding RST_STREAM here is very important. It prevents + from invoking subsequent callbacks for the same stream + ID. */ + rv = nghttp2_session_add_rst_stream( + session, subject_stream->stream_id, NGHTTP2_COMPRESSION_ERROR); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + rv = + nghttp2_session_terminate_session(session, NGHTTP2_COMPRESSION_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return NGHTTP2_ERR_HEADER_COMP; + } + in += proclen; + inlen -= (size_t)proclen; + *readlen_ptr += (size_t)proclen; + + DEBUGF("recv: proclen=%zd\n", proclen); + + if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { + rv = 0; + if (subject_stream) { + if (session_enforce_http_messaging(session)) { + rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, + trailer); + + if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) { + /* Don't overwrite rv here */ + int rv2; + + rv2 = session_call_on_invalid_header(session, frame, &nv); + if (rv2 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = NGHTTP2_ERR_HTTP_HEADER; + } else { + if (rv2 != 0) { + return rv2; + } + + /* header is ignored */ + DEBUGF("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + rv2 = session_call_error_callback( + session, NGHTTP2_ERR_HTTP_HEADER, + "Ignoring received invalid HTTP header field: frame type: " + "%u, stream: %d, name: [%.*s], value: [%.*s]", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + if (nghttp2_is_fatal(rv2)) { + return rv2; + } + } + } + + if (rv == NGHTTP2_ERR_HTTP_HEADER) { + DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + rv = session_call_error_callback( + session, NGHTTP2_ERR_HTTP_HEADER, + "Invalid HTTP header field was received: frame type: " + "%u, stream: %d, name: [%.*s], value: [%.*s]", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = session_handle_invalid_stream2(session, + subject_stream->stream_id, + frame, NGHTTP2_ERR_HTTP_HEADER); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + } + if (rv == 0) { + rv = session_call_on_header(session, frame, &nv); + /* This handles NGHTTP2_ERR_PAUSE and + NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ + if (rv != 0) { + return rv; + } + } + } + } + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + nghttp2_hd_inflate_end_headers(&session->hd_inflater); + break; + } + if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { + break; + } + } + return 0; +} + +/* + * Call this function when HEADERS frame was completely received. + * + * This function returns 0 if it succeeds, or one of negative error + * codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_end_stream_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv; + + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (session->server && session_enforce_http_messaging(session) && + frame->headers.cat == NGHTTP2_HCAT_REQUEST && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) && + !(stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) && + (stream->http_flags & NGHTTP2_HTTP_FLAG_PRIORITY)) { + rv = session_update_stream_priority(session, stream, stream->http_extpri); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + } + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return 0; + } + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +static int session_after_header_block_received(nghttp2_session *session) { + int rv = 0; + nghttp2_frame *frame = &session->iframe.frame; + nghttp2_stream *stream; + + /* We don't call on_frame_recv_callback if stream has been closed + already or being closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { + return 0; + } + + if (session_enforce_http_messaging(session)) { + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + nghttp2_stream *subject_stream; + + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + if (subject_stream) { + rv = nghttp2_http_on_request_headers(subject_stream, frame); + } + } else { + assert(frame->hd.type == NGHTTP2_HEADERS); + switch (frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: + rv = nghttp2_http_on_request_headers(stream, frame); + break; + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + rv = nghttp2_http_on_response_headers(stream); + break; + case NGHTTP2_HCAT_HEADERS: + if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + assert(!session->server); + rv = nghttp2_http_on_response_headers(stream); + } else { + rv = nghttp2_http_on_trailer_headers(stream, frame); + } + break; + default: + assert(0); + } + if (rv == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + rv = nghttp2_http_on_remote_end_stream(stream); + } + } + if (rv != 0) { + int32_t stream_id; + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + stream_id = frame->push_promise.promised_stream_id; + } else { + stream_id = frame->hd.stream_id; + } + + rv = session_handle_invalid_stream2(session, stream_id, frame, + NGHTTP2_ERR_HTTP_MESSAGING); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.type == NGHTTP2_HEADERS && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + /* Don't call nghttp2_session_close_stream_if_shut_rdwr + because RST_STREAM has been submitted. */ + } + return 0; + } + } + + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + + return session_end_stream_headers_received(session, frame, stream); +} + +int nghttp2_session_on_request_headers_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + nghttp2_stream *stream; + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: stream_id == 0"); + } + + /* If client receives idle stream from server, it is invalid + regardless stream ID is even or odd. This is because client is + not expected to receive request from server. */ + if (!session->server) { + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: client received request"); + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + assert(session->server); + + if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) { + if (frame->hd.stream_id == 0 || + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: invalid stream_id"); + } + + /* RFC 7540 says if an endpoint receives a HEADERS with invalid + * stream ID (e.g, numerically smaller than previous), it MUST + * issue connection error with error code PROTOCOL_ERROR. It is a + * bit hard to detect this, since we cannot remember all streams + * we observed so far. + * + * You might imagine this is really easy. But no. HTTP/2 is + * asynchronous protocol, and usually client and server do not + * share the complete picture of open/closed stream status. For + * example, after server sends RST_STREAM for a stream, client may + * send trailer HEADERS for that stream. If naive server detects + * that, and issued connection error, then it is a bug of server + * implementation since client is not wrong if it did not get + * RST_STREAM when it issued trailer HEADERS. + * + * At the moment, we are very conservative here. We only use + * connection error if stream ID refers idle stream, or we are + * sure that stream is half-closed(remote) or closed. Otherwise + * we just ignore HEADERS for now. + */ + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + session->last_recv_stream_id = frame->hd.stream_id; + + if (session_is_incoming_concurrent_streams_max(session)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: max concurrent streams exceeded"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We just ignore stream after GOAWAY was sent */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself"); + } + + if (session_is_incoming_concurrent_streams_pending_max(session)) { + return session_inflate_handle_invalid_stream(session, frame, + NGHTTP2_ERR_REFUSED_STREAM); + } + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, + &frame->headers.pri_spec, NGHTTP2_STREAM_OPENING, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_session_adjust_closed_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session->last_proc_stream_id = session->last_recv_stream_id; + + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv; + /* This function is only called if stream->state == + NGHTTP2_STREAM_OPENING and stream_id is local side initiated. */ + assert(stream->state == NGHTTP2_STREAM_OPENING && + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)); + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "response HEADERS: stream_id == 0"); + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + /* half closed (remote): from the spec: + + If an endpoint receives additional frames for a stream that is + in this state it MUST respond with a stream error (Section + 5.4.2) of type STREAM_CLOSED. + + We go further, and make it connection error. + */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + stream->state = NGHTTP2_STREAM_OPENED; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv = 0; + assert(stream->state == NGHTTP2_STREAM_RESERVED); + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "push response HEADERS: stream_id == 0"); + } + + if (session->server) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "HEADERS: no HEADERS allowed from client in reserved state"); + } + + if (session_is_incoming_concurrent_streams_max(session)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "push response HEADERS: max concurrent streams exceeded"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We don't accept new stream after GOAWAY was sent. */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (session_is_incoming_concurrent_streams_pending_max(session)) { + return session_inflate_handle_invalid_stream(session, frame, + NGHTTP2_ERR_REFUSED_STREAM); + } + + nghttp2_stream_promise_fulfilled(stream); + if (!nghttp2_session_is_my_stream_id(session, stream->stream_id)) { + --session->num_incoming_reserved_streams; + } + ++session->num_incoming_streams; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv = 0; + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0"); + } + if ((stream->shut_flags & NGHTTP2_SHUT_RD)) { + /* half closed (remote): from the spec: + + If an endpoint receives additional frames for a stream that is + in this state it MUST respond with a stream error (Section + 5.4.2) of type STREAM_CLOSED. + + we go further, and make it connection error. + */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + if (stream->state == NGHTTP2_STREAM_OPENED) { + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + /* If this is remote peer initiated stream, it is OK unless it + has sent END_STREAM frame already. But if stream is in + NGHTTP2_STREAM_CLOSING, we discard the frame. This is a race + condition. */ + if (stream->state != NGHTTP2_STREAM_CLOSING) { + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +static int session_process_headers_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + nghttp2_stream *stream; + + nghttp2_frame_unpack_headers_payload(&frame->headers, iframe->sbuf.pos); + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + frame->headers.cat = NGHTTP2_HCAT_REQUEST; + return nghttp2_session_on_request_headers_received(session, frame); + } + + if (stream->state == NGHTTP2_STREAM_RESERVED) { + frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; + return nghttp2_session_on_push_response_headers_received(session, frame, + stream); + } + + if (stream->state == NGHTTP2_STREAM_OPENING && + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + frame->headers.cat = NGHTTP2_HCAT_RESPONSE; + return nghttp2_session_on_response_headers_received(session, frame, stream); + } + + frame->headers.cat = NGHTTP2_HCAT_HEADERS; + return nghttp2_session_on_headers_received(session, frame, stream); +} + +int nghttp2_session_on_priority_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + + assert(!session_no_rfc7540_pri_no_fallback(session)); + + if (frame->hd.stream_id == 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY: stream_id == 0"); + } + + if (frame->priority.pri_spec.stream_id == frame->hd.stream_id) { + return nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "depend on itself"); + } + + if (!session->server) { + /* Re-prioritization works only in server */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + + if (!stream) { + /* PRIORITY against idle stream can create anchor node in + dependency tree. */ + if (!session_detect_idle_stream(session, frame->hd.stream_id)) { + return 0; + } + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, + &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL); + + if (stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } else { + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_priority_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + assert(!session_no_rfc7540_pri_no_fallback(session)); + + nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos); + + return nghttp2_session_on_priority_received(session, frame); +} + +static int session_update_stream_reset_ratelim(nghttp2_session *session) { + if (!session->server || (session->goaway_flags & NGHTTP2_GOAWAY_SUBMITTED)) { + return 0; + } + + nghttp2_ratelim_update(&session->stream_reset_ratelim, + nghttp2_time_now_sec()); + + if (nghttp2_ratelim_drain(&session->stream_reset_ratelim, 1) == 0) { + return 0; + } + + return nghttp2_session_add_goaway(session, session->last_recv_stream_id, + NGHTTP2_INTERNAL_ERROR, NULL, 0, + NGHTTP2_GOAWAY_AUX_NONE); +} + +int nghttp2_session_on_rst_stream_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + if (frame->hd.stream_id == 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "RST_STREAM: stream_id == 0"); + } + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "RST_STREAM: stream in idle"); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (stream) { + /* We may use stream->shut_flags for strict error checking. */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + } + + rv = session_call_on_frame_received(session, frame); + if (rv != 0) { + return rv; + } + rv = nghttp2_session_close_stream(session, frame->hd.stream_id, + frame->rst_stream.error_code); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_update_stream_reset_ratelim(session); +} + +static int session_process_rst_stream_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, iframe->sbuf.pos); + + return nghttp2_session_on_rst_stream_received(session, frame); +} + +static int update_remote_initial_window_size_func(void *entry, void *ptr) { + int rv; + nghttp2_update_window_size_arg *arg; + nghttp2_stream *stream; + + arg = (nghttp2_update_window_size_arg *)ptr; + stream = (nghttp2_stream *)entry; + + rv = nghttp2_stream_update_remote_initial_window_size( + stream, arg->new_window_size, arg->old_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + /* If window size gets positive, push deferred DATA frame to + outbound queue. */ + if (stream->remote_window_size > 0 && + nghttp2_stream_check_deferred_by_flow_control(stream)) { + + rv = session_resume_deferred_stream_item( + arg->session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return 0; +} + +/* + * Updates the remote initial window size of all active streams. If + * error occurs, all streams may not be updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int +session_update_remote_initial_window_size(nghttp2_session *session, + int32_t new_initial_window_size) { + nghttp2_update_window_size_arg arg; + + arg.session = session; + arg.new_window_size = new_initial_window_size; + arg.old_window_size = (int32_t)session->remote_settings.initial_window_size; + + return nghttp2_map_each(&session->streams, + update_remote_initial_window_size_func, &arg); +} + +static int update_local_initial_window_size_func(void *entry, void *ptr) { + int rv; + nghttp2_update_window_size_arg *arg; + nghttp2_stream *stream; + arg = (nghttp2_update_window_size_arg *)ptr; + stream = (nghttp2_stream *)entry; + rv = nghttp2_stream_update_local_initial_window_size( + stream, arg->new_window_size, arg->old_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + if (stream->window_update_queued) { + return 0; + } + + if (arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + return session_update_stream_consumed_size(arg->session, stream, 0); + } + + if (nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + + rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE, + stream->stream_id, + stream->recv_window_size); + if (rv != 0) { + return rv; + } + + stream->recv_window_size = 0; + } + return 0; +} + +/* + * Updates the local initial window size of all active streams. If + * error occurs, all streams may not be updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int +session_update_local_initial_window_size(nghttp2_session *session, + int32_t new_initial_window_size, + int32_t old_initial_window_size) { + nghttp2_update_window_size_arg arg; + arg.session = session; + arg.new_window_size = new_initial_window_size; + arg.old_window_size = old_initial_window_size; + return nghttp2_map_each(&session->streams, + update_local_initial_window_size_func, &arg); +} + +/* + * Apply SETTINGS values |iv| having |niv| elements to the local + * settings. We assumes that all values in |iv| is correct, since we + * validated them in nghttp2_session_add_settings() already. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * The header table size is out of range + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_update_local_settings(nghttp2_session *session, + nghttp2_settings_entry *iv, + size_t niv) { + int rv; + size_t i; + int32_t new_initial_window_size = -1; + uint32_t header_table_size = 0; + uint32_t min_header_table_size = UINT32_MAX; + uint8_t header_table_size_seen = 0; + /* For NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, use the value last + seen. For NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, use both minimum + value and last seen value. */ + for (i = 0; i < niv; ++i) { + switch (iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + header_table_size_seen = 1; + header_table_size = iv[i].value; + min_header_table_size = nghttp2_min(min_header_table_size, iv[i].value); + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + new_initial_window_size = (int32_t)iv[i].value; + break; + } + } + if (header_table_size_seen) { + if (min_header_table_size < header_table_size) { + rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, + min_header_table_size); + if (rv != 0) { + return rv; + } + } + + rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, + header_table_size); + if (rv != 0) { + return rv; + } + } + if (new_initial_window_size != -1) { + rv = session_update_local_initial_window_size( + session, new_initial_window_size, + (int32_t)session->local_settings.initial_window_size); + if (rv != 0) { + return rv; + } + } + + for (i = 0; i < niv; ++i) { + switch (iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + session->local_settings.header_table_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + session->local_settings.enable_push = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + session->local_settings.max_concurrent_streams = iv[i].value; + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + session->local_settings.initial_window_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + session->local_settings.max_frame_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + session->local_settings.max_header_list_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + session->local_settings.enable_connect_protocol = iv[i].value; + break; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + session->local_settings.no_rfc7540_priorities = iv[i].value; + break; + } + } + + return 0; +} + +int nghttp2_session_on_settings_received(nghttp2_session *session, + nghttp2_frame *frame, int noack) { + int rv; + size_t i; + nghttp2_mem *mem; + nghttp2_inflight_settings *settings; + + mem = &session->mem; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: stream_id != 0"); + } + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + if (frame->settings.niv != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FRAME_SIZE_ERROR, + "SETTINGS: ACK and payload != 0"); + } + + settings = session->inflight_settings_head; + + if (!settings) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "SETTINGS: unexpected ACK"); + } + + rv = nghttp2_session_update_local_settings(session, settings->iv, + settings->niv); + + session->inflight_settings_head = settings->next; + + inflight_settings_del(settings, mem); + + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + return session_handle_invalid_connection(session, frame, rv, NULL); + } + return session_call_on_frame_received(session, frame); + } + + if (!session->remote_settings_received) { + session->remote_settings.max_concurrent_streams = + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + session->remote_settings_received = 1; + } + + for (i = 0; i < frame->settings.niv; ++i) { + nghttp2_settings_entry *entry = &frame->settings.iv[i]; + + switch (entry->settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + + rv = nghttp2_hd_deflate_change_table_size(&session->hd_deflater, + entry->value); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } else { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_HEADER_COMP, NULL); + } + } + + session->remote_settings.header_table_size = entry->value; + + break; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_ENBLE_PUSH"); + } + + if (!session->server && entry->value != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: server attempted to enable push"); + } + + session->remote_settings.enable_push = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + + session->remote_settings.max_concurrent_streams = entry->value; + + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + + /* Update the initial window size of the all active streams */ + /* Check that initial_window_size < (1u << 31) */ + if (entry->value > NGHTTP2_MAX_WINDOW_SIZE) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FLOW_CONTROL, + "SETTINGS: too large SETTINGS_INITIAL_WINDOW_SIZE"); + } + + rv = session_update_remote_initial_window_size(session, + (int32_t)entry->value); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FLOW_CONTROL, NULL); + } + + session->remote_settings.initial_window_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + + if (entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN || + entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE"); + } + + session->remote_settings.max_frame_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + + session->remote_settings.max_header_list_size = entry->value; + + break; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_ENABLE_CONNECT_PROTOCOL"); + } + + if (!session->server && + session->remote_settings.enable_connect_protocol && + entry->value == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: server attempted to disable " + "SETTINGS_ENABLE_CONNECT_PROTOCOL"); + } + + session->remote_settings.enable_connect_protocol = entry->value; + + break; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_NO_RFC7540_PRIORITIES"); + } + + if (session->remote_settings.no_rfc7540_priorities != UINT32_MAX && + session->remote_settings.no_rfc7540_priorities != entry->value) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: SETTINGS_NO_RFC7540_PRIORITIES cannot be changed"); + } + + session->remote_settings.no_rfc7540_priorities = entry->value; + + break; + } + } + + if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) { + session->remote_settings.no_rfc7540_priorities = 0; + + if (session->server && session->pending_no_rfc7540_priorities && + (session->opt_flags & + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) { + session->fallback_rfc7540_priorities = 1; + } + } + + if (!noack && !session_is_closing(session)) { + rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0); + + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_handle_invalid_connection(session, frame, + NGHTTP2_ERR_INTERNAL, NULL); + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_settings_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + size_t i; + nghttp2_settings_entry min_header_size_entry; + + if (iframe->max_niv) { + min_header_size_entry = iframe->iv[iframe->max_niv - 1]; + + if (min_header_size_entry.value < UINT32_MAX) { + /* If we have less value, then we must have + SETTINGS_HEADER_TABLE_SIZE in i < iframe->niv */ + for (i = 0; i < iframe->niv; ++i) { + if (iframe->iv[i].settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { + break; + } + } + + assert(i < iframe->niv); + + if (min_header_size_entry.value != iframe->iv[i].value) { + iframe->iv[iframe->niv++] = iframe->iv[i]; + iframe->iv[i] = min_header_size_entry; + } + } + } + + nghttp2_frame_unpack_settings_payload(&frame->settings, iframe->iv, + iframe->niv); + + iframe->iv = NULL; + iframe->niv = 0; + iframe->max_niv = 0; + + return nghttp2_session_on_settings_received(session, frame, 0 /* ACK */); +} + +int nghttp2_session_on_push_promise_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + nghttp2_stream *promised_stream; + nghttp2_priority_spec pri_spec; + + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream_id == 0"); + } + if (session->server || session->local_settings.enable_push == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled"); + } + + if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We just discard PUSH_PROMISE after GOAWAY was sent */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (!session_is_new_peer_stream_id(session, + frame->push_promise.promised_stream_id)) { + /* The spec says if an endpoint receives a PUSH_PROMISE with + illegal stream ID is subject to a connection error of type + PROTOCOL_ERROR. */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PUSH_PROMISE: invalid promised_stream_id"); + } + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle"); + } + + session->last_recv_stream_id = frame->push_promise.promised_stream_id; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING || + !session->pending_enable_push || + session->num_incoming_reserved_streams >= + session->max_incoming_reserved_streams) { + /* Currently, client does not retain closed stream, so we don't + check NGHTTP2_SHUT_RD condition here. */ + + rv = nghttp2_session_add_rst_stream( + session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); + if (rv != 0) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, + "PUSH_PROMISE: stream closed"); + } + + nghttp2_priority_spec_init(&pri_spec, stream->stream_id, + NGHTTP2_DEFAULT_WEIGHT, 0); + + promised_stream = nghttp2_session_open_stream( + session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_RESERVED, NULL); + + if (!promised_stream) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't call nghttp2_session_adjust_closed_stream(), since we + don't keep closed stream in client side */ + + session->last_proc_stream_id = session->last_recv_stream_id; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +static int session_process_push_promise_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, + iframe->sbuf.pos); + + return nghttp2_session_on_push_promise_received(session, frame); +} + +int nghttp2_session_on_ping_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PING: stream_id != 0"); + } + if ((session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK) == 0 && + (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 && + !session_is_closing(session)) { + /* Peer sent ping, so ping it back */ + rv = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK, + frame->ping.opaque_data); + if (rv != 0) { + return rv; + } + } + return session_call_on_frame_received(session, frame); +} + +static int session_process_ping_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_ping_payload(&frame->ping, iframe->sbuf.pos); + + return nghttp2_session_on_ping_received(session, frame); +} + +int nghttp2_session_on_goaway_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "GOAWAY: stream_id != 0"); + } + /* Spec says Endpoints MUST NOT increase the value they send in the + last stream identifier. */ + if ((frame->goaway.last_stream_id > 0 && + !nghttp2_session_is_my_stream_id(session, + frame->goaway.last_stream_id)) || + session->remote_last_stream_id < frame->goaway.last_stream_id) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "GOAWAY: invalid last_stream_id"); + } + + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + + session->remote_last_stream_id = frame->goaway.last_stream_id; + + rv = session_call_on_frame_received(session, frame); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_close_stream_on_goaway(session, frame->goaway.last_stream_id, + 0); +} + +static int session_process_goaway_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_goaway_payload(&frame->goaway, iframe->sbuf.pos, + iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_goaway_received(session, frame); +} + +static int +session_on_connection_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + /* Handle connection-level flow control */ + if (frame->window_update.window_size_increment == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE: window_size_increment == 0"); + } + + if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + session->remote_window_size) { + return session_handle_invalid_connection(session, frame, + NGHTTP2_ERR_FLOW_CONTROL, NULL); + } + session->remote_window_size += frame->window_update.window_size_increment; + + return session_call_on_frame_received(session, frame); +} + +static int session_on_stream_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE to idle stream"); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + if (state_reserved_remote(session, stream)) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream"); + } + if (frame->window_update.window_size_increment == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE: window_size_increment == 0"); + } + if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + stream->remote_window_size) { + return session_handle_invalid_stream(session, frame, + NGHTTP2_ERR_FLOW_CONTROL); + } + stream->remote_window_size += frame->window_update.window_size_increment; + + if (stream->remote_window_size > 0 && + nghttp2_stream_check_deferred_by_flow_control(stream)) { + + rv = session_resume_deferred_stream_item( + session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + if (frame->hd.stream_id == 0) { + return session_on_connection_window_update_received(session, frame); + } else { + return session_on_stream_window_update_received(session, frame); + } +} + +static int session_process_window_update_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_window_update_payload(&frame->window_update, + iframe->sbuf.pos); + + return nghttp2_session_on_window_update_received(session, frame); +} + +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_altsvc *altsvc; + nghttp2_stream *stream; + + altsvc = frame->ext.payload; + + /* session->server case has been excluded */ + + if (frame->hd.stream_id == 0) { + if (altsvc->origin_len == 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + } else { + if (altsvc->origin_len > 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return 0; + } + } + + if (altsvc->field_value_len == 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_origin_received(nghttp2_session *session, + nghttp2_frame *frame) { + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_priority_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_priority_update *priority_update; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + nghttp2_extpri extpri; + int rv; + + assert(session->server); + + priority_update = frame->ext.payload; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: stream_id == 0"); + } + + if (nghttp2_session_is_my_stream_id(session, priority_update->stream_id)) { + if (session_detect_idle_stream(session, priority_update->stream_id)) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: prioritizing idle push is not allowed"); + } + + /* TODO Ignore priority signal to a push stream for now */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, priority_update->stream_id); + if (stream) { + /* Stream already exists. */ + if (stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) { + return session_call_on_frame_received(session, frame); + } + } else if (session_detect_idle_stream(session, priority_update->stream_id)) { + if (session->num_idle_streams + session->num_incoming_streams >= + session->local_settings.max_concurrent_streams) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: max concurrent streams exceeded"); + } + + nghttp2_priority_spec_default_init(&pri_spec); + stream = nghttp2_session_open_stream(session, priority_update->stream_id, + NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + } else { + return session_call_on_frame_received(session, frame); + } + + extpri.urgency = NGHTTP2_EXTPRI_DEFAULT_URGENCY; + extpri.inc = 0; + + rv = nghttp2_http_parse_priority(&extpri, priority_update->field_value, + priority_update->field_value_len); + if (rv != 0) { + /* Just ignore field_value if it cannot be parsed. */ + return session_call_on_frame_received(session, frame); + } + + rv = session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_altsvc_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_altsvc_payload( + &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + /* nghttp2_frame_unpack_altsvc_payload steals buffer from + iframe->lbuf */ + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_altsvc_received(session, frame); +} + +static int session_process_origin_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + nghttp2_mem *mem = &session->mem; + int rv; + + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf), mem); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* Ignore ORIGIN frame which cannot be parsed. */ + return 0; + } + + return nghttp2_session_on_origin_received(session, frame); +} + +static int session_process_priority_update_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_priority_update_payload(&frame->ext, iframe->sbuf.pos, + nghttp2_buf_len(&iframe->sbuf)); + + return nghttp2_session_on_priority_update_received(session, frame); +} + +static int session_process_extension_frame(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + rv = session_call_unpack_extension_callback(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + /* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */ + if (rv != 0) { + return 0; + } + + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_data_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + nghttp2_stream *stream; + + /* We don't call on_frame_recv_callback if stream has been closed + already or being closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { + /* This should be treated as stream error, but it results in lots + of RST_STREAM. So just ignore frame against nonexistent stream + for now. */ + return 0; + } + + if (session_enforce_http_messaging(session) && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + if (nghttp2_http_on_remote_end_stream(stream) != 0) { + rv = nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + /* Don't call nghttp2_session_close_stream_if_shut_rdwr because + RST_STREAM has been submitted. */ + return 0; + } + } + + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return 0; +} + +/* For errors, this function only returns FATAL error. */ +static int session_process_data_frame(nghttp2_session *session) { + int rv; + nghttp2_frame *public_data_frame = &session->iframe.frame; + rv = nghttp2_session_on_data_received(session, public_data_frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return 0; +} + +/* + * Now we have SETTINGS synchronization, flow control error can be + * detected strictly. If DATA frame is received with length > 0 and + * current received window size + delta length is strictly larger than + * local window size, it is subject to FLOW_CONTROL_ERROR, so return + * -1. Note that local_window_size is calculated after SETTINGS ACK is + * received from peer, so peer must honor this limit. If the resulting + * recv_window_size is strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * return -1 too. + */ +static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta, + int32_t local_window_size) { + if (*recv_window_size_ptr > local_window_size - (int32_t)delta || + *recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - (int32_t)delta) { + return -1; + } + *recv_window_size_ptr += (int32_t)delta; + return 0; +} + +int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size, + int send_window_update) { + int rv; + rv = adjust_recv_window_size(&stream->recv_window_size, delta_size, + stream->local_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + /* We don't have to send WINDOW_UPDATE if the data received is the + last chunk in the incoming stream. */ + /* We have to use local_settings here because it is the constraint + the remote endpoint should honor. */ + if (send_window_update && + !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + stream->window_update_queued == 0 && + nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, + stream->stream_id, + stream->recv_window_size); + if (rv != 0) { + return rv; + } + + stream->recv_window_size = 0; + } + return 0; +} + +int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session, + size_t delta_size) { + int rv; + rv = adjust_recv_window_size(&session->recv_window_size, delta_size, + session->local_window_size); + if (rv != 0) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + session->window_update_queued == 0 && + nghttp2_should_send_window_update(session->local_window_size, + session->recv_window_size)) { + /* Use stream ID 0 to update connection-level flow control + window */ + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size); + if (rv != 0) { + return rv; + } + + session->recv_window_size = 0; + } + return 0; +} + +static int session_update_consumed_size(nghttp2_session *session, + int32_t *consumed_size_ptr, + int32_t *recv_window_size_ptr, + uint8_t window_update_queued, + int32_t stream_id, size_t delta_size, + int32_t local_window_size) { + int32_t recv_size; + int rv; + + if ((size_t)*consumed_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta_size) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + *consumed_size_ptr += (int32_t)delta_size; + + if (window_update_queued == 0) { + /* recv_window_size may be smaller than consumed_size, because it + may be decreased by negative value with + nghttp2_submit_window_update(). */ + recv_size = nghttp2_min(*consumed_size_ptr, *recv_window_size_ptr); + + if (nghttp2_should_send_window_update(local_window_size, recv_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, + stream_id, recv_size); + + if (rv != 0) { + return rv; + } + + *recv_window_size_ptr -= recv_size; + *consumed_size_ptr -= recv_size; + } + } + + return 0; +} + +static int session_update_stream_consumed_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size) { + return session_update_consumed_size( + session, &stream->consumed_size, &stream->recv_window_size, + stream->window_update_queued, stream->stream_id, delta_size, + stream->local_window_size); +} + +static int session_update_connection_consumed_size(nghttp2_session *session, + size_t delta_size) { + return session_update_consumed_size( + session, &session->consumed_size, &session->recv_window_size, + session->window_update_queued, 0, delta_size, session->local_window_size); +} + +/* + * Checks that we can receive the DATA frame for stream, which is + * indicated by |session->iframe.frame.hd.stream_id|. If it is a + * connection error situation, GOAWAY frame will be issued by this + * function. + * + * If the DATA frame is allowed, returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_IGN_PAYLOAD + * The reception of DATA frame is connection error; or should be + * ignored. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_on_data_received_fail_fast(nghttp2_session *session) { + int rv; + nghttp2_stream *stream; + nghttp2_inbound_frame *iframe; + int32_t stream_id; + const char *failure_reason; + uint32_t error_code = NGHTTP2_PROTOCOL_ERROR; + + iframe = &session->iframe; + stream_id = iframe->frame.hd.stream_id; + + if (stream_id == 0) { + /* The spec says that if a DATA frame is received whose stream ID + is 0, the recipient MUST respond with a connection error of + type PROTOCOL_ERROR. */ + failure_reason = "DATA: stream_id == 0"; + goto fail; + } + + if (session_detect_idle_stream(session, stream_id)) { + failure_reason = "DATA: stream in idle"; + error_code = NGHTTP2_PROTOCOL_ERROR; + goto fail; + } + + stream = nghttp2_session_get_stream(session, stream_id); + if (!stream) { + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { + failure_reason = "DATA: stream closed"; + error_code = NGHTTP2_STREAM_CLOSED; + goto fail; + } + + return NGHTTP2_ERR_IGN_PAYLOAD; + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + failure_reason = "DATA: stream in half-closed(remote)"; + error_code = NGHTTP2_STREAM_CLOSED; + goto fail; + } + + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_IGN_PAYLOAD; + } + if (stream->state != NGHTTP2_STREAM_OPENED) { + failure_reason = "DATA: stream not opened"; + goto fail; + } + return 0; + } + if (stream->state == NGHTTP2_STREAM_RESERVED) { + failure_reason = "DATA: stream in reserved"; + goto fail; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_IGN_PAYLOAD; + } + return 0; +fail: + rv = nghttp2_session_terminate_session_with_reason(session, error_code, + failure_reason); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_PAYLOAD; +} + +static size_t inbound_frame_payload_readlen(nghttp2_inbound_frame *iframe, + const uint8_t *in, + const uint8_t *last) { + return nghttp2_min((size_t)(last - in), iframe->payloadleft); +} + +/* + * Resets iframe->sbuf and advance its mark pointer by |left| bytes. + */ +static void inbound_frame_set_mark(nghttp2_inbound_frame *iframe, size_t left) { + nghttp2_buf_reset(&iframe->sbuf); + iframe->sbuf.mark += left; +} + +static size_t inbound_frame_buf_read(nghttp2_inbound_frame *iframe, + const uint8_t *in, const uint8_t *last) { + size_t readlen; + + readlen = + nghttp2_min((size_t)(last - in), nghttp2_buf_mark_avail(&iframe->sbuf)); + + iframe->sbuf.last = nghttp2_cpymem(iframe->sbuf.last, in, readlen); + + return readlen; +} + +/* + * Unpacks SETTINGS entry in iframe->sbuf. + */ +static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) { + nghttp2_settings_entry iv; + nghttp2_settings_entry *min_header_table_size_entry; + size_t i; + + nghttp2_frame_unpack_settings_entry(&iv, iframe->sbuf.pos); + + switch (iv.settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + case NGHTTP2_SETTINGS_ENABLE_PUSH: + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + break; + default: + DEBUGF("recv: unknown settings id=0x%02x\n", iv.settings_id); + + iframe->iv[iframe->niv++] = iv; + + return; + } + + for (i = 0; i < iframe->niv; ++i) { + if (iframe->iv[i].settings_id == iv.settings_id) { + iframe->iv[i] = iv; + break; + } + } + + if (i == iframe->niv) { + iframe->iv[iframe->niv++] = iv; + } + + if (iv.settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { + /* Keep track of minimum value of SETTINGS_HEADER_TABLE_SIZE */ + min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; + + if (iv.value < min_header_table_size_entry->value) { + min_header_table_size_entry->value = iv.value; + } + } +} + +/* + * Checks PADDED flags and set iframe->sbuf to read them accordingly. + * If padding is set, this function returns 1. If no padding is set, + * this function returns 0. On error, returns -1. + */ +static int inbound_frame_handle_pad(nghttp2_inbound_frame *iframe, + nghttp2_frame_hd *hd) { + if (hd->flags & NGHTTP2_FLAG_PADDED) { + if (hd->length < 1) { + return -1; + } + inbound_frame_set_mark(iframe, 1); + return 1; + } + DEBUGF("recv: no padding in payload\n"); + return 0; +} + +/* + * Computes number of padding based on flags. This function returns + * the calculated length if it succeeds, or -1. + */ +static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) { + size_t padlen; + + /* 1 for Pad Length field */ + padlen = (size_t)(iframe->sbuf.pos[0] + 1); + + DEBUGF("recv: padlen=%zu\n", padlen); + + /* We cannot use iframe->frame.hd.length because of CONTINUATION */ + if (padlen - 1 > iframe->payloadleft) { + return -1; + } + + iframe->padlen = padlen; + + return (ssize_t)padlen; +} + +/* + * This function returns the effective payload length in the data of + * length |readlen| when the remaining payload is |payloadleft|. The + * |payloadleft| does not include |readlen|. If padding was started + * strictly before this data chunk, this function returns -1. + */ +static ssize_t inbound_frame_effective_readlen(nghttp2_inbound_frame *iframe, + size_t payloadleft, + size_t readlen) { + size_t trail_padlen = + nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); + + if (trail_padlen > payloadleft) { + size_t padlen; + padlen = trail_padlen - payloadleft; + if (readlen < padlen) { + return -1; + } + return (ssize_t)(readlen - padlen); + } + return (ssize_t)(readlen); +} + +static const uint8_t static_in[] = {0}; + +ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, + size_t inlen) { + const uint8_t *first, *last; + nghttp2_inbound_frame *iframe = &session->iframe; + size_t readlen; + ssize_t padlen; + int rv; + int busy = 0; + nghttp2_frame_hd cont_hd; + nghttp2_stream *stream; + size_t pri_fieldlen; + nghttp2_mem *mem; + + if (in == NULL) { + assert(inlen == 0); + in = static_in; + } + + first = in; + last = in + inlen; + + DEBUGF("recv: connection recv_window_size=%d, local_window=%d\n", + session->recv_window_size, session->local_window_size); + + mem = &session->mem; + + /* We may have idle streams more than we expect (e.g., + nghttp2_session_change_stream_priority() or + nghttp2_session_create_idle_stream()). Adjust them here. */ + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (!nghttp2_session_want_read(session)) { + return (ssize_t)inlen; + } + + for (;;) { + switch (iframe->state) { + case NGHTTP2_IB_READ_CLIENT_MAGIC: + readlen = nghttp2_min(inlen, iframe->payloadleft); + + if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - + iframe->payloadleft], + in, readlen) != 0) { + return NGHTTP2_ERR_BAD_CLIENT_MAGIC; + } + + iframe->payloadleft -= readlen; + in += readlen; + + if (iframe->payloadleft == 0) { + session_inbound_frame_reset(session); + iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS; + } + + break; + case NGHTTP2_IB_READ_FIRST_SETTINGS: + DEBUGF("recv: [IB_READ_FIRST_SETTINGS]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + if (iframe->sbuf.pos[3] != NGHTTP2_SETTINGS || + (iframe->sbuf.pos[4] & NGHTTP2_FLAG_ACK)) { + rv = session_call_error_callback( + session, NGHTTP2_ERR_SETTINGS_EXPECTED, + "Remote peer returned unexpected data while we expected " + "SETTINGS frame. Perhaps, peer does not support HTTP/2 " + "properly."); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "SETTINGS expected"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + iframe->state = NGHTTP2_IB_READ_HEAD; + + /* Fall through */ + case NGHTTP2_IB_READ_HEAD: { + int on_begin_frame_called = 0; + + DEBUGF("recv: [IB_READ_HEAD]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos); + iframe->payloadleft = iframe->frame.hd.length; + + DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", + iframe->frame.hd.length, iframe->frame.hd.type, + iframe->frame.hd.flags, iframe->frame.hd.stream_id); + + if (iframe->frame.hd.length > session->local_settings.max_frame_size) { + DEBUGF("recv: length is too large %zu > %u\n", iframe->frame.hd.length, + session->local_settings.max_frame_size); + + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_FRAME_SIZE_ERROR, "too large frame size"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_DATA: { + DEBUGF("recv: DATA\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PADDED); + /* Check stream is open. If it is not open or closing, + ignore payload. */ + busy = 1; + + rv = session_on_data_received_fail_fast(session); + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + if (rv == NGHTTP2_ERR_IGN_PAYLOAD) { + DEBUGF("recv: DATA not allowed stream_id=%d\n", + iframe->frame.hd.stream_id); + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "DATA: insufficient padding space"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_PAD_DATA; + break; + } + + iframe->state = NGHTTP2_IB_READ_DATA; + break; + } + case NGHTTP2_HEADERS: + + DEBUGF("recv: HEADERS\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PRIORITY); + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "HEADERS: insufficient padding space"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_NBYTE; + break; + } + + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + + if (pri_fieldlen > 0) { + if (iframe->payloadleft < pri_fieldlen) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, pri_fieldlen); + + break; + } + + /* Call on_begin_frame_callback here because + session_process_headers_frame() may call + on_begin_headers_callback */ + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + on_begin_frame_called = 1; + + rv = session_process_headers_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PRIORITY: + DEBUGF("recv: PRIORITY\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) { + busy = 1; + + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN); + + break; + case NGHTTP2_RST_STREAM: + case NGHTTP2_WINDOW_UPDATE: +#ifdef DEBUGBUILD + switch (iframe->frame.hd.type) { + case NGHTTP2_RST_STREAM: + DEBUGF("recv: RST_STREAM\n"); + break; + case NGHTTP2_WINDOW_UPDATE: + DEBUGF("recv: WINDOW_UPDATE\n"); + break; + } +#endif /* DEBUGBUILD */ + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft != 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + case NGHTTP2_SETTINGS: + DEBUGF("recv: SETTINGS\n"); + + iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; + + if ((iframe->frame.hd.length % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) || + ((iframe->frame.hd.flags & NGHTTP2_FLAG_ACK) && + iframe->payloadleft > 0)) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + /* Check the settings flood counter early to be safe */ + if (session->obq_flood_counter_ >= session->max_outbound_ack && + !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) { + return NGHTTP2_ERR_FLOODED; + } + + iframe->state = NGHTTP2_IB_READ_SETTINGS; + + if (iframe->payloadleft) { + nghttp2_settings_entry *min_header_table_size_entry; + + /* We allocate iv with additional one entry, to store the + minimum header table size. */ + iframe->max_niv = + iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1; + + if (iframe->max_niv - 1 > session->max_settings) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_ENHANCE_YOUR_CALM, + "SETTINGS: too many setting entries"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) * + iframe->max_niv); + + if (!iframe->iv) { + return NGHTTP2_ERR_NOMEM; + } + + min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; + min_header_table_size_entry->settings_id = + NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + min_header_table_size_entry->value = UINT32_MAX; + + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); + break; + } + + busy = 1; + + inbound_frame_set_mark(iframe, 0); + + break; + case NGHTTP2_PUSH_PROMISE: + DEBUGF("recv: PUSH_PROMISE\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED); + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PUSH_PROMISE: insufficient padding space"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_NBYTE; + break; + } + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + case NGHTTP2_PING: + DEBUGF("recv: PING\n"); + + iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; + + if (iframe->payloadleft != 8) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 8); + + break; + case NGHTTP2_GOAWAY: + DEBUGF("recv: GOAWAY\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft < 8) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 8); + + break; + case NGHTTP2_CONTINUATION: + DEBUGF("recv: unexpected CONTINUATION\n"); + + /* Receiving CONTINUATION in this state are subject to + connection error of type PROTOCOL_ERROR */ + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "CONTINUATION: unexpected"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + default: + DEBUGF("recv: extension frame\n"); + + if (check_ext_type_set(session->user_recv_ext_types, + iframe->frame.hd.type)) { + if (!session->callbacks.unpack_extension_callback) { + /* Silently ignore unknown frame type. */ + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD; + + break; + } else { + switch (iframe->frame.hd.type) { + case NGHTTP2_ALTSVC: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) == + 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: ALTSVC\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = &iframe->ext_frame_payload.altsvc; + + if (session->server) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + if (iframe->payloadleft < 2) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 2); + + break; + case NGHTTP2_ORIGIN: + if (!(session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: ORIGIN\n"); + + iframe->frame.ext.payload = &iframe->ext_frame_payload.origin; + + if (session->server || iframe->frame.hd.stream_id || + (iframe->frame.hd.flags & 0xf0)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft) { + iframe->raw_lbuf = nghttp2_mem_malloc(mem, iframe->payloadleft); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->payloadleft); + } else { + busy = 1; + } + + iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD; + + break; + case NGHTTP2_PRIORITY_UPDATE: + if ((session->builtin_recv_ext_types & + NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: PRIORITY_UPDATE\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = + &iframe->ext_frame_payload.priority_update; + + if (!session->server) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PRIORITY_UPDATE is received from server"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (!session_no_rfc7540_pri_no_fallback(session) || + iframe->payloadleft > sizeof(iframe->raw_sbuf)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, iframe->payloadleft); + + break; + default: + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } + } + + if (!on_begin_frame_called) { + switch (iframe->state) { + case NGHTTP2_IB_IGN_HEADER_BLOCK: + case NGHTTP2_IB_IGN_PAYLOAD: + case NGHTTP2_IB_FRAME_SIZE_ERROR: + case NGHTTP2_IB_IGN_DATA: + case NGHTTP2_IB_IGN_ALL: + break; + default: + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + + break; + } + case NGHTTP2_IB_READ_NBYTE: + DEBUGF("recv: [IB_READ_NBYTE]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + iframe->payloadleft -= readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zd\n", readlen, + iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_HEADERS: + if (iframe->padlen == 0 && + (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0 || + (size_t)padlen + pri_fieldlen > 1 + iframe->payloadleft) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + iframe->frame.headers.padlen = (size_t)padlen; + + if (pri_fieldlen > 0) { + if (iframe->payloadleft < pri_fieldlen) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, pri_fieldlen); + break; + } else { + /* Truncate buffers used for padding spec */ + inbound_frame_set_mark(iframe, 0); + } + } + + rv = session_process_headers_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PRIORITY: + if (!session_no_rfc7540_pri_no_fallback(session) && + session->remote_settings.no_rfc7540_priorities != 1) { + rv = session_process_priority_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_RST_STREAM: + rv = session_process_rst_stream_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_PUSH_PROMISE: + if (iframe->padlen == 0 && + (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0 || (size_t)padlen + 4 /* promised stream id */ + > 1 + iframe->payloadleft) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PUSH_PROMISE: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->frame.push_promise.padlen = (size_t)padlen; + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + } + + rv = session_process_push_promise_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.push_promise.promised_stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PING: + rv = session_process_ping_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_GOAWAY: { + size_t debuglen; + + /* 8 is Last-stream-ID + Error Code */ + debuglen = iframe->frame.hd.length - 8; + + if (debuglen > 0) { + iframe->raw_lbuf = nghttp2_mem_malloc(mem, debuglen); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, debuglen); + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_GOAWAY_DEBUG; + + break; + } + case NGHTTP2_WINDOW_UPDATE: + rv = session_process_window_update_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_ALTSVC: { + size_t origin_len; + + origin_len = nghttp2_get_uint16(iframe->sbuf.pos); + + DEBUGF("recv: origin_len=%zu\n", origin_len); + + if (origin_len > iframe->payloadleft) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (iframe->frame.hd.length > 2) { + iframe->raw_lbuf = + nghttp2_mem_malloc(mem, iframe->frame.hd.length - 2); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->frame.hd.length); + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD; + + break; + case NGHTTP2_PRIORITY_UPDATE: + DEBUGF("recv: prioritized_stream_id=%d\n", + nghttp2_get_uint32(iframe->sbuf.pos) & NGHTTP2_STREAM_ID_MASK); + + rv = session_process_priority_update_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + } + default: + /* This is unknown frame */ + session_inbound_frame_reset(session); + + break; + } + break; + case NGHTTP2_IB_READ_HEADER_BLOCK: + case NGHTTP2_IB_IGN_HEADER_BLOCK: { + ssize_t data_readlen; + size_t trail_padlen; + int final; +#ifdef DEBUGBUILD + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + DEBUGF("recv: [IB_READ_HEADER_BLOCK]\n"); + } else { + DEBUGF("recv: [IB_IGN_HEADER_BLOCK]\n"); + } +#endif /* DEBUGBUILD */ + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft - readlen); + + data_readlen = inbound_frame_effective_readlen( + iframe, iframe->payloadleft - readlen, readlen); + + if (data_readlen == -1) { + /* everything is padding */ + data_readlen = 0; + } + + trail_padlen = nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); + + final = (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) && + iframe->payloadleft - (size_t)data_readlen == trail_padlen; + + if (data_readlen > 0 || (data_readlen == 0 && final)) { + size_t hd_proclen = 0; + + DEBUGF("recv: block final=%d\n", final); + + rv = + inflate_header_block(session, &iframe->frame, &hd_proclen, + (uint8_t *)in, (size_t)data_readlen, final, + iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_PAUSE) { + in += hd_proclen; + iframe->payloadleft -= hd_proclen; + + return (ssize_t)(in - first); + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + /* The application says no more headers. We decompress the + rest of the header block but not invoke on_header_callback + and on_frame_recv_callback. */ + in += hd_proclen; + iframe->payloadleft -= hd_proclen; + + /* Use promised stream ID for PUSH_PROMISE */ + rv = nghttp2_session_add_rst_stream( + session, + iframe->frame.hd.type == NGHTTP2_PUSH_PROMISE + ? iframe->frame.push_promise.promised_stream_id + : iframe->frame.hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + in += readlen; + iframe->payloadleft -= readlen; + + if (rv == NGHTTP2_ERR_HEADER_COMP) { + /* GOAWAY is already issued */ + if (iframe->payloadleft == 0) { + session_inbound_frame_reset(session); + } else { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + } + break; + } + } else { + in += readlen; + iframe->payloadleft -= readlen; + } + + if (iframe->payloadleft) { + break; + } + + if ((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) { + + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_HDLEN); + + iframe->padlen = 0; + + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION; + } else { + iframe->state = NGHTTP2_IB_IGN_CONTINUATION; + } + } else { + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + rv = session_after_header_block_received(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + session_inbound_frame_reset(session); + } + break; + } + case NGHTTP2_IB_IGN_PAYLOAD: + DEBUGF("recv: [IB_IGN_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + break; + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_HEADERS: + case NGHTTP2_PUSH_PROMISE: + case NGHTTP2_CONTINUATION: + /* Mark inflater bad so that we won't perform further decoding */ + session->hd_inflater.ctx.bad = 1; + break; + default: + break; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_FRAME_SIZE_ERROR: + DEBUGF("recv: [IB_FRAME_SIZE_ERROR]\n"); + + rv = session_handle_frame_size_error(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + assert(iframe->state == NGHTTP2_IB_IGN_ALL); + + return (ssize_t)inlen; + case NGHTTP2_IB_READ_SETTINGS: + DEBUGF("recv: [IB_READ_SETTINGS]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + break; + } + + if (readlen > 0) { + inbound_frame_set_settings_entry(iframe); + } + if (iframe->payloadleft) { + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); + break; + } + + rv = session_process_settings_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_GOAWAY_DEBUG: + DEBUGF("recv: [IB_READ_GOAWAY_DEBUG]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_goaway_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_EXPECT_CONTINUATION: + case NGHTTP2_IB_IGN_CONTINUATION: +#ifdef DEBUGBUILD + if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + fprintf(stderr, "recv: [IB_EXPECT_CONTINUATION]\n"); + } else { + fprintf(stderr, "recv: [IB_IGN_CONTINUATION]\n"); + } +#endif /* DEBUGBUILD */ + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->sbuf.pos); + iframe->payloadleft = cont_hd.length; + + DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", + cont_hd.length, cont_hd.type, cont_hd.flags, cont_hd.stream_id); + + if (cont_hd.type != NGHTTP2_CONTINUATION || + cont_hd.stream_id != iframe->frame.hd.stream_id) { + DEBUGF("recv: expected stream_id=%d, type=%d, but got stream_id=%d, " + "type=%u\n", + iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION, + cont_hd.stream_id, cont_hd.type); + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "unexpected non-CONTINUATION frame or stream_id is invalid"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + /* CONTINUATION won't bear NGHTTP2_PADDED flag */ + + iframe->frame.hd.flags = + (uint8_t)(iframe->frame.hd.flags | + (cont_hd.flags & NGHTTP2_FLAG_END_HEADERS)); + iframe->frame.hd.length += cont_hd.length; + + busy = 1; + + if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + rv = session_call_on_begin_frame(session, &cont_hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } else { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + } + + break; + case NGHTTP2_IB_READ_PAD_DATA: + DEBUGF("recv: [IB_READ_PAD_DATA]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + iframe->payloadleft -= readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zu\n", readlen, + iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + /* Pad Length field is subject to flow control */ + rv = nghttp2_session_update_recv_connection_window_size(session, readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + /* Pad Length field is consumed immediately */ + rv = + nghttp2_session_consume(session, iframe->frame.hd.stream_id, readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); + if (stream) { + rv = nghttp2_session_update_recv_stream_window_size( + session, stream, readlen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + busy = 1; + + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "DATA: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->frame.data.padlen = (size_t)padlen; + + iframe->state = NGHTTP2_IB_READ_DATA; + + break; + case NGHTTP2_IB_READ_DATA: + stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); + + if (!stream) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + + DEBUGF("recv: [IB_READ_DATA]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + ssize_t data_readlen; + + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + rv = nghttp2_session_update_recv_stream_window_size( + session, stream, readlen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + data_readlen = inbound_frame_effective_readlen( + iframe, iframe->payloadleft, readlen); + + if (data_readlen == -1) { + /* everything is padding */ + data_readlen = 0; + } + + padlen = (ssize_t)readlen - data_readlen; + + if (padlen > 0) { + /* Padding is considered as "consumed" immediately */ + rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id, + (size_t)padlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + + DEBUGF("recv: data_readlen=%zd\n", data_readlen); + + if (data_readlen > 0) { + if (session_enforce_http_messaging(session)) { + if (nghttp2_http_on_data_chunk(stream, (size_t)data_readlen) != 0) { + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + /* Consume all data for connection immediately here */ + rv = session_update_connection_consumed_size( + session, (size_t)data_readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_DATA) { + return (ssize_t)inlen; + } + } + + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + } + if (session->callbacks.on_data_chunk_recv_callback) { + rv = session->callbacks.on_data_chunk_recv_callback( + session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, + in - readlen, (size_t)data_readlen, session->user_data); + if (rv == NGHTTP2_ERR_PAUSE) { + return (ssize_t)(in - first); + } + + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + } + } + + if (iframe->payloadleft) { + break; + } + + rv = session_process_data_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_IGN_DATA: + DEBUGF("recv: [IB_IGN_DATA]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + /* Update connection-level flow control window for ignored + DATA frame too */ + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + + /* Ignored DATA is considered as "consumed" immediately. */ + rv = session_update_connection_consumed_size(session, readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + } + + if (iframe->payloadleft) { + break; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_IGN_ALL: + return (ssize_t)inlen; + case NGHTTP2_IB_READ_EXTENSION_PAYLOAD: + DEBUGF("recv: [IB_READ_EXTENSION_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + rv = session_call_on_extension_chunk_recv_callback( + session, in - readlen, readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv != 0) { + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } + + if (iframe->payloadleft > 0) { + break; + } + + rv = session_process_extension_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_ALTSVC_PAYLOAD: + DEBUGF("recv: [IB_READ_ALTSVC_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_altsvc_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_ORIGIN_PAYLOAD: + DEBUGF("recv: [IB_READ_ORIGIN_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_origin_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + } + + if (!busy && in == last) { + break; + } + + busy = 0; + } + + assert(in == last); + + return (ssize_t)(in - first); +} + +int nghttp2_session_recv(nghttp2_session *session) { + uint8_t buf[NGHTTP2_INBOUND_BUFFER_LENGTH]; + while (1) { + ssize_t readlen; + readlen = session_recv(session, buf, sizeof(buf)); + if (readlen > 0) { + ssize_t proclen = nghttp2_session_mem_recv(session, buf, (size_t)readlen); + if (proclen < 0) { + return (int)proclen; + } + assert(proclen == readlen); + } else if (readlen == 0 || readlen == NGHTTP2_ERR_WOULDBLOCK) { + return 0; + } else if (readlen == NGHTTP2_ERR_EOF) { + return NGHTTP2_ERR_EOF; + } else if (readlen < 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } +} + +/* + * Returns the number of active streams, which includes streams in + * reserved state. + */ +static size_t session_get_num_active_streams(nghttp2_session *session) { + return nghttp2_map_size(&session->streams) - session->num_closed_streams - + session->num_idle_streams; +} + +int nghttp2_session_want_read(nghttp2_session *session) { + size_t num_active_streams; + + /* If this flag is set, we don't want to read. The application + should drop the connection. */ + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { + return 0; + } + + num_active_streams = session_get_num_active_streams(session); + + /* Unless termination GOAWAY is sent or received, we always want to + read incoming frames. */ + + if (num_active_streams > 0) { + return 1; + } + + /* If there is no active streams and GOAWAY has been sent or + received, we are done with this session. */ + return (session->goaway_flags & + (NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0; +} + +int nghttp2_session_want_write(nghttp2_session *session) { + /* If these flag is set, we don't want to write any data. The + application should drop the connection. */ + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { + return 0; + } + + /* + * Unless termination GOAWAY is sent or received, we want to write + * frames if there is pending ones. If pending frame is request/push + * response HEADERS and concurrent stream limit is reached, we don't + * want to write them. + */ + return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) || + nghttp2_outbound_queue_top(&session->ob_reg) || + ((!nghttp2_pq_empty(&session->root.obq) || + !session_sched_empty(session)) && + session->remote_window_size > 0) || + (nghttp2_outbound_queue_top(&session->ob_syn) && + !session_is_outgoing_concurrent_streams_max(session)); +} + +int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + + if ((flags & NGHTTP2_FLAG_ACK) && + session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_ping_init(&frame->ping, flags, opaque_data); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_ping_free(&frame->ping); + nghttp2_mem_free(mem, item); + return rv; + } + + if (flags & NGHTTP2_FLAG_ACK) { + ++session->obq_flood_counter_; + } + + return 0; +} + +int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id, + uint32_t error_code, const uint8_t *opaque_data, + size_t opaque_data_len, uint8_t aux_flags) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + uint8_t *opaque_data_copy = NULL; + nghttp2_goaway_aux_data *aux_data; + nghttp2_mem *mem; + + mem = &session->mem; + + if (nghttp2_session_is_my_stream_id(session, last_stream_id)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (opaque_data_len) { + if (opaque_data_len + 8 > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + opaque_data_copy = nghttp2_mem_malloc(mem, opaque_data_len); + if (opaque_data_copy == NULL) { + return NGHTTP2_ERR_NOMEM; + } + memcpy(opaque_data_copy, opaque_data, opaque_data_len); + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + nghttp2_mem_free(mem, opaque_data_copy); + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + /* last_stream_id must not be increased from the value previously + sent */ + last_stream_id = nghttp2_min(last_stream_id, session->local_last_stream_id); + + nghttp2_frame_goaway_init(&frame->goaway, last_stream_id, error_code, + opaque_data_copy, opaque_data_len); + + aux_data = &item->aux_data.goaway; + aux_data->flags = aux_flags; + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_goaway_free(&frame->goaway, mem); + nghttp2_mem_free(mem, item); + return rv; + } + + session->goaway_flags |= NGHTTP2_GOAWAY_SUBMITTED; + + return 0; +} + +int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + int32_t window_size_increment) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_window_update_init(&frame->window_update, flags, stream_id, + window_size_increment); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_window_update_free(&frame->window_update); + nghttp2_mem_free(mem, item); + return rv; + } + return 0; +} + +static void +session_append_inflight_settings(nghttp2_session *session, + nghttp2_inflight_settings *settings) { + nghttp2_inflight_settings **i; + + for (i = &session->inflight_settings_head; *i; i = &(*i)->next) + ; + + *i = settings; +} + +int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv) { + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_settings_entry *iv_copy; + size_t i; + int rv; + nghttp2_mem *mem; + nghttp2_inflight_settings *inflight_settings = NULL; + uint8_t no_rfc7540_pri = session->pending_no_rfc7540_priorities; + + mem = &session->mem; + + if (flags & NGHTTP2_FLAG_ACK) { + if (niv != 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + } + + if (!nghttp2_iv_check(iv, niv)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + for (i = 0; i < niv; ++i) { + if (iv[i].settings_id != NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES) { + continue; + } + + if (no_rfc7540_pri == UINT8_MAX) { + no_rfc7540_pri = (uint8_t)iv[i].value; + continue; + } + + if (iv[i].value != (uint32_t)no_rfc7540_pri) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + if (niv > 0) { + iv_copy = nghttp2_frame_iv_copy(iv, niv, mem); + if (iv_copy == NULL) { + nghttp2_mem_free(mem, item); + return NGHTTP2_ERR_NOMEM; + } + } else { + iv_copy = NULL; + } + + if ((flags & NGHTTP2_FLAG_ACK) == 0) { + rv = inflight_settings_new(&inflight_settings, iv, niv, mem); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + nghttp2_mem_free(mem, iv_copy); + nghttp2_mem_free(mem, item); + return rv; + } + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv); + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + /* The only expected error is fatal one */ + assert(nghttp2_is_fatal(rv)); + + inflight_settings_del(inflight_settings, mem); + + nghttp2_frame_settings_free(&frame->settings, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + if (flags & NGHTTP2_FLAG_ACK) { + ++session->obq_flood_counter_; + } else { + session_append_inflight_settings(session, inflight_settings); + } + + /* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS and ENABLE_PUSH + here. We use it to refuse the incoming stream and PUSH_PROMISE + with RST_STREAM. */ + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { + session->pending_local_max_concurrent_stream = iv[i - 1].value; + break; + } + } + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_PUSH) { + session->pending_enable_push = (uint8_t)iv[i - 1].value; + break; + } + } + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) { + session->pending_enable_connect_protocol = (uint8_t)iv[i - 1].value; + break; + } + } + + if (no_rfc7540_pri == UINT8_MAX) { + session->pending_no_rfc7540_priorities = 0; + } else { + session->pending_no_rfc7540_priorities = no_rfc7540_pri; + } + + return 0; +} + +int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, + size_t datamax, nghttp2_frame *frame, + nghttp2_data_aux_data *aux_data, + nghttp2_stream *stream) { + int rv; + uint32_t data_flags; + ssize_t payloadlen; + ssize_t padded_payloadlen; + nghttp2_buf *buf; + size_t max_payloadlen; + + assert(bufs->head == bufs->cur); + + buf = &bufs->cur->buf; + + if (session->callbacks.read_length_callback) { + + payloadlen = session->callbacks.read_length_callback( + session, frame->hd.type, stream->stream_id, session->remote_window_size, + stream->remote_window_size, session->remote_settings.max_frame_size, + session->user_data); + + DEBUGF("send: read_length_callback=%zd\n", payloadlen); + + payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream, + payloadlen); + + DEBUGF("send: read_length_callback after flow control=%zd\n", payloadlen); + + if (payloadlen <= 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if ((size_t)payloadlen > nghttp2_buf_avail(buf)) { + /* Resize the current buffer(s). The reason why we do +1 for + buffer size is for possible padding field. */ + rv = nghttp2_bufs_realloc(&session->aob.framebufs, + (size_t)(NGHTTP2_FRAME_HDLEN + 1 + payloadlen)); + + if (rv != 0) { + DEBUGF("send: realloc buffer failed rv=%d", rv); + /* If reallocation failed, old buffers are still in tact. So + use safe limit. */ + payloadlen = (ssize_t)datamax; + + DEBUGF("send: use safe limit payloadlen=%zd", payloadlen); + } else { + assert(&session->aob.framebufs == bufs); + + buf = &bufs->cur->buf; + } + } + datamax = (size_t)payloadlen; + } + + /* Current max DATA length is less then buffer chunk size */ + assert(nghttp2_buf_avail(buf) >= datamax); + + data_flags = NGHTTP2_DATA_FLAG_NONE; + payloadlen = aux_data->data_prd.read_callback( + session, frame->hd.stream_id, buf->pos, datamax, &data_flags, + &aux_data->data_prd.source, session->user_data); + + if (payloadlen == NGHTTP2_ERR_DEFERRED || + payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE || + payloadlen == NGHTTP2_ERR_PAUSE) { + DEBUGF("send: DATA postponed due to %s\n", + nghttp2_strerror((int)payloadlen)); + + return (int)payloadlen; + } + + if (payloadlen < 0 || datamax < (size_t)payloadlen) { + /* This is the error code when callback is failed. */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + buf->last = buf->pos + payloadlen; + buf->pos -= NGHTTP2_FRAME_HDLEN; + + /* Clear flags, because this may contain previous flags of previous + DATA */ + frame->hd.flags = NGHTTP2_FLAG_NONE; + + if (data_flags & NGHTTP2_DATA_FLAG_EOF) { + aux_data->eof = 1; + /* If NGHTTP2_DATA_FLAG_NO_END_STREAM is set, don't set + NGHTTP2_FLAG_END_STREAM */ + if ((aux_data->flags & NGHTTP2_FLAG_END_STREAM) && + (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) == 0) { + frame->hd.flags |= NGHTTP2_FLAG_END_STREAM; + } + } + + if (data_flags & NGHTTP2_DATA_FLAG_NO_COPY) { + if (session->callbacks.send_data_callback == NULL) { + DEBUGF("NGHTTP2_DATA_FLAG_NO_COPY requires send_data_callback set\n"); + + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + aux_data->no_copy = 1; + } + + frame->hd.length = (size_t)payloadlen; + frame->data.padlen = 0; + + max_payloadlen = nghttp2_min(datamax, frame->hd.length + NGHTTP2_MAX_PADLEN); + + padded_payloadlen = + session_call_select_padding(session, frame, max_payloadlen); + + if (nghttp2_is_fatal((int)padded_payloadlen)) { + return (int)padded_payloadlen; + } + + frame->data.padlen = (size_t)(padded_payloadlen - payloadlen); + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_frame_add_pad(bufs, &frame->hd, frame->data.padlen, + aux_data->no_copy); + + session_reschedule_stream(session, stream); + + if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) && + (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) { + /* DATA payload length is 0, and DATA frame does not bear + END_STREAM. In this case, there is no point to send 0 length + DATA frame. */ + return NGHTTP2_ERR_CANCEL; + } + + return 0; +} + +void *nghttp2_session_get_stream_user_data(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream) { + return stream->stream_user_data; + } else { + return NULL; + } +} + +int nghttp2_session_set_stream_user_data(nghttp2_session *session, + int32_t stream_id, + void *stream_user_data) { + nghttp2_stream *stream; + nghttp2_frame *frame; + nghttp2_outbound_item *item; + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream) { + stream->stream_user_data = stream_user_data; + return 0; + } + + if (session->server || !nghttp2_session_is_my_stream_id(session, stream_id) || + !nghttp2_outbound_queue_top(&session->ob_syn)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (frame->hd.stream_id > stream_id || + (uint32_t)stream_id >= session->next_stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + for (item = session->ob_syn.head; item; item = item->qnext) { + if (item->frame.hd.stream_id < stream_id) { + continue; + } + + if (item->frame.hd.stream_id > stream_id) { + break; + } + + item->aux_data.headers.stream_user_data = stream_user_data; + return 0; + } + + return NGHTTP2_ERR_INVALID_ARGUMENT; +} + +int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) { + int rv; + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL || !nghttp2_stream_check_deferred_item(stream)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = session_resume_deferred_stream_item(session, stream, + NGHTTP2_STREAM_FLAG_DEFERRED_USER); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session) { + return nghttp2_outbound_queue_size(&session->ob_urgent) + + nghttp2_outbound_queue_size(&session->ob_reg) + + nghttp2_outbound_queue_size(&session->ob_syn); + /* TODO account for item attached to stream */ +} + +int32_t +nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + return stream->recv_window_size < 0 ? 0 : stream->recv_window_size; +} + +int32_t +nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + return stream->local_window_size; +} + +int32_t nghttp2_session_get_stream_local_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + int32_t size; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + + size = stream->local_window_size - stream->recv_window_size; + + /* size could be negative if local endpoint reduced + SETTINGS_INITIAL_WINDOW_SIZE */ + if (size < 0) { + return 0; + } + + return size; +} + +int32_t +nghttp2_session_get_effective_recv_data_length(nghttp2_session *session) { + return session->recv_window_size < 0 ? 0 : session->recv_window_size; +} + +int32_t +nghttp2_session_get_effective_local_window_size(nghttp2_session *session) { + return session->local_window_size; +} + +int32_t nghttp2_session_get_local_window_size(nghttp2_session *session) { + return session->local_window_size - session->recv_window_size; +} + +int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + + /* stream->remote_window_size can be negative when + SETTINGS_INITIAL_WINDOW_SIZE is changed. */ + return nghttp2_max(0, stream->remote_window_size); +} + +int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session) { + return session->remote_window_size; +} + +uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, + nghttp2_settings_id id) { + switch (id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return session->remote_settings.header_table_size; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return session->remote_settings.enable_push; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return session->remote_settings.max_concurrent_streams; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + return session->remote_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->remote_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return session->remote_settings.max_header_list_size; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + return session->remote_settings.enable_connect_protocol; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + return session->remote_settings.no_rfc7540_priorities; + } + + assert(0); + abort(); /* if NDEBUG is set */ +} + +uint32_t nghttp2_session_get_local_settings(nghttp2_session *session, + nghttp2_settings_id id) { + switch (id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return session->local_settings.header_table_size; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return session->local_settings.enable_push; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return session->local_settings.max_concurrent_streams; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + return session->local_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->local_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return session->local_settings.max_header_list_size; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + return session->local_settings.enable_connect_protocol; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + return session->local_settings.no_rfc7540_priorities; + } + + assert(0); + abort(); /* if NDEBUG is set */ +} + +static int nghttp2_session_upgrade_internal(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data) { + nghttp2_stream *stream; + nghttp2_frame frame; + nghttp2_settings_entry *iv; + size_t niv; + int rv; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = &session->mem; + + if ((!session->server && session->next_stream_id != 1) || + (session->server && session->last_recv_stream_id >= 1)) { + return NGHTTP2_ERR_PROTO; + } + if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + /* SETTINGS frame contains too many settings */ + if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH > + session->max_settings) { + return NGHTTP2_ERR_TOO_MANY_SETTINGS; + } + rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload, + settings_payloadlen, mem); + if (rv != 0) { + return rv; + } + + if (session->server) { + nghttp2_frame_hd_init(&frame.hd, settings_payloadlen, NGHTTP2_SETTINGS, + NGHTTP2_FLAG_NONE, 0); + frame.settings.iv = iv; + frame.settings.niv = niv; + rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */); + } else { + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv); + } + nghttp2_mem_free(mem, iv); + if (rv != 0) { + return rv; + } + + nghttp2_priority_spec_default_init(&pri_spec); + + stream = nghttp2_session_open_stream( + session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_OPENING, + session->server ? NULL : stream_user_data); + if (stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't call nghttp2_session_adjust_closed_stream(), since this + should be the first stream open. */ + + if (session->server) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + session->last_recv_stream_id = 1; + session->last_proc_stream_id = 1; + } else { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + session->last_sent_stream_id = 1; + session->next_stream_id += 2; + } + return 0; +} + +int nghttp2_session_upgrade(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data) { + int rv; + nghttp2_stream *stream; + + rv = nghttp2_session_upgrade_internal(session, settings_payload, + settings_payloadlen, stream_user_data); + if (rv != 0) { + return rv; + } + + stream = nghttp2_session_get_stream(session, 1); + assert(stream); + + /* We have no information about request header fields when Upgrade + was happened. So we don't know the request method here. If + request method is HEAD, we have a trouble because we may have + nonzero content-length header field in response headers, and we + will going to check it against the actual DATA frames, but we may + get mismatch because HEAD response body must be empty. Because + of this reason, nghttp2_session_upgrade() was deprecated in favor + of nghttp2_session_upgrade2(), which has |head_request| parameter + to indicate that request method is HEAD or not. */ + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND; + return 0; +} + +int nghttp2_session_upgrade2(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, int head_request, + void *stream_user_data) { + int rv; + nghttp2_stream *stream; + + rv = nghttp2_session_upgrade_internal(session, settings_payload, + settings_payloadlen, stream_user_data); + if (rv != 0) { + return rv; + } + + stream = nghttp2_session_get_stream(session, 1); + assert(stream); + + if (head_request) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + } + + return 0; +} + +int nghttp2_session_get_stream_local_close(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return -1; + } + + return (stream->shut_flags & NGHTTP2_SHUT_WR) != 0; +} + +int nghttp2_session_get_stream_remote_close(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return -1; + } + + return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0; +} + +int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, + size_t size) { + int rv; + nghttp2_stream *stream; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return 0; + } + + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_connection(nghttp2_session *session, size_t size) { + int rv; + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id, + size_t size) { + int rv; + nghttp2_stream *stream; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return 0; + } + + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_set_next_stream_id(nghttp2_session *session, + int32_t next_stream_id) { + if (next_stream_id <= 0 || + session->next_stream_id > (uint32_t)next_stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (session->server) { + if (next_stream_id % 2) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } else if (next_stream_id % 2 == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + session->next_stream_id = (uint32_t)next_stream_id; + return 0; +} + +uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session) { + return session->next_stream_id; +} + +int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) { + return session->last_proc_stream_id; +} + +nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session, + int32_t stream_id) { + if (stream_id == 0) { + return &session->root; + } + + return nghttp2_session_get_stream_raw(session, stream_id); +} + +nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) { + return &session->root; +} + +int nghttp2_session_check_server_session(nghttp2_session *session) { + return session->server; +} + +int nghttp2_session_change_stream_priority( + nghttp2_session *session, int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + int rv; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec_copy; + + if (session->pending_no_rfc7540_priorities == 1) { + return 0; + } + + if (stream_id == 0 || stream_id == pri_spec->stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri_spec_copy = *pri_spec; + nghttp2_priority_spec_normalize_weight(&pri_spec_copy); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + /* We don't intentionally call nghttp2_session_adjust_idle_stream() + so that idle stream created by this function, and existing ones + are kept for application. We will adjust number of idle stream + in nghttp2_session_mem_send or nghttp2_session_mem_recv is + called. */ + return 0; +} + +int nghttp2_session_create_idle_stream(nghttp2_session *session, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec_copy; + + if (session->pending_no_rfc7540_priorities == 1) { + return 0; + } + + if (stream_id == 0 || stream_id == pri_spec->stream_id || + !session_detect_idle_stream(session, stream_id)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri_spec_copy = *pri_spec; + nghttp2_priority_spec_normalize_weight(&pri_spec_copy); + + stream = + nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't intentionally call nghttp2_session_adjust_idle_stream() + so that idle stream created by this function, and existing ones + are kept for application. We will adjust number of idle stream + in nghttp2_session_mem_send or nghttp2_session_mem_recv is + called. */ + return 0; +} + +size_t +nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session) { + return nghttp2_hd_inflate_get_dynamic_table_size(&session->hd_inflater); +} + +size_t +nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session) { + return nghttp2_hd_deflate_get_dynamic_table_size(&session->hd_deflater); +} + +void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data) { + session->user_data = user_data; +} + +int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, + const nghttp2_extpri *extpri_in, int ignore_client_signal) { + nghttp2_stream *stream; + nghttp2_extpri extpri = *extpri_in; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->pending_no_rfc7540_priorities != 1) { + return 0; + } + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (extpri.urgency > NGHTTP2_EXTPRI_URGENCY_LOW) { + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW; + } + + if (ignore_client_signal) { + stream->flags |= NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES; + } + + return session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); +} + +int nghttp2_session_get_extpri_stream_priority(nghttp2_session *session, + nghttp2_extpri *extpri, + int32_t stream_id) { + nghttp2_stream *stream; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->pending_no_rfc7540_priorities != 1) { + return 0; + } + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + nghttp2_extpri_from_uint8(extpri, stream->extpri); + + return 0; +} diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h new file mode 100644 index 0000000..b119329 --- /dev/null +++ b/lib/nghttp2_session.h @@ -0,0 +1,974 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_SESSION_H +#define NGHTTP2_SESSION_H + +#ifdef HAVE_CONFIG_H +# include +#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_stream.c b/lib/nghttp2_stream.c new file mode 100644 index 0000000..f1951f8 --- /dev/null +++ b/lib/nghttp2_stream.c @@ -0,0 +1,1016 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_stream.h" + +#include +#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_stream.h b/lib/nghttp2_stream.h new file mode 100644 index 0000000..71b9fb1 --- /dev/null +++ b/lib/nghttp2_stream.h @@ -0,0 +1,441 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_STREAM_H +#define NGHTTP2_STREAM_H + +#ifdef HAVE_CONFIG_H +# include +#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_submit.c b/lib/nghttp2_submit.c new file mode 100644 index 0000000..f5554eb --- /dev/null +++ b/lib/nghttp2_submit.c @@ -0,0 +1,900 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_submit.h" + +#include +#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_submit.h b/lib/nghttp2_submit.h new file mode 100644 index 0000000..74d702f --- /dev/null +++ b/lib/nghttp2_submit.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_SUBMIT_H +#define NGHTTP2_SUBMIT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_SUBMIT_H */ diff --git a/lib/nghttp2_time.c b/lib/nghttp2_time.c new file mode 100644 index 0000000..947b544 --- /dev/null +++ b/lib/nghttp2_time.c @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_time.h" + +#ifdef HAVE_WINDOWS_H +# include +#endif /* HAVE_WINDOWS_H */ + +#include + +#if !defined(HAVE_GETTICKCOUNT64) || defined(__CYGWIN__) +static uint64_t time_now_sec(void) { + time_t t = time(NULL); + + if (t == -1) { + return 0; + } + + return (uint64_t)t; +} +#endif /* !HAVE_GETTICKCOUNT64 || __CYGWIN__ */ + +#if defined(HAVE_GETTICKCOUNT64) && !defined(__CYGWIN__) +uint64_t nghttp2_time_now_sec(void) { return GetTickCount64() / 1000; } +#elif defined(HAVE_CLOCK_GETTIME) && defined(HAVE_DECL_CLOCK_MONOTONIC) && \ + HAVE_DECL_CLOCK_MONOTONIC +uint64_t nghttp2_time_now_sec(void) { + struct timespec tp; + int rv = clock_gettime(CLOCK_MONOTONIC, &tp); + + if (rv == -1) { + return time_now_sec(); + } + + return (uint64_t)tp.tv_sec; +} +#else /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \ + (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */ +uint64_t nghttp2_time_now_sec(void) { return time_now_sec(); } +#endif /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \ + (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */ diff --git a/lib/nghttp2_time.h b/lib/nghttp2_time.h new file mode 100644 index 0000000..03c0bbe --- /dev/null +++ b/lib/nghttp2_time.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_TIME_H +#define NGHTTP2_TIME_H + +#ifdef HAVE_CONFIG_H +# include +#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_version.c b/lib/nghttp2_version.c new file mode 100644 index 0000000..4211f2c --- /dev/null +++ b/lib/nghttp2_version.c @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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/sfparse.c b/lib/sfparse.c new file mode 100644 index 0000000..efa2850 --- /dev/null +++ b/lib/sfparse.c @@ -0,0 +1,1146 @@ +/* + * sfparse + * + * Copyright (c) 2023 sfparse contributors + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2015 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "sfparse.h" + +#include +#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/sfparse.h b/lib/sfparse.h new file mode 100644 index 0000000..1474db1 --- /dev/null +++ b/lib/sfparse.h @@ -0,0 +1,409 @@ +/* + * sfparse + * + * Copyright (c) 2023 sfparse contributors + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2015 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SFPARSE_H +#define SFPARSE_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1800) +/* MSVC < 2013 does not have inttypes.h because it is not C99 + compliant. See compiler macros and version number in + https://sourceforge.net/p/predef/wiki/Compilers/ */ +# include +#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/version.rc.in b/lib/version.rc.in new file mode 100644 index 0000000..4edfa7a --- /dev/null +++ b/lib/version.rc.in @@ -0,0 +1,40 @@ +#include + +VS_VERSION_INFO VERSIONINFO + +FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 +PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 +FILEFLAGSMASK 0x3fL +FILEOS 0x40004L +FILETYPE 0x2L +FILESUBTYPE 0x0L +#ifdef _DEBUG + #define VER_STR "@PROJECT_VERSION@.0 (MSVC debug)" + #define DBG "d" + FILEFLAGS 0x1L +#else + #define VER_STR "@PROJECT_VERSION@.0 (MSVC release)" + #define DBG "" + FILEFLAGS 0x0L +#endif +BEGIN +BLOCK "StringFileInfo" +BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "https://nghttp2.org/" + VALUE "FileDescription", "nghttp2; HTTP/2 C library" + VALUE "FileVersion", VER_STR + VALUE "InternalName", "nghttp2" DBG + VALUE "LegalCopyright", "The MIT License" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "nghttp2" DBG ".dll" + VALUE "ProductName", "NGHTTP2." + VALUE "ProductVersion", VER_STR + END +END +BLOCK "VarFileInfo" +BEGIN +VALUE "Translation", 0x409, 1200 +END +END diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..ca36397 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# 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/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..8edf515 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,1018 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# 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/m4/libxml2.m4 b/m4/libxml2.m4 new file mode 100644 index 0000000..68cd824 --- /dev/null +++ b/m4/libxml2.m4 @@ -0,0 +1,188 @@ +# Configure paths for LIBXML2 +# Mike Hommey 2004-06-19 +# use CPPFLAGS instead of CFLAGS +# Toshio Kuratomi 2001-04-21 +# Adapted from: +# Configure paths for GLIB +# Owen Taylor 97-11-3 + +dnl AM_PATH_XML2([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for XML, and define XML_CPPFLAGS and XML_LIBS +dnl +AC_DEFUN([AM_PATH_XML2],[ +AC_ARG_WITH(xml-prefix, + [ --with-xml-prefix=PFX Prefix where libxml is installed (optional)], + xml_config_prefix="$withval", xml_config_prefix="") +AC_ARG_WITH(xml-exec-prefix, + [ --with-xml-exec-prefix=PFX Exec prefix where libxml is installed (optional)], + xml_config_exec_prefix="$withval", xml_config_exec_prefix="") +AC_ARG_ENABLE(xmltest, + [ --disable-xmltest Do not try to compile and run a test LIBXML program],, + enable_xmltest=yes) + + if test x$xml_config_exec_prefix != x ; then + xml_config_args="$xml_config_args" + if test x${XML2_CONFIG+set} != xset ; then + XML2_CONFIG=$xml_config_exec_prefix/bin/xml2-config + fi + fi + if test x$xml_config_prefix != x ; then + xml_config_args="$xml_config_args --prefix=$xml_config_prefix" + if test x${XML2_CONFIG+set} != xset ; then + XML2_CONFIG=$xml_config_prefix/bin/xml2-config + fi + fi + + AC_PATH_PROG(XML2_CONFIG, xml2-config, no) + min_xml_version=ifelse([$1], ,2.0.0,[$1]) + AC_MSG_CHECKING(for libxml - version >= $min_xml_version) + no_xml="" + if test "$XML2_CONFIG" = "no" ; then + no_xml=yes + else + XML_CPPFLAGS=`$XML2_CONFIG $xml_config_args --cflags` + XML_LIBS=`$XML2_CONFIG $xml_config_args --libs` + xml_config_major_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + xml_config_minor_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + xml_config_micro_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + if test "x$enable_xmltest" = "xyes" ; then + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LIBS="$LIBS" + CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS" + LIBS="$XML_LIBS $LIBS" +dnl +dnl Now check if the installed libxml is sufficiently new. +dnl (Also sanity checks the results of xml2-config to some extent) +dnl + rm -f conf.xmltest + AC_TRY_RUN([ +#include +#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/makebashcompletion b/makebashcompletion new file mode 100755 index 0000000..9e88d9d --- /dev/null +++ b/makebashcompletion @@ -0,0 +1,7 @@ +#!/bin/sh -e + +BCPATH=doc/bash_completion + +for prog in nghttp nghttpd nghttpx h2load; do + $BCPATH/make_bash_completion.py src/$prog > $BCPATH/$prog +done diff --git a/makemanpages b/makemanpages new file mode 100755 index 0000000..0b7c54e --- /dev/null +++ b/makemanpages @@ -0,0 +1,12 @@ +#!/bin/sh -e + +for prog in nghttp nghttpd nghttpx h2load; do + src/$prog -h | ./help2rst.py -i doc/$prog.h2r > doc/$prog.1.rst +done + +cd doc +make man + +for prog in nghttp nghttpd nghttpx h2load; do + cp manual/man/$prog.1 $prog.1 +done diff --git a/makerelease.sh b/makerelease.sh new file mode 100755 index 0000000..b65e08f --- /dev/null +++ b/makerelease.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e + +TAG=$1 +PREV_TAG=$2 + +git checkout refs/tags/$TAG +git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog + +git submodule update --init + +autoreconf -i +./configure --with-mruby && \ + make dist-bzip2 && make dist-gzip && make dist-xz || echo "error" + +rm -f checksums.txt + +VERSION=`echo -n $TAG | sed -E 's|^v([0-9]+\.[0-9]+\.[0-9]+)(-DEV)?$|\1|'` +for f in nghttp2-$VERSION.tar.bz2 nghttp2-$VERSION.tar.gz nghttp2-$VERSION.tar.xz; do + sha256sum $f >> checksums.txt + gpg --armor --detach-sign $f +done + +make distclean diff --git a/mkcipherlist.py b/mkcipherlist.py new file mode 100755 index 0000000..e366f5e --- /dev/null +++ b/mkcipherlist.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This script read cipher suite list csv file [1] and prints out id +# and name of black listed cipher suites. The output is used by +# src/ssl.cc. +# +# [1] http://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv +# [2] http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + +import re +import sys +import csv + +# From RFC 7540 +blacklist = [ + 'TLS_NULL_WITH_NULL_NULL', + 'TLS_RSA_WITH_NULL_MD5', + 'TLS_RSA_WITH_NULL_SHA', + 'TLS_RSA_EXPORT_WITH_RC4_40_MD5', + 'TLS_RSA_WITH_RC4_128_MD5', + 'TLS_RSA_WITH_RC4_128_SHA', + 'TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5', + 'TLS_RSA_WITH_IDEA_CBC_SHA', + 'TLS_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_RSA_WITH_DES_CBC_SHA', + 'TLS_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_DSS_WITH_DES_CBC_SHA', + 'TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_RSA_WITH_DES_CBC_SHA', + 'TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DHE_DSS_WITH_DES_CBC_SHA', + 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DHE_RSA_WITH_DES_CBC_SHA', + 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_anon_EXPORT_WITH_RC4_40_MD5', + 'TLS_DH_anon_WITH_RC4_128_MD5', + 'TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_anon_WITH_DES_CBC_SHA', + 'TLS_DH_anon_WITH_3DES_EDE_CBC_SHA', + 'TLS_KRB5_WITH_DES_CBC_SHA', + 'TLS_KRB5_WITH_3DES_EDE_CBC_SHA', + 'TLS_KRB5_WITH_RC4_128_SHA', + 'TLS_KRB5_WITH_IDEA_CBC_SHA', + 'TLS_KRB5_WITH_DES_CBC_MD5', + 'TLS_KRB5_WITH_3DES_EDE_CBC_MD5', + 'TLS_KRB5_WITH_RC4_128_MD5', + 'TLS_KRB5_WITH_IDEA_CBC_MD5', + 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA', + 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA', + 'TLS_KRB5_EXPORT_WITH_RC4_40_SHA', + 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5', + 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5', + 'TLS_KRB5_EXPORT_WITH_RC4_40_MD5', + 'TLS_PSK_WITH_NULL_SHA', + 'TLS_DHE_PSK_WITH_NULL_SHA', + 'TLS_RSA_PSK_WITH_NULL_SHA', + 'TLS_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DH_DSS_WITH_AES_128_CBC_SHA', + 'TLS_DH_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DH_anon_WITH_AES_128_CBC_SHA', + 'TLS_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DH_DSS_WITH_AES_256_CBC_SHA', + 'TLS_DH_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DH_anon_WITH_AES_256_CBC_SHA', + 'TLS_RSA_WITH_NULL_SHA256', + 'TLS_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DH_DSS_WITH_AES_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_AES_256_CBC_SHA256', + 'TLS_DH_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256', + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DH_anon_WITH_AES_128_CBC_SHA256', + 'TLS_DH_anon_WITH_AES_256_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_PSK_WITH_RC4_128_SHA', + 'TLS_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_PSK_WITH_AES_128_CBC_SHA', + 'TLS_PSK_WITH_AES_256_CBC_SHA', + 'TLS_DHE_PSK_WITH_RC4_128_SHA', + 'TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA', + 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA', + 'TLS_RSA_PSK_WITH_RC4_128_SHA', + 'TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA', + 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA', + 'TLS_RSA_WITH_SEED_CBC_SHA', + 'TLS_DH_DSS_WITH_SEED_CBC_SHA', + 'TLS_DH_RSA_WITH_SEED_CBC_SHA', + 'TLS_DHE_DSS_WITH_SEED_CBC_SHA', + 'TLS_DHE_RSA_WITH_SEED_CBC_SHA', + 'TLS_DH_anon_WITH_SEED_CBC_SHA', + 'TLS_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_AES_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_AES_256_GCM_SHA384', + 'TLS_DH_anon_WITH_AES_128_GCM_SHA256', + 'TLS_DH_anon_WITH_AES_256_GCM_SHA384', + 'TLS_PSK_WITH_AES_128_GCM_SHA256', + 'TLS_PSK_WITH_AES_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_AES_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_AES_256_GCM_SHA384', + 'TLS_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_PSK_WITH_NULL_SHA256', + 'TLS_PSK_WITH_NULL_SHA384', + 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_NULL_SHA256', + 'TLS_DHE_PSK_WITH_NULL_SHA384', + 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_NULL_SHA256', + 'TLS_RSA_PSK_WITH_NULL_SHA384', + 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV', + 'TLS_ECDH_ECDSA_WITH_NULL_SHA', + 'TLS_ECDH_ECDSA_WITH_RC4_128_SHA', + 'TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_NULL_SHA', + 'TLS_ECDHE_ECDSA_WITH_RC4_128_SHA', + 'TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDH_RSA_WITH_NULL_SHA', + 'TLS_ECDH_RSA_WITH_RC4_128_SHA', + 'TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_NULL_SHA', + 'TLS_ECDHE_RSA_WITH_RC4_128_SHA', + 'TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDH_anon_WITH_NULL_SHA', + 'TLS_ECDH_anon_WITH_RC4_128_SHA', + 'TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_anon_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_anon_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_ECDHE_PSK_WITH_RC4_128_SHA', + 'TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_ECDHE_PSK_WITH_NULL_SHA', + 'TLS_ECDHE_PSK_WITH_NULL_SHA256', + 'TLS_ECDHE_PSK_WITH_NULL_SHA384', + 'TLS_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_anon_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_anon_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_anon_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_anon_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_PSK_WITH_ARIA_128_GCM_SHA256', + 'TLS_PSK_WITH_ARIA_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_WITH_AES_128_CCM', + 'TLS_RSA_WITH_AES_256_CCM', + 'TLS_RSA_WITH_AES_128_CCM_8', + 'TLS_RSA_WITH_AES_256_CCM_8', + 'TLS_PSK_WITH_AES_128_CCM', + 'TLS_PSK_WITH_AES_256_CCM', + 'TLS_PSK_WITH_AES_128_CCM_8', + 'TLS_PSK_WITH_AES_256_CCM_8', +] + +ciphers = [] +found = set() +for hl, name, _, _, _ in csv.reader(sys.stdin): + if name not in blacklist: + continue + + found.add(name) + + high, low = hl.split(',') + + id = high + low[2:] + 'u' + ciphers.append((id, name)) + +print('''\ +enum {''') + +for id, name in ciphers: + print('{} = {},'.format(name, id)) + +print('''\ +}; +''') + +for id, name in ciphers: + print('''\ +case {}:'''.format(name)) + +if len(found) != len(blacklist): + print('{} found out of {}; not all cipher was found: {}'.format( + len(found), len(blacklist), + found.symmetric_difference(blacklist))) diff --git a/mkhufftbl.py b/mkhufftbl.py new file mode 100755 index 0000000..a9f10d7 --- /dev/null +++ b/mkhufftbl.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This script reads Huffman Code table [1] and generates symbol table +# and decoding tables in C language. The resulting code is used in +# lib/nghttp2_hd_huffman.h and lib/nghttp2_hd_huffman_data.c +# +# [1] https://httpwg.org/specs/rfc7541.html + +import re +import sys +from io import StringIO + +# From [1] +HUFFMAN_CODE_TABLE = """\ + ( 0) |11111111|11000 1ff8 [13] + ( 1) |11111111|11111111|1011000 7fffd8 [23] + ( 2) |11111111|11111111|11111110|0010 fffffe2 [28] + ( 3) |11111111|11111111|11111110|0011 fffffe3 [28] + ( 4) |11111111|11111111|11111110|0100 fffffe4 [28] + ( 5) |11111111|11111111|11111110|0101 fffffe5 [28] + ( 6) |11111111|11111111|11111110|0110 fffffe6 [28] + ( 7) |11111111|11111111|11111110|0111 fffffe7 [28] + ( 8) |11111111|11111111|11111110|1000 fffffe8 [28] + ( 9) |11111111|11111111|11101010 ffffea [24] + ( 10) |11111111|11111111|11111111|111100 3ffffffc [30] + ( 11) |11111111|11111111|11111110|1001 fffffe9 [28] + ( 12) |11111111|11111111|11111110|1010 fffffea [28] + ( 13) |11111111|11111111|11111111|111101 3ffffffd [30] + ( 14) |11111111|11111111|11111110|1011 fffffeb [28] + ( 15) |11111111|11111111|11111110|1100 fffffec [28] + ( 16) |11111111|11111111|11111110|1101 fffffed [28] + ( 17) |11111111|11111111|11111110|1110 fffffee [28] + ( 18) |11111111|11111111|11111110|1111 fffffef [28] + ( 19) |11111111|11111111|11111111|0000 ffffff0 [28] + ( 20) |11111111|11111111|11111111|0001 ffffff1 [28] + ( 21) |11111111|11111111|11111111|0010 ffffff2 [28] + ( 22) |11111111|11111111|11111111|111110 3ffffffe [30] + ( 23) |11111111|11111111|11111111|0011 ffffff3 [28] + ( 24) |11111111|11111111|11111111|0100 ffffff4 [28] + ( 25) |11111111|11111111|11111111|0101 ffffff5 [28] + ( 26) |11111111|11111111|11111111|0110 ffffff6 [28] + ( 27) |11111111|11111111|11111111|0111 ffffff7 [28] + ( 28) |11111111|11111111|11111111|1000 ffffff8 [28] + ( 29) |11111111|11111111|11111111|1001 ffffff9 [28] + ( 30) |11111111|11111111|11111111|1010 ffffffa [28] + ( 31) |11111111|11111111|11111111|1011 ffffffb [28] +' ' ( 32) |010100 14 [ 6] +'!' ( 33) |11111110|00 3f8 [10] +'"' ( 34) |11111110|01 3f9 [10] +'#' ( 35) |11111111|1010 ffa [12] +'$' ( 36) |11111111|11001 1ff9 [13] +'%' ( 37) |010101 15 [ 6] +'&' ( 38) |11111000 f8 [ 8] +''' ( 39) |11111111|010 7fa [11] +'(' ( 40) |11111110|10 3fa [10] +')' ( 41) |11111110|11 3fb [10] +'*' ( 42) |11111001 f9 [ 8] +'+' ( 43) |11111111|011 7fb [11] +',' ( 44) |11111010 fa [ 8] +'-' ( 45) |010110 16 [ 6] +'.' ( 46) |010111 17 [ 6] +'/' ( 47) |011000 18 [ 6] +'0' ( 48) |00000 0 [ 5] +'1' ( 49) |00001 1 [ 5] +'2' ( 50) |00010 2 [ 5] +'3' ( 51) |011001 19 [ 6] +'4' ( 52) |011010 1a [ 6] +'5' ( 53) |011011 1b [ 6] +'6' ( 54) |011100 1c [ 6] +'7' ( 55) |011101 1d [ 6] +'8' ( 56) |011110 1e [ 6] +'9' ( 57) |011111 1f [ 6] +':' ( 58) |1011100 5c [ 7] +';' ( 59) |11111011 fb [ 8] +'<' ( 60) |11111111|1111100 7ffc [15] +'=' ( 61) |100000 20 [ 6] +'>' ( 62) |11111111|1011 ffb [12] +'?' ( 63) |11111111|00 3fc [10] +'@' ( 64) |11111111|11010 1ffa [13] +'A' ( 65) |100001 21 [ 6] +'B' ( 66) |1011101 5d [ 7] +'C' ( 67) |1011110 5e [ 7] +'D' ( 68) |1011111 5f [ 7] +'E' ( 69) |1100000 60 [ 7] +'F' ( 70) |1100001 61 [ 7] +'G' ( 71) |1100010 62 [ 7] +'H' ( 72) |1100011 63 [ 7] +'I' ( 73) |1100100 64 [ 7] +'J' ( 74) |1100101 65 [ 7] +'K' ( 75) |1100110 66 [ 7] +'L' ( 76) |1100111 67 [ 7] +'M' ( 77) |1101000 68 [ 7] +'N' ( 78) |1101001 69 [ 7] +'O' ( 79) |1101010 6a [ 7] +'P' ( 80) |1101011 6b [ 7] +'Q' ( 81) |1101100 6c [ 7] +'R' ( 82) |1101101 6d [ 7] +'S' ( 83) |1101110 6e [ 7] +'T' ( 84) |1101111 6f [ 7] +'U' ( 85) |1110000 70 [ 7] +'V' ( 86) |1110001 71 [ 7] +'W' ( 87) |1110010 72 [ 7] +'X' ( 88) |11111100 fc [ 8] +'Y' ( 89) |1110011 73 [ 7] +'Z' ( 90) |11111101 fd [ 8] +'[' ( 91) |11111111|11011 1ffb [13] +'\' ( 92) |11111111|11111110|000 7fff0 [19] +']' ( 93) |11111111|11100 1ffc [13] +'^' ( 94) |11111111|111100 3ffc [14] +'_' ( 95) |100010 22 [ 6] +'`' ( 96) |11111111|1111101 7ffd [15] +'a' ( 97) |00011 3 [ 5] +'b' ( 98) |100011 23 [ 6] +'c' ( 99) |00100 4 [ 5] +'d' (100) |100100 24 [ 6] +'e' (101) |00101 5 [ 5] +'f' (102) |100101 25 [ 6] +'g' (103) |100110 26 [ 6] +'h' (104) |100111 27 [ 6] +'i' (105) |00110 6 [ 5] +'j' (106) |1110100 74 [ 7] +'k' (107) |1110101 75 [ 7] +'l' (108) |101000 28 [ 6] +'m' (109) |101001 29 [ 6] +'n' (110) |101010 2a [ 6] +'o' (111) |00111 7 [ 5] +'p' (112) |101011 2b [ 6] +'q' (113) |1110110 76 [ 7] +'r' (114) |101100 2c [ 6] +'s' (115) |01000 8 [ 5] +'t' (116) |01001 9 [ 5] +'u' (117) |101101 2d [ 6] +'v' (118) |1110111 77 [ 7] +'w' (119) |1111000 78 [ 7] +'x' (120) |1111001 79 [ 7] +'y' (121) |1111010 7a [ 7] +'z' (122) |1111011 7b [ 7] +'{' (123) |11111111|1111110 7ffe [15] +'|' (124) |11111111|100 7fc [11] +'}' (125) |11111111|111101 3ffd [14] +'~' (126) |11111111|11101 1ffd [13] + (127) |11111111|11111111|11111111|1100 ffffffc [28] + (128) |11111111|11111110|0110 fffe6 [20] + (129) |11111111|11111111|010010 3fffd2 [22] + (130) |11111111|11111110|0111 fffe7 [20] + (131) |11111111|11111110|1000 fffe8 [20] + (132) |11111111|11111111|010011 3fffd3 [22] + (133) |11111111|11111111|010100 3fffd4 [22] + (134) |11111111|11111111|010101 3fffd5 [22] + (135) |11111111|11111111|1011001 7fffd9 [23] + (136) |11111111|11111111|010110 3fffd6 [22] + (137) |11111111|11111111|1011010 7fffda [23] + (138) |11111111|11111111|1011011 7fffdb [23] + (139) |11111111|11111111|1011100 7fffdc [23] + (140) |11111111|11111111|1011101 7fffdd [23] + (141) |11111111|11111111|1011110 7fffde [23] + (142) |11111111|11111111|11101011 ffffeb [24] + (143) |11111111|11111111|1011111 7fffdf [23] + (144) |11111111|11111111|11101100 ffffec [24] + (145) |11111111|11111111|11101101 ffffed [24] + (146) |11111111|11111111|010111 3fffd7 [22] + (147) |11111111|11111111|1100000 7fffe0 [23] + (148) |11111111|11111111|11101110 ffffee [24] + (149) |11111111|11111111|1100001 7fffe1 [23] + (150) |11111111|11111111|1100010 7fffe2 [23] + (151) |11111111|11111111|1100011 7fffe3 [23] + (152) |11111111|11111111|1100100 7fffe4 [23] + (153) |11111111|11111110|11100 1fffdc [21] + (154) |11111111|11111111|011000 3fffd8 [22] + (155) |11111111|11111111|1100101 7fffe5 [23] + (156) |11111111|11111111|011001 3fffd9 [22] + (157) |11111111|11111111|1100110 7fffe6 [23] + (158) |11111111|11111111|1100111 7fffe7 [23] + (159) |11111111|11111111|11101111 ffffef [24] + (160) |11111111|11111111|011010 3fffda [22] + (161) |11111111|11111110|11101 1fffdd [21] + (162) |11111111|11111110|1001 fffe9 [20] + (163) |11111111|11111111|011011 3fffdb [22] + (164) |11111111|11111111|011100 3fffdc [22] + (165) |11111111|11111111|1101000 7fffe8 [23] + (166) |11111111|11111111|1101001 7fffe9 [23] + (167) |11111111|11111110|11110 1fffde [21] + (168) |11111111|11111111|1101010 7fffea [23] + (169) |11111111|11111111|011101 3fffdd [22] + (170) |11111111|11111111|011110 3fffde [22] + (171) |11111111|11111111|11110000 fffff0 [24] + (172) |11111111|11111110|11111 1fffdf [21] + (173) |11111111|11111111|011111 3fffdf [22] + (174) |11111111|11111111|1101011 7fffeb [23] + (175) |11111111|11111111|1101100 7fffec [23] + (176) |11111111|11111111|00000 1fffe0 [21] + (177) |11111111|11111111|00001 1fffe1 [21] + (178) |11111111|11111111|100000 3fffe0 [22] + (179) |11111111|11111111|00010 1fffe2 [21] + (180) |11111111|11111111|1101101 7fffed [23] + (181) |11111111|11111111|100001 3fffe1 [22] + (182) |11111111|11111111|1101110 7fffee [23] + (183) |11111111|11111111|1101111 7fffef [23] + (184) |11111111|11111110|1010 fffea [20] + (185) |11111111|11111111|100010 3fffe2 [22] + (186) |11111111|11111111|100011 3fffe3 [22] + (187) |11111111|11111111|100100 3fffe4 [22] + (188) |11111111|11111111|1110000 7ffff0 [23] + (189) |11111111|11111111|100101 3fffe5 [22] + (190) |11111111|11111111|100110 3fffe6 [22] + (191) |11111111|11111111|1110001 7ffff1 [23] + (192) |11111111|11111111|11111000|00 3ffffe0 [26] + (193) |11111111|11111111|11111000|01 3ffffe1 [26] + (194) |11111111|11111110|1011 fffeb [20] + (195) |11111111|11111110|001 7fff1 [19] + (196) |11111111|11111111|100111 3fffe7 [22] + (197) |11111111|11111111|1110010 7ffff2 [23] + (198) |11111111|11111111|101000 3fffe8 [22] + (199) |11111111|11111111|11110110|0 1ffffec [25] + (200) |11111111|11111111|11111000|10 3ffffe2 [26] + (201) |11111111|11111111|11111000|11 3ffffe3 [26] + (202) |11111111|11111111|11111001|00 3ffffe4 [26] + (203) |11111111|11111111|11111011|110 7ffffde [27] + (204) |11111111|11111111|11111011|111 7ffffdf [27] + (205) |11111111|11111111|11111001|01 3ffffe5 [26] + (206) |11111111|11111111|11110001 fffff1 [24] + (207) |11111111|11111111|11110110|1 1ffffed [25] + (208) |11111111|11111110|010 7fff2 [19] + (209) |11111111|11111111|00011 1fffe3 [21] + (210) |11111111|11111111|11111001|10 3ffffe6 [26] + (211) |11111111|11111111|11111100|000 7ffffe0 [27] + (212) |11111111|11111111|11111100|001 7ffffe1 [27] + (213) |11111111|11111111|11111001|11 3ffffe7 [26] + (214) |11111111|11111111|11111100|010 7ffffe2 [27] + (215) |11111111|11111111|11110010 fffff2 [24] + (216) |11111111|11111111|00100 1fffe4 [21] + (217) |11111111|11111111|00101 1fffe5 [21] + (218) |11111111|11111111|11111010|00 3ffffe8 [26] + (219) |11111111|11111111|11111010|01 3ffffe9 [26] + (220) |11111111|11111111|11111111|1101 ffffffd [28] + (221) |11111111|11111111|11111100|011 7ffffe3 [27] + (222) |11111111|11111111|11111100|100 7ffffe4 [27] + (223) |11111111|11111111|11111100|101 7ffffe5 [27] + (224) |11111111|11111110|1100 fffec [20] + (225) |11111111|11111111|11110011 fffff3 [24] + (226) |11111111|11111110|1101 fffed [20] + (227) |11111111|11111111|00110 1fffe6 [21] + (228) |11111111|11111111|101001 3fffe9 [22] + (229) |11111111|11111111|00111 1fffe7 [21] + (230) |11111111|11111111|01000 1fffe8 [21] + (231) |11111111|11111111|1110011 7ffff3 [23] + (232) |11111111|11111111|101010 3fffea [22] + (233) |11111111|11111111|101011 3fffeb [22] + (234) |11111111|11111111|11110111|0 1ffffee [25] + (235) |11111111|11111111|11110111|1 1ffffef [25] + (236) |11111111|11111111|11110100 fffff4 [24] + (237) |11111111|11111111|11110101 fffff5 [24] + (238) |11111111|11111111|11111010|10 3ffffea [26] + (239) |11111111|11111111|1110100 7ffff4 [23] + (240) |11111111|11111111|11111010|11 3ffffeb [26] + (241) |11111111|11111111|11111100|110 7ffffe6 [27] + (242) |11111111|11111111|11111011|00 3ffffec [26] + (243) |11111111|11111111|11111011|01 3ffffed [26] + (244) |11111111|11111111|11111100|111 7ffffe7 [27] + (245) |11111111|11111111|11111101|000 7ffffe8 [27] + (246) |11111111|11111111|11111101|001 7ffffe9 [27] + (247) |11111111|11111111|11111101|010 7ffffea [27] + (248) |11111111|11111111|11111101|011 7ffffeb [27] + (249) |11111111|11111111|11111111|1110 ffffffe [28] + (250) |11111111|11111111|11111101|100 7ffffec [27] + (251) |11111111|11111111|11111101|101 7ffffed [27] + (252) |11111111|11111111|11111101|110 7ffffee [27] + (253) |11111111|11111111|11111101|111 7ffffef [27] + (254) |11111111|11111111|11111110|000 7fffff0 [27] + (255) |11111111|11111111|11111011|10 3ffffee [26] +EOS (256) |11111111|11111111|11111111|111111 3fffffff [30] +""" + +class Node: + + def __init__(self, term = None): + self.term = term + self.left = None + self.right = None + self.trans = [] + self.id = None + self.accept = False + +class Context: + + def __init__(self): + self.next_id_ = 0 + self.root = Node() + + def next_id(self): + id = self.next_id_ + self.next_id_ += 1 + return id + +def _add(node, sym, bits): + if len(bits) == 0: + node.term = sym + return + else: + if bits[0] == '0': + if node.left is None: + node.left = Node() + child = node.left + else: + if node.right is None: + node.right = Node() + child = node.right + _add(child, sym, bits[1:]) + +def huffman_tree_add(ctx, sym, bits): + _add(ctx.root, sym, bits) + +def _set_node_id(ctx, node, prefix): + if node.term is not None: + return + if len(prefix) <= 7 and [1] * len(prefix) == prefix: + node.accept = True + node.id = ctx.next_id() + _set_node_id(ctx, node.left, prefix + [0]) + _set_node_id(ctx, node.right, prefix + [1]) + +def huffman_tree_set_node_id(ctx): + _set_node_id(ctx, ctx.root, []) + +def _traverse(node, sym, start_node, root, left): + if left == 0: + if sym == 256: + sym = None + node = None + start_node.trans.append((node, sym)) + return + + if node.term is not None: + node = root + + def go(node): + if node.term is not None: + assert sym is None + nsym = node.term + else: + nsym = sym + + _traverse(node, nsym, start_node, root, left - 1) + + go(node.left) + go(node.right) + +def _build_transition_table(ctx, node): + if node is None: + return + _traverse(node, None, node, ctx.root, 4) + _build_transition_table(ctx, node.left) + _build_transition_table(ctx, node.right) + +def huffman_tree_build_transition_table(ctx): + _build_transition_table(ctx, ctx.root) + +NGHTTP2_HUFF_ACCEPTED = 1 << 14 +NGHTTP2_HUFF_SYM = 1 << 15 + +def _print_transition_table(node): + if node.term is not None: + return + print('/* {} */'.format(node.id)) + print('{') + for nd, sym in node.trans: + flags = 0 + if sym is None: + out = 0 + else: + out = sym + flags |= NGHTTP2_HUFF_SYM + if nd is None: + id = 256 + else: + id = nd.id + if id is None: + # if nd.id is None, it is a leaf node + id = 0 + flags |= NGHTTP2_HUFF_ACCEPTED + elif nd.accept: + flags |= NGHTTP2_HUFF_ACCEPTED + print(' {{0x{:02x}, {}}},'.format(id | flags, out)) + print('},') + _print_transition_table(node.left) + _print_transition_table(node.right) + +def huffman_tree_print_transition_table(ctx): + _print_transition_table(ctx.root) + print('/* 256 */') + print('{') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print('},') + +if __name__ == '__main__': + ctx = Context() + symbol_tbl = [(None, 0) for i in range(257)] + + for line in StringIO(HUFFMAN_CODE_TABLE): + m = re.match( + r'.*\(\s*(\d+)\)\s+([|01]+)\s+(\S+)\s+\[\s*(\d+)\].*', line) + if m: + sym = int(m.group(1)) + bits = re.sub(r'\|', '', m.group(2)) + code = m.group(3) + nbits = int(m.group(4)) + if len(code) > 8: + raise Error('Code is more than 4 bytes long') + assert(len(bits) == nbits) + symbol_tbl[sym] = (nbits, code) + huffman_tree_add(ctx, sym, bits) + + huffman_tree_set_node_id(ctx) + huffman_tree_build_transition_table(ctx) + + print('''\ +typedef struct { + uint32_t nbits; + uint32_t code; +} nghttp2_huff_sym; +''') + + print('''\ +const nghttp2_huff_sym huff_sym_table[] = {''') + for i in range(257): + nbits = symbol_tbl[i][0] + k = int(symbol_tbl[i][1], 16) + k = k << (32 - nbits) + print('''\ + {{ {}, 0x{}u }}{}\ +'''.format(symbol_tbl[i][0], hex(k)[2:], ',' if i < 256 else '')) + print('};') + print() + + print('''\ +enum {{ + NGHTTP2_HUFF_ACCEPTED = {}, + NGHTTP2_HUFF_SYM = {}, +}} nghttp2_huff_decode_flag; +'''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM)) + + print('''\ +typedef struct { + uint16_t fstate; + uint8_t sym; +} nghttp2_huff_decode; +''') + + print('''\ +const nghttp2_huff_decode huff_decode_table[][16] = {''') + huffman_tree_print_transition_table(ctx) + print('};') diff --git a/mkstatichdtbl.py b/mkstatichdtbl.py new file mode 100755 index 0000000..20f0c15 --- /dev/null +++ b/mkstatichdtbl.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This scripts reads static table entries [1] and generates +# nghttp2_hd_static_entry table. This table is used in +# lib/nghttp2_hd.c. +# +# [1] https://httpwg.org/specs/rfc7541.html + +import re, sys + +def hd_map_hash(name): + h = 2166136261 + + # FNV hash variant: http://isthe.com/chongo/tech/comp/fnv/ + for c in name: + h ^= ord(c) + h *= 16777619 + h &= 0xffffffff + + return h + +entries = [] +for line in sys.stdin: + m = re.match(r'(\d+)\s+(\S+)\s+(\S.*)?', line) + val = m.group(3).strip() if m.group(3) else '' + entries.append((int(m.group(1)), m.group(2), val)) + +print('static nghttp2_hd_entry static_table[] = {') +idx = 0 +for i, ent in enumerate(entries): + if entries[idx][1] != ent[1]: + idx = i + print('MAKE_STATIC_ENT("{}", "{}", {}, {}u),'\ + .format(ent[1], ent[2], entries[idx][0] - 1, hd_map_hash(ent[1]))) +print('};') diff --git a/nghttpx.conf.sample b/nghttpx.conf.sample new file mode 100644 index 0000000..97d7954 --- /dev/null +++ b/nghttpx.conf.sample @@ -0,0 +1,29 @@ +# +# Sample configuration file for nghttpx. +# +# * Line staring '#' is treated as comment. +# +# * The option name in the configuration file is the long command-line +# option name with leading '--' stripped (e.g., frontend). Put '=' +# between option name and value. Don't put extra leading or trailing +# spaces. +# +# * The options which do not take argument in the command-line *take* +# argument in the configuration file. Specify 'yes' as argument +# (e.g., http2-proxy=yes). If other string is given, it disables the +# option. +# +# * To specify private key and certificate file, use private-key-file +# and certificate-file. See the examples below. +# +# * conf option cannot be used in the configuration file. It will be +# ignored. +# +# Examples: +# +# frontend=0.0.0.0,3000 +# backend=127.0.0.1,80 +# private-key-file=/path/to/server.key +# certificate-file=/path/to/server.crt +# http2-proxy=no +# workers=1 diff --git a/pre-commit b/pre-commit new file mode 100755 index 0000000..1dfe1f5 --- /dev/null +++ b/pre-commit @@ -0,0 +1,27 @@ +#!/bin/sh -e +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# + +CLANGFORMATDIFF=`git config --get clangformatdiff.binary` + +if [ -z "$CLANGFORMATDIFF" ]; then + CLANGFORMATDIFF=clang-format-diff.py +fi + +errors=`git diff-index --cached --diff-filter=ACMR -p HEAD lib src examples tests | $CLANGFORMATDIFF -p1` + +if [ -n "$errors" ]; then + echo "$errors" + echo "--" + echo "[ERROR] We have detected the difference between the code to commit" + echo "and clang-format style rules. Please fix this problem in either:" + echo "1) Apply patch above." + echo "2) Use clang-format to format lines." + echo "3) Reformat these lines manually." + echo "Aborting commit." + exit 1 +fi diff --git a/proxy.pac.sample b/proxy.pac.sample new file mode 100644 index 0000000..9283920 --- /dev/null +++ b/proxy.pac.sample @@ -0,0 +1,6 @@ +function FindProxyForURL(url, host) { + // For SPDY proxy + return "HTTPS localhost:3000"; + // For conventional HTTP proxy + // return "PROXY localhost:3000"; +} diff --git a/releasechk b/releasechk new file mode 100755 index 0000000..0c05cca --- /dev/null +++ b/releasechk @@ -0,0 +1,6 @@ +#!/bin/sh -e + +autoreconf -i +git submodule update --init +./configure --with-mruby --with-neverbleed +make -j8 distcheck DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror" diff --git a/script/CMakeLists.txt b/script/CMakeLists.txt new file mode 100644 index 0000000..f898858 --- /dev/null +++ b/script/CMakeLists.txt @@ -0,0 +1,5 @@ +# EXTRA_DIST = README.rst +install( + PROGRAMS fetch-ocsp-response + DESTINATION "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}" +) diff --git a/script/Makefile.am b/script/Makefile.am new file mode 100644 index 0000000..387f33c --- /dev/null +++ b/script/Makefile.am @@ -0,0 +1,25 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +EXTRA_DIST = README.rst CMakeLists.txt +dist_pkgdata_SCRIPTS = fetch-ocsp-response diff --git a/script/README.rst b/script/README.rst new file mode 100644 index 0000000..a9f7737 --- /dev/null +++ b/script/README.rst @@ -0,0 +1,10 @@ +fetch-ocsp-response is a Python script which performs OCSP query and +get response. It uses openssl command under the hood. nghttpx uses +it to enable OCSP stapling feature. + +fetch-ocsp-response is a translation from original fetch-ocsp-response +written in Perl and which has been developed as part of h2o project +(https://github.com/h2o/h2o). + +fetch-ocsp-response is usually installed under $(pkgdatadir), which is +$(prefix)/share/nghttp2. diff --git a/script/fetch-ocsp-response b/script/fetch-ocsp-response new file mode 100755 index 0000000..7c4785b --- /dev/null +++ b/script/fetch-ocsp-response @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# This program was translated from the program originally developed by +# h2o project (https://github.com/h2o/h2o), written in Perl. It had +# the following copyright notice: + +# Copyright (c) 2015 DeNA Co., Ltd. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from __future__ import unicode_literals +import argparse +import io +import os +import os.path +import re +import shutil +import subprocess +import sys +import tempfile + +# make this program work for both Python 3 and Python 2. +try: + from urllib.parse import urlparse + stdout_bwrite = sys.stdout.buffer.write +except ImportError: + from urlparse import urlparse + stdout_bwrite = sys.stdout.write + + +def die(msg): + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(255) + + +def tempfail(msg): + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(os.EX_TEMPFAIL) + + +def run_openssl(args, allow_tempfail=False): + buf = io.BytesIO() + try: + p = subprocess.Popen(args, stdout=subprocess.PIPE) + except Exception as e: + die('failed to invoke {}:{}'.format(args, e)) + try: + while True: + data = p.stdout.read() + if len(data) == 0: + break + buf.write(data) + if p.wait() != 0: + raise Exception('nonzero return code {}'.format(p.returncode)) + return buf.getvalue() + except Exception as e: + msg = 'OpenSSL exited abnormally: {}:{}'.format(args, e) + tempfail(msg) if allow_tempfail else die(msg) + + +def read_file(path): + with open(path, 'rb') as f: + return f.read() + + +def write_file(path, data): + with open(path, 'wb') as f: + f.write(data) + + +def detect_openssl_version(cmd): + return run_openssl([cmd, 'version']).decode('utf-8').strip() + + +def extract_ocsp_uri(cmd, cert_fn): + # obtain ocsp uri + ocsp_uri = run_openssl( + [cmd, 'x509', '-in', cert_fn, '-noout', + '-ocsp_uri']).decode('utf-8').strip() + + if not re.match(r'^https?://', ocsp_uri): + die('failed to extract ocsp URI from {}'.format(cert_fn)) + + return ocsp_uri + + +def save_issuer_certificate(issuer_fn, cert_fn): + # save issuer certificate + chain = read_file(cert_fn).decode('utf-8') + m = re.match( + r'.*?-----END CERTIFICATE-----.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)', + chain, re.DOTALL) + if not m: + die('--issuer option was not used, and failed to extract issuer certificate from the certificate') + write_file(issuer_fn, (m.group(1) + '\n').encode('utf-8')) + + +def send_and_receive_ocsp(respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, + ocsp_host, openssl_version): + # obtain response (without verification) + sys.stderr.write('sending OCSP request to {}\n'.format(ocsp_uri)) + args = [ + cmd, 'ocsp', '-issuer', issuer_fn, '-cert', cert_fn, '-url', ocsp_uri, + '-noverify', '-respout', respder_fn + ] + ver = openssl_version.lower() + if ver.startswith('openssl 1.0.') or ver.startswith('libressl '): + args.extend(['-header', 'Host', ocsp_host]) + resp = run_openssl(args, allow_tempfail=True) + return resp.decode('utf-8') + + +def verify_response(cmd, tempdir, issuer_fn, respder_fn): + # verify the response + sys.stderr.write('verifying the response signature\n') + + verify_fn = os.path.join(tempdir, 'verify.out') + + # try from exotic options + allextra = [ + # for comodo + ['-VAfile', issuer_fn], + # these options are only available in OpenSSL >= 1.0.2 + ['-partial_chain', '-trusted_first', '-CAfile', issuer_fn], + # for OpenSSL <= 1.0.1 + ['-CAfile', issuer_fn], + ] + + for extra in allextra: + with open(verify_fn, 'w+b') as f: + args = [cmd, 'ocsp', '-respin', respder_fn] + args.extend(extra) + p = subprocess.Popen(args, stdout=f, stderr=f) + if p.wait() == 0: + # OpenSSL <= 1.0.1, openssl ocsp still returns exit + # code 0 even if verification was failed. So check + # the error message in stderr output. + f.seek(0) + if f.read().decode('utf-8').find( + 'Response Verify Failure') != -1: + continue + sys.stderr.write('verify OK (used: {})\n'.format(extra)) + return True + + sys.stderr.write(read_file(verify_fn).decode('utf-8')) + return False + + +def fetch_ocsp_response(cmd, cert_fn, tempdir, issuer_fn=None): + openssl_version = detect_openssl_version(cmd) + + sys.stderr.write( + 'fetch-ocsp-response (using {})\n'.format(openssl_version)) + + ocsp_uri = extract_ocsp_uri(cmd, cert_fn) + ocsp_host = urlparse(ocsp_uri).netloc + + if not issuer_fn: + issuer_fn = os.path.join(tempdir, 'issuer.crt') + save_issuer_certificate(issuer_fn, cert_fn) + + respder_fn = os.path.join(tempdir, 'resp.der') + resp = send_and_receive_ocsp( + respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, ocsp_host, + openssl_version) + + sys.stderr.write('{}\n'.format(resp)) + + # OpenSSL 1.0.2 still returns exit code 0 even if ocsp responder + # returned error status (e.g., trylater(3)) + if resp.find('Responder Error:') != -1: + raise Exception('responder returned error') + + if not verify_response(cmd, tempdir, issuer_fn, respder_fn): + tempfail('failed to verify the response') + + # success + res = read_file(respder_fn) + stdout_bwrite(res) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description= + '''The command issues an OCSP request for given server certificate, verifies the response and prints the resulting DER.''', + epilog= + '''The command exits 0 if successful, or 75 (EX_TEMPFAIL) on temporary error. Other exit codes may be returned in case of hard errors.''') + parser.add_argument( + '--issuer', + metavar='FILE', + help= + 'issuer certificate (if omitted, is extracted from the certificate chain)') + parser.add_argument('--openssl', + metavar='CMD', + help='openssl command to use (default: "openssl")', + default='openssl') + parser.add_argument('certificate', + help='path to certificate file to validate') + args = parser.parse_args() + + tempdir = None + try: + # Python3.2 has tempfile.TemporaryDirectory, which has nice + # feature to delete its tree by cleanup() function. We have + # to support Python2.7, so we have to do this manually. + tempdir = tempfile.mkdtemp() + fetch_ocsp_response(args.openssl, args.certificate, tempdir, + args.issuer) + finally: + if tempdir: + shutil.rmtree(tempdir) diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..27631a7 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,13 @@ +# programs +deflatehd +h2load +inflatehd +nghttp +nghttpd +nghttpx + +# build +libnghttpx.a + +# tests +nghttpx-unittest diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..201c5a2 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,243 @@ +file(GLOB c_sources *.c) +set_source_files_properties(${c_sources} PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}") +file(GLOB cxx_sources *.cc) +set_source_files_properties(${cxx_sources} PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}") + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include" + + ${JEMALLOC_INCLUDE_DIRS} + ${LIBXML2_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ${LIBNGTCP2_INCLUDE_DIRS} + ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ${LIBCARES_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${LIBBPF_INCLUDE_DIRS} +) + +# XXX per-target? +link_libraries( + nghttp2 + ${JEMALLOC_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ${LIBNGTCP2_LIBRARIES} + ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${LIBCARES_LIBRARIES} + ${JANSSON_LIBRARIES} + ${ZLIB_LIBRARIES} + ${APP_LIBRARIES} + ${LIBBPF_LIBRARIES} +) + +if(ENABLE_APP) + set(HELPER_OBJECTS + util.cc + http2.cc timegm.c app_helper.cc nghttp2_gzip.c + ) + + # nghttp client + set(NGHTTP_SOURCES + ${HELPER_OBJECTS} + nghttp.cc + tls.cc + ) + if(HAVE_LIBXML2) + list(APPEND NGHTTP_SOURCES HtmlParser.cc) + endif() + + # nghttpd + set(NGHTTPD_SOURCES + ${HELPER_OBJECTS} + nghttpd.cc + tls.cc + HttpServer.cc + ) + + # h2load + set(H2LOAD_SOURCES + util.cc + http2.cc h2load.cc + timegm.c + tls.cc + h2load_http2_session.cc + h2load_http1_session.cc + ) + if(ENABLE_HTTP3) + list(APPEND H2LOAD_SOURCES + h2load_http3_session.cc + h2load_quic.cc + quic.cc + ) + endif() + + # Common libnhttpx sources (used for nghttpx and unit tests) + set(NGHTTPX_SRCS + util.cc http2.cc timegm.c + app_helper.cc + tls.cc + shrpx_config.cc + shrpx_accept_handler.cc + shrpx_connection_handler.cc + shrpx_client_handler.cc + shrpx_http2_upstream.cc + shrpx_https_upstream.cc + shrpx_downstream.cc + shrpx_downstream_connection.cc + shrpx_http_downstream_connection.cc + shrpx_http2_downstream_connection.cc + shrpx_http2_session.cc + shrpx_downstream_queue.cc + shrpx_log.cc + shrpx_http.cc + shrpx_io_control.cc + shrpx_tls.cc + shrpx_worker.cc + shrpx_log_config.cc + shrpx_connect_blocker.cc + shrpx_live_check.cc + shrpx_downstream_connection_pool.cc + shrpx_rate_limit.cc + shrpx_connection.cc + shrpx_memcached_dispatcher.cc + shrpx_memcached_connection.cc + shrpx_worker_process.cc + shrpx_signal.cc + shrpx_router.cc + shrpx_api_downstream_connection.cc + shrpx_health_monitor_downstream_connection.cc + shrpx_null_downstream_connection.cc + shrpx_exec.cc + shrpx_dns_resolver.cc + shrpx_dual_dns_resolver.cc + shrpx_dns_tracker.cc + xsi_strerror.c + ) + if(HAVE_MRUBY) + list(APPEND NGHTTPX_SRCS + shrpx_mruby.cc + shrpx_mruby_module.cc + shrpx_mruby_module_env.cc + shrpx_mruby_module_request.cc + shrpx_mruby_module_response.cc + ) + endif() + if(ENABLE_HTTP3) + list(APPEND NGHTTPX_SRCS + shrpx_quic.cc + shrpx_quic_listener.cc + shrpx_quic_connection_handler.cc + shrpx_http3_upstream.cc + http3.cc + quic.cc + ) + endif() + add_library(nghttpx_static STATIC ${NGHTTPX_SRCS}) + set_target_properties(nghttpx_static PROPERTIES ARCHIVE_OUTPUT_NAME nghttpx) + + set(NGHTTPX-bin_SOURCES + shrpx.cc + ) + + if(HAVE_SYSTEMD) + target_link_libraries(nghttpx_static ${SYSTEMD_LIBRARIES}) + target_compile_definitions(nghttpx_static PUBLIC HAVE_LIBSYSTEMD) + target_include_directories(nghttpx_static PUBLIC ${SYSTEMD_INCLUDE_DIRS}) + endif() + + if(HAVE_MRUBY) + target_link_libraries(nghttpx_static mruby-lib) + endif() + + if(HAVE_NEVERBLEED) + target_link_libraries(nghttpx_static neverbleed) + endif() + + + if(HAVE_CUNIT) + set(NGHTTPX_UNITTEST_SOURCES + shrpx-unittest.cc + shrpx_tls_test.cc + shrpx_downstream_test.cc + shrpx_config_test.cc + shrpx_worker_test.cc + shrpx_http_test.cc + shrpx_router_test.cc + http2_test.cc + util_test.cc + nghttp2_gzip_test.c + nghttp2_gzip.c + buffer_test.cc + memchunk_test.cc + template_test.cc + base64_test.cc + ) + add_executable(nghttpx-unittest EXCLUDE_FROM_ALL + ${NGHTTPX_UNITTEST_SOURCES} + $ + $ + ) + target_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/src/HtmlParser.cc b/src/HtmlParser.cc new file mode 100644 index 0000000..591c4c7 --- /dev/null +++ b/src/HtmlParser.cc @@ -0,0 +1,217 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "HtmlParser.h" + +#include + +#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/src/HtmlParser.h b/src/HtmlParser.h new file mode 100644 index 0000000..1e84688 --- /dev/null +++ b/src/HtmlParser.h @@ -0,0 +1,94 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTML_PARSER_H +#define HTML_PARSER_H + +#include "nghttp2_config.h" + +#include +#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/src/HttpServer.cc b/src/HttpServer.cc new file mode 100644 index 0000000..0385cd0 --- /dev/null +++ b/src/HttpServer.cc @@ -0,0 +1,2248 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "HttpServer.h" + +#include +#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_alpn_result() != 0) { + return -1; + } + + read_ = &Http2Handler::read_tls; + write_ = &Http2Handler::write_tls; + + if (connection_made() != 0) { + return -1; + } + + if (sessions_->get_config()->verbose) { + if (SSL_session_reused(ssl_)) { + std::cerr << "SSL/TLS session reused" << std::endl; + } + } + + return 0; +} + +int Http2Handler::read_tls() { + std::array 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_alpn_result() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + // Check the negotiated protocol in ALPN + SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len); + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (sessions_->get_config()->verbose) { + std::cout << "The negotiated protocol: " << proto << std::endl; + } + if (util::check_h2_is_selected(proto)) { + return 0; + } + } + if (sessions_->get_config()->verbose) { + std::cerr << "Client did not advertise HTTP/2 protocol." + << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" + << std::endl; + } + return -1; +} + +int Http2Handler::submit_file_response(const StringRef &status, Stream *stream, + time_t last_modified, off_t file_length, + const std::string *content_type, + nghttp2_data_provider *data_prd) { + std::string last_modified_str; + auto nva = make_array(http2::make_nv_ls_nocopy(":status", status), + http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER), + http2::make_nv_ll("cache-control", "max-age=3600"), + http2::make_nv_ls("date", sessions_->get_cached_date()), + http2::make_nv_ll("", ""), http2::make_nv_ll("", ""), + http2::make_nv_ll("", ""), http2::make_nv_ll("", "")); + size_t nvlen = 4; + if (!get_config()->no_content_length) { + nva[nvlen++] = http2::make_nv_ls_nocopy( + "content-length", + util::make_string_ref_uint(stream->balloc, file_length)); + } + if (last_modified != 0) { + last_modified_str = util::http_date(last_modified); + nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str); + } + if (content_type) { + nva[nvlen++] = http2::make_nv_ls("content-type", *content_type); + } + auto &trailer_names = get_config()->trailer_names; + if (!trailer_names.empty()) { + nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names); + } + return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen, + data_prd); +} + +int Http2Handler::submit_response(const StringRef &status, int32_t stream_id, + const HeaderRefs &headers, + nghttp2_data_provider *data_prd) { + auto nva = std::vector(); + 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)}, + }; +} + +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 + +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 + +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 (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } +#endif // OPENSSL_NO_EC + + if (!config_->dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(config_->dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + std::cerr << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + +#if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + std::cerr << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + std::cerr << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } +#else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + + if (dh == nullptr) { + std::cerr << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +#endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(), + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; + return -1; + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, + config_->cert_file.c_str()) != 1) { + std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; + return -1; + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + std::cerr << "SSL_CTX_check_private_key failed." << std::endl; + return -1; + } + if (config_->verify_client) { + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + + next_proto = util::get_default_alpn(); + + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this); + } + + auto loop = EV_DEFAULT; + + Sessions sessions(this, loop, config_, ssl_ctx); + if (start_listen(this, loop, &sessions, config_) != 0) { + std::cerr << "Could not listen" << std::endl; + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + } + return -1; + } + + ev_run(loop, 0); + + SSL_CTX_free(ssl_ctx); + + return 0; +} + +const Config *HttpServer::get_config() const { return config_; } + +const StatusPage *HttpServer::get_status_page(int status) const { + switch (status) { + case 200: + return &status_pages_[IDX_200]; + case 301: + return &status_pages_[IDX_301]; + case 400: + return &status_pages_[IDX_400]; + case 404: + return &status_pages_[IDX_404]; + case 405: + return &status_pages_[IDX_405]; + default: + assert(0); + } + return nullptr; +} + +} // namespace nghttp2 diff --git a/src/HttpServer.h b/src/HttpServer.h new file mode 100644 index 0000000..949bd1f --- /dev/null +++ b/src/HttpServer.h @@ -0,0 +1,253 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTTP_SERVER_H +#define HTTP_SERVER_H + +#include "nghttp2_config.h" + +#include + +#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_alpn_result(); + + int submit_file_response(const StringRef &status, Stream *stream, + time_t last_modified, off_t file_length, + const std::string *content_type, + nghttp2_data_provider *data_prd); + + int submit_response(const StringRef &status, int32_t stream_id, + nghttp2_data_provider *data_prd); + + int submit_response(const StringRef &status, int32_t stream_id, + const HeaderRefs &headers, + nghttp2_data_provider *data_prd); + + int submit_non_final_response(const std::string &status, int32_t stream_id); + + int submit_push_promise(Stream *stream, const StringRef &push_path); + + int submit_rst_stream(Stream *stream, uint32_t error_code); + + void add_stream(int32_t stream_id, std::unique_ptr stream); + 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/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..f112ac2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,257 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = testdata + +EXTRA_DIST = \ + CMakeLists.txt \ + test.example.com.pem \ + test.nghttp2.org.pem + +bin_PROGRAMS = +check_PROGRAMS = +TESTS = + +AM_CFLAGS = $(WARNCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS) +AM_CPPFLAGS = \ + -DPKGDATADIR='"$(pkgdatadir)"' \ + -DPKGLIBDIR='"$(pkglibdir)"' \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/third-party \ + -I$(top_srcdir)/third-party/llhttp/include \ + @JEMALLOC_CFLAGS@ \ + @LIBXML2_CFLAGS@ \ + @LIBEV_CFLAGS@ \ + @LIBNGHTTP3_CFLAGS@ \ + @LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS@ \ + @LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS@ \ + @LIBNGTCP2_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @LIBCARES_CFLAGS@ \ + @JANSSON_CFLAGS@ \ + @LIBBPF_CFLAGS@ \ + @ZLIB_CFLAGS@ \ + @EXTRA_DEFS@ \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ + +LDADD = $(top_builddir)/lib/libnghttp2.la \ + $(top_builddir)/third-party/liburl-parser.la \ + $(top_builddir)/third-party/libllhttp.la \ + @JEMALLOC_LIBS@ \ + @LIBXML2_LIBS@ \ + @LIBEV_LIBS@ \ + @LIBNGHTTP3_LIBS@ \ + @LIBNGTCP2_CRYPTO_QUICTLS_LIBS@ \ + @LIBNGTCP2_CRYPTO_BORINGSSL_LIBS@ \ + @LIBNGTCP2_LIBS@ \ + @OPENSSL_LIBS@ \ + @LIBCARES_LIBS@ \ + @SYSTEMD_LIBS@ \ + @JANSSON_LIBS@ \ + @LIBBPF_LIBS@ \ + @ZLIB_LIBS@ \ + @APPLDFLAGS@ + +if ENABLE_APP + +bin_PROGRAMS += nghttp nghttpd nghttpx + +HELPER_OBJECTS = util.cc \ + http2.cc timegm.c app_helper.cc nghttp2_gzip.c +HELPER_HFILES = util.h \ + http2.h timegm.h app_helper.h nghttp2_config.h \ + nghttp2_gzip.h network.h + +HTML_PARSER_OBJECTS = +HTML_PARSER_HFILES = HtmlParser.h + +if HAVE_LIBXML2 +HTML_PARSER_OBJECTS += HtmlParser.cc +endif # HAVE_LIBXML2 + +nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc nghttp.h \ + ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \ + tls.cc tls.h ssl_compat.h + +nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \ + tls.cc tls.h ssl_compat.h \ + HttpServer.cc HttpServer.h + +bin_PROGRAMS += h2load + +h2load_SOURCES = util.cc util.h \ + http2.cc http2.h h2load.cc h2load.h \ + timegm.c timegm.h \ + tls.cc tls.h ssl_compat.h \ + h2load_session.h \ + h2load_http2_session.cc h2load_http2_session.h \ + h2load_http1_session.cc h2load_http1_session.h + +if ENABLE_HTTP3 +h2load_SOURCES += \ + h2load_http3_session.cc h2load_http3_session.h \ + h2load_quic.cc h2load_quic.h \ + quic.cc quic.h +endif # ENABLE_HTTP3 + +NGHTTPX_SRCS = \ + util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ + app_helper.cc app_helper.h \ + tls.cc tls.h ssl_compat.h \ + shrpx_config.cc shrpx_config.h \ + shrpx_error.h \ + shrpx_accept_handler.cc shrpx_accept_handler.h \ + shrpx_connection_handler.cc shrpx_connection_handler.h \ + shrpx_client_handler.cc shrpx_client_handler.h \ + shrpx_upstream.h \ + shrpx_http2_upstream.cc shrpx_http2_upstream.h \ + shrpx_https_upstream.cc shrpx_https_upstream.h \ + shrpx_downstream.cc shrpx_downstream.h \ + shrpx_downstream_connection.cc shrpx_downstream_connection.h \ + shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \ + shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \ + shrpx_http2_session.cc shrpx_http2_session.h \ + shrpx_downstream_queue.cc shrpx_downstream_queue.h \ + shrpx_log.cc shrpx_log.h \ + shrpx_http.cc shrpx_http.h \ + shrpx_io_control.cc shrpx_io_control.h \ + shrpx_tls.cc shrpx_tls.h \ + shrpx_worker.cc shrpx_worker.h \ + shrpx_log_config.cc shrpx_log_config.h \ + shrpx_connect_blocker.cc shrpx_connect_blocker.h \ + shrpx_live_check.cc shrpx_live_check.h \ + shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \ + shrpx_rate_limit.cc shrpx_rate_limit.h \ + shrpx_connection.cc shrpx_connection.h \ + shrpx_memcached_dispatcher.cc shrpx_memcached_dispatcher.h \ + shrpx_memcached_connection.cc shrpx_memcached_connection.h \ + shrpx_memcached_request.h \ + shrpx_memcached_result.h \ + shrpx_worker_process.cc shrpx_worker_process.h \ + shrpx_process.h \ + shrpx_signal.cc shrpx_signal.h \ + shrpx_router.cc shrpx_router.h \ + shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \ + shrpx_health_monitor_downstream_connection.cc \ + shrpx_health_monitor_downstream_connection.h \ + shrpx_null_downstream_connection.cc shrpx_null_downstream_connection.h \ + shrpx_exec.cc shrpx_exec.h \ + shrpx_dns_resolver.cc shrpx_dns_resolver.h \ + shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \ + shrpx_dns_tracker.cc shrpx_dns_tracker.h \ + buffer.h memchunk.h template.h allocator.h \ + xsi_strerror.c xsi_strerror.h + +if HAVE_MRUBY +NGHTTPX_SRCS += \ + shrpx_mruby.cc shrpx_mruby.h \ + shrpx_mruby_module.cc shrpx_mruby_module.h \ + shrpx_mruby_module_env.cc shrpx_mruby_module_env.h \ + shrpx_mruby_module_request.cc shrpx_mruby_module_request.h \ + shrpx_mruby_module_response.cc shrpx_mruby_module_response.h +endif # HAVE_MRUBY + +if ENABLE_HTTP3 +NGHTTPX_SRCS += \ + shrpx_quic.cc shrpx_quic.h \ + shrpx_quic_listener.cc shrpx_quic_listener.h \ + shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ + shrpx_http3_upstream.cc shrpx_http3_upstream.h \ + http3.cc http3.h \ + quic.cc quic.h +endif # ENABLE_HTTP3 + +noinst_LIBRARIES = libnghttpx.a +libnghttpx_a_SOURCES = ${NGHTTPX_SRCS} +libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS} + +nghttpx_SOURCES = shrpx.cc shrpx.h +nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS} +nghttpx_LDADD = libnghttpx.a ${LDADD} + +if HAVE_MRUBY +libnghttpx_a_CPPFLAGS += \ + -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@ +nghttpx_LDADD += -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@ +endif # HAVE_MRUBY + +if HAVE_NEVERBLEED +libnghttpx_a_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed +nghttpx_LDADD += ${top_builddir}/third-party/libneverbleed.la +endif # HAVE_NEVERBLEED + +if HAVE_CUNIT +check_PROGRAMS += nghttpx-unittest +nghttpx_unittest_SOURCES = shrpx-unittest.cc \ + shrpx_tls_test.cc shrpx_tls_test.h \ + shrpx_downstream_test.cc shrpx_downstream_test.h \ + shrpx_config_test.cc shrpx_config_test.h \ + shrpx_worker_test.cc shrpx_worker_test.h \ + shrpx_http_test.cc shrpx_http_test.h \ + shrpx_router_test.cc shrpx_router_test.h \ + http2_test.cc http2_test.h \ + util_test.cc util_test.h \ + nghttp2_gzip_test.c nghttp2_gzip_test.h \ + nghttp2_gzip.c nghttp2_gzip.h \ + buffer_test.cc buffer_test.h \ + memchunk_test.cc memchunk_test.h \ + template_test.cc template_test.h \ + base64_test.cc base64_test.h +nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \ + -DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\" +nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@ + +if HAVE_MRUBY +nghttpx_unittest_CPPFLAGS += \ + -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@ +nghttpx_unittest_LDADD += \ + -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@ +endif # HAVE_MRUBY + +if HAVE_NEVERBLEED +nghttpx_unittest_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed +nghttpx_unittest_LDADD += ${top_builddir}/third-party/libneverbleed.la +endif # HAVE_NEVERBLEED + +TESTS += nghttpx-unittest +endif # HAVE_CUNIT + +endif # ENABLE_APP + +if ENABLE_HPACK_TOOLS + +bin_PROGRAMS += inflatehd deflatehd + +HPACK_TOOLS_COMMON_SRCS = \ + comp_helper.c comp_helper.h \ + util.cc util.h \ + timegm.c timegm.h + +inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) + +deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) + +endif # ENABLE_HPACK_TOOLS diff --git a/src/allocator.h b/src/allocator.h new file mode 100644 index 0000000..97b9a41 --- /dev/null +++ b/src/allocator.h @@ -0,0 +1,273 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ALLOCATOR_H +#define ALLOCATOR_H + +#include "nghttp2_config.h" + +#ifndef _WIN32 +# include +#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/src/app_helper.cc b/src/app_helper.cc new file mode 100644 index 0000000..ef92762 --- /dev/null +++ b/src/app_helper.cc @@ -0,0 +1,518 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#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/src/app_helper.h b/src/app_helper.h new file mode 100644 index 0000000..5424054 --- /dev/null +++ b/src/app_helper.h @@ -0,0 +1,98 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef APP_HELPER_H +#define APP_HELPER_H + +#include "nghttp2_config.h" + +#include +#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/src/base64.h b/src/base64.h new file mode 100644 index 0000000..1bd51af --- /dev/null +++ b/src/base64.h @@ -0,0 +1,225 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef BASE64_H +#define BASE64_H + +#include "nghttp2_config.h" + +#include + +#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/src/base64_test.cc b/src/base64_test.cc new file mode 100644 index 0000000..4324bd7 --- /dev/null +++ b/src/base64_test.cc @@ -0,0 +1,121 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "base64_test.h" + +#include +#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/src/base64_test.h b/src/base64_test.h new file mode 100644 index 0000000..8bdb84f --- /dev/null +++ b/src/base64_test.h @@ -0,0 +1,39 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef BASE64_TEST_H +#define BASE64_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_base64_encode(void); +void test_base64_decode(void); + +} // namespace nghttp2 + +#endif // BASE64_TEST_H diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..1921edf --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,78 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef BUFFER_H +#define BUFFER_H + +#include "nghttp2_config.h" + +#include +#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/src/buffer_test.cc b/src/buffer_test.cc new file mode 100644 index 0000000..38688ed --- /dev/null +++ b/src/buffer_test.cc @@ -0,0 +1,78 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "buffer_test.h" + +#include +#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/src/buffer_test.h b/src/buffer_test.h new file mode 100644 index 0000000..6789aa3 --- /dev/null +++ b/src/buffer_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef BUFFER_TEST_H +#define BUFFER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_buffer_write(void); + +} // namespace nghttp2 + +#endif // BUFFER_TEST_H diff --git a/src/ca-config.json b/src/ca-config.json new file mode 100644 index 0000000..5788d07 --- /dev/null +++ b/src/ca-config.json @@ -0,0 +1,17 @@ +{ + "signing": { + "default": { + "expiry": "87600h" + }, + "profiles": { + "server": { + "expiry": "87600h", + "usages": [ + "signing", + "key encipherment", + "server auth" + ] + } + } + } +} diff --git a/src/ca.nghttp2.org-key.pem b/src/ca.nghttp2.org-key.pem new file mode 100644 index 0000000..6ce8707 --- /dev/null +++ b/src/ca.nghttp2.org-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA1kVkF8QSUwW/HV9EFRPSMoiOVmYwB8vqKDtT0d6MFiKAM8/Y +JFUq2uKlUydgT4IPE7PATvVcIj3GtL9XzPhscqYO/S0Y7scyTE2VAPmtz+StPWf2 +wZ1IQR09HrnDTc44KvYGZpefBZkD9UjbmJ9a1ZmJjJiMr3hTnKE/sxZ2+dMsnMZX +N822cfaHyTN+T0+Tyw5vBBboCDsZzxmf+9FFIDJNs3NL34cR8EZRhpfaegapH8bt +OJ+D+RZ2kg7E/YYkGcS6NodvTjSUFCFHpWjHCfTFhn/owBIAooCdWorh6dc8Q72l +AodwNLXS8uuPgPqM5s4Cz57m7Zgs4OilNmIdawIDAQABAoIBAQCwqLtygLye6KD+ +RXorapEmCsJX5553/x6Klwdvg+25ni5XCWjp47IWj0DBQzi7tL5bfxrxvod8z7QR +d6SbIMLA77px8Ima7G7CzEAqcrBkM+TFOP8P+G4HCWVH/N5SOtDCUt9KHH4Grna9 +95jdx5yreRAX8/oh/bHp9GRBcicbpwYMVWOnjTE2seEUYQOpdpYdP4bOPUvAju0l +mwmy2/dDGmbibktN3sdHEhDodKu+Znv7nFZo0jzhlyoXse653WcvaQeZZYuojvSe +Sr92DvPp7UaYrb4KvT7ujXiPavSV2m/4EmGtyqevUf2dZ6sfMXZjmXsjWz9txhWp +4BgbHyHRAoGBAPqyuNj2CDD3FE7N3Hxyba8d+ZtsVUNawjq2gwOvT9NLsMstOGyH +OCc1v4W6Sq4w1wo4nIJyY8kNZwtReaTHOPZlDgBhVvk/x8eLBu+QTMRyocRt1LoD +8HyKxWSAnYTtCh/GUEQ37amIqvOJ5GNL+25WDzevLa5kMYWG743uxEupAoGBANrN +c/fVxepvP0GISlLpL3aZCFGAjMrq3xUYcf/w4wPoMq6AdpIPeRVBmJ1/Uqw1FkV8 +NRKJNPE2YcMuv8iMeQlacoPd34KT9ob80EYVlMwAkeC0NK+FfiM/UteR0wB49gmi +ugX9YlJytOP9aUgPvEGT6l+XtgGC44W1TQWe62zzAoGBAKZenNU+0UjNb6isbToZ +Jjkkh1Vhm2PLg0I7hM6ZNTxf6r+rDtrXEajTvnocmxrmRo796r+W8immv09/jl6P +53l8rsIJ1xIqBYai+MNa29cyy6/zw0x++MVtwnlj8SUZubJEhVgAVbRAglKEnBBZ +iE48xnSJyKMG0uZuGePzJEmhAoGBAIOHJcNBumum3DuklikpC+MbMyjrQbdpYRjp +TP4x7AWZO34ysxQyQPNKL1feBfCHKRA0DiNKX4zwx+vw2lDQQKIiwNwMMCPqljOn +HfxDVOMdJJQTP+iTMrQ1iLMVceXC0QQR0glvu/8b/SlgWD19WAmDxUwZgst9xw/F +YLuUQKmJAoGAREeTugd4hc0U/YV/BQQjSCLhl11EtVry/oQMHj8KZpIJhP7tj8lw +hSE0+z04oMhiTeq55PYKQkTo5l6V4PW0zfpEwlKEEm0erab1G9Ddh7us47XFcKLl +Rmk192EVZ0lQuzftsYv7dzRLiAR7yDFXwD1ELIK/uPkwBtu7wtHlq+M= +-----END RSA PRIVATE KEY----- diff --git a/src/ca.nghttp2.org.csr b/src/ca.nghttp2.org.csr new file mode 100644 index 0000000..37ee560 --- /dev/null +++ b/src/ca.nghttp2.org.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICwjCCAaoCAQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2Eu +bmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWRWQX +xBJTBb8dX0QVE9IyiI5WZjAHy+ooO1PR3owWIoAzz9gkVSra4qVTJ2BPgg8Ts8BO +9VwiPca0v1fM+Gxypg79LRjuxzJMTZUA+a3P5K09Z/bBnUhBHT0eucNNzjgq9gZm +l58FmQP1SNuYn1rVmYmMmIyveFOcoT+zFnb50yycxlc3zbZx9ofJM35PT5PLDm8E +FugIOxnPGZ/70UUgMk2zc0vfhxHwRlGGl9p6Bqkfxu04n4P5FnaSDsT9hiQZxLo2 +h29ONJQUIUelaMcJ9MWGf+jAEgCigJ1aiuHp1zxDvaUCh3A0tdLy64+A+ozmzgLP +nubtmCzg6KU2Yh1rAgMBAAGgHzAdBgkqhkiG9w0BCQ4xEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBACI5v8GbOXKv38h9/tuGEwJ9uxpYEljgGt8h +QL5lwfEifh/7A8b39b9JEzWk5hnMRCOb8J6Jc3/6nmVgtKkQ+Mceupqpwsp1gT/v +uUoAkJE03Iuja9zLhHmy74oZ7LWOQrZ1T7Z0eGQ+5u+LBZiPKnKxmkLCQoUPTbc4 +NQ9BbKhr8OaoJ4DDvJnszcL7to6kih7SkdoNZsq4zB0/ai/cPhvoVgkYfbLH2++D +Tcs7TqU2L7gKzqXUtHeAKM2y81ewL7QTrcYzgiW86s3NmquxZG5pq0mjD+P4BYLc +MOdnCxKbBuE/1R29pa6+JKgc46jOa2yRgv5+8rXkkpu53Ke3FGc= +-----END CERTIFICATE REQUEST----- diff --git a/src/ca.nghttp2.org.csr.json b/src/ca.nghttp2.org.csr.json new file mode 100644 index 0000000..69d9c6a --- /dev/null +++ b/src/ca.nghttp2.org.csr.json @@ -0,0 +1,17 @@ +{ + "CN": "ca.nghttp2.org", + "key": { + "algo": "rsa", + "size": 2048 + }, + "ca": { + "expiry": "87600h" + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/src/ca.nghttp2.org.pem b/src/ca.nghttp2.org.pem new file mode 100644 index 0000000..e50acfc --- /dev/null +++ b/src/ca.nghttp2.org.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIUe4dvx8haIjsT3ZpNCMrl62Xk6E0wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBeMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRcwFQYDVQQDEw5jYS5uZ2h0dHAyLm9yZzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANZFZBfEElMFvx1fRBUT0jKIjlZmMAfL6ig7U9He +jBYigDPP2CRVKtripVMnYE+CDxOzwE71XCI9xrS/V8z4bHKmDv0tGO7HMkxNlQD5 +rc/krT1n9sGdSEEdPR65w03OOCr2BmaXnwWZA/VI25ifWtWZiYyYjK94U5yhP7MW +dvnTLJzGVzfNtnH2h8kzfk9Pk8sObwQW6Ag7Gc8Zn/vRRSAyTbNzS9+HEfBGUYaX +2noGqR/G7Tifg/kWdpIOxP2GJBnEujaHb040lBQhR6Voxwn0xYZ/6MASAKKAnVqK +4enXPEO9pQKHcDS10vLrj4D6jObOAs+e5u2YLODopTZiHWsCAwEAAaNjMGEwDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNA5xVR1Zcax +RJL9VC6pzuLmvduGMB8GA1UdIwQYMBaAFNA5xVR1ZcaxRJL9VC6pzuLmvduGMA0G +CSqGSIb3DQEBCwUAA4IBAQCmdVfn/hUyEdvkKG7svg5d8o6BENOj8695KtWmzJjK +zxH8J5Vy3mn89XrHQ+BOYXCDPyhs0aDS8aq3Z+HY0n9z1oAicyGzlVwZQQNX3YId +Y2vcf7qu/2ATm/1S+mebE1/EXMUlWISKKUYXjggCwFgjDhH87Ai+A8MKScVdmqgL +Hf+fRSzH3ToW7BCXlRl5bPAq2g+v1ALYc8wU9cT1MYm4dqAXh870LGFyUpaSWmFr +TtX1DXBTgLp62syNlDthAvGigYFDtCa4cDM2vdTD9wpec2V9EKpfVqiRDDuYjUVX +UXl27MvkNWnEBKCIoNv5abWXpZVG2zQdEMmUOkVuAXUC +-----END CERTIFICATE----- diff --git a/src/comp_helper.c b/src/comp_helper.c new file mode 100644 index 0000000..98db08a --- /dev/null +++ b/src/comp_helper.c @@ -0,0 +1,133 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "comp_helper.h" +#include + +static void dump_val(json_t *jent, const char *key, uint8_t *val, size_t len) { + json_object_set_new(jent, key, json_pack("s#", val, len)); +} + +#define NGHTTP2_HD_ENTRY_OVERHEAD 32 + +json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater) { + json_t *obj, *entries; + size_t i; + size_t len = nghttp2_hd_deflate_get_num_table_entries(deflater); + + obj = json_object(); + entries = json_array(); + /* The first index of dynamic table is 62 */ + for (i = 62; i <= len; ++i) { + const nghttp2_nv *nv = nghttp2_hd_deflate_get_table_entry(deflater, i); + json_t *outent = json_object(); + json_object_set_new(outent, "index", json_integer((json_int_t)i)); + dump_val(outent, "name", nv->name, nv->namelen); + dump_val(outent, "value", nv->value, nv->valuelen); + json_object_set_new(outent, "size", + json_integer((json_int_t)(nv->namelen + nv->valuelen + + NGHTTP2_HD_ENTRY_OVERHEAD))); + json_array_append_new(entries, outent); + } + json_object_set_new(obj, "entries", entries); + json_object_set_new( + obj, "size", + json_integer( + (json_int_t)nghttp2_hd_deflate_get_dynamic_table_size(deflater))); + json_object_set_new( + obj, "max_size", + json_integer( + (json_int_t)nghttp2_hd_deflate_get_max_dynamic_table_size(deflater))); + + return obj; +} + +json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater) { + json_t *obj, *entries; + size_t i; + size_t len = nghttp2_hd_inflate_get_num_table_entries(inflater); + + obj = json_object(); + entries = json_array(); + /* The first index of dynamic table is 62 */ + for (i = 62; i <= len; ++i) { + const nghttp2_nv *nv = nghttp2_hd_inflate_get_table_entry(inflater, i); + json_t *outent = json_object(); + json_object_set_new(outent, "index", json_integer((json_int_t)i)); + dump_val(outent, "name", nv->name, nv->namelen); + dump_val(outent, "value", nv->value, nv->valuelen); + json_object_set_new(outent, "size", + json_integer((json_int_t)(nv->namelen + nv->valuelen + + NGHTTP2_HD_ENTRY_OVERHEAD))); + json_array_append_new(entries, outent); + } + json_object_set_new(obj, "entries", entries); + json_object_set_new( + obj, "size", + json_integer( + (json_int_t)nghttp2_hd_inflate_get_dynamic_table_size(inflater))); + json_object_set_new( + obj, "max_size", + json_integer( + (json_int_t)nghttp2_hd_inflate_get_max_dynamic_table_size(inflater))); + + return obj; +} + +json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen) { + json_t *nv_pair = json_object(); + char *cname = malloc(namelen + 1); + if (cname == NULL) { + return NULL; + } + memcpy(cname, name, namelen); + cname[namelen] = '\0'; + json_object_set_new(nv_pair, cname, json_pack("s#", value, valuelen)); + free(cname); + return nv_pair; +} + +json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen) { + json_t *headers; + size_t i; + + headers = json_array(); + for (i = 0; i < nvlen; ++i) { + json_array_append_new(headers, dump_header(nva[i].name, nva[i].namelen, + nva[i].value, nva[i].valuelen)); + } + return headers; +} + +void output_json_header(void) { + printf("{\n" + " \"cases\":\n" + " [\n"); +} + +void output_json_footer(void) { + printf(" ]\n" + "}\n"); +} diff --git a/src/comp_helper.h b/src/comp_helper.h new file mode 100644 index 0000000..131ed21 --- /dev/null +++ b/src/comp_helper.h @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_COMP_HELPER_H +#define NGHTTP2_COMP_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#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/src/deflatehd.cc b/src/deflatehd.cc new file mode 100644 index 0000000..7dcfccf --- /dev/null +++ b/src/deflatehd.cc @@ -0,0 +1,450 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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/src/h2load.cc b/src/h2load.cc new file mode 100644 index 0000000..0f07610 --- /dev/null +++ b/src/h2load.cc @@ -0,0 +1,3292 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load.h" + +#include +#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 + +namespace { +std::ofstream keylog_file; +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + +Config::Config() + : ciphers(tls::DEFAULT_CIPHER_LIST), + tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_" + "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"), + groups("X25519:P-256:P-384:P-521"), + data_length(-1), + data(nullptr), + addrs(nullptr), + nreqs(1), + nclients(1), + nthreads(1), + max_concurrent_streams(1), + window_bits(30), + connection_window_bits(30), + max_frame_size(16_k), + rate(0), + rate_period(1.0), + duration(0.0), + warm_up_time(0.0), + conn_active_timeout(0.), + conn_inactivity_timeout(0.), + no_tls_proto(PROTO_HTTP2), + header_table_size(4_k), + encoder_header_table_size(4_k), + data_fd(-1), + log_fd(-1), + qlog_file_base(), + port(0), + default_port(0), + connect_to_port(0), + verbose(false), + timing_script(false), + base_uri_unix(false), + unix_addr{}, + rps(0.), + no_udp_gso(false), + max_udp_payload_size(0), + ktls(false) {} + +Config::~Config() { + if (addrs) { + if (base_uri_unix) { + delete addrs; + } else { + freeaddrinfo(addrs); + } + } + + if (data_fd != -1) { + close(data_fd); + } +} + +bool Config::is_rate_mode() const { return (this->rate != 0); } +bool Config::is_timing_based_mode() const { return (this->duration > 0); } +bool Config::has_base_uri() const { return (!this->base_uri.empty()); } +bool Config::rps_enabled() const { return this->rps > 0.0; } +bool Config::is_quic() const { +#ifdef ENABLE_HTTP3 + return !alpn_list.empty() && + (alpn_list[0] == NGHTTP3_ALPN_H3 || alpn_list[0] == "\x5h3-29"); +#else // !ENABLE_HTTP3 + return false; +#endif // !ENABLE_HTTP3 +} +Config config; + +namespace { +constexpr size_t MAX_SAMPLES = 1000000; +} // namespace + +Stats::Stats(size_t req_todo, size_t nclients) + : req_todo(req_todo), + req_started(0), + req_done(0), + req_success(0), + req_status_success(0), + req_failed(0), + req_error(0), + req_timedout(0), + bytes_total(0), + bytes_head(0), + bytes_head_decomp(0), + bytes_body(0), + status(), + udp_dgram_recv(0), + udp_dgram_sent(0) {} + +Stream::Stream() : req_stat{}, status_success(-1) {} + +namespace { +std::random_device rd; +} // namespace + +namespace { +std::mt19937 gen(rd()); +} // namespace + +namespace { +void sampling_init(Sampling &smp, size_t max_samples) { + smp.n = 0; + smp.max_samples = max_samples; +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(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) { + 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; + } +} +} // namespace + +void Client::report_tls_info() { + if (worker->id == 0 && !worker->tls_info_report_done) { + worker->tls_info_report_done = true; + auto cipher = SSL_get_current_cipher(ssl); + std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n" + << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl; + print_server_tmp_key(ssl); + } +} + +void Client::report_app_info() { + if (worker->id == 0 && !worker->app_info_report_done) { + worker->app_info_report_done = true; + std::cout << "Application protocol: " << selected_proto << std::endl; + } +} + +void Client::terminate_session() { +#ifdef ENABLE_HTTP3 + if (config.is_quic()) { + quic.close_requested = true; + } +#endif // ENABLE_HTTP3 + if (session) { + session->terminate(); + } + // http1 session needs writecb to tear down session. + signal_write(); +} + +void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); } + +void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen) { + auto itr = streams.find(stream_id); + if (itr == std::end(streams)) { + return; + } + auto &stream = (*itr).second; + + if (worker->current_phase != Phase::MAIN_DURATION) { + // If the stream is for warm-up phase, then mark as a success + // But we do not update the count for 2xx, 3xx, etc status codes + // Same has been done in on_status_code function + stream.status_success = 1; + return; + } + + if (stream.status_success == -1 && namelen == 7 && + util::streq_l(":status", name, namelen)) { + int status = 0; + for (size_t i = 0; i < valuelen; ++i) { + if ('0' <= value[i] && value[i] <= '9') { + status *= 10; + status += value[i] - '0'; + if (status > 999) { + stream.status_success = 0; + return; + } + } else { + break; + } + } + + if (status < 200) { + return; + } + + stream.req_stat.status = status; + if (status >= 200 && status < 300) { + ++worker->stats.status[2]; + stream.status_success = 1; + } else if (status < 400) { + ++worker->stats.status[3]; + stream.status_success = 1; + } else if (status < 600) { + ++worker->stats.status[status / 100]; + stream.status_success = 0; + } else { + stream.status_success = 0; + } + } +} + +void Client::on_status_code(int32_t stream_id, uint16_t status) { + auto itr = streams.find(stream_id); + if (itr == std::end(streams)) { + return; + } + auto &stream = (*itr).second; + + if (worker->current_phase != Phase::MAIN_DURATION) { + stream.status_success = 1; + return; + } + + stream.req_stat.status = status; + if (status >= 200 && status < 300) { + ++worker->stats.status[2]; + stream.status_success = 1; + } else if (status < 400) { + ++worker->stats.status[3]; + stream.status_success = 1; + } else if (status < 600) { + ++worker->stats.status[status / 100]; + stream.status_success = 0; + } else { + stream.status_success = 0; + } +} + +void Client::on_stream_close(int32_t stream_id, bool success, bool final) { + if (worker->current_phase == Phase::MAIN_DURATION) { + if (req_inflight > 0) { + --req_inflight; + } + auto req_stat = get_req_stat(stream_id); + if (!req_stat) { + return; + } + + req_stat->stream_close_time = std::chrono::steady_clock::now(); + if (success) { + req_stat->completed = true; + ++worker->stats.req_success; + ++cstat.req_success; + + if (streams[stream_id].status_success == 1) { + ++worker->stats.req_status_success; + } else { + ++worker->stats.req_failed; + } + + worker->sample_req_stat(req_stat); + + // Count up in successful cases only + ++worker->request_times_smp.n; + } else { + ++worker->stats.req_failed; + ++worker->stats.req_error; + } + ++worker->stats.req_done; + ++req_done; + + if (worker->config->log_fd != -1) { + auto start = std::chrono::duration_cast( + 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; + + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); + + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 + assert(session); + if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) && + !util::streq_l("h3-29", proto)) { + return -1; + } +#endif // ENABLE_HTTP3 + } else if (util::check_h2_is_selected(proto)) { + session = std::make_unique(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.alpn_list) { + if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) { + std::cout << "Server does not support ALPN. Falling back to HTTP/1.1." + << std::endl; + session = std::make_unique(this); + selected_proto = NGHTTP2_H1_1.str(); + break; + } + } + } + + if (!selected_proto.empty()) { + report_app_info(); + } + + if (!session) { + std::cout + << "No supported protocol was negotiated. Supported protocols were:" + << std::endl; + for (const auto &proto : config.alpn_list) { + std::cout << proto.substr(1) << std::endl; + } + disconnect(); + return -1; + } + } else { + switch (config.no_tls_proto) { + case Config::PROTO_HTTP2: + session = std::make_unique(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 + +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_ALPN_LIST[] = "h2,h2-16,h2-14,http/1.1"; +} // namespace + +namespace { +void print_help(std::ostream &out) { + print_usage(out); + + auto config = Config(); + + out << R"( + 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. + --alpn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. The parameter must be delimited + by a single comma only and any white spaces are treated + as a part of protocol string. + Default: )" + << DEFAULT_ALPN_LIST << R"( + --h1 Short hand for --alpn-list=http/1.1 + --no-tls-proto=http/1.1, which effectively force + http/1.1 for both http and https URI. + --header-table-size= + 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) { + std::string datafile; + std::string logfile; + bool nreqs_set_manually = false; + while (1) { + static int flag = 0; + constexpr static option long_options[] = { + {"requests", required_argument, nullptr, 'n'}, + {"clients", required_argument, nullptr, 'c'}, + {"data", required_argument, nullptr, 'd'}, + {"threads", required_argument, nullptr, 't'}, + {"max-concurrent-streams", required_argument, nullptr, 'm'}, + {"window-bits", required_argument, nullptr, 'w'}, + {"max-frame-size", required_argument, nullptr, 'f'}, + {"connection-window-bits", required_argument, nullptr, 'W'}, + {"input-file", required_argument, nullptr, 'i'}, + {"header", required_argument, nullptr, 'H'}, + {"no-tls-proto", required_argument, nullptr, 'p'}, + {"verbose", no_argument, nullptr, 'v'}, + {"help", no_argument, nullptr, 'h'}, + {"version", no_argument, &flag, 1}, + {"ciphers", required_argument, &flag, 2}, + {"rate", required_argument, nullptr, 'r'}, + {"connection-active-timeout", required_argument, nullptr, 'T'}, + {"connection-inactivity-timeout", required_argument, nullptr, 'N'}, + {"duration", required_argument, nullptr, 'D'}, + {"timing-script-file", required_argument, &flag, 3}, + {"base-uri", required_argument, nullptr, 'B'}, + {"npn-list", required_argument, &flag, 4}, + {"rate-period", required_argument, &flag, 5}, + {"h1", no_argument, &flag, 6}, + {"header-table-size", required_argument, &flag, 7}, + {"encoder-header-table-size", required_argument, &flag, 8}, + {"warm-up-time", required_argument, &flag, 9}, + {"log-file", required_argument, &flag, 10}, + {"connect-to", required_argument, &flag, 11}, + {"rps", required_argument, &flag, 12}, + {"groups", required_argument, &flag, 13}, + {"tls13-ciphers", required_argument, &flag, 14}, + {"no-udp-gso", no_argument, &flag, 15}, + {"qlog-file-base", required_argument, &flag, 16}, + {"max-udp-payload-size", required_argument, &flag, 17}, + {"ktls", no_argument, &flag, 18}, + {"alpn-list", required_argument, &flag, 19}, + {nullptr, 0, nullptr, 0}}; + int option_index = 0; + auto c = getopt_long(argc, argv, + "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'n': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-n: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nreqs = n; + nreqs_set_manually = true; + break; + } + case 'c': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-c: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nclients = n; + break; + } + case 'd': + datafile = optarg; + break; + case 't': { +#ifdef NOTHREADS + std::cerr << "-t: WARNING: Threading disabled at build time, " + << "no threads created." << std::endl; +#else + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-t: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nthreads = n; +#endif // NOTHREADS + break; + } + case 'm': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-m: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.max_concurrent_streams = n; + break; + } + case 'w': + case 'W': { + auto n = util::parse_uint(optarg); + if (n == -1 || n > 30) { + std::cerr << "-" << static_cast(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 5: + // rate-period + config.rate_period = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.rate_period)) { + std::cerr << "--rate-period: value error " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 6: + // --h1 + config.alpn_list = + util::parse_config_str_list(StringRef::from_lit("http/1.1")); + config.no_tls_proto = Config::PROTO_HTTP1_1; + break; + case 7: + // --header-table-size + if (parse_header_table_size(config.header_table_size, + "header-table-size", optarg) != 0) { + exit(EXIT_FAILURE); + } + break; + case 8: + // --encoder-header-table-size + if (parse_header_table_size(config.encoder_header_table_size, + "encoder-header-table-size", optarg) != 0) { + exit(EXIT_FAILURE); + } + break; + case 9: + // --warm-up-time + config.warm_up_time = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.warm_up_time)) { + std::cerr << "--warm-up-time: value error " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 10: + // --log-file + logfile = optarg; + break; + case 11: { + // --connect-to + auto p = util::split_hostport(StringRef{optarg}); + int64_t port = 0; + if (p.first.empty() || + (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) { + std::cerr << "--connect-to: Invalid value " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.connect_to_host = p.first.str(); + config.connect_to_port = port; + break; + } + case 12: { + char *end; + auto v = std::strtod(optarg, &end); + if (end == optarg || *end != '\0' || !std::isfinite(v) || + 1. / v < 1e-6) { + std::cerr << "--rps: Invalid value " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.rps = v; + break; + } + case 13: + // --groups + config.groups = optarg; + break; + case 14: + // --tls13-ciphers + config.tls13_ciphers = optarg; + break; + case 15: + // --no-udp-gso + config.no_udp_gso = true; + break; + case 16: + // --qlog-file-base + config.qlog_file_base = optarg; + break; + case 17: { + // --max-udp-payload-size + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--max-udp-payload-size: bad option value: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + if (static_cast(n) > 64_k) { + std::cerr << "--max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } + config.max_udp_payload_size = n; + break; + } + case 18: + // --ktls + config.ktls = true; + break; + case 4: + // npn-list option + std::cerr << "--npn-list: deprecated. Use --alpn-list instead." + << std::endl; + // fall through + case 19: + // alpn-list option + config.alpn_list = util::parse_config_str_list(StringRef{optarg}); + break; + } + break; + default: + break; + } + } + + if (argc == optind) { + if (config.ifile.empty()) { + std::cerr << "no URI or input file given" << std::endl; + exit(EXIT_FAILURE); + } + } + + if (config.nclients == 0) { + std::cerr << "-c: the number of clients must be strictly greater than 0." + << std::endl; + exit(EXIT_FAILURE); + } + + if (config.alpn_list.empty()) { + config.alpn_list = + util::parse_config_str_list(StringRef::from_lit(DEFAULT_ALPN_LIST)); + } + + // serialize the APLN tokens + for (auto &proto : config.alpn_list) { + proto.insert(proto.begin(), static_cast(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 (!config.qlog_file_base.empty() && !config.is_quic()) { + std::cerr << "Warning: --qlog-file-base: only effective in quic, ignoring." + << std::endl; + } + + struct sigaction act {}; + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + + auto ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + std::cerr << "Failed to create SSL_CTX: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + exit(EXIT_FAILURE); + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + +#ifdef SSL_OP_ENABLE_KTLS + if (config.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS + if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { + std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed" + << std::endl; + exit(EXIT_FAILURE); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { + std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed" + << std::endl; + exit(EXIT_FAILURE); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +#endif // ENABLE_HTTP3 + } else if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, + nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { + std::cerr << "Could not set TLS versions" << std::endl; + exit(EXIT_FAILURE); + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) { + std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + exit(EXIT_FAILURE); + } + +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) { + std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + exit(EXIT_FAILURE); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL + + if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { + std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; + exit(EXIT_FAILURE); + } + + std::vector proto_list; + for (const auto &proto : config.alpn_list) { + std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list)); + } + + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); + + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); + } + } + + std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION; + Headers shared_nva; + shared_nva.emplace_back(":scheme", config.scheme); + shared_nva.emplace_back(":authority", make_http_authority(config)); + shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST"); + shared_nva.emplace_back("user-agent", user_agent); + + // list header fields that can be overridden. + auto override_hdrs = make_array(":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/src/h2load.h b/src/h2load.h new file mode 100644 index 0000000..11bb54c --- /dev/null +++ b/src/h2load.h @@ -0,0 +1,510 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_H +#define H2LOAD_H + +#include "nghttp2_config.h" + +#include +#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 ALPN protocol strings in the order of + // preference. + std::vector alpn_list; + // The number of request per second for each client. + double rps; + // Disables GSO for UDP connections. + bool no_udp_gso; + // The maximum UDP datagram payload size to send. + size_t max_udp_payload_size; + // Enable ktls. + bool ktls; + + Config(); + ~Config(); + + bool is_rate_mode() const; + bool is_timing_based_mode() const; + bool has_base_uri() const; + bool rps_enabled() const; + bool is_quic() const; +}; + +struct RequestStat { + // time point when request was sent + std::chrono::steady_clock::time_point request_time; + // same, but in wall clock reference frame + std::chrono::system_clock::time_point request_wall_time; + // time point when stream was closed + std::chrono::steady_clock::time_point stream_close_time; + // upload data length sent so far + int64_t data_offset; + // HTTP status code + int status; + // true if stream was successfully closed. This means stream was + // not reset, but it does not mean HTTP level error (e.g., 404). + bool completed; +}; + +struct ClientStat { + // time client started (i.e., first connect starts) + std::chrono::steady_clock::time_point client_start_time; + // time client end (i.e., client somehow processed all requests it + // is responsible for, and disconnected) + std::chrono::steady_clock::time_point client_end_time; + // The number of requests completed successful, but not necessarily + // means successful HTTP status code. + size_t req_success; + + // The following 3 numbers are overwritten each time when connection + // is made. + + // time connect starts + std::chrono::steady_clock::time_point connect_start_time; + // time to connect + std::chrono::steady_clock::time_point connect_time; + // time to first byte (TTFB) + std::chrono::steady_clock::time_point ttfb; +}; + +struct SDStat { + // min, max, mean and sd (standard deviation) + double min, max, mean, sd; + // percentage of samples inside mean -/+ sd + double within_sd; +}; + +struct SDStats { + // time for request + SDStat request; + // time for connect + SDStat connect; + // time to first byte (TTFB) + SDStat ttfb; + // request per second for each client + SDStat rps; +}; + +struct Stats { + Stats(size_t req_todo, size_t nclients); + // The total number of requests + size_t req_todo; + // The number of requests issued so far + size_t req_started; + // The number of requests finished + size_t req_done; + // The number of requests completed successful, but not necessarily + // means successful HTTP status code. + size_t req_success; + // The number of requests marked as success. HTTP status code is + // also considered as success. This is subset of req_done. + size_t req_status_success; + // The number of requests failed. This is subset of req_done. + size_t req_failed; + // The number of requests failed due to network errors. This is + // subset of req_failed. + size_t req_error; + // The number of requests that failed due to timeout. + size_t req_timedout; + // The number of bytes received on the "wire". If SSL/TLS is used, + // this is the number of decrypted bytes the application received. + int64_t bytes_total; + // The number of bytes received for header fields. This is + // compressed version. + int64_t bytes_head; + // The number of bytes received for header fields after they are + // decompressed. + int64_t bytes_head_decomp; + // The number of bytes received in DATA frame. + int64_t bytes_body; + // The number of each HTTP status category, status[i] is status code + // in the range [i*100, (i+1)*100). + std::array 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/src/h2load_http1_session.cc b/src/h2load_http1_session.cc new file mode 100644 index 0000000..6bdcd04 --- /dev/null +++ b/src/h2load_http1_session.cc @@ -0,0 +1,306 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 British Broadcasting Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_http1_session.h" + +#include +#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/src/h2load_http1_session.h b/src/h2load_http1_session.h new file mode 100644 index 0000000..cc10f50 --- /dev/null +++ b/src/h2load_http1_session.h @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 British Broadcasting Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_HTTP1_SESSION_H +#define H2LOAD_HTTP1_SESSION_H + +#include "h2load_session.h" + +#include + +#include "llhttp.h" + +namespace h2load { + +struct Client; + +class Http1Session : public Session { +public: + Http1Session(Client *client); + virtual ~Http1Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + Client *get_client(); + int32_t stream_req_counter_; + int32_t stream_resp_counter_; + +private: + Client *client_; + llhttp_t htp_; + bool complete_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP1_SESSION_H diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc new file mode 100644 index 0000000..b058a33 --- /dev/null +++ b/src/h2load_http2_session.cc @@ -0,0 +1,314 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_http2_session.h" + +#include +#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) { + 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); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + client->worker->stats.bytes_head += + frame->hd.length - frame->headers.padlen - + ((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0); + // fall through + case NGHTTP2_DATA: + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + client->record_ttfb(); + } + break; + } + return 0; +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto client = static_cast(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/src/h2load_http2_session.h b/src/h2load_http2_session.h new file mode 100644 index 0000000..1b9b9f7 --- /dev/null +++ b/src/h2load_http2_session.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_HTTP2_SESSION_H +#define H2LOAD_HTTP2_SESSION_H + +#include "h2load_session.h" + +#include + +namespace h2load { + +struct Client; + +class Http2Session : public Session { +public: + Http2Session(Client *client); + virtual ~Http2Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + +private: + Client *client_; + nghttp2_session *session_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP2_SESSION_H diff --git a/src/h2load_http3_session.cc b/src/h2load_http3_session.cc new file mode 100644 index 0000000..d491cba --- /dev/null +++ b/src/h2load_http3_session.cc @@ -0,0 +1,474 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_http3_session.h" + +#include + +#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 end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto s = static_cast(user_data); + if (s->end_stream(stream_id) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::end_stream(int64_t stream_id) { + client_->record_ttfb(); + + return 0; +} + +namespace { +int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + auto s = static_cast(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, + h2load::end_stream, + h2load::reset_stream, + nullptr, // shutdown + }; + + auto config = client_->worker->config; + + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = config->header_table_size; + settings.qpack_blocked_streams = 100; + + auto mem = nghttp3_mem_default(); + + rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this); + if (rv != 0) { + std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t ctrl_stream_id; + + rv = + ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id, + nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id, + nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id, + qpack_dec_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + auto nconsumed = nghttp3_conn_read_stream( + conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (nconsumed < 0) { + std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed) + << std::endl; + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, + nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, 0); + return -1; + } + return nconsumed; +} + +ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin, + nghttp3_vec *vec, size_t veccnt) { + auto sveccnt = + nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt); + if (sveccnt < 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, + nghttp3_err_infer_quic_app_error_code(sveccnt), nullptr, 0); + return -1; + } + return sveccnt; +} + +void Http3Session::block_stream(int64_t stream_id) { + nghttp3_conn_block_stream(conn_, stream_id); +} + +int Http3Session::unblock_stream(int64_t stream_id) { + if (nghttp3_conn_unblock_stream(conn_, stream_id) != 0) { + return -1; + } + + return 0; +} + +void Http3Session::shutdown_stream_write(int64_t stream_id) { + nghttp3_conn_shutdown_stream_write(conn_, stream_id); +} + +int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) { + auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen); + if (rv != 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv), + nullptr, 0); + return -1; + } + return 0; +} + +int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) { + auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen); + if (rv != 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv), + nullptr, 0); + return -1; + } + return 0; +} + +} // namespace h2load diff --git a/src/h2load_http3_session.h b/src/h2load_http3_session.h new file mode 100644 index 0000000..8610417 --- /dev/null +++ b/src/h2load_http3_session.h @@ -0,0 +1,84 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_HTTP3_SESSION_H +#define H2LOAD_HTTP3_SESSION_H + +#include "h2load_session.h" + +#include + +namespace h2load { + +struct Client; + +class Http3Session : public Session { +public: + Http3Session(Client *client); + virtual ~Http3Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + + int init_conn(); + int stream_close(int64_t stream_id, uint64_t app_error_code); + int end_stream(int64_t stream_id); + void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen); + void consume(int64_t stream_id, size_t nconsumed); + void begin_headers(int64_t stream_id); + void recv_header(int64_t stream_id, const nghttp3_vec *name, + const nghttp3_vec *value); + int stop_sending(int64_t stream_id, uint64_t app_error_code); + int reset_stream(int64_t stream_id, uint64_t app_error_code); + + int close_stream(int64_t stream_id, uint64_t app_error_code); + int shutdown_stream_read(int64_t stream_id); + int extend_max_local_streams(); + int64_t submit_request_internal(); + + ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec, + size_t veccnt); + void block_stream(int64_t stream_id); + int unblock_stream(int64_t stream_id); + void shutdown_stream_write(int64_t stream_id); + int add_write_offset(int64_t stream_id, size_t ndatalen); + int add_ack_offset(int64_t stream_id, size_t datalen); + + void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags); + +private: + Client *client_; + nghttp3_conn *conn_; + size_t npending_request_; + size_t reqidx_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP3_SESSION_H diff --git a/src/h2load_quic.cc b/src/h2load_quic.cc new file mode 100644 index 0000000..e492a3e --- /dev/null +++ b/src/h2load_quic.cc @@ -0,0 +1,844 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "h2load_quic.h" + +#include + +#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->alpn_list.size()); + + uint32_t quic_version; + + if (config->alpn_list[0] == NGHTTP3_ALPN_H3) { + quic_version = NGTCP2_PROTO_VER_V1; + } else { + quic_version = NGTCP2_PROTO_VER_MIN; + } + + rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version, + &callbacks, &settings, ¶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/src/h2load_quic.h b/src/h2load_quic.h new file mode 100644 index 0000000..225f00f --- /dev/null +++ b/src/h2load_quic.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_QUIC_H +#define H2LOAD_QUIC_H + +#include "nghttp2_config.h" + +#include + +#include "h2load.h" + +namespace h2load { +void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents); +} // namespace h2load + +#endif // H2LOAD_QUIC_H diff --git a/src/h2load_session.h b/src/h2load_session.h new file mode 100644 index 0000000..ab3b8ec --- /dev/null +++ b/src/h2load_session.h @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H2LOAD_SESSION_H +#define H2LOAD_SESSION_H + +#include "nghttp2_config.h" + +#include + +#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/src/http-parser.patch b/src/http-parser.patch new file mode 100644 index 0000000..ef80940 --- /dev/null +++ b/src/http-parser.patch @@ -0,0 +1,28 @@ +commit a143133d43420ef89e4ba0d84c73998863cf9f81 +Author: Tatsuhiro Tsujikawa +Date: Wed Jul 11 18:46:00 2012 +0900 + + Use http_parser for tunneling connection transparently + +diff --git a/examples/http-parser/http_parser.c b/examples/http-parser/http_parser.c +index 0c11eb8..610da57 100644 +--- a/examples/http-parser/http_parser.c ++++ b/examples/http-parser/http_parser.c +@@ -1627,9 +1627,14 @@ size_t http_parser_execute (http_parser *parser, + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { +- parser->state = NEW_MESSAGE(); +- CALLBACK_NOTIFY(message_complete); +- return (p - data) + 1; ++ /* We want to use http_parser for tunneling connection ++ transparently */ ++ /* Read body until EOF */ ++ parser->state = s_body_identity_eof; ++ break; ++ /* parser->state = NEW_MESSAGE(); */ ++ /* CALLBACK_NOTIFY(message_complete); */ ++ /* return (p - data) + 1; */ + } + + if (parser->flags & F_SKIPBODY) { diff --git a/src/http2.cc b/src/http2.cc new file mode 100644 index 0000000..661c4c9 --- /dev/null +++ b/src/http2.cc @@ -0,0 +1,2096 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "http2.h" + +#include "llhttp.h" + +#include "util.h" + +namespace nghttp2 { + +namespace http2 { + +StringRef get_reason_phrase(unsigned int status_code) { + switch (status_code) { + case 100: + return StringRef::from_lit("Continue"); + case 101: + return StringRef::from_lit("Switching Protocols"); + case 103: + return StringRef::from_lit("Early Hints"); + case 200: + return StringRef::from_lit("OK"); + case 201: + return StringRef::from_lit("Created"); + case 202: + return StringRef::from_lit("Accepted"); + case 203: + return StringRef::from_lit("Non-Authoritative Information"); + case 204: + return StringRef::from_lit("No Content"); + case 205: + return StringRef::from_lit("Reset Content"); + case 206: + return StringRef::from_lit("Partial Content"); + case 300: + return StringRef::from_lit("Multiple Choices"); + case 301: + return StringRef::from_lit("Moved Permanently"); + case 302: + return StringRef::from_lit("Found"); + case 303: + return StringRef::from_lit("See Other"); + case 304: + return StringRef::from_lit("Not Modified"); + case 305: + return StringRef::from_lit("Use Proxy"); + // case 306: return StringRef::from_lit("(Unused)"); + case 307: + return StringRef::from_lit("Temporary Redirect"); + case 308: + return StringRef::from_lit("Permanent Redirect"); + case 400: + return StringRef::from_lit("Bad Request"); + case 401: + return StringRef::from_lit("Unauthorized"); + case 402: + return StringRef::from_lit("Payment Required"); + case 403: + return StringRef::from_lit("Forbidden"); + case 404: + return StringRef::from_lit("Not Found"); + case 405: + return StringRef::from_lit("Method Not Allowed"); + case 406: + return StringRef::from_lit("Not Acceptable"); + case 407: + return StringRef::from_lit("Proxy Authentication Required"); + case 408: + return StringRef::from_lit("Request Timeout"); + case 409: + return StringRef::from_lit("Conflict"); + case 410: + return StringRef::from_lit("Gone"); + case 411: + return StringRef::from_lit("Length Required"); + case 412: + return StringRef::from_lit("Precondition Failed"); + case 413: + return StringRef::from_lit("Payload Too Large"); + case 414: + return StringRef::from_lit("URI Too Long"); + case 415: + return StringRef::from_lit("Unsupported Media Type"); + case 416: + return StringRef::from_lit("Requested Range Not Satisfiable"); + case 417: + return StringRef::from_lit("Expectation Failed"); + case 421: + return StringRef::from_lit("Misdirected Request"); + case 425: + // https://tools.ietf.org/html/rfc8470 + return StringRef::from_lit("Too Early"); + case 426: + return StringRef::from_lit("Upgrade Required"); + case 428: + return StringRef::from_lit("Precondition Required"); + case 429: + return StringRef::from_lit("Too Many Requests"); + case 431: + return StringRef::from_lit("Request Header Fields Too Large"); + case 451: + return StringRef::from_lit("Unavailable For Legal Reasons"); + case 500: + return StringRef::from_lit("Internal Server Error"); + case 501: + return StringRef::from_lit("Not Implemented"); + case 502: + return StringRef::from_lit("Bad Gateway"); + case 503: + return StringRef::from_lit("Service Unavailable"); + case 504: + return StringRef::from_lit("Gateway Timeout"); + case 505: + return StringRef::from_lit("HTTP Version Not Supported"); + case 511: + return StringRef::from_lit("Network Authentication Required"); + default: + return StringRef{}; + } +} + +StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code) { + switch (status_code) { + case 100: + return StringRef::from_lit("100"); + case 101: + return StringRef::from_lit("101"); + case 103: + return StringRef::from_lit("103"); + case 200: + return StringRef::from_lit("200"); + case 201: + return StringRef::from_lit("201"); + case 202: + return StringRef::from_lit("202"); + case 203: + return StringRef::from_lit("203"); + case 204: + return StringRef::from_lit("204"); + case 205: + return StringRef::from_lit("205"); + case 206: + return StringRef::from_lit("206"); + case 300: + return StringRef::from_lit("300"); + case 301: + return StringRef::from_lit("301"); + case 302: + return StringRef::from_lit("302"); + case 303: + return StringRef::from_lit("303"); + case 304: + return StringRef::from_lit("304"); + case 305: + return StringRef::from_lit("305"); + // case 306: return StringRef::from_lit("306"); + case 307: + return StringRef::from_lit("307"); + case 308: + return StringRef::from_lit("308"); + case 400: + return StringRef::from_lit("400"); + case 401: + return StringRef::from_lit("401"); + case 402: + return StringRef::from_lit("402"); + case 403: + return StringRef::from_lit("403"); + case 404: + return StringRef::from_lit("404"); + case 405: + return StringRef::from_lit("405"); + case 406: + return StringRef::from_lit("406"); + case 407: + return StringRef::from_lit("407"); + case 408: + return StringRef::from_lit("408"); + case 409: + return StringRef::from_lit("409"); + case 410: + return StringRef::from_lit("410"); + case 411: + return StringRef::from_lit("411"); + case 412: + return StringRef::from_lit("412"); + case 413: + return StringRef::from_lit("413"); + case 414: + return StringRef::from_lit("414"); + case 415: + return StringRef::from_lit("415"); + case 416: + return StringRef::from_lit("416"); + case 417: + return StringRef::from_lit("417"); + case 421: + return StringRef::from_lit("421"); + case 426: + return StringRef::from_lit("426"); + case 428: + return StringRef::from_lit("428"); + case 429: + return StringRef::from_lit("429"); + case 431: + return StringRef::from_lit("431"); + case 451: + return StringRef::from_lit("451"); + case 500: + return StringRef::from_lit("500"); + case 501: + return StringRef::from_lit("501"); + case 502: + return StringRef::from_lit("502"); + case 503: + return StringRef::from_lit("503"); + case 504: + return StringRef::from_lit("504"); + case 505: + return StringRef::from_lit("505"); + case 511: + return StringRef::from_lit("511"); + default: + return util::make_string_ref_uint(balloc, status_code); + } +} + +void capitalize(DefaultMemchunks *buf, const StringRef &s) { + buf->append(util::upcase(s[0])); + for (size_t i = 1; i < s.size(); ++i) { + if (s[i - 1] == '-') { + buf->append(util::upcase(s[i])); + } else { + buf->append(s[i]); + } + } +} + +bool lws(const char *value) { + for (; *value; ++value) { + switch (*value) { + case '\t': + case ' ': + continue; + default: + return false; + } + } + return true; +} + +void copy_url_component(std::string &dest, const http_parser_url *u, int field, + const char *url) { + if (u->field_set & (1 << field)) { + dest.assign(url + u->field_data[field].off, u->field_data[field].len); + } +} + +Headers::value_type to_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int32_t token) { + return Header(std::string(reinterpret_cast(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 followed by "" (e.g., + // loadpolicy="") here. + if (first + patlen + 2 <= last) { + if (*(first + patlen) != '"' || *(first + patlen + 1) != '"') { + return false; + } + } else { + // here we got invalid production (anchor=") or anchor=? + return false; + } + } + } + return true; +} +} // namespace + +namespace { +// Returns true if link-param consists of only parmname, and it +// matches string [pat, pat + patlen). +bool check_link_param_without_value(const char *first, const char *last, + const char *pat, size_t patlen) { + if (first + patlen > last) { + return false; + } + + if (first + patlen == last) { + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + switch (*(first + patlen)) { + case ';': + case ',': + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + return false; +} +} // namespace + +namespace { +std::pair +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/src/http2.h b/src/http2.h new file mode 100644 index 0000000..7cfe461 --- /dev/null +++ b/src/http2.h @@ -0,0 +1,457 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTTP2_H +#define HTTP2_H + +#include "nghttp2_config.h" + +#include +#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/src/http2_test.cc b/src/http2_test.cc new file mode 100644 index 0000000..f8be9f4 --- /dev/null +++ b/src/http2_test.cc @@ -0,0 +1,1249 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "http2_test.h" + +#include +#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/src/http2_test.h b/src/http2_test.h new file mode 100644 index 0000000..382470d --- /dev/null +++ b/src/http2_test.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP2_TEST_H +#define SHRPX_HTTP2_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_http2_add_header(void); +void test_http2_get_header(void); +void test_http2_copy_headers_to_nva(void); +void test_http2_build_http1_headers_from_headers(void); +void test_http2_lws(void); +void test_http2_rewrite_location_uri(void); +void test_http2_parse_http_status_code(void); +void test_http2_index_header(void); +void test_http2_lookup_token(void); +void test_http2_parse_link_header(void); +void test_http2_path_join(void); +void test_http2_normalize_path(void); +void test_http2_rewrite_clean_path(void); +void test_http2_get_pure_path_component(void); +void test_http2_construct_push_component(void); +void test_http2_contains_trailers(void); +void test_http2_check_transfer_encoding(void); + +} // namespace shrpx + +#endif // SHRPX_HTTP2_TEST_H diff --git a/src/http3.cc b/src/http3.cc new file mode 100644 index 0000000..61134ad --- /dev/null +++ b/src/http3.cc @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "http3.h" + +namespace nghttp2 { + +namespace http3 { + +namespace { +nghttp3_nv make_nv_internal(const std::string &name, const std::string &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +namespace { +nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +nghttp3_nv make_nv(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +namespace { +void copy_headers_to_nva_internal(std::vector &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/src/http3.h b/src/http3.h new file mode 100644 index 0000000..81ee0d7 --- /dev/null +++ b/src/http3.h @@ -0,0 +1,123 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTTP3_H +#define HTTP3_H + +#include "nghttp2_config.h" + +#include +#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/src/inflatehd.cc b/src/inflatehd.cc new file mode 100644 index 0000000..f484042 --- /dev/null +++ b/src/inflatehd.cc @@ -0,0 +1,289 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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/src/libevent_util.cc b/src/libevent_util.cc new file mode 100644 index 0000000..3b60b6d --- /dev/null +++ b/src/libevent_util.cc @@ -0,0 +1,162 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "libevent_util.h" + +#include +#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/src/libevent_util.h b/src/libevent_util.h new file mode 100644 index 0000000..1d1ee91 --- /dev/null +++ b/src/libevent_util.h @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef LIBEVENT_UTIL_H +#define LIBEVENT_UTIL_H + +#include "nghttp2_config.h" + +#include +#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/src/memchunk.h b/src/memchunk.h new file mode 100644 index 0000000..7a7f2e9 --- /dev/null +++ b/src/memchunk.h @@ -0,0 +1,664 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef MEMCHUNK_H +#define MEMCHUNK_H + +#include "nghttp2_config.h" + +#include +#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/src/memchunk_test.cc b/src/memchunk_test.cc new file mode 100644 index 0000000..236d9ea --- /dev/null +++ b/src/memchunk_test.cc @@ -0,0 +1,340 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "memchunk_test.h" + +#include + +#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/src/memchunk_test.h b/src/memchunk_test.h new file mode 100644 index 0000000..7d677e7 --- /dev/null +++ b/src/memchunk_test.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef MEMCHUNK_TEST_H +#define MEMCHUNK_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_pool_recycle(void); +void test_memchunks_append(void); +void test_memchunks_drain(void); +void test_memchunks_riovec(void); +void test_memchunks_recycle(void); +void test_memchunks_reset(void); +void test_peek_memchunks_append(void); +void test_peek_memchunks_disable_peek_drain(void); +void test_peek_memchunks_disable_peek_no_drain(void); +void test_peek_memchunks_reset(void); + +} // namespace nghttp2 + +#endif // MEMCHUNK_TEST_H diff --git a/src/network.h b/src/network.h new file mode 100644 index 0000000..45311d8 --- /dev/null +++ b/src/network.h @@ -0,0 +1,67 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NETWORK_H +#define NETWORK_H + +#ifdef HAVE_CONFIG_H +# include +#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/src/nghttp.cc b/src/nghttp.cc new file mode 100644 index 0000000..41a88c6 --- /dev/null +++ b/src/nghttp.cc @@ -0,0 +1,3115 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp.h" + +#include +#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; + + auto param = SSL_get0_param(ssl); + X509_VERIFY_PARAM_set_hostflags(param, 0); + X509_VERIFY_PARAM_set1_host(param, host_string.c_str(), + host_string.size()); + SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb); + + if (!util::numeric_host(host_string.c_str())) { + SSL_set_tlsext_host_name(ssl, host_string.c_str()); + } + } + + rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen); + + if (rv != 0 && errno != EINPROGRESS) { + if (ssl) { + SSL_free(ssl); + ssl = nullptr; + } + close(fd); + fd = -1; + continue; + } + break; + } + + if (fd == -1) { + return -1; + } + + writefn = &HttpClient::connected; + + if (need_upgrade()) { + on_readfn = &HttpClient::on_upgrade_read; + on_writefn = &HttpClient::on_upgrade_connect; + } else { + on_readfn = &HttpClient::on_read; + on_writefn = &HttpClient::on_write; + } + + ev_io_set(&rev, fd, EV_READ); + ev_io_set(&wev, fd, EV_WRITE); + + ev_io_start(loop, &wev); + + ev_timer_again(loop, &wt); + + return 0; +} + +void HttpClient::disconnect() { + state = ClientState::IDLE; + + for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) { + if ((*req)->continue_timer) { + (*req)->continue_timer->stop(); + } + } + + ev_timer_stop(loop, &settings_timer); + + ev_timer_stop(loop, &rt); + ev_timer_stop(loop, &wt); + + ev_io_stop(loop, &rev); + ev_io_stop(loop, &wev); + + nghttp2_session_del(session); + session = nullptr; + + if (ssl) { + SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN); + ERR_clear_error(); + SSL_shutdown(ssl); + SSL_free(ssl); + ssl = nullptr; + } + + if (fd != -1) { + shutdown(fd, SHUT_WR); + close(fd); + fd = -1; + } +} + +int HttpClient::read_clear() { + ev_timer_again(loop, &rt); + + std::array 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", std::move(token68)}, + {"accept", "*/*"}, + {"user-agent", "nghttp2/" NGHTTP2_VERSION}}; + auto initial_headerslen = headers.size(); + + for (auto &kv : config.headers) { + size_t i; + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + for (i = 0; i < initial_headerslen; ++i) { + if (kv.name == headers[i].name) { + headers[i].value = kv.value; + break; + } + } + if (i < initial_headerslen) { + continue; + } + headers.emplace_back(kv.name, kv.value, kv.no_index); + } + + req += " HTTP/1.1\r\n"; + + for (auto &kv : headers) { + req += kv.name; + req += ": "; + req += kv.value; + req += "\r\n"; + } + req += "\r\n"; + + wb.append(req); + + if (config.verbose) { + print_timer(); + std::cout << " HTTP Upgrade request\n" << req << std::endl; + } + + if (!reqvec[0]->data_prd) { + // record request time if this is a part of real request. + reqvec[0]->record_request_start_time(); + reqvec[0]->req_nva = std::move(headers); + } + + on_writefn = &HttpClient::noop; + + signal_write(); + + return 0; +} + +int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) { + int rv; + + auto htperr = + llhttp_execute(htp.get(), reinterpret_cast(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 ALPN result + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (config.verbose) { + std::cout << "The negotiated protocol: " << proto << std::endl; + } + if (!util::check_h2_is_selected(proto)) { + next_proto = nullptr; + } + } + if (!next_proto) { + print_protocol_nego_error(); + return -1; + } + } + + rv = nghttp2_session_client_new2(&session, callbacks, this, + config.http2_option); + + if (rv != 0) { + return -1; + } + if (need_upgrade()) { + // Adjust stream user-data depending on the existence of upload + // data + Request *stream_user_data = nullptr; + if (!reqvec[0]->data_prd) { + stream_user_data = reqvec[0].get(); + } + // If HEAD is used, that is only when user specified it with -H + // option. + auto head_request = stream_user_data && stream_user_data->method == "HEAD"; + rv = nghttp2_session_upgrade2(session, settings_payload.data(), + settings_payloadlen, head_request, + stream_user_data); + if (rv != 0) { + std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: " + << nghttp2_strerror(rv) << std::endl; + return -1; + } + if (stream_user_data) { + stream_user_data->stream_id = 1; + request_done(stream_user_data); + } + } + // If upgrade succeeds, the SETTINGS value sent with + // HTTP2-Settings header field has already been submitted to + // session object. + if (!need_upgrade()) { + std::array 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 + +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; + } + } + + auto proto_list = util::get_default_alpn(); + + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); + } + { + HttpClient client{callbacks, loop, ssl_ctx}; + + int32_t dep_stream_id = 0; + + if (!config.no_dep) { + dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id; + } + + for (auto &req : requests) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0); + + for (int i = 0; i < config.multiply; ++i) { + client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req), + pri_spec); + } + } + client.update_hostport(); + + client.record_start_time(); + + if (client.resolve_host(host, port) != 0) { + goto fin; + } + + client.record_domain_lookup_end_time(); + + if (client.initiate_connection() != 0) { + std::cerr << "[ERROR] Could not connect to " << host << ", port " << port + << std::endl; + goto fin; + } + + ev_set_userdata(loop, &client); + ev_run(loop, 0); + ev_set_userdata(loop, nullptr); + +#ifdef HAVE_JANSSON + if (!config.harfile.empty()) { + FILE *outfile; + if (config.harfile == "-") { + outfile = stdout; + } else { + outfile = fopen(config.harfile.c_str(), "wb"); + } + + if (outfile) { + client.output_har(outfile); + + if (outfile != stdout) { + fclose(outfile); + } + } else { + std::cerr << "Cannot open file " << config.harfile << ". " + << "har file could not be created." << std::endl; + } + } +#endif // HAVE_JANSSON + + if (client.success != client.reqvec.size()) { + std::cerr << "Some requests were not processed. total=" + << client.reqvec.size() << ", processed=" << client.success + << std::endl; + } + if (config.stat) { + print_stats(client); + } + } +fin: + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + } + return result; +} +} // namespace + +namespace { +ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + int rv; + auto req = static_cast( + 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) { + 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/src/nghttp.h b/src/nghttp.h new file mode 100644 index 0000000..a880414 --- /dev/null +++ b/src/nghttp.h @@ -0,0 +1,310 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP_H +#define NGHTTP_H + +#include "nghttp2_config.h" + +#include +#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/src/nghttp2_config.h b/src/nghttp2_config.h new file mode 100644 index 0000000..8e6cd05 --- /dev/null +++ b/src/nghttp2_config.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_CONFIG_H +#define NGHTTP2_CONFIG_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +#endif // NGHTTP2_CONFIG_H diff --git a/src/nghttp2_gzip.c b/src/nghttp2_gzip.c new file mode 100644 index 0000000..5f17ab2 --- /dev/null +++ b/src/nghttp2_gzip.c @@ -0,0 +1,87 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_gzip.h" + +#include + +int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr) { + int rv; + *inflater_ptr = calloc(1, sizeof(nghttp2_gzip)); + if (*inflater_ptr == NULL) { + return -1; + } + rv = inflateInit2(&(*inflater_ptr)->zst, 47); + if (rv != Z_OK) { + free(*inflater_ptr); + return -1; + } + return 0; +} + +void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater) { + if (inflater != NULL) { + inflateEnd(&inflater->zst); + free(inflater); + } +} + +int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out, + size_t *outlen_ptr, const uint8_t *in, + size_t *inlen_ptr) { + int rv; + if (inflater->finished) { + return -1; + } + inflater->zst.avail_in = (unsigned int)*inlen_ptr; + inflater->zst.next_in = (unsigned char *)in; + inflater->zst.avail_out = (unsigned int)*outlen_ptr; + inflater->zst.next_out = out; + + rv = inflate(&inflater->zst, Z_NO_FLUSH); + + *inlen_ptr -= inflater->zst.avail_in; + *outlen_ptr -= inflater->zst.avail_out; + switch (rv) { + case Z_STREAM_END: + inflater->finished = 1; + /* FALL THROUGH */ + case Z_OK: + case Z_BUF_ERROR: + return 0; + case Z_DATA_ERROR: + case Z_STREAM_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + return -1; + default: + assert(0); + /* We need this for some compilers */ + return 0; + } +} + +int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater) { + return inflater->finished; +} diff --git a/src/nghttp2_gzip.h b/src/nghttp2_gzip.h new file mode 100644 index 0000000..a40352c --- /dev/null +++ b/src/nghttp2_gzip.h @@ -0,0 +1,122 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_GZIP_H + +# ifdef HAVE_CONFIG_H +# include +# 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/src/nghttp2_gzip_test.c b/src/nghttp2_gzip_test.c new file mode 100644 index 0000000..de19d5d --- /dev/null +++ b/src/nghttp2_gzip_test.c @@ -0,0 +1,111 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_gzip_test.h" + +#include +#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/src/nghttp2_gzip_test.h b/src/nghttp2_gzip_test.h new file mode 100644 index 0000000..8d554f7 --- /dev/null +++ b/src/nghttp2_gzip_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_GZIP_TEST_H +#define NGHTTP2_GZIP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#ifdef __cplusplus +extern "C" { +#endif + +void test_nghttp2_gzip_inflate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP2_GZIP_TEST_H */ diff --git a/src/nghttpd.cc b/src/nghttpd.cc new file mode 100644 index 0000000..9cad874 --- /dev/null +++ b/src/nghttpd.cc @@ -0,0 +1,502 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#ifdef __sgi +# define daemon _daemonize +#endif + +#ifdef HAVE_UNISTD_H +# include +#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) { + 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/src/quic.cc b/src/quic.cc new file mode 100644 index 0000000..1c68a5b --- /dev/null +++ b/src/quic.cc @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "quic.h" + +#include + +#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/src/quic.h b/src/quic.h new file mode 100644 index 0000000..d72ae1f --- /dev/null +++ b/src/quic.h @@ -0,0 +1,56 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef QUIC_H +#define QUIC_H + +#include "nghttp2_config.h" + +#include "stdint.h" + +namespace quic { + +enum class ErrorType { + Transport, + TransportVersionNegotiation, + TransportIdleTimeout, + Application, +}; + +struct Error { + Error(ErrorType type, uint64_t code) : type(type), code(code) {} + Error() : type(ErrorType::Transport), code(0) {} + + ErrorType type; + uint64_t code; +}; + +Error err_transport(int liberr); +Error err_transport_idle_timeout(); +Error err_transport_tls(int alert); +Error err_application(int liberr); + +} // namespace quic + +#endif // QUIC_H diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc new file mode 100644 index 0000000..f089adf --- /dev/null +++ b/src/shrpx-unittest.cc @@ -0,0 +1,246 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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; + + shrpx::create_config(); + + // initialize the CUnit test registry + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + // add a suite to the registry + pSuite = CU_add_suite("shrpx_TestSuite", init_suite1, clean_suite1); + if (nullptr == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + + // add the tests to the suite + if (!CU_add_test(pSuite, "tls_create_lookup_tree", + shrpx::test_shrpx_tls_create_lookup_tree) || + !CU_add_test(pSuite, "tls_cert_lookup_tree_add_ssl_ctx", + shrpx::test_shrpx_tls_cert_lookup_tree_add_ssl_ctx) || + !CU_add_test(pSuite, "tls_tls_hostname_match", + shrpx::test_shrpx_tls_tls_hostname_match) || + !CU_add_test(pSuite, "tls_tls_verify_numeric_hostname", + shrpx::test_shrpx_tls_verify_numeric_hostname) || + !CU_add_test(pSuite, "tls_tls_verify_dns_hostname", + shrpx::test_shrpx_tls_verify_dns_hostname) || + !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || + !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) || + !CU_add_test(pSuite, "http2_copy_headers_to_nva", + shrpx::test_http2_copy_headers_to_nva) || + !CU_add_test(pSuite, "http2_build_http1_headers_from_headers", + shrpx::test_http2_build_http1_headers_from_headers) || + !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) || + !CU_add_test(pSuite, "http2_rewrite_location_uri", + shrpx::test_http2_rewrite_location_uri) || + !CU_add_test(pSuite, "http2_parse_http_status_code", + shrpx::test_http2_parse_http_status_code) || + !CU_add_test(pSuite, "http2_index_header", + shrpx::test_http2_index_header) || + !CU_add_test(pSuite, "http2_lookup_token", + shrpx::test_http2_lookup_token) || + !CU_add_test(pSuite, "http2_parse_link_header", + shrpx::test_http2_parse_link_header) || + !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) || + !CU_add_test(pSuite, "http2_normalize_path", + shrpx::test_http2_normalize_path) || + !CU_add_test(pSuite, "http2_rewrite_clean_path", + shrpx::test_http2_rewrite_clean_path) || + !CU_add_test(pSuite, "http2_get_pure_path_component", + shrpx::test_http2_get_pure_path_component) || + !CU_add_test(pSuite, "http2_construct_push_component", + shrpx::test_http2_construct_push_component) || + !CU_add_test(pSuite, "http2_contains_trailers", + shrpx::test_http2_contains_trailers) || + !CU_add_test(pSuite, "http2_check_transfer_encoding", + shrpx::test_http2_check_transfer_encoding) || + !CU_add_test(pSuite, "downstream_field_store_append_last_header", + shrpx::test_downstream_field_store_append_last_header) || + !CU_add_test(pSuite, "downstream_field_store_header", + shrpx::test_downstream_field_store_header) || + !CU_add_test(pSuite, "downstream_crumble_request_cookie", + shrpx::test_downstream_crumble_request_cookie) || + !CU_add_test(pSuite, "downstream_assemble_request_cookie", + shrpx::test_downstream_assemble_request_cookie) || + !CU_add_test(pSuite, "downstream_rewrite_location_response_header", + shrpx::test_downstream_rewrite_location_response_header) || + !CU_add_test(pSuite, "downstream_supports_non_final_response", + shrpx::test_downstream_supports_non_final_response) || + !CU_add_test(pSuite, "downstream_find_affinity_cookie", + shrpx::test_downstream_find_affinity_cookie) || + !CU_add_test(pSuite, "config_parse_header", + shrpx::test_shrpx_config_parse_header) || + !CU_add_test(pSuite, "config_parse_log_format", + shrpx::test_shrpx_config_parse_log_format) || + !CU_add_test(pSuite, "config_read_tls_ticket_key_file", + shrpx::test_shrpx_config_read_tls_ticket_key_file) || + !CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256", + shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) || + !CU_add_test(pSuite, "worker_match_downstream_addr_group", + shrpx::test_shrpx_worker_match_downstream_addr_group) || + !CU_add_test(pSuite, "http_create_forwarded", + shrpx::test_shrpx_http_create_forwarded) || + !CU_add_test(pSuite, "http_create_via_header_value", + shrpx::test_shrpx_http_create_via_header_value) || + !CU_add_test(pSuite, "http_create_affinity_cookie", + shrpx::test_shrpx_http_create_affinity_cookie) || + !CU_add_test(pSuite, "http_create_atlsvc_header_field_value", + shrpx::test_shrpx_http_create_altsvc_header_value) || + !CU_add_test(pSuite, "http_check_http_scheme", + shrpx::test_shrpx_http_check_http_scheme) || + !CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) || + !CU_add_test(pSuite, "router_match_wildcard", + shrpx::test_shrpx_router_match_wildcard) || + !CU_add_test(pSuite, "router_match_prefix", + shrpx::test_shrpx_router_match_prefix) || + !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || + !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || + !CU_add_test(pSuite, "util_inp_strlower", + shrpx::test_util_inp_strlower) || + !CU_add_test(pSuite, "util_to_base64", shrpx::test_util_to_base64) || + !CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) || + !CU_add_test(pSuite, "util_percent_encode_token", + shrpx::test_util_percent_encode_token) || + !CU_add_test(pSuite, "util_percent_decode", + shrpx::test_util_percent_decode) || + !CU_add_test(pSuite, "util_quote_string", + shrpx::test_util_quote_string) || + !CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) || + !CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) || + !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) || + !CU_add_test(pSuite, "util_ipv6_numeric_addr", + shrpx::test_util_ipv6_numeric_addr) || + !CU_add_test(pSuite, "util_utos", shrpx::test_util_utos) || + !CU_add_test(pSuite, "util_make_string_ref_uint", + shrpx::test_util_make_string_ref_uint) || + !CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) || + !CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) || + !CU_add_test(pSuite, "util_parse_uint_with_unit", + shrpx::test_util_parse_uint_with_unit) || + !CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) || + !CU_add_test(pSuite, "util_parse_duration_with_unit", + shrpx::test_util_parse_duration_with_unit) || + !CU_add_test(pSuite, "util_duration_str", + shrpx::test_util_duration_str) || + !CU_add_test(pSuite, "util_format_duration", + shrpx::test_util_format_duration) || + !CU_add_test(pSuite, "util_starts_with", shrpx::test_util_starts_with) || + !CU_add_test(pSuite, "util_ends_with", shrpx::test_util_ends_with) || + !CU_add_test(pSuite, "util_parse_http_date", + shrpx::test_util_parse_http_date) || + !CU_add_test(pSuite, "util_localtime_date", + shrpx::test_util_localtime_date) || + !CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) || + !CU_add_test(pSuite, "util_parse_config_str_list", + shrpx::test_util_parse_config_str_list) || + !CU_add_test(pSuite, "util_make_http_hostport", + shrpx::test_util_make_http_hostport) || + !CU_add_test(pSuite, "util_make_hostport", + shrpx::test_util_make_hostport) || + !CU_add_test(pSuite, "util_strifind", shrpx::test_util_strifind) || + !CU_add_test(pSuite, "util_random_alpha_digit", + shrpx::test_util_random_alpha_digit) || + !CU_add_test(pSuite, "util_format_hex", shrpx::test_util_format_hex) || + !CU_add_test(pSuite, "util_is_hex_string", + shrpx::test_util_is_hex_string) || + !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) || + !CU_add_test(pSuite, "util_extract_host", + shrpx::test_util_extract_host) || + !CU_add_test(pSuite, "util_split_hostport", + shrpx::test_util_split_hostport) || + !CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) || + !CU_add_test(pSuite, "util_rstrip", shrpx::test_util_rstrip) || + !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || + !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) || + !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || + !CU_add_test(pSuite, "memchunk_append", nghttp2::test_memchunks_append) || + !CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) || + !CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) || + !CU_add_test(pSuite, "memchunk_recycle", + nghttp2::test_memchunks_recycle) || + !CU_add_test(pSuite, "memchunk_reset", nghttp2::test_memchunks_reset) || + !CU_add_test(pSuite, "peek_memchunk_append", + nghttp2::test_peek_memchunks_append) || + !CU_add_test(pSuite, "peek_memchunk_disable_peek_drain", + nghttp2::test_peek_memchunks_disable_peek_drain) || + !CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain", + nghttp2::test_peek_memchunks_disable_peek_no_drain) || + !CU_add_test(pSuite, "peek_memchunk_reset", + nghttp2::test_peek_memchunks_reset) || + !CU_add_test(pSuite, "template_immutable_string", + nghttp2::test_template_immutable_string) || + !CU_add_test(pSuite, "template_string_ref", + nghttp2::test_template_string_ref) || + !CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) || + !CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) { + CU_cleanup_registry(); + return CU_get_error(); + } + + // Run all tests using the CUnit Basic interface + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_tests_failed = CU_get_number_of_tests_failed(); + CU_cleanup_registry(); + if (CU_get_error() == CUE_SUCCESS) { + return num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return CU_get_error(); + } +} diff --git a/src/shrpx.cc b/src/shrpx.cc new file mode 100644 index 0000000..b42054c --- /dev/null +++ b/src/shrpx.cc @@ -0,0 +1,5329 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx.h" + +#include +#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_ALPN_LIST = + StringRef::from_lit("h2,h2-16,h2-14,http/1.1"); +} // namespace + +namespace { +constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#ifdef TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.3"); +#else // !TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#endif // !TLS1_3_VERSION +} // namespace + +namespace { +constexpr auto DEFAULT_ACCESSLOG_FORMAT = + StringRef::from_lit(R"($remote_addr - - [$time_local] )" + R"("$request" $status $body_bytes_sent )" + R"("$http_referer" "$http_user_agent")"); +} // namespace + +namespace { +void fill_default_config(Config *config) { + config->num_worker = 1; + config->conf_path = StringRef::from_lit("/etc/nghttpx/nghttpx.conf"); + config->pid = getpid(); + +#ifdef NOTHREADS + config->single_thread = true; +#endif // NOTHREADS + + if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) { + config->ev_loop_flags = ev_recommended_backends() | EVBACKEND_KQUEUE; + } + + auto &tlsconf = config->tls; + { + auto &ticketconf = tlsconf.ticket; + { + auto &memcachedconf = ticketconf.memcached; + memcachedconf.max_retry = 3; + memcachedconf.max_fail = 2; + memcachedconf.interval = 10_min; + memcachedconf.family = AF_UNSPEC; + } + + auto &session_cacheconf = tlsconf.session_cache; + { + auto &memcachedconf = session_cacheconf.memcached; + memcachedconf.family = AF_UNSPEC; + } + + ticketconf.cipher = EVP_aes_128_cbc(); + } + + { + auto &ocspconf = tlsconf.ocsp; + // ocsp update interval = 14400 secs = 4 hours, borrowed from h2o + ocspconf.update_interval = 4_h; + ocspconf.fetch_ocsp_response_file = + StringRef::from_lit(PKGDATADIR "/fetch-ocsp-response"); + } + + { + auto &dyn_recconf = tlsconf.dyn_rec; + dyn_recconf.warmup_threshold = 1_m; + dyn_recconf.idle_timeout = 1_s; + } + + tlsconf.session_timeout = std::chrono::hours(12); + tlsconf.ciphers = StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.client.ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.client.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.min_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MIN_PROTO_VERSION); + tlsconf.max_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MAX_PROTO_VERSION); + tlsconf.max_early_data = 16_k; + tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); + + auto &httpconf = config->http; + httpconf.server_name = StringRef::from_lit("nghttpx"); + httpconf.no_host_rewrite = true; + httpconf.request_header_field_buffer = 64_k; + httpconf.max_request_header_fields = 100; + httpconf.response_header_field_buffer = 64_k; + httpconf.max_response_header_fields = 500; + httpconf.redirect_https_port = StringRef::from_lit("443"); + httpconf.max_requests = std::numeric_limits::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. + --alpn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. The parameter must be delimited + by a single comma only and any white spaces are treated + as a part of protocol string. + Default: )" + << DEFAULT_ALPN_LIST + << R"( + --verify-client + Require and verify client certificate. + --verify-client-cacert= + Path 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.alpn_list.empty()) { + tlsconf.alpn_list = util::split_str(DEFAULT_ALPN_LIST, ','); + } + + if (!tlsconf.tls_proto_list.empty()) { + tlsconf.tls_proto_mask = tls::create_tls_proto_mask(tlsconf.tls_proto_list); + } + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (tlsconf.min_proto_version > tlsconf.max_proto_version) { + LOG(ERROR) << "tls-max-proto-version must be equal to or larger than " + "tls-min-proto-version"; + return -1; + } + + if (tls::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.alpn_list) != 0) { + return -1; + } + + tlsconf.bio_method = create_bio_method(); + + auto &listenerconf = config->conn.listener; + auto &upstreamconf = config->conn.upstream; + + if (listenerconf.addrs.empty()) { + UpstreamAddr addr{}; + addr.host = StringRef::from_lit("*"); + addr.port = 3000; + addr.tls = true; + addr.family = AF_INET; + addr.index = 0; + listenerconf.addrs.push_back(addr); + addr.family = AF_INET6; + addr.index = 1; + listenerconf.addrs.push_back(std::move(addr)); + } + + if (upstreamconf.worker_connections == 0) { + upstreamconf.worker_connections = std::numeric_limits::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; + +#ifdef HAVE_LIBBPF + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); +#endif // HAVE_LIBBPF + + Log::set_severity_level(NOTICE); + create_config(); + fill_default_config(mod_config()); + + // make copy of stderr + store_original_fds(); + + // First open log files with default configuration, so that we can + // log errors/warnings while reading configuration files. + reopen_log_files(get_config()->logging); + + suconfig.original_argv = argv; + + // We have to copy argv, since getopt_long may change its content. + suconfig.argc = argc; + suconfig.argv = new char *[argc]; + + for (int i = 0; i < argc; ++i) { + suconfig.argv[i] = strdup(argv[i]); + if (suconfig.argv[i] == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to copy argv: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + } + + suconfig.cwd = getcwd(nullptr, 0); + if (suconfig.cwd == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to get current working directory: errno=" << error; + exit(EXIT_FAILURE); + } + + auto &cmdcfgs = suconfig.cmdcfgs; + + while (1) { + static int flag = 0; + static constexpr option long_options[] = { + {SHRPX_OPT_DAEMON.c_str(), no_argument, nullptr, 'D'}, + {SHRPX_OPT_LOG_LEVEL.c_str(), required_argument, nullptr, 'L'}, + {SHRPX_OPT_BACKEND.c_str(), required_argument, nullptr, 'b'}, + {SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), required_argument, + nullptr, 'c'}, + {SHRPX_OPT_FRONTEND.c_str(), required_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {SHRPX_OPT_INSECURE.c_str(), no_argument, nullptr, 'k'}, + {SHRPX_OPT_WORKERS.c_str(), required_argument, nullptr, 'n'}, + {SHRPX_OPT_CLIENT_PROXY.c_str(), no_argument, nullptr, 'p'}, + {SHRPX_OPT_HTTP2_PROXY.c_str(), no_argument, nullptr, 's'}, + {"version", no_argument, nullptr, 'v'}, + {SHRPX_OPT_FRONTEND_FRAME_DEBUG.c_str(), no_argument, nullptr, 'o'}, + {SHRPX_OPT_ADD_X_FORWARDED_FOR.c_str(), no_argument, &flag, 1}, + {SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT.c_str(), required_argument, + &flag, 2}, + {SHRPX_OPT_FRONTEND_READ_TIMEOUT.c_str(), required_argument, &flag, 3}, + {SHRPX_OPT_FRONTEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 4}, + {SHRPX_OPT_BACKEND_READ_TIMEOUT.c_str(), required_argument, &flag, 5}, + {SHRPX_OPT_BACKEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 6}, + {SHRPX_OPT_ACCESSLOG_FILE.c_str(), required_argument, &flag, 7}, + {SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag, + 8}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 9}, + {SHRPX_OPT_PID_FILE.c_str(), required_argument, &flag, 10}, + {SHRPX_OPT_USER.c_str(), required_argument, &flag, 11}, + {"conf", required_argument, &flag, 12}, + {SHRPX_OPT_SYSLOG_FACILITY.c_str(), required_argument, &flag, 14}, + {SHRPX_OPT_BACKLOG.c_str(), required_argument, &flag, 15}, + {SHRPX_OPT_CIPHERS.c_str(), required_argument, &flag, 16}, + {SHRPX_OPT_CLIENT.c_str(), no_argument, &flag, 17}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 18}, + {SHRPX_OPT_CACERT.c_str(), required_argument, &flag, 19}, + {SHRPX_OPT_BACKEND_IPV4.c_str(), no_argument, &flag, 20}, + {SHRPX_OPT_BACKEND_IPV6.c_str(), no_argument, &flag, 21}, + {SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE.c_str(), required_argument, &flag, + 22}, + {SHRPX_OPT_NO_VIA.c_str(), no_argument, &flag, 23}, + {SHRPX_OPT_SUBCERT.c_str(), required_argument, &flag, 24}, + {SHRPX_OPT_HTTP2_BRIDGE.c_str(), no_argument, &flag, 25}, + {SHRPX_OPT_BACKEND_HTTP_PROXY_URI.c_str(), required_argument, &flag, + 26}, + {SHRPX_OPT_BACKEND_NO_TLS.c_str(), no_argument, &flag, 27}, + {SHRPX_OPT_OCSP_STARTUP.c_str(), no_argument, &flag, 28}, + {SHRPX_OPT_FRONTEND_NO_TLS.c_str(), no_argument, &flag, 29}, + {SHRPX_OPT_NO_VERIFY_OCSP.c_str(), no_argument, &flag, 30}, + {SHRPX_OPT_BACKEND_TLS_SNI_FIELD.c_str(), required_argument, &flag, 31}, + {SHRPX_OPT_DH_PARAM_FILE.c_str(), required_argument, &flag, 33}, + {SHRPX_OPT_READ_RATE.c_str(), required_argument, &flag, 34}, + {SHRPX_OPT_READ_BURST.c_str(), required_argument, &flag, 35}, + {SHRPX_OPT_WRITE_RATE.c_str(), required_argument, &flag, 36}, + {SHRPX_OPT_WRITE_BURST.c_str(), required_argument, &flag, 37}, + {SHRPX_OPT_NPN_LIST.c_str(), required_argument, &flag, 38}, + {SHRPX_OPT_VERIFY_CLIENT.c_str(), no_argument, &flag, 39}, + {SHRPX_OPT_VERIFY_CLIENT_CACERT.c_str(), required_argument, &flag, 40}, + {SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE.c_str(), required_argument, &flag, + 41}, + {SHRPX_OPT_CLIENT_CERT_FILE.c_str(), required_argument, &flag, 42}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER.c_str(), + required_argument, &flag, 43}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER.c_str(), + required_argument, &flag, 44}, + {SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING.c_str(), no_argument, &flag, 45}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 46}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 47}, + {SHRPX_OPT_TLS_PROTO_LIST.c_str(), required_argument, &flag, 48}, + {SHRPX_OPT_PADDING.c_str(), required_argument, &flag, 49}, + {SHRPX_OPT_WORKER_READ_RATE.c_str(), required_argument, &flag, 50}, + {SHRPX_OPT_WORKER_READ_BURST.c_str(), required_argument, &flag, 51}, + {SHRPX_OPT_WORKER_WRITE_RATE.c_str(), required_argument, &flag, 52}, + {SHRPX_OPT_WORKER_WRITE_BURST.c_str(), required_argument, &flag, 53}, + {SHRPX_OPT_ALTSVC.c_str(), required_argument, &flag, 54}, + {SHRPX_OPT_ADD_RESPONSE_HEADER.c_str(), required_argument, &flag, 55}, + {SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS.c_str(), required_argument, + &flag, 56}, + {SHRPX_OPT_ACCESSLOG_SYSLOG.c_str(), no_argument, &flag, 57}, + {SHRPX_OPT_ERRORLOG_FILE.c_str(), required_argument, &flag, 58}, + {SHRPX_OPT_ERRORLOG_SYSLOG.c_str(), no_argument, &flag, 59}, + {SHRPX_OPT_STREAM_READ_TIMEOUT.c_str(), required_argument, &flag, 60}, + {SHRPX_OPT_STREAM_WRITE_TIMEOUT.c_str(), required_argument, &flag, 61}, + {SHRPX_OPT_NO_LOCATION_REWRITE.c_str(), no_argument, &flag, 62}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST.c_str(), + required_argument, &flag, 63}, + {SHRPX_OPT_LISTENER_DISABLE_TIMEOUT.c_str(), required_argument, &flag, + 64}, + {SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR.c_str(), no_argument, &flag, + 65}, + {SHRPX_OPT_ACCESSLOG_FORMAT.c_str(), required_argument, &flag, 66}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND.c_str(), + required_argument, &flag, 67}, + {SHRPX_OPT_TLS_TICKET_KEY_FILE.c_str(), required_argument, &flag, 68}, + {SHRPX_OPT_RLIMIT_NOFILE.c_str(), required_argument, &flag, 69}, + {SHRPX_OPT_BACKEND_RESPONSE_BUFFER.c_str(), required_argument, &flag, + 71}, + {SHRPX_OPT_BACKEND_REQUEST_BUFFER.c_str(), required_argument, &flag, + 72}, + {SHRPX_OPT_NO_HOST_REWRITE.c_str(), no_argument, &flag, 73}, + {SHRPX_OPT_NO_SERVER_PUSH.c_str(), no_argument, &flag, 74}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER.c_str(), + required_argument, &flag, 76}, + {SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE.c_str(), required_argument, &flag, + 77}, + {SHRPX_OPT_OCSP_UPDATE_INTERVAL.c_str(), required_argument, &flag, 78}, + {SHRPX_OPT_NO_OCSP.c_str(), no_argument, &flag, 79}, + {SHRPX_OPT_HEADER_FIELD_BUFFER.c_str(), required_argument, &flag, 80}, + {SHRPX_OPT_MAX_HEADER_FIELDS.c_str(), required_argument, &flag, 81}, + {SHRPX_OPT_ADD_REQUEST_HEADER.c_str(), required_argument, &flag, 82}, + {SHRPX_OPT_INCLUDE.c_str(), required_argument, &flag, 83}, + {SHRPX_OPT_TLS_TICKET_KEY_CIPHER.c_str(), required_argument, &flag, 84}, + {SHRPX_OPT_HOST_REWRITE.c_str(), no_argument, &flag, 85}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED.c_str(), required_argument, + &flag, 86}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED.c_str(), required_argument, &flag, + 87}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL.c_str(), required_argument, + &flag, 88}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY.c_str(), + required_argument, &flag, 89}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL.c_str(), required_argument, + &flag, 90}, + {SHRPX_OPT_MRUBY_FILE.c_str(), required_argument, &flag, 91}, + {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL.c_str(), no_argument, &flag, 93}, + {SHRPX_OPT_FASTOPEN.c_str(), required_argument, &flag, 94}, + {SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD.c_str(), required_argument, + &flag, 95}, + {SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 96}, + {SHRPX_OPT_ADD_FORWARDED.c_str(), required_argument, &flag, 97}, + {SHRPX_OPT_STRIP_INCOMING_FORWARDED.c_str(), no_argument, &flag, 98}, + {SHRPX_OPT_FORWARDED_BY.c_str(), required_argument, &flag, 99}, + {SHRPX_OPT_FORWARDED_FOR.c_str(), required_argument, &flag, 100}, + {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 101}, + {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS.c_str(), required_argument, &flag, + 102}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, &flag, 103}, + {SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 104}, + {SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS.c_str(), required_argument, &flag, + 105}, + {SHRPX_OPT_BACKEND_HTTP1_TLS.c_str(), no_argument, &flag, 106}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS.c_str(), no_argument, &flag, + 108}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 109}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 110}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS.c_str(), no_argument, &flag, + 111}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 112}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 113}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 114}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 115}, + {SHRPX_OPT_BACKEND_ADDRESS_FAMILY.c_str(), required_argument, &flag, + 116}, + {SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 117}, + {SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 118}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND.c_str(), required_argument, + &flag, 119}, + {SHRPX_OPT_BACKEND_TLS.c_str(), no_argument, &flag, 120}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST.c_str(), required_argument, + &flag, 121}, + {SHRPX_OPT_ERROR_PAGE.c_str(), required_argument, &flag, 122}, + {SHRPX_OPT_NO_KQUEUE.c_str(), no_argument, &flag, 123}, + {SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 124}, + {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 125}, + {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126}, + {SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127}, + {SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128}, + {SHRPX_OPT_NO_SERVER_REWRITE.c_str(), no_argument, &flag, 129}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE.c_str(), + no_argument, &flag, 130}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE.c_str(), no_argument, + &flag, 131}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 132}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 133}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 134}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 135}, + {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 136}, + {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 137}, + {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 138}, + {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 139}, + {SHRPX_OPT_ECDH_CURVES.c_str(), required_argument, &flag, 140}, + {SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141}, + {SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag, + 142}, + {SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143}, + {SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144}, + {SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145}, + {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, + &flag, 146}, + {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147}, + {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, + &flag, 149}, + {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150}, + {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151}, + {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag, + 152}, + {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, + 153}, + {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154}, + {SHRPX_OPT_FRONTEND_MAX_REQUESTS.c_str(), required_argument, &flag, + 155}, + {SHRPX_OPT_SINGLE_THREAD.c_str(), no_argument, &flag, 156}, + {SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 157}, + {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument, + &flag, 158}, + {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159}, + {SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED.c_str(), no_argument, &flag, + 160}, + {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag, + 161}, + {SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162}, + {SHRPX_OPT_TLS_MAX_EARLY_DATA.c_str(), required_argument, &flag, 163}, + {SHRPX_OPT_TLS13_CIPHERS.c_str(), required_argument, &flag, 164}, + {SHRPX_OPT_TLS13_CLIENT_CIPHERS.c_str(), required_argument, &flag, 165}, + {SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA.c_str(), no_argument, &flag, + 166}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, &flag, 167}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, + &flag, 168}, + {SHRPX_OPT_QUIC_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, + 169}, + {SHRPX_OPT_NO_QUIC_BPF.c_str(), no_argument, &flag, 170}, + {SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171}, + {SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT.c_str(), required_argument, + &flag, 172}, + {SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 173}, + {SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG.c_str(), no_argument, &flag, 174}, + {SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE.c_str(), required_argument, &flag, + 175}, + {SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 176}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE.c_str(), required_argument, + &flag, 177}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 178}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 179}, + {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180}, + {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag, + 181}, + {SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag, + 182}, + {SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(), + required_argument, &flag, 183}, + {SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185}, + {SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag, + 186}, + {SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187}, + {SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188}, + {SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(), + required_argument, &flag, 189}, + {SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag, + 190}, + {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191}, + {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192}, + {SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193}, + {nullptr, 0, nullptr, 0}}; + + int option_index = 0; + int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'D': + cmdcfgs.emplace_back(SHRPX_OPT_DAEMON, StringRef::from_lit("yes")); + break; + case 'L': + cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, StringRef{optarg}); + break; + case 'b': + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, StringRef{optarg}); + break; + case 'c': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 'f': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, StringRef{optarg}); + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'k': + cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, StringRef::from_lit("yes")); + break; + case 'n': + cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, StringRef{optarg}); + break; + case 'o': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, + StringRef::from_lit("yes")); + break; + case 'p': + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, StringRef::from_lit("yes")); + break; + case 's': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, StringRef::from_lit("yes")); + break; + case 'v': + print_version(std::cout); + exit(EXIT_SUCCESS); + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --add-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_ADD_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 2: + // --frontend-http2-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT, + StringRef{optarg}); + break; + case 3: + // --frontend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT, + StringRef{optarg}); + break; + case 4: + // --frontend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 5: + // --backend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, StringRef{optarg}); + break; + case 6: + // --backend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 7: + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, StringRef{optarg}); + break; + case 8: + // --backend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 9: + // --frontend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 10: + cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, StringRef{optarg}); + break; + case 11: + cmdcfgs.emplace_back(SHRPX_OPT_USER, StringRef{optarg}); + break; + case 12: + // --conf + mod_config()->conf_path = + make_string_ref(mod_config()->balloc, StringRef{optarg}); + break; + case 14: + // --syslog-facility + cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, StringRef{optarg}); + break; + case 15: + // --backlog + cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, StringRef{optarg}); + break; + case 16: + // --ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, StringRef{optarg}); + break; + case 17: + // --client + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, StringRef::from_lit("yes")); + break; + case 18: + // --backend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 19: + // --cacert + cmdcfgs.emplace_back(SHRPX_OPT_CACERT, StringRef{optarg}); + break; + case 20: + // --backend-ipv4 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4, + StringRef::from_lit("yes")); + break; + case 21: + // --backend-ipv6 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6, + StringRef::from_lit("yes")); + break; + case 22: + // --private-key-passwd-file + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE, + StringRef{optarg}); + break; + case 23: + // --no-via + cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, StringRef::from_lit("yes")); + break; + case 24: + // --subcert + cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, StringRef{optarg}); + break; + case 25: + // --http2-bridge + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE, + StringRef::from_lit("yes")); + break; + case 26: + // --backend-http-proxy-uri + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI, + StringRef{optarg}); + break; + case 27: + // --backend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 28: + // --ocsp-startup + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_STARTUP, + StringRef::from_lit("yes")); + break; + case 29: + // --frontend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 30: + // --no-verify-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_VERIFY_OCSP, + StringRef::from_lit("yes")); + break; + case 31: + // --backend-tls-sni-field + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD, + StringRef{optarg}); + break; + case 33: + // --dh-param-file + cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, StringRef{optarg}); + break; + case 34: + // --read-rate + cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, StringRef{optarg}); + break; + case 35: + // --read-burst + cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, StringRef{optarg}); + break; + case 36: + // --write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, StringRef{optarg}); + break; + case 37: + // --write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, StringRef{optarg}); + break; + case 38: + // --npn-list + cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, StringRef{optarg}); + break; + case 39: + // --verify-client + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT, + StringRef::from_lit("yes")); + break; + case 40: + // --verify-client-cacert + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, StringRef{optarg}); + break; + case 41: + // --client-private-key-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 42: + // --client-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, StringRef{optarg}); + break; + case 43: + // --frontend-http2-dump-request-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + StringRef{optarg}); + break; + case 44: + // --frontend-http2-dump-response-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + StringRef{optarg}); + break; + case 45: + // --http2-no-cookie-crumbling + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING, + StringRef::from_lit("yes")); + break; + case 46: + // --frontend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 47: + // --backend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 48: + // --tls-proto-list + cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, StringRef{optarg}); + break; + case 49: + // --padding + cmdcfgs.emplace_back(SHRPX_OPT_PADDING, StringRef{optarg}); + break; + case 50: + // --worker-read-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, StringRef{optarg}); + break; + case 51: + // --worker-read-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, StringRef{optarg}); + break; + case 52: + // --worker-write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, StringRef{optarg}); + break; + case 53: + // --worker-write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, StringRef{optarg}); + break; + case 54: + // --altsvc + cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, StringRef{optarg}); + break; + case 55: + // --add-response-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, StringRef{optarg}); + break; + case 56: + // --worker-frontend-connections + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS, + StringRef{optarg}); + break; + case 57: + // --accesslog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 58: + // --errorlog-file + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, StringRef{optarg}); + break; + case 59: + // --errorlog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 60: + // --stream-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, StringRef{optarg}); + break; + case 61: + // --stream-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, StringRef{optarg}); + break; + case 62: + // --no-location-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE, + StringRef::from_lit("yes")); + break; + case 63: + // --backend-http1-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 64: + // --listener-disable-timeout + cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT, + StringRef{optarg}); + break; + case 65: + // --strip-incoming-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 66: + // --accesslog-format + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, StringRef{optarg}); + break; + case 67: + // --backend-http1-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 68: + // --tls-ticket-key-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, StringRef{optarg}); + break; + case 69: + // --rlimit-nofile + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, StringRef{optarg}); + break; + case 71: + // --backend-response-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER, + StringRef{optarg}); + break; + case 72: + // --backend-request-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER, + StringRef{optarg}); + break; + case 73: + // --no-host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 74: + // --no-server-push + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH, + StringRef::from_lit("yes")); + break; + case 76: + // --backend-http2-connections-per-worker + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + StringRef{optarg}); + break; + case 77: + // --fetch-ocsp-response-file + cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE, + StringRef{optarg}); + break; + case 78: + // --ocsp-update-interval + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, StringRef{optarg}); + break; + case 79: + // --no-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, StringRef::from_lit("yes")); + break; + case 80: + // --header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, StringRef{optarg}); + break; + case 81: + // --max-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, StringRef{optarg}); + break; + case 82: + // --add-request-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_REQUEST_HEADER, StringRef{optarg}); + break; + case 83: + // --include + cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, StringRef{optarg}); + break; + case 84: + // --tls-ticket-key-cipher + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_CIPHER, + StringRef{optarg}); + break; + case 85: + // --host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 86: + // --tls-session-cache-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED, + StringRef{optarg}); + break; + case 87: + // --tls-ticket-key-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED, + StringRef{optarg}); + break; + case 88: + // --tls-ticket-key-memcached-interval + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL, + StringRef{optarg}); + break; + case 89: + // --tls-ticket-key-memcached-max-retry + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY, + StringRef{optarg}); + break; + case 90: + // --tls-ticket-key-memcached-max-fail + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, + StringRef{optarg}); + break; + case 91: + // --mruby-file + cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, StringRef{optarg}); + break; + case 93: + // --accept-proxy-protocol + cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, + StringRef::from_lit("yes")); + break; + case 94: + // --fastopen + cmdcfgs.emplace_back(SHRPX_OPT_FASTOPEN, StringRef{optarg}); + break; + case 95: + // --tls-dyn-rec-warmup-threshold + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD, + StringRef{optarg}); + break; + case 96: + // --tls-dyn-rec-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 97: + // --add-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_ADD_FORWARDED, StringRef{optarg}); + break; + case 98: + // --strip-incoming-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_FORWARDED, + StringRef::from_lit("yes")); + break; + case 99: + // --forwarded-by + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_BY, StringRef{optarg}); + break; + case 100: + // --forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, StringRef{optarg}); + break; + case 101: + // --response-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 102: + // --max-response-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, + StringRef{optarg}); + break; + case 103: + // --no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 104: + // --request-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 105: + // --max-request-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, + StringRef{optarg}); + break; + case 106: + // --backend-http1-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, + StringRef::from_lit("yes")); + break; + case 108: + // --tls-session-cache-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 109: + // --tls-session-cache-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 110: + // --tls-session-cache-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 111: + // --tls-ticket-key-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 112: + // --tls-ticket-key-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 113: + // --tls-ticket-key-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 114: + // --tls-ticket-key-memcached-address-family + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 115: + // --tls-session-cache-memcached-address-family + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 116: + // --backend-address-family + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 117: + // --frontend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 118: + // --backend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 119: + // --backend-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 120: + // --backend-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, StringRef::from_lit("yes")); + break; + case 121: + // --backend-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 122: + // --error-page + cmdcfgs.emplace_back(SHRPX_OPT_ERROR_PAGE, StringRef{optarg}); + break; + case 123: + // --no-kqueue + cmdcfgs.emplace_back(SHRPX_OPT_NO_KQUEUE, StringRef::from_lit("yes")); + break; + case 124: + // --frontend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 125: + // --backend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 126: + // --api-max-request-body + cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg}); + break; + case 127: + // --backend-max-backoff + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg}); + break; + case 128: + // --server-name + cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg}); + break; + case 129: + // --no-server-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_REWRITE, + StringRef::from_lit("yes")); + break; + case 130: + // --frontend-http2-optimize-write-buffer-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, + StringRef::from_lit("yes")); + break; + case 131: + // --frontend-http2-optimize-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, + StringRef::from_lit("yes")); + break; + case 132: + // --frontend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 133: + // --frontend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 134: + // --backend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 135: + // --backend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 136: + // --frontend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 137: + // --frontend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 138: + // --backend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 139: + // --backend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 140: + // --ecdh-curves + cmdcfgs.emplace_back(SHRPX_OPT_ECDH_CURVES, StringRef{optarg}); + break; + case 141: + // --tls-sct-dir + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SCT_DIR, StringRef{optarg}); + break; + case 142: + // --backend-connect-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT, + StringRef{optarg}); + break; + case 143: + // --dns-cache-timeout + cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg}); + break; + case 144: + // --dns-lookup-timeou + cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg}); + break; + case 145: + // --dns-max-try + cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg}); + break; + case 146: + // --frontend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 147: + // --psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg}); + break; + case 148: + // --client-psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg}); + break; + case 149: + // --client-no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 150: + // --client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 151: + // --accesslog-write-early + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY, + StringRef::from_lit("yes")); + break; + case 152: + // --tls-min-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION, + StringRef{optarg}); + break; + case 153: + // --tls-max-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, + StringRef{optarg}); + break; + case 154: + // --redirect-https-port + cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg}); + break; + case 155: + // --frontend-max-requests + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_MAX_REQUESTS, + StringRef{optarg}); + break; + case 156: + // --single-thread + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_THREAD, + StringRef::from_lit("yes")); + break; + case 157: + // --no-add-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 158: + // --no-strip-incoming-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 159: + // --single-process + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS, + StringRef::from_lit("yes")); + break; + case 160: + // --verify-client-tolerate-expired + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED, + StringRef::from_lit("yes")); + break; + case 161: + // --ignore-per-pattern-mruby-error + cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR, + StringRef::from_lit("yes")); + break; + case 162: + // --tls-no-postpone-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 163: + // --tls-max-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_EARLY_DATA, StringRef{optarg}); + break; + case 164: + // --tls13-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CIPHERS, StringRef{optarg}); + break; + case 165: + // --tls13-client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 166: + // --no-strip-incoming-early-data + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 167: + // --no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 168: + // --client-no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 169: + // --quic-bpf-program-file + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_BPF_PROGRAM_FILE, + StringRef{optarg}); + break; + case 170: + // --no-quic-bpf + cmdcfgs.emplace_back(SHRPX_OPT_NO_QUIC_BPF, StringRef::from_lit("yes")); + break; + case 171: + // --http2-altsvc + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg}); + break; + case 172: + // --frontend-http3-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT, + StringRef{optarg}); + break; + case 173: + // --frontend-quic-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 174: + // --frontend-quic-debug-log + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG, + StringRef::from_lit("yes")); + break; + case 175: + // --frontend-http3-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE, + StringRef{optarg}); + break; + case 176: + // --frontend-http3-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 177: + // --frontend-http3-max-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE, + StringRef{optarg}); + break; + case 178: + // --frontend-http3-max-connection-window-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 179: + // --frontend-http3-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 180: + // --frontend-quic-early-data + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 181: + // --frontend-quic-qlog-dir + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR, + StringRef{optarg}); + break; + case 182: + // --frontend-quic-require-token + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN, + StringRef::from_lit("yes")); + break; + case 183: + // --frontend-quic-congestion-controller + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER, + StringRef{optarg}); + break; + case 185: + // --quic-server-id + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg}); + break; + case 186: + // --frontend-quic-secret-file + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE, + StringRef{optarg}); + break; + case 187: + // --rlimit-memlock + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg}); + break; + case 188: + // --max-worker-processes + cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg}); + break; + case 189: + // --worker-process-grace-shutdown-period + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD, + StringRef{optarg}); + break; + case 190: + // --frontend-quic-initial-rtt + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT, + StringRef{optarg}); + break; + case 191: + // --require-http-scheme + cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME, + StringRef::from_lit("yes")); + break; + case 192: + // --tls-ktls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_KTLS, StringRef::from_lit("yes")); + break; + case 193: + // --alpn-list + cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg}); + break; + default: + break; + } + break; + default: + break; + } + } + + if (argc - optind >= 2) { + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, StringRef{argv[optind++]}); + cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, StringRef{argv[optind++]}); + } + + rv = process_options(mod_config(), cmdcfgs); + if (rv != 0) { + return -1; + } + + if (event_loop() != 0) { + return -1; + } + + LOG(NOTICE) << "Shutdown momentarily"; + + delete_log_config(); + + return 0; +} + +} // namespace shrpx + +int main(int argc, char **argv) { return run_app(shrpx::main, argc, argv); } diff --git a/src/shrpx.h b/src/shrpx.h new file mode 100644 index 0000000..d881ef5 --- /dev/null +++ b/src/shrpx.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_H +#define SHRPX_H + +#ifdef HAVE_CONFIG_H +# include +#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/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc new file mode 100644 index 0000000..01b6415 --- /dev/null +++ b/src/shrpx_accept_handler.cc @@ -0,0 +1,111 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_accept_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#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/src/shrpx_accept_handler.h b/src/shrpx_accept_handler.h new file mode 100644 index 0000000..853e9a2 --- /dev/null +++ b/src/shrpx_accept_handler.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_ACCEPT_HANDLER_H +#define SHRPX_ACCEPT_HANDLER_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +class ConnectionHandler; +struct UpstreamAddr; + +class AcceptHandler { +public: + AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h); + ~AcceptHandler(); + void accept_connection(); + void enable(); + void disable(); + int get_fd() const; + +private: + ev_io wev_; + ConnectionHandler *conn_hnr_; + const UpstreamAddr *faddr_; +}; + +} // namespace shrpx + +#endif // SHRPX_ACCEPT_HANDLER_H diff --git a/src/shrpx_api_downstream_connection.cc b/src/shrpx_api_downstream_connection.cc new file mode 100644 index 0000000..254ab59 --- /dev/null +++ b/src/shrpx_api_downstream_connection.cc @@ -0,0 +1,478 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_api_downstream_connection.h" + +#include +#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/src/shrpx_api_downstream_connection.h b/src/shrpx_api_downstream_connection.h new file mode 100644 index 0000000..5d4182f --- /dev/null +++ b/src/shrpx_api_downstream_connection.h @@ -0,0 +1,114 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_API_DOWNSTREAM_CONNECTION_H +#define SHRPX_API_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class Worker; + +// If new method is added, don't forget to update API_METHOD_STRING as +// well. +enum APIMethod { + API_METHOD_GET, + API_METHOD_POST, + API_METHOD_PUT, + API_METHOD_MAX, +}; + +// API status code, which is independent from HTTP status code. But +// generally, 2xx code for SUCCESS, and otherwise FAILURE. +enum class APIStatusCode { + SUCCESS, + FAILURE, +}; + +class APIDownstreamConnection; + +struct APIEndpoint { + // Endpoint path. It must start with "/api/". + StringRef path; + // true if we evaluate request body. + bool require_body; + // Allowed methods. This is bitwise OR of one or more of (1 << + // APIMethod value). + uint8_t allowed_methods; + std::function 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/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc new file mode 100644 index 0000000..1f0c01c --- /dev/null +++ b/src/shrpx_client_handler.cc @@ -0,0 +1,1703 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_client_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#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; + + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + + StringRef proto; + + if (next_proto) { + proto = StringRef{next_proto, next_proto_len}; + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated next protocol: " << proto; + } + } else { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1"; + } + + proto = StringRef::from_lit("http/1.1"); + } + + if (!tls::in_proto_list(get_config()->tls.alpn_list, proto)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated protocol is not supported: " << proto; + } + return -1; + } + + if (util::check_h2_is_selected(proto)) { + on_read_ = &ClientHandler::upstream_http2_connhd_read; + + auto http2_upstream = std::make_unique(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/src/shrpx_client_handler.h b/src/shrpx_client_handler.h new file mode 100644 index 0000000..511dd91 --- /dev/null +++ b/src/shrpx_client_handler.h @@ -0,0 +1,236 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CLIENT_HANDLER_H +#define SHRPX_CLIENT_HANDLER_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_config.cc b/src/shrpx_config.cc new file mode 100644 index 0000000..d6d0d07 --- /dev/null +++ b/src/shrpx_config.cc @@ -0,0 +1,4694 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_config.h" + +#ifdef HAVE_PWD_H +# include +#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 defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto sct_dir = + StringRef{std::begin(param) + str_size("sct-dir="), std::end(param)}; + if (sct_dir.empty()) { + LOG(ERROR) << "subcert: " << param << ": empty sct-dir"; + return -1; + } + out.sct_dir = sct_dir; +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + LOG(WARN) << "subcert: sct-dir is ignored because underlying TLS library " + "does not support SCT"; +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + } else if (!param.empty()) { + LOG(ERROR) << "subcert: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +namespace { +// Reads *.sct files from directory denoted by |dir_path|. |dir_path| +// must be NULL-terminated string. +int read_tls_sct_from_dir(std::vector &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 + +#ifndef OPENSSL_NO_PSK +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.psk_secrets. This function +// returns 0 if it succeeds, or -1. +int parse_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": could not open file " << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": could not fine separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + auto identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + auto secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + auto rv = tlsconf.psk_secrets.emplace(identity, secret); + if (!rv.second) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": identity has already been registered at line " << lineno; + return -1; + } + } + + return 0; +} +} // namespace +#endif // !OPENSSL_NO_PSK + +#ifndef OPENSSL_NO_PSK +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.client.psk. This function returns +// 0 if it succeeds, or -1. +int parse_client_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file " + << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": could not find separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + tlsconf.client.psk.identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + tlsconf.client.psk.secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + return 0; + } + + return 0; +} +} // namespace +#endif // !OPENSSL_NO_PSK + +// generated by gennghttpxfun.py +int option_lookup_token(const char *name, size_t namelen) { + switch (namelen) { + case 4: + switch (name[3]) { + case 'f': + if (util::strieq_l("con", name, 3)) { + return SHRPX_OPTID_CONF; + } + break; + case 'r': + if (util::strieq_l("use", name, 3)) { + return SHRPX_OPTID_USER; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'a': + if (util::strieq_l("no-vi", name, 5)) { + return SHRPX_OPTID_NO_VIA; + } + break; + case 'c': + if (util::strieq_l("altsv", name, 5)) { + return SHRPX_OPTID_ALTSVC; + } + break; + case 'n': + if (util::strieq_l("daemo", name, 5)) { + return SHRPX_OPTID_DAEMON; + } + break; + case 't': + if (util::strieq_l("cacer", name, 5)) { + return SHRPX_OPTID_CACERT; + } + if (util::strieq_l("clien", name, 5)) { + return SHRPX_OPTID_CLIENT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'd': + if (util::strieq_l("backen", name, 6)) { + return SHRPX_OPTID_BACKEND; + } + break; + case 'e': + if (util::strieq_l("includ", name, 6)) { + return SHRPX_OPTID_INCLUDE; + } + break; + case 'g': + if (util::strieq_l("backlo", name, 6)) { + return SHRPX_OPTID_BACKLOG; + } + if (util::strieq_l("paddin", name, 6)) { + return SHRPX_OPTID_PADDING; + } + break; + case 'p': + if (util::strieq_l("no-ocs", name, 6)) { + return SHRPX_OPTID_NO_OCSP; + } + break; + case 's': + if (util::strieq_l("cipher", name, 6)) { + return SHRPX_OPTID_CIPHERS; + } + if (util::strieq_l("worker", name, 6)) { + return SHRPX_OPTID_WORKERS; + } + break; + case 't': + if (util::strieq_l("subcer", name, 6)) { + return SHRPX_OPTID_SUBCERT; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'd': + if (util::strieq_l("fronten", name, 7)) { + return SHRPX_OPTID_FRONTEND; + } + break; + case 'e': + if (util::strieq_l("insecur", name, 7)) { + return SHRPX_OPTID_INSECURE; + } + if (util::strieq_l("pid-fil", name, 7)) { + return SHRPX_OPTID_PID_FILE; + } + break; + case 'n': + if (util::strieq_l("fastope", name, 7)) { + return SHRPX_OPTID_FASTOPEN; + } + break; + case 's': + if (util::strieq_l("tls-ktl", name, 7)) { + return SHRPX_OPTID_TLS_KTLS; + } + break; + case 't': + if (util::strieq_l("npn-lis", name, 7)) { + return SHRPX_OPTID_NPN_LIST; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'e': + if (util::strieq_l("no-kqueu", name, 8)) { + return SHRPX_OPTID_NO_KQUEUE; + } + if (util::strieq_l("read-rat", name, 8)) { + return SHRPX_OPTID_READ_RATE; + } + break; + case 'l': + if (util::strieq_l("log-leve", name, 8)) { + return SHRPX_OPTID_LOG_LEVEL; + } + break; + case 't': + if (util::strieq_l("alpn-lis", name, 8)) { + return SHRPX_OPTID_ALPN_LIST; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'e': + if (util::strieq_l("error-pag", name, 9)) { + return SHRPX_OPTID_ERROR_PAGE; + } + if (util::strieq_l("mruby-fil", name, 9)) { + return SHRPX_OPTID_MRUBY_FILE; + } + if (util::strieq_l("write-rat", name, 9)) { + return SHRPX_OPTID_WRITE_RATE; + } + break; + case 't': + if (util::strieq_l("read-burs", name, 9)) { + return SHRPX_OPTID_READ_BURST; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'e': + if (util::strieq_l("server-nam", name, 10)) { + return SHRPX_OPTID_SERVER_NAME; + } + break; + case 'f': + if (util::strieq_l("no-quic-bp", name, 10)) { + return SHRPX_OPTID_NO_QUIC_BPF; + } + break; + case 'r': + if (util::strieq_l("tls-sct-di", name, 10)) { + return SHRPX_OPTID_TLS_SCT_DIR; + } + break; + case 's': + if (util::strieq_l("backend-tl", name, 10)) { + return SHRPX_OPTID_BACKEND_TLS; + } + if (util::strieq_l("ecdh-curve", name, 10)) { + return SHRPX_OPTID_ECDH_CURVES; + } + if (util::strieq_l("psk-secret", name, 10)) { + return SHRPX_OPTID_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("write-burs", name, 10)) { + return SHRPX_OPTID_WRITE_BURST; + } + break; + case 'y': + if (util::strieq_l("dns-max-tr", name, 10)) { + return SHRPX_OPTID_DNS_MAX_TRY; + } + if (util::strieq_l("http2-prox", name, 10)) { + return SHRPX_OPTID_HTTP2_PROXY; + } + break; + } + break; + case 12: + switch (name[11]) { + case '4': + if (util::strieq_l("backend-ipv", name, 11)) { + return SHRPX_OPTID_BACKEND_IPV4; + } + break; + case '6': + if (util::strieq_l("backend-ipv", name, 11)) { + return SHRPX_OPTID_BACKEND_IPV6; + } + break; + case 'c': + if (util::strieq_l("http2-altsv", name, 11)) { + return SHRPX_OPTID_HTTP2_ALTSVC; + } + break; + case 'e': + if (util::strieq_l("host-rewrit", name, 11)) { + return SHRPX_OPTID_HOST_REWRITE; + } + if (util::strieq_l("http2-bridg", name, 11)) { + return SHRPX_OPTID_HTTP2_BRIDGE; + } + break; + case 'p': + if (util::strieq_l("ocsp-startu", name, 11)) { + return SHRPX_OPTID_OCSP_STARTUP; + } + break; + case 'y': + if (util::strieq_l("client-prox", name, 11)) { + return SHRPX_OPTID_CLIENT_PROXY; + } + if (util::strieq_l("forwarded-b", name, 11)) { + return SHRPX_OPTID_FORWARDED_BY; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (util::strieq_l("add-forwarde", name, 12)) { + return SHRPX_OPTID_ADD_FORWARDED; + } + if (util::strieq_l("single-threa", name, 12)) { + return SHRPX_OPTID_SINGLE_THREAD; + } + break; + case 'e': + if (util::strieq_l("dh-param-fil", name, 12)) { + return SHRPX_OPTID_DH_PARAM_FILE; + } + if (util::strieq_l("errorlog-fil", name, 12)) { + return SHRPX_OPTID_ERRORLOG_FILE; + } + if (util::strieq_l("rlimit-nofil", name, 12)) { + return SHRPX_OPTID_RLIMIT_NOFILE; + } + break; + case 'r': + if (util::strieq_l("forwarded-fo", name, 12)) { + return SHRPX_OPTID_FORWARDED_FOR; + } + break; + case 's': + if (util::strieq_l("tls13-cipher", name, 12)) { + return SHRPX_OPTID_TLS13_CIPHERS; + } + break; + case 't': + if (util::strieq_l("verify-clien", name, 12)) { + return SHRPX_OPTID_VERIFY_CLIENT; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'd': + if (util::strieq_l("quic-server-i", name, 13)) { + return SHRPX_OPTID_QUIC_SERVER_ID; + } + break; + case 'e': + if (util::strieq_l("accesslog-fil", name, 13)) { + return SHRPX_OPTID_ACCESSLOG_FILE; + } + break; + case 'h': + if (util::strieq_l("no-server-pus", name, 13)) { + return SHRPX_OPTID_NO_SERVER_PUSH; + } + break; + case 'k': + if (util::strieq_l("rlimit-memloc", name, 13)) { + return SHRPX_OPTID_RLIMIT_MEMLOCK; + } + break; + case 'p': + if (util::strieq_l("no-verify-ocs", name, 13)) { + return SHRPX_OPTID_NO_VERIFY_OCSP; + } + break; + case 's': + if (util::strieq_l("backend-no-tl", name, 13)) { + return SHRPX_OPTID_BACKEND_NO_TLS; + } + if (util::strieq_l("client-cipher", name, 13)) { + return SHRPX_OPTID_CLIENT_CIPHERS; + } + if (util::strieq_l("single-proces", name, 13)) { + return SHRPX_OPTID_SINGLE_PROCESS; + } + break; + case 't': + if (util::strieq_l("tls-proto-lis", name, 13)) { + return SHRPX_OPTID_TLS_PROTO_LIST; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (util::strieq_l("no-host-rewrit", name, 14)) { + return SHRPX_OPTID_NO_HOST_REWRITE; + } + break; + case 'g': + if (util::strieq_l("errorlog-syslo", name, 14)) { + return SHRPX_OPTID_ERRORLOG_SYSLOG; + } + break; + case 's': + if (util::strieq_l("frontend-no-tl", name, 14)) { + return SHRPX_OPTID_FRONTEND_NO_TLS; + } + break; + case 'y': + if (util::strieq_l("syslog-facilit", name, 14)) { + return SHRPX_OPTID_SYSLOG_FACILITY; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'e': + if (util::strieq_l("certificate-fil", name, 15)) { + return SHRPX_OPTID_CERTIFICATE_FILE; + } + if (util::strieq_l("client-cert-fil", name, 15)) { + return SHRPX_OPTID_CLIENT_CERT_FILE; + } + if (util::strieq_l("private-key-fil", name, 15)) { + return SHRPX_OPTID_PRIVATE_KEY_FILE; + } + if (util::strieq_l("worker-read-rat", name, 15)) { + return SHRPX_OPTID_WORKER_READ_RATE; + } + break; + case 'g': + if (util::strieq_l("accesslog-syslo", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_SYSLOG; + } + break; + case 't': + if (util::strieq_l("accesslog-forma", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_FORMAT; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (util::strieq_l("no-server-rewrit", name, 16)) { + return SHRPX_OPTID_NO_SERVER_REWRITE; + } + if (util::strieq_l("worker-write-rat", name, 16)) { + return SHRPX_OPTID_WORKER_WRITE_RATE; + } + break; + case 's': + if (util::strieq_l("backend-http1-tl", name, 16)) { + return SHRPX_OPTID_BACKEND_HTTP1_TLS; + } + if (util::strieq_l("max-header-field", name, 16)) { + return SHRPX_OPTID_MAX_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("dns-cache-timeou", name, 16)) { + return SHRPX_OPTID_DNS_CACHE_TIMEOUT; + } + if (util::strieq_l("worker-read-burs", name, 16)) { + return SHRPX_OPTID_WORKER_READ_BURST; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'a': + if (util::strieq_l("tls-max-early-dat", name, 17)) { + return SHRPX_OPTID_TLS_MAX_EARLY_DATA; + } + break; + case 'r': + if (util::strieq_l("add-request-heade", name, 17)) { + return SHRPX_OPTID_ADD_REQUEST_HEADER; + } + break; + case 's': + if (util::strieq_l("client-psk-secret", name, 17)) { + return SHRPX_OPTID_CLIENT_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("dns-lookup-timeou", name, 17)) { + return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT; + } + if (util::strieq_l("worker-write-burs", name, 17)) { + return SHRPX_OPTID_WORKER_WRITE_BURST; + } + break; + } + break; + case 19: + switch (name[18]) { + case 'e': + if (util::strieq_l("no-location-rewrit", name, 18)) { + return SHRPX_OPTID_NO_LOCATION_REWRITE; + } + if (util::strieq_l("require-http-schem", name, 18)) { + return SHRPX_OPTID_REQUIRE_HTTP_SCHEME; + } + if (util::strieq_l("tls-ticket-key-fil", name, 18)) { + return SHRPX_OPTID_TLS_TICKET_KEY_FILE; + } + break; + case 'f': + if (util::strieq_l("backend-max-backof", name, 18)) { + return SHRPX_OPTID_BACKEND_MAX_BACKOFF; + } + break; + case 'r': + if (util::strieq_l("add-response-heade", name, 18)) { + return SHRPX_OPTID_ADD_RESPONSE_HEADER; + } + if (util::strieq_l("add-x-forwarded-fo", name, 18)) { + return SHRPX_OPTID_ADD_X_FORWARDED_FOR; + } + if (util::strieq_l("header-field-buffe", name, 18)) { + return SHRPX_OPTID_HEADER_FIELD_BUFFER; + } + break; + case 't': + if (util::strieq_l("redirect-https-por", name, 18)) { + return SHRPX_OPTID_REDIRECT_HTTPS_PORT; + } + if (util::strieq_l("stream-read-timeou", name, 18)) { + return SHRPX_OPTID_STREAM_READ_TIMEOUT; + } + break; + } + break; + case 20: + switch (name[19]) { + case 'g': + if (util::strieq_l("frontend-frame-debu", name, 19)) { + return SHRPX_OPTID_FRONTEND_FRAME_DEBUG; + } + break; + case 'l': + if (util::strieq_l("ocsp-update-interva", name, 19)) { + return SHRPX_OPTID_OCSP_UPDATE_INTERVAL; + } + break; + case 's': + if (util::strieq_l("max-worker-processe", name, 19)) { + return SHRPX_OPTID_MAX_WORKER_PROCESSES; + } + if (util::strieq_l("tls13-client-cipher", name, 19)) { + return SHRPX_OPTID_TLS13_CLIENT_CIPHERS; + } + break; + case 't': + if (util::strieq_l("backend-read-timeou", name, 19)) { + return SHRPX_OPTID_BACKEND_READ_TIMEOUT; + } + if (util::strieq_l("stream-write-timeou", name, 19)) { + return SHRPX_OPTID_STREAM_WRITE_TIMEOUT; + } + if (util::strieq_l("verify-client-cacer", name, 19)) { + return SHRPX_OPTID_VERIFY_CLIENT_CACERT; + } + break; + case 'y': + if (util::strieq_l("api-max-request-bod", name, 19)) { + return SHRPX_OPTID_API_MAX_REQUEST_BODY; + } + break; + } + break; + case 21: + switch (name[20]) { + case 'd': + if (util::strieq_l("backend-tls-sni-fiel", name, 20)) { + return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD; + } + break; + case 'e': + if (util::strieq_l("quic-bpf-program-fil", name, 20)) { + return SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE; + } + break; + case 'l': + if (util::strieq_l("accept-proxy-protoco", name, 20)) { + return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL; + } + break; + case 'n': + if (util::strieq_l("tls-max-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MAX_PROTO_VERSION; + } + if (util::strieq_l("tls-min-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MIN_PROTO_VERSION; + } + break; + case 'r': + if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { + return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; + } + break; + case 's': + if (util::strieq_l("frontend-max-request", name, 20)) { + return SHRPX_OPTID_FRONTEND_MAX_REQUESTS; + } + break; + case 't': + if (util::strieq_l("backend-write-timeou", name, 20)) { + return SHRPX_OPTID_BACKEND_WRITE_TIMEOUT; + } + if (util::strieq_l("frontend-read-timeou", name, 20)) { + return SHRPX_OPTID_FRONTEND_READ_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("accesslog-write-earl", name, 20)) { + return SHRPX_OPTID_ACCESSLOG_WRITE_EARLY; + } + break; + } + break; + case 22: + switch (name[21]) { + case 'i': + if (util::strieq_l("backend-http-proxy-ur", name, 21)) { + return SHRPX_OPTID_BACKEND_HTTP_PROXY_URI; + } + break; + case 'r': + if (util::strieq_l("backend-request-buffe", name, 21)) { + return SHRPX_OPTID_BACKEND_REQUEST_BUFFER; + } + if (util::strieq_l("frontend-quic-qlog-di", name, 21)) { + return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR; + } + break; + case 't': + if (util::strieq_l("frontend-write-timeou", name, 21)) { + return SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("backend-address-famil", name, 21)) { + return SHRPX_OPTID_BACKEND_ADDRESS_FAMILY; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'e': + if (util::strieq_l("client-private-key-fil", name, 22)) { + return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE; + } + if (util::strieq_l("private-key-passwd-fil", name, 22)) { + return SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE; + } + break; + case 'g': + if (util::strieq_l("frontend-quic-debug-lo", name, 22)) { + return SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG; + } + break; + case 'r': + if (util::strieq_l("backend-response-buffe", name, 22)) { + return SHRPX_OPTID_BACKEND_RESPONSE_BUFFER; + } + break; + case 't': + if (util::strieq_l("backend-connect-timeou", name, 22)) { + return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT; + } + break; + } + break; + case 24: + switch (name[23]) { + case 'a': + if (util::strieq_l("frontend-quic-early-dat", name, 23)) { + return SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA; + } + break; + case 'd': + if (util::strieq_l("strip-incoming-forwarde", name, 23)) { + return SHRPX_OPTID_STRIP_INCOMING_FORWARDED; + } + if (util::strieq_l("tls-ticket-key-memcache", name, 23)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED; + } + break; + case 'e': + if (util::strieq_l("fetch-ocsp-response-fil", name, 23)) { + return SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE; + } + break; + case 'o': + if (util::strieq_l("no-add-x-forwarded-prot", name, 23)) { + return SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO; + } + break; + case 't': + if (util::strieq_l("listener-disable-timeou", name, 23)) { + return SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT; + } + if (util::strieq_l("tls-dyn-rec-idle-timeou", name, 23)) { + return SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT; + } + break; + } + break; + case 25: + switch (name[24]) { + case 'e': + if (util::strieq_l("backend-http2-window-siz", name, 24)) { + return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE; + } + if (util::strieq_l("frontend-quic-secret-fil", name, 24)) { + return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE; + } + break; + case 'g': + if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) { + return SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING; + } + break; + case 's': + if (util::strieq_l("backend-http2-window-bit", name, 24)) { + return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-request-header-field", name, 24)) { + return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("frontend-quic-initial-rt", name, 24)) { + return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT; + } + break; + } + break; + case 26: + switch (name[25]) { + case 'a': + if (util::strieq_l("tls-no-postpone-early-dat", name, 25)) { + return SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA; + } + break; + case 'e': + if (util::strieq_l("frontend-http2-window-siz", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE; + } + if (util::strieq_l("frontend-http3-window-siz", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE; + } + break; + case 's': + if (util::strieq_l("frontend-http2-window-bit", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-response-header-field", name, 25)) { + return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("backend-keep-alive-timeou", name, 25)) { + return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT; + } + if (util::strieq_l("frontend-quic-idle-timeou", name, 25)) { + return SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT; + } + if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) { + return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST; + } + if (util::strieq_l("no-http2-cipher-block-lis", name, 25)) { + return SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST; + } + break; + } + break; + case 27: + switch (name[26]) { + case 'd': + if (util::strieq_l("tls-session-cache-memcache", name, 26)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED; + } + break; + case 'n': + if (util::strieq_l("frontend-quic-require-toke", name, 26)) { + return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN; + } + break; + case 'r': + if (util::strieq_l("request-header-field-buffe", name, 26)) { + return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER; + } + break; + case 's': + if (util::strieq_l("worker-frontend-connection", name, 26)) { + return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS; + } + break; + case 't': + if (util::strieq_l("frontend-http2-read-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT; + } + if (util::strieq_l("frontend-http3-read-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT; + } + if (util::strieq_l("frontend-keep-alive-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT; + } + break; + } + break; + case 28: + switch (name[27]) { + case 'a': + if (util::strieq_l("no-strip-incoming-early-dat", name, 27)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA; + } + break; + case 'd': + if (util::strieq_l("tls-dyn-rec-warmup-threshol", name, 27)) { + return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD; + } + break; + case 'r': + if (util::strieq_l("response-header-field-buffe", name, 27)) { + return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER; + } + break; + case 's': + if (util::strieq_l("http2-max-concurrent-stream", name, 27)) { + return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS; + } + if (util::strieq_l("tls-ticket-key-memcached-tl", name, 27)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("backend-connections-per-hos", name, 27)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST; + } + break; + } + break; + case 30: + switch (name[29]) { + case 'd': + if (util::strieq_l("verify-client-tolerate-expire", name, 29)) { + return SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED; + } + break; + case 'e': + if (util::strieq_l("frontend-http3-max-window-siz", name, 29)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE; + } + break; + case 'r': + if (util::strieq_l("ignore-per-pattern-mruby-erro", name, 29)) { + return SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR; + } + if (util::strieq_l("strip-incoming-x-forwarded-fo", name, 29)) { + return SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR; + } + break; + case 't': + if (util::strieq_l("backend-http2-settings-timeou", name, 29)) { + return SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 31: + switch (name[30]) { + case 's': + if (util::strieq_l("tls-session-cache-memcached-tl", name, 30)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("frontend-http2-settings-timeou", name, 30)) { + return SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 32: + switch (name[31]) { + case 'd': + if (util::strieq_l("backend-connections-per-fronten", name, 31)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 33: + switch (name[32]) { + case 'l': + if (util::strieq_l("tls-ticket-key-memcached-interva", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL; + } + if (util::strieq_l("tls-ticket-key-memcached-max-fai", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL; + } + break; + case 't': + if (util::strieq_l("client-no-http2-cipher-black-lis", name, 32)) { + return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST; + } + if (util::strieq_l("client-no-http2-cipher-block-lis", name, 32)) { + return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST; + } + break; + } + break; + case 34: + switch (name[33]) { + case 'e': + if (util::strieq_l("tls-ticket-key-memcached-cert-fil", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE; + } + break; + case 'r': + if (util::strieq_l("frontend-http2-dump-request-heade", name, 33)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER; + } + break; + case 't': + if (util::strieq_l("backend-http1-connections-per-hos", name, 33)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST; + } + break; + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-max-retr", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY; + } + break; + } + break; + case 35: + switch (name[34]) { + case 'e': + if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) { + return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE; + } + break; + case 'o': + if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO; + } + break; + case 'r': + if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER; + } + if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) { + return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER; + } + break; + } + break; + case 36: + switch (name[35]) { + case 'd': + if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) { + return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD; + } + break; + case 'e': + if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE; + } + break; + case 'r': + if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER; + } + break; + case 's': + if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS; + } + if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS; + } + break; + } + break; + case 37: + switch (name[36]) { + case 'e': + if (util::strieq_l("frontend-http2-connection-window-siz", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("frontend-http3-connection-window-siz", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("tls-session-cache-memcached-cert-fil", name, 36)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE; + } + break; + case 's': + if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS; + } + if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS; + } + if (util::strieq_l("frontend-http3-max-concurrent-stream", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS; + } + break; + } + break; + case 38: + switch (name[37]) { + case 'd': + if (util::strieq_l("backend-http1-connections-per-fronten", name, 37)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 39: + switch (name[38]) { + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-address-famil", name, 38)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 40: + switch (name[39]) { + case 'e': + if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + break; + } + break; + case 41: + switch (name[40]) { + case 'e': + if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE; + } + if (util::strieq_l("frontend-http3-max-connection-window-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("tls-ticket-key-memcached-private-key-fil", name, + 40)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE; + } + break; + } + break; + case 42: + switch (name[41]) { + case 'y': + if (util::strieq_l("tls-session-cache-memcached-address-famil", name, + 41)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 44: + switch (name[43]) { + case 'e': + if (util::strieq_l("tls-session-cache-memcached-private-key-fil", name, + 43)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE; + } + break; + } + break; + } + return -1; +} + +int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, + std::set &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_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: + config->tls.ecdh_curves = make_string_ref(config->balloc, optarg); + return 0; + case SHRPX_OPTID_TLS_SCT_DIR: +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg); +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support SCT"; + return 0; +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + case SHRPX_OPTID_DNS_CACHE_TIMEOUT: + return parse_duration(&config->dns.timeout.cache, opt, optarg); + case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT: + return parse_duration(&config->dns.timeout.lookup, opt, optarg); + case SHRPX_OPTID_DNS_MAX_TRY: { + int n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n > 5) { + LOG(ERROR) << opt << ": must be smaller than or equal to 5"; + return -1; + } + + config->dns.max_try = n; + return 0; + } + case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.idle_read, opt, + optarg); + case SHRPX_OPTID_PSK_SECRETS: +#ifndef OPENSSL_NO_PSK + return parse_psk_secrets(config, optarg); +#else // OPENSSL_NO_PSK + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // OPENSSL_NO_PSK + case SHRPX_OPTID_CLIENT_PSK_SECRETS: +#ifndef OPENSSL_NO_PSK + return parse_client_psk_secrets(config, optarg); +#else // OPENSSL_NO_PSK + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // OPENSSL_NO_PSK + case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST: + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead."; + // fall through + case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST: + config->tls.client.no_http2_cipher_block_list = + util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_CLIENT_CIPHERS: + config->tls.client.ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS13_CLIENT_CIPHERS: + config->tls.client.tls13_ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_WRITE_EARLY: + config->logging.access.write_early = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MIN_PROTO_VERSION: + return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); + case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: + return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); + case SHRPX_OPTID_REDIRECT_HTTPS_PORT: { + auto n = util::parse_uint(optarg); + if (n == -1 || n < 0 || n > 65535) { + LOG(ERROR) << opt + << ": bad value. Specify an integer in the range [0, " + "65535], inclusive"; + return -1; + } + config->http.redirect_https_port = make_string_ref(config->balloc, optarg); + return 0; + } + case SHRPX_OPTID_FRONTEND_MAX_REQUESTS: + return parse_uint(&config->http.max_requests, opt, optarg); + case SHRPX_OPTID_SINGLE_THREAD: + config->single_thread = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_SINGLE_PROCESS: + config->single_process = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO: + config->http.xfp.add = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO: + config->http.xfp.strip_incoming = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_OCSP_STARTUP: + config->tls.ocsp.startup = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_VERIFY_OCSP: + config->tls.ocsp.no_verify = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED: + config->tls.client_verify.tolerate_expired = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR: + config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA: + config->tls.no_postpone_early_data = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MAX_EARLY_DATA: { + return parse_uint_with_unit(&config->tls.max_early_data, opt, optarg); + } + case SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA: + config->http.early_data.strip_incoming = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE: +#ifdef ENABLE_HTTP3 + config->quic.bpf.prog_file = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_NO_QUIC_BPF: +#ifdef ENABLE_HTTP3 + config->quic.bpf.disabled = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_HTTP2_ALTSVC: { + AltSvc altsvc{}; + + if (parse_altsvc(altsvc, opt, optarg) != 0) { + return -1; + } + + config->http.http2_altsvcs.push_back(std::move(altsvc)); + + return 0; + } + case SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT: +#ifdef ENABLE_HTTP3 + return parse_duration(&config->conn.upstream.timeout.http3_read, opt, + optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT: +#ifdef ENABLE_HTTP3 + return parse_duration(&config->quic.upstream.timeout.idle, opt, optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG: +#ifdef ENABLE_HTTP3 + config->quic.upstream.debug.log = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.window_size, opt, + optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.max_window_size, opt, + optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.max_connection_window_size, + opt, optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS: +#ifdef ENABLE_HTTP3 + return parse_uint(&config->http3.upstream.max_concurrent_streams, opt, + optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA: +#ifdef ENABLE_HTTP3 + config->quic.upstream.early_data = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR: +#ifdef ENABLE_HTTP3 + config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN: +#ifdef ENABLE_HTTP3 + config->quic.upstream.require_token = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER: +#ifdef ENABLE_HTTP3 + if (util::strieq_l("cubic", optarg)) { + config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC; + } else if (util::strieq_l("bbr", optarg)) { + config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR; + } else { + LOG(ERROR) << opt << ": must be either cubic or bbr"; + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_QUIC_SERVER_ID: +#ifdef ENABLE_HTTP3 + if (optarg.size() != config->quic.server_id.size() * 2 || + !util::is_hex_string(optarg)) { + LOG(ERROR) << opt << ": must be a hex-string"; + return -1; + } + util::decode_hex(std::begin(config->quic.server_id), optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE: +#ifdef ENABLE_HTTP3 + config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_RLIMIT_MEMLOCK: { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 0) { + LOG(ERROR) << opt << ": specify the integer more than or equal to 0"; + + return -1; + } + + config->rlimit_memlock = n; + + return 0; + } + case SHRPX_OPTID_MAX_WORKER_PROCESSES: + return parse_uint(&config->max_worker_processes, opt, optarg); + case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD: + return parse_duration(&config->worker_process_grace_shutdown_period, opt, + optarg); + case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: { +#ifdef ENABLE_HTTP3 + return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg); +#endif // ENABLE_HTTP3 + + return 0; + } + case SHRPX_OPTID_REQUIRE_HTTP_SCHEME: + config->http.require_http_scheme = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_TLS_KTLS: + config->tls.ktls = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_NPN_LIST: + LOG(WARN) << opt << ": deprecated. Use alpn-list instead."; + // fall through + case SHRPX_OPTID_ALPN_LIST: { + auto list = util::split_str(optarg, ','); + config->tls.alpn_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.alpn_list[i] = make_string_ref(config->balloc, list[i]); + } + + return 0; + } + case SHRPX_OPTID_CONF: + LOG(WARN) << "conf: ignored"; + + return 0; + } + + LOG(ERROR) << "Unknown option: " << opt; + + return -1; +} + +int load_config(Config *config, const char *filename, + std::set &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/src/shrpx_config.h b/src/shrpx_config.h new file mode 100644 index 0000000..7f316eb --- /dev/null +++ b/src/shrpx_config.h @@ -0,0 +1,1450 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONFIG_H +#define SHRPX_CONFIG_H + +#include "shrpx.h" + +#include +#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 auto SHRPX_OPT_ALPN_LIST = StringRef::from_lit("alpn-list"); + +constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; + +constexpr char DEFAULT_DOWNSTREAM_HOST[] = "127.0.0.1"; +constexpr int16_t DEFAULT_DOWNSTREAM_PORT = 80; + +enum class Proto { + NONE, + HTTP1, + HTTP2, + HTTP3, + MEMCACHED, +}; + +enum class SessionAffinity { + // No session affinity + NONE, + // Client IP affinity + IP, + // Cookie based affinity + COOKIE, +}; + +enum class SessionAffinityCookieSecure { + // Secure attribute of session affinity cookie is determined by the + // request scheme. + AUTO, + // Secure attribute of session affinity cookie is always set. + YES, + // Secure attribute of session affinity cookie is always unset. + NO, +}; + +enum class SessionAffinityCookieStickiness { + // Backend server might be changed when an existing backend server + // is removed, or new backend server is added. + LOOSE, + // Backend server might be changed when a designated backend server + // is removed, but adding new backend server does not cause + // breakage. + STRICT, +}; + +struct AffinityConfig { + // Type of session affinity. + SessionAffinity type; + struct { + // Name of a cookie to use. + StringRef name; + // Path which a cookie is applied to. + StringRef path; + // Secure attribute + SessionAffinityCookieSecure secure; + // Affinity Stickiness + SessionAffinityCookieStickiness stickiness; + } cookie; +}; + +enum shrpx_forwarded_param { + FORWARDED_NONE = 0, + FORWARDED_BY = 0x1, + FORWARDED_FOR = 0x2, + FORWARDED_HOST = 0x4, + FORWARDED_PROTO = 0x8, +}; + +enum class ForwardedNode { + OBFUSCATED, + IP, +}; + +struct AltSvc { + StringRef protocol_id, host, origin, service, params; + + uint16_t port; +}; + +enum class UpstreamAltMode { + // No alternative mode + NONE, + // API processing mode + API, + // Health monitor mode + HEALTHMON, +}; + +struct UpstreamAddr { + // The unique index of this address. + size_t index; + // The frontend address (e.g., FQDN, hostname, IP address). If + // |host_unix| is true, this is UNIX domain socket path. This must + // be NULL terminated string. + StringRef host; + // For TCP socket, this is :. 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 ALPN protocol strings in the order of + // preference. + std::vector alpn_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_ALPN_LIST, + SHRPX_OPTID_ALTSVC, + SHRPX_OPTID_API_MAX_REQUEST_BODY, + SHRPX_OPTID_BACKEND, + SHRPX_OPTID_BACKEND_ADDRESS_FAMILY, + SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT, + SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND, + SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST, + SHRPX_OPTID_BACKEND_HTTP_PROXY_URI, + SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST, + SHRPX_OPTID_BACKEND_HTTP1_TLS, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT, + SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS, + SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE, + SHRPX_OPTID_BACKEND_IPV4, + SHRPX_OPTID_BACKEND_IPV6, + SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT, + SHRPX_OPTID_BACKEND_MAX_BACKOFF, + SHRPX_OPTID_BACKEND_NO_TLS, + SHRPX_OPTID_BACKEND_READ_TIMEOUT, + SHRPX_OPTID_BACKEND_REQUEST_BUFFER, + SHRPX_OPTID_BACKEND_RESPONSE_BUFFER, + SHRPX_OPTID_BACKEND_TLS, + SHRPX_OPTID_BACKEND_TLS_SNI_FIELD, + SHRPX_OPTID_BACKEND_WRITE_TIMEOUT, + SHRPX_OPTID_BACKLOG, + SHRPX_OPTID_CACERT, + SHRPX_OPTID_CERTIFICATE_FILE, + SHRPX_OPTID_CIPHERS, + SHRPX_OPTID_CLIENT, + SHRPX_OPTID_CLIENT_CERT_FILE, + SHRPX_OPTID_CLIENT_CIPHERS, + SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST, + SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST, + SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE, + SHRPX_OPTID_CLIENT_PROXY, + SHRPX_OPTID_CLIENT_PSK_SECRETS, + SHRPX_OPTID_CONF, + SHRPX_OPTID_DAEMON, + SHRPX_OPTID_DH_PARAM_FILE, + SHRPX_OPTID_DNS_CACHE_TIMEOUT, + SHRPX_OPTID_DNS_LOOKUP_TIMEOUT, + SHRPX_OPTID_DNS_MAX_TRY, + SHRPX_OPTID_ECDH_CURVES, + SHRPX_OPTID_ERROR_PAGE, + SHRPX_OPTID_ERRORLOG_FILE, + SHRPX_OPTID_ERRORLOG_SYSLOG, + SHRPX_OPTID_FASTOPEN, + SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE, + SHRPX_OPTID_FORWARDED_BY, + SHRPX_OPTID_FORWARDED_FOR, + SHRPX_OPTID_FRONTEND, + SHRPX_OPTID_FRONTEND_FRAME_DEBUG, + SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, + SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS, + SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT, + SHRPX_OPTID_FRONTEND_MAX_REQUESTS, + SHRPX_OPTID_FRONTEND_NO_TLS, + SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER, + SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG, + SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA, + SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT, + SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT, + SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR, + SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN, + SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE, + SHRPX_OPTID_FRONTEND_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT, + SHRPX_OPTID_HEADER_FIELD_BUFFER, + SHRPX_OPTID_HOST_REWRITE, + SHRPX_OPTID_HTTP2_ALTSVC, + SHRPX_OPTID_HTTP2_BRIDGE, + SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING, + SHRPX_OPTID_HTTP2_PROXY, + SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR, + SHRPX_OPTID_INCLUDE, + SHRPX_OPTID_INSECURE, + SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT, + SHRPX_OPTID_LOG_LEVEL, + SHRPX_OPTID_MAX_HEADER_FIELDS, + SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS, + SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS, + SHRPX_OPTID_MAX_WORKER_PROCESSES, + SHRPX_OPTID_MRUBY_FILE, + SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO, + SHRPX_OPTID_NO_HOST_REWRITE, + SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST, + SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST, + SHRPX_OPTID_NO_KQUEUE, + SHRPX_OPTID_NO_LOCATION_REWRITE, + SHRPX_OPTID_NO_OCSP, + SHRPX_OPTID_NO_QUIC_BPF, + SHRPX_OPTID_NO_SERVER_PUSH, + SHRPX_OPTID_NO_SERVER_REWRITE, + SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA, + SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO, + SHRPX_OPTID_NO_VERIFY_OCSP, + SHRPX_OPTID_NO_VIA, + SHRPX_OPTID_NPN_LIST, + SHRPX_OPTID_OCSP_STARTUP, + SHRPX_OPTID_OCSP_UPDATE_INTERVAL, + SHRPX_OPTID_PADDING, + SHRPX_OPTID_PID_FILE, + SHRPX_OPTID_PRIVATE_KEY_FILE, + SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE, + SHRPX_OPTID_PSK_SECRETS, + SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE, + SHRPX_OPTID_QUIC_SERVER_ID, + SHRPX_OPTID_READ_BURST, + SHRPX_OPTID_READ_RATE, + SHRPX_OPTID_REDIRECT_HTTPS_PORT, + SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER, + SHRPX_OPTID_REQUIRE_HTTP_SCHEME, + SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER, + SHRPX_OPTID_RLIMIT_MEMLOCK, + SHRPX_OPTID_RLIMIT_NOFILE, + SHRPX_OPTID_SERVER_NAME, + SHRPX_OPTID_SINGLE_PROCESS, + SHRPX_OPTID_SINGLE_THREAD, + SHRPX_OPTID_STREAM_READ_TIMEOUT, + SHRPX_OPTID_STREAM_WRITE_TIMEOUT, + SHRPX_OPTID_STRIP_INCOMING_FORWARDED, + SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR, + SHRPX_OPTID_SUBCERT, + SHRPX_OPTID_SYSLOG_FACILITY, + SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT, + SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, + SHRPX_OPTID_TLS_KTLS, + SHRPX_OPTID_TLS_MAX_EARLY_DATA, + SHRPX_OPTID_TLS_MAX_PROTO_VERSION, + SHRPX_OPTID_TLS_MIN_PROTO_VERSION, + SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA, + SHRPX_OPTID_TLS_PROTO_LIST, + SHRPX_OPTID_TLS_SCT_DIR, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS, + SHRPX_OPTID_TLS_TICKET_KEY_CIPHER, + SHRPX_OPTID_TLS_TICKET_KEY_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS, + SHRPX_OPTID_TLS13_CIPHERS, + SHRPX_OPTID_TLS13_CLIENT_CIPHERS, + SHRPX_OPTID_USER, + SHRPX_OPTID_VERIFY_CLIENT, + SHRPX_OPTID_VERIFY_CLIENT_CACERT, + SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED, + SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS, + SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD, + SHRPX_OPTID_WORKER_READ_BURST, + SHRPX_OPTID_WORKER_READ_RATE, + SHRPX_OPTID_WORKER_WRITE_BURST, + SHRPX_OPTID_WORKER_WRITE_RATE, + SHRPX_OPTID_WORKERS, + SHRPX_OPTID_WRITE_BURST, + SHRPX_OPTID_WRITE_RATE, + SHRPX_OPTID_MAXIDX, +}; + +// Looks up token for given option name |name| of length |namelen|. +int option_lookup_token(const char *name, size_t namelen); + +// Parses option name |opt| and value |optarg|. The results are +// stored into the object pointed by |config|. This function returns 0 +// if it succeeds, or -1. The |included_set| contains the all paths +// already included while processing this configuration, to avoid loop +// in --include option. The |pattern_addr_indexer| contains a pair of +// pattern of backend, and its index in DownstreamConfig::addr_groups. +// It is introduced to speed up loading configuration file with lots +// of backends. +int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, + std::set &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/src/shrpx_config_test.cc b/src/shrpx_config_test.cc new file mode 100644 index 0000000..a8f0962 --- /dev/null +++ b/src/shrpx_config_test.cc @@ -0,0 +1,249 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_config_test.h" + +#ifdef HAVE_UNISTD_H +# include +#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/src/shrpx_config_test.h b/src/shrpx_config_test.h new file mode 100644 index 0000000..a30de41 --- /dev/null +++ b/src/shrpx_config_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONFIG_TEST_H +#define SHRPX_CONFIG_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_config_parse_header(void); +void test_shrpx_config_parse_log_format(void); +void test_shrpx_config_read_tls_ticket_key_file(void); +void test_shrpx_config_read_tls_ticket_key_file_aes_256(void); +void test_shrpx_config_match_downstream_addr_group(void); + +} // namespace shrpx + +#endif // SHRPX_CONFIG_TEST_H diff --git a/src/shrpx_connect_blocker.cc b/src/shrpx_connect_blocker.cc new file mode 100644 index 0000000..ff767b0 --- /dev/null +++ b/src/shrpx_connect_blocker.cc @@ -0,0 +1,143 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_connect_blocker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto connect_blocker = static_cast(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/src/shrpx_connect_blocker.h b/src/shrpx_connect_blocker.h new file mode 100644 index 0000000..1ebe789 --- /dev/null +++ b/src/shrpx_connect_blocker.h @@ -0,0 +1,86 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONNECT_BLOCKER_H +#define SHRPX_CONNECT_BLOCKER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_connection.cc b/src/shrpx_connection.cc new file mode 100644 index 0000000..a5ab390 --- /dev/null +++ b/src/shrpx_connection.cc @@ -0,0 +1,1275 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_connection.h" + +#ifdef HAVE_UNISTD_H +# include +#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 { + +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) { + BIO_set_init(b, 1); + + return 1; +} +} // namespace + +namespace { +int shrpx_bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + + return 1; +} +} // namespace + +BIO_METHOD *create_bio_method() { + auto meth = BIO_meth_new(BIO_TYPE_FD, "nghttpx-bio"); + BIO_meth_set_write(meth, shrpx_bio_write); + BIO_meth_set_read(meth, shrpx_bio_read); + BIO_meth_set_puts(meth, shrpx_bio_puts); + BIO_meth_set_gets(meth, shrpx_bio_gets); + BIO_meth_set_ctrl(meth, shrpx_bio_ctrl); + BIO_meth_set_create(meth, shrpx_bio_create); + BIO_meth_set_destroy(meth, shrpx_bio_destroy); + + return meth; +} + +void Connection::set_ssl(SSL *ssl) { + tls.ssl = ssl; + + SSL_set_app_data(tls.ssl, this); +} + +namespace { +// We should buffer at least full encrypted TLS record here. +// Theoretically, peer can send client hello in several TLS records, +// which could exceed this limit, but it is not portable, and we don't +// have to handle such exotic behaviour. +bool read_buffer_full(DefaultPeekMemchunks &rbuf) { + return rbuf.rleft_buffered() >= 20_k; +} +} // namespace + +int Connection::tls_handshake() { + wlimit.stopw(); + ev_timer_stop(loop, &wt); + + auto &tlsconf = get_config()->tls; + + if (!tls.server_handshake || tlsconf.session_cache.memcached.host.empty()) { + return tls_handshake_simple(); + } + + std::array buf; + + if (ev_is_active(&rev)) { + auto nread = read_clear(buf.data(), buf.size()); + if (nread < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake read error"; + } + return -1; + } + tls.rbuf.append(buf.data(), nread); + if (read_buffer_full(tls.rbuf)) { + rlimit.stopw(); + } + } + + if (tls.initial_handshake_done) { + return write_tls_pending_handshake(); + } + + switch (tls.handshake_state) { + case TLSHandshakeState::WAIT_FOR_SESSION_CACHE: + return SHRPX_ERR_INPROGRESS; + case TLSHandshakeState::GOT_SESSION_CACHE: { + // Use the same trick invented by @kazuho in h2o project. + + // Discard all outgoing data. + tls.wbuf.reset(); + // Rewind buffered incoming data to replay client hello. + tls.rbuf.disable_peek(false); + + auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl); + auto ssl_opts = SSL_get_options(tls.ssl); + SSL_free(tls.ssl); + + auto ssl = tls::create_ssl(ssl_ctx); + if (!ssl) { + return -1; + } + if (ssl_opts & SSL_OP_NO_TICKET) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + + set_ssl(ssl); + + prepare_server_handshake(); + + tls.handshake_state = TLSHandshakeState::NORMAL; + break; + } + case TLSHandshakeState::CANCEL_SESSION_CACHE: + tls.handshake_state = TLSHandshakeState::NORMAL; + break; + default: + break; + } + + int rv; + + ERR_clear_error(); + +#ifdef NGHTTP2_GENUINE_OPENSSL + if (!tls.server_handshake || tls.early_data_finish) { + rv = SSL_do_handshake(tls.ssl); + } else { + for (;;) { + size_t nread; + + rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + // If we have early data, and server sends ServerHello, assume + // that handshake is completed in server side, and start + // processing request. If we don't exit handshake code here, + // server waits for EndOfEarlyData and Finished message from + // client, which voids the purpose of 0-RTT data. The left + // over of handshake is done through write_tls or read_tls. + if (tlsconf.no_postpone_early_data && + (tls.handshake_state == TLSHandshakeState::WRITE_STARTED || + tls.wbuf.rleft()) && + tls.earlybuf.rleft()) { + rv = 1; + } + + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + tls.earlybuf.append(buf.data(), nread); + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data; total " + << tls.earlybuf.rleft() << " bytes"; + } + tls.early_data_finish = true; + // The same reason stated above. + if (tlsconf.no_postpone_early_data && + (tls.handshake_state == TLSHandshakeState::WRITE_STARTED || + tls.wbuf.rleft()) && + tls.earlybuf.rleft()) { + rv = 1; + } else { + ERR_clear_error(); + rv = SSL_do_handshake(tls.ssl); + } + break; + } + } + } +#else // !NGHTTP2_GENUINE_OPENSSL + rv = SSL_do_handshake(tls.ssl); +#endif // !NGHTTP2_GENUINE_OPENSSL + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (read_buffer_full(tls.rbuf)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake message is too large"; + } + return -1; + } + break; + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + struct iovec iov[1]; + auto iovcnt = tls.wbuf.riovec(iov, 1); + auto nwrite = writev_clear(iov, iovcnt); + if (nwrite > 0) { + tls.wbuf.drain(nwrite); + } + + return SHRPX_ERR_NETWORK; + } + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (tls.handshake_state == TLSHandshakeState::WAIT_FOR_SESSION_CACHE) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + + // Don't send handshake data if handshake was completed in OpenSSL + // routine. We have to check HTTP/2 requirement if HTTP/2 was + // negotiated before sending finished message to the peer. + if ((rv != 1 +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + || SSL_in_init(tls.ssl) +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + ) && + tls.wbuf.rleft()) { + // First write indicates that resumption stuff has done. + if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) { + tls.handshake_state = TLSHandshakeState::WRITE_STARTED; + // If peek has already disabled, this is noop. + tls.rbuf.disable_peek(true); + } + std::array iov; + auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size()); + auto nwrite = writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake write error"; + } + return -1; + } + tls.wbuf.drain(nwrite); + + if (tls.wbuf.rleft()) { + wlimit.startw(); + ev_timer_again(loop, &wt); + } + } + + if (!read_buffer_full(tls.rbuf)) { + // We may have stopped reading + rlimit.startw(); + } + + if (rv != 1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) && + SSL_in_init(tls.ssl)) { + auto nread = SSL_read(tls.ssl, buf.data(), buf.size()); + if (nread <= 0) { + auto err = SSL_get_error(tls.ssl, nread); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } else { + tls.earlybuf.append(buf.data(), nread); + } + + if (SSL_in_init(tls.ssl)) { + return SHRPX_ERR_INPROGRESS; + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // Handshake was done + + rv = check_http2_requirement(); + if (rv != 0) { + return -1; + } + + // Just in case + tls.rbuf.disable_peek(true); + + tls.initial_handshake_done = true; + + return write_tls_pending_handshake(); +} + +int Connection::tls_handshake_simple() { + wlimit.stopw(); + ev_timer_stop(loop, &wt); + + if (tls.initial_handshake_done) { + return write_tls_pending_handshake(); + } + + if (SSL_get_fd(tls.ssl) == -1) { + SSL_set_fd(tls.ssl, fd); + } + + int rv; +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto &tlsconf = get_config()->tls; + std::array buf; +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL + + ERR_clear_error(); + +#ifdef NGHTTP2_GENUINE_OPENSSL + if (!tls.server_handshake || tls.early_data_finish) { + rv = SSL_do_handshake(tls.ssl); + } else { + for (;;) { + size_t nread; + + rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + // If we have early data, and server sends ServerHello, assume + // that handshake is completed in server side, and start + // processing request. If we don't exit handshake code here, + // server waits for EndOfEarlyData and Finished message from + // client, which voids the purpose of 0-RTT data. The left + // over of handshake is done through write_tls or read_tls. + if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { + rv = 1; + } + + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + tls.earlybuf.append(buf.data(), nread); + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data; total " + << tls.earlybuf.rleft() << " bytes"; + } + tls.early_data_finish = true; + // The same reason stated above. + if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { + rv = 1; + } else { + ERR_clear_error(); + rv = SSL_do_handshake(tls.ssl); + } + break; + } + } + } +#else // !NGHTTP2_GENUINE_OPENSSL + rv = SSL_do_handshake(tls.ssl); +#endif // !NGHTTP2_GENUINE_OPENSSL + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (read_buffer_full(tls.rbuf)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake message is too large"; + } + return -1; + } + break; + case SSL_ERROR_WANT_WRITE: + wlimit.startw(); + ev_timer_again(loop, &wt); + break; + case SSL_ERROR_SSL: { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + } + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (rv != 1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) && + SSL_in_init(tls.ssl)) { + auto nread = SSL_read(tls.ssl, buf.data(), buf.size()); + if (nread <= 0) { + auto err = SSL_get_error(tls.ssl, nread); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } else { + tls.earlybuf.append(buf.data(), nread); + } + + if (SSL_in_init(tls.ssl)) { + return SHRPX_ERR_INPROGRESS; + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // Handshake was done + + rv = check_http2_requirement(); + if (rv != 0) { + return -1; + } + + tls.initial_handshake_done = true; + + return write_tls_pending_handshake(); +} + +int Connection::write_tls_pending_handshake() { + // Send handshake data left in the buffer + while (tls.wbuf.rleft()) { + std::array iov; + auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size()); + auto nwrite = writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake write error"; + } + return -1; + } + if (nwrite == 0) { + wlimit.startw(); + ev_timer_again(loop, &wt); + + return SHRPX_ERR_INPROGRESS; + } + tls.wbuf.drain(nwrite); + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!SSL_in_init(tls.ssl)) { + // This will send a session ticket. + auto nwrite = SSL_write(tls.ssl, "", 0); + if (nwrite < 0) { + auto err = SSL_get_error(tls.ssl, nwrite); + switch (err) { + case SSL_ERROR_WANT_READ: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // We have to start read watcher, since later stage of code expects + // this. + rlimit.startw(); + + // We may have whole request in tls.rbuf. This means that we don't + // get notified further read event. This is especially true for + // HTTP/1.1. + handle_tls_pending_read(); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL/TLS handshake completed"; + nghttp2::tls::TLSSessionInfo tls_info{}; + if (nghttp2::tls::get_tls_session_info(&tls_info, tls.ssl)) { + LOG(INFO) << "cipher=" << tls_info.cipher + << " protocol=" << tls_info.protocol + << " resumption=" << (tls_info.session_reused ? "yes" : "no") + << " session_id=" + << util::format_hex(tls_info.session_id, + tls_info.session_id_length); + } + } + + return 0; +} + +int Connection::check_http2_requirement() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + + SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len); + if (next_proto == nullptr || + !util::check_h2_is_selected(StringRef{next_proto, next_proto_len})) { + return 0; + } + if (!nghttp2::tls::check_http2_tls_version(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used."; + } + return -1; + } + + auto check_block_list = false; + if (tls.server_handshake) { + check_block_list = !get_config()->tls.no_http2_cipher_block_list; + } else { + check_block_list = !get_config()->tls.client.no_http2_cipher_block_list; + } + + if (check_block_list && + nghttp2::tls::check_http2_cipher_block_list(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite " + "block list. HTTP/2 must not be used."; + } + return -1; + } + + return 0; +} + +namespace { +constexpr size_t SHRPX_SMALL_WRITE_LIMIT = 1300; +} // namespace + +size_t Connection::get_tls_write_limit() { + + if (tls_dyn_rec_warmup_threshold == 0) { + return std::numeric_limits::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(); + +#ifdef NGHTTP2_GENUINE_OPENSSL + int rv; + if (SSL_is_init_finished(tls.ssl)) { + rv = SSL_write(tls.ssl, data, len); + } else { + size_t nwrite; + rv = SSL_write_early_data(tls.ssl, data, len, &nwrite); + // Use the same semantics with SSL_write. + if (rv == 1) { + rv = nwrite; + } + } +#else // !NGHTTP2_GENUINE_OPENSSL + auto rv = SSL_write(tls.ssl, data, len); +#endif // !NGHTTP2_GENUINE_OPENSSL + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_WANT_WRITE: + tls.last_writelen = len; + // starting write watcher and timer is done in write_clear via + // bio otherwise. + if (!via_bio) { + wlimit.startw(); + ev_timer_again(loop, &wt); + } + + return 0; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (!via_bio) { + wlimit.drain(rv); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + } + + update_tls_warmup_writelen(rv); + + return rv; +} + +ssize_t Connection::read_tls(void *data, size_t len) { + ERR_clear_error(); + +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (tls.earlybuf.rleft()) { + return tls.earlybuf.remove(data, len); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL + + // SSL_read requires the same arguments (buf pointer and its + // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. + // rlimit_.avail() or rlimit_.avail() may return different length + // than the length previously passed to SSL_read, which violates + // OpenSSL assumption. To avoid this, we keep last length passed + // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O + // blocking. + if (tls.last_readlen == 0) { + len = std::min(len, rlimit.avail()); + if (len == 0) { + return 0; + } + } else { + len = tls.last_readlen; + tls.last_readlen = 0; + } + + auto &tlsconf = get_config()->tls; + auto via_bio = + tls.server_handshake && !tlsconf.session_cache.memcached.host.empty(); + +#ifdef NGHTTP2_GENUINE_OPENSSL + if (!tls.early_data_finish) { + // TLSv1.3 handshake is still going on. + size_t nread; + auto rv = SSL_read_early_data(tls.ssl, data, len, &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + tls.last_readlen = len; + return 0; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data"; + } + tls.early_data_finish = true; + // We may have stopped write watcher in write_tls. + wlimit.startw(); + } + + if (!via_bio) { + rlimit.drain(nread); + } + + return nread; + } +#endif // NGHTTP2_GENUINE_OPENSSL + + auto rv = SSL_read(tls.ssl, data, len); + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + tls.last_readlen = len; + return 0; + case SSL_ERROR_WANT_WRITE: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (!via_bio) { + rlimit.drain(rv); + } + + return rv; +} + +ssize_t Connection::write_clear(const void *data, size_t len) { + len = std::min(len, wlimit.avail()); + if (len == 0) { + return 0; + } + + ssize_t nwrite; + while ((nwrite = write(fd, data, len)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + wlimit.startw(); + ev_timer_again(loop, &wt); + return 0; + } + return SHRPX_ERR_NETWORK; + } + + wlimit.drain(nwrite); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + + return nwrite; +} + +ssize_t Connection::writev_clear(struct iovec *iov, int iovcnt) { + iovcnt = limit_iovec(iov, iovcnt, wlimit.avail()); + if (iovcnt == 0) { + return 0; + } + + ssize_t nwrite; + while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + wlimit.startw(); + ev_timer_again(loop, &wt); + return 0; + } + return SHRPX_ERR_NETWORK; + } + + wlimit.drain(nwrite); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + + return nwrite; +} + +ssize_t Connection::read_clear(void *data, size_t len) { + len = std::min(len, rlimit.avail()); + if (len == 0) { + return 0; + } + + ssize_t nread; + while ((nread = read(fd, data, len)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + rlimit.drain(nread); + + return nread; +} + +ssize_t Connection::read_nolim_clear(void *data, size_t len) { + ssize_t nread; + while ((nread = read(fd, data, len)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + return nread; +} + +ssize_t Connection::peek_clear(void *data, size_t len) { + ssize_t nread; + while ((nread = recv(fd, data, len, MSG_PEEK)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + return nread; +} + +void Connection::handle_tls_pending_read() { + if (!ev_is_active(&rev)) { + return; + } + rlimit.handle_tls_pending_read(); +} + +int Connection::get_tcp_hint(TCPHint *hint) const { +#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT) + struct tcp_info tcp_info; + socklen_t tcp_info_len = sizeof(tcp_info); + int rv; + + rv = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len); + + if (rv != 0) { + return -1; + } + + auto avail_packets = tcp_info.tcpi_snd_cwnd > tcp_info.tcpi_unacked + ? tcp_info.tcpi_snd_cwnd - tcp_info.tcpi_unacked + : 0; + + // http://www.slideshare.net/kazuho/programming-tcp-for-responsiveness + + // TODO 29 (5 (header) + 8 (explicit nonce) + 16 (tag)) is TLS + // overhead for AES-GCM. For CHACHA20_POLY1305, it is 21 since it + // does not need 8 bytes explicit nonce. + // + // For TLSv1.3, AES-GCM and CHACHA20_POLY1305 overhead are now 22 + // bytes (5 (header) + 1 (ContentType) + 16 (tag)). + size_t tls_overhead; +# ifdef TLS1_3_VERSION + if (SSL_version(tls.ssl) == TLS1_3_VERSION) { + tls_overhead = 22; + } else +# endif // TLS1_3_VERSION + { + tls_overhead = 29; + } + + auto writable_size = + (avail_packets + 2) * (tcp_info.tcpi_snd_mss - tls_overhead); + if (writable_size > 16_k) { + writable_size = writable_size & ~(16_k - 1); + } else { + if (writable_size < 536) { + LOG(INFO) << "writable_size is too small: " << writable_size; + } + // TODO is this required? + writable_size = std::max(writable_size, static_cast(536 * 2)); + } + + // if (LOG_ENABLED(INFO)) { + // LOG(INFO) << "snd_cwnd=" << tcp_info.tcpi_snd_cwnd + // << ", unacked=" << tcp_info.tcpi_unacked + // << ", snd_mss=" << tcp_info.tcpi_snd_mss + // << ", rtt=" << tcp_info.tcpi_rtt << "us" + // << ", rcv_space=" << tcp_info.tcpi_rcv_space + // << ", writable=" << writable_size; + // } + + hint->write_buffer_size = writable_size; + // TODO tcpi_rcv_space is considered as rwin, is that correct? + hint->rwin = tcp_info.tcpi_rcv_space; + + return 0; +#else // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) + return -1; +#endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) +} + +void Connection::again_rt(ev_tstamp t) { + read_timeout = t; + rt.repeat = t; + ev_timer_again(loop, &rt); + last_read = std::chrono::steady_clock::now(); +} + +void Connection::again_rt() { + rt.repeat = read_timeout; + ev_timer_again(loop, &rt); + last_read = std::chrono::steady_clock::now(); +} + +bool Connection::expired_rt() { + auto delta = read_timeout - util::ev_tstamp_from( + std::chrono::steady_clock::now() - last_read); + if (delta < 1e-9) { + return true; + } + rt.repeat = delta; + ev_timer_again(loop, &rt); + return false; +} + +} // namespace shrpx diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h new file mode 100644 index 0000000..10526f7 --- /dev/null +++ b/src/shrpx_connection.h @@ -0,0 +1,203 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONNECTION_H +#define SHRPX_CONNECTION_H + +#include "shrpx_config.h" + +#include + +#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 changing timeout. + void again_rt(); + // Returns true if read timer expired. + bool expired_rt(); + +#ifdef ENABLE_HTTP3 + // This must be the first member of Connection. + ngtcp2_crypto_conn_ref conn_ref; +#endif // ENABLE_HTTP3 + TLSConnection tls; + ev_io wev; + ev_io rev; + ev_timer wt; + ev_timer rt; + RateLimit wlimit; + RateLimit rlimit; + struct ev_loop *loop; + void *data; + int fd; + size_t tls_dyn_rec_warmup_threshold; + std::chrono::steady_clock::duration tls_dyn_rec_idle_timeout; + // Application protocol used over the connection. This field is not + // used in this object at the moment. The rest of the program may + // use this value when it is useful. + Proto proto; + // The point of time when last read is observed. Note: since we use + // |rt| as idle timer, the activity is not limited to read. + std::chrono::steady_clock::time_point last_read; + // Timeout for read timer |rt|. + ev_tstamp read_timeout; +}; + +#ifdef ENABLE_HTTP3 +static_assert(std::is_standard_layout::value, + "Connection is not standard layout"); +#endif // ENABLE_HTTP3 + +// Creates BIO_method shared by all SSL objects. +BIO_METHOD *create_bio_method(); + +} // namespace shrpx + +#endif // SHRPX_CONNECTION_H diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc new file mode 100644 index 0000000..330e832 --- /dev/null +++ b/src/shrpx_connection_handler.cc @@ -0,0 +1,1319 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_connection_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#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); + 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); + 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); + + 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/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h new file mode 100644 index 0000000..f3748ab --- /dev/null +++ b/src/shrpx_connection_handler.h @@ -0,0 +1,322 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_CONNECTION_HANDLER_H +#define SHRPX_CONNECTION_HANDLER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc new file mode 100644 index 0000000..f83ecb7 --- /dev/null +++ b/src/shrpx_dns_resolver.cc @@ -0,0 +1,353 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_dns_resolver.h" + +#include +#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/src/shrpx_dns_resolver.h b/src/shrpx_dns_resolver.h new file mode 100644 index 0000000..e622f99 --- /dev/null +++ b/src/shrpx_dns_resolver.h @@ -0,0 +1,118 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DNS_RESOLVER_H +#define SHRPX_DNS_RESOLVER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc new file mode 100644 index 0000000..57387ce --- /dev/null +++ b/src/shrpx_dns_tracker.cc @@ -0,0 +1,328 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_dns_tracker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "util.h" + +namespace shrpx { + +namespace { +void gccb(struct ev_loop *loop, ev_timer *w, int revents) { + auto dns_tracker = static_cast(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/src/shrpx_dns_tracker.h b/src/shrpx_dns_tracker.h new file mode 100644 index 0000000..c7caac0 --- /dev/null +++ b/src/shrpx_dns_tracker.h @@ -0,0 +1,121 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DNS_TRACKER_H +#define SHRPX_DNS_TRACKER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_downstream.cc b/src/shrpx_downstream.cc new file mode 100644 index 0000000..9ea52b4 --- /dev/null +++ b/src/shrpx_downstream.cc @@ -0,0 +1,1189 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream.h" + +#include + +#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/src/shrpx_downstream.h b/src/shrpx_downstream.h new file mode 100644 index 0000000..146cae5 --- /dev/null +++ b/src/shrpx_downstream.h @@ -0,0 +1,628 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_H +#define SHRPX_DOWNSTREAM_H + +#include "shrpx.h" + +#include +#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/src/shrpx_downstream_connection.cc b/src/shrpx_downstream_connection.cc new file mode 100644 index 0000000..16a2d6f --- /dev/null +++ b/src/shrpx_downstream_connection.cc @@ -0,0 +1,48 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream_connection.h" + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +DownstreamConnection::DownstreamConnection() + : client_handler_(nullptr), downstream_(nullptr) {} + +DownstreamConnection::~DownstreamConnection() {} + +void DownstreamConnection::set_client_handler(ClientHandler *handler) { + client_handler_ = handler; +} + +ClientHandler *DownstreamConnection::get_client_handler() { + return client_handler_; +} + +Downstream *DownstreamConnection::get_downstream() { return downstream_; } + +} // namespace shrpx diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h new file mode 100644 index 0000000..8efdcbe --- /dev/null +++ b/src/shrpx_downstream_connection.h @@ -0,0 +1,81 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_CONNECTION_H +#define SHRPX_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_downstream_connection_pool.cc b/src/shrpx_downstream_connection_pool.cc new file mode 100644 index 0000000..0ee66b6 --- /dev/null +++ b/src/shrpx_downstream_connection_pool.cc @@ -0,0 +1,66 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream_connection_pool.h" +#include "shrpx_downstream_connection.h" + +namespace shrpx { + +DownstreamConnectionPool::DownstreamConnectionPool() {} + +DownstreamConnectionPool::~DownstreamConnectionPool() { remove_all(); } + +void DownstreamConnectionPool::remove_all() { + for (auto dconn : pool_) { + delete dconn; + } + + pool_.clear(); +} + +void DownstreamConnectionPool::add_downstream_connection( + std::unique_ptr 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/src/shrpx_downstream_connection_pool.h b/src/shrpx_downstream_connection_pool.h new file mode 100644 index 0000000..34dc30d --- /dev/null +++ b/src/shrpx_downstream_connection_pool.h @@ -0,0 +1,53 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_CONNECTION_POOL_H +#define SHRPX_DOWNSTREAM_CONNECTION_POOL_H + +#include "shrpx.h" + +#include +#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/src/shrpx_downstream_queue.cc b/src/shrpx_downstream_queue.cc new file mode 100644 index 0000000..f8906e8 --- /dev/null +++ b/src/shrpx_downstream_queue.cc @@ -0,0 +1,175 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream_queue.h" + +#include +#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/src/shrpx_downstream_queue.h b/src/shrpx_downstream_queue.h new file mode 100644 index 0000000..a5b980f --- /dev/null +++ b/src/shrpx_downstream_queue.h @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_QUEUE_H +#define SHRPX_DOWNSTREAM_QUEUE_H + +#include "shrpx.h" + +#include +#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/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc new file mode 100644 index 0000000..6100b18 --- /dev/null +++ b/src/shrpx_downstream_test.cc @@ -0,0 +1,231 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_downstream_test.h" + +#include + +#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/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h new file mode 100644 index 0000000..ef06ea3 --- /dev/null +++ b/src/shrpx_downstream_test.h @@ -0,0 +1,44 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DOWNSTREAM_TEST_H +#define SHRPX_DOWNSTREAM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_downstream_field_store_append_last_header(void); +void test_downstream_field_store_header(void); +void test_downstream_crumble_request_cookie(void); +void test_downstream_assemble_request_cookie(void); +void test_downstream_rewrite_location_response_header(void); +void test_downstream_supports_non_final_response(void); +void test_downstream_find_affinity_cookie(void); + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_TEST_H diff --git a/src/shrpx_dual_dns_resolver.cc b/src/shrpx_dual_dns_resolver.cc new file mode 100644 index 0000000..8c6c5c9 --- /dev/null +++ b/src/shrpx_dual_dns_resolver.cc @@ -0,0 +1,93 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_dual_dns_resolver.h" + +namespace shrpx { + +DualDNSResolver::DualDNSResolver(struct ev_loop *loop, int family) + : family_(family), resolv4_(loop), resolv6_(loop) { + auto cb = [this](DNSResolverStatus, const Address *) { + Address result; + + auto status = this->get_status(&result); + switch (status) { + case DNSResolverStatus::ERROR: + case DNSResolverStatus::OK: + break; + default: + return; + } + + auto cb = this->get_complete_cb(); + cb(status, &result); + }; + + if (family_ == AF_UNSPEC || family_ == AF_INET) { + resolv4_.set_complete_cb(cb); + } + if (family_ == AF_UNSPEC || family_ == AF_INET6) { + resolv6_.set_complete_cb(cb); + } +} + +int DualDNSResolver::resolve(const StringRef &host) { + int rv4 = 0, rv6 = 0; + if (family_ == AF_UNSPEC || family_ == AF_INET) { + rv4 = resolv4_.resolve(host, AF_INET); + } + if (family_ == AF_UNSPEC || family_ == AF_INET6) { + rv6 = resolv6_.resolve(host, AF_INET6); + } + + if (rv4 != 0 && rv6 != 0) { + return -1; + } + + return 0; +} + +CompleteCb DualDNSResolver::get_complete_cb() const { return complete_cb_; } + +void DualDNSResolver::set_complete_cb(CompleteCb cb) { complete_cb_ = cb; } + +DNSResolverStatus DualDNSResolver::get_status(Address *result) const { + auto rv6 = resolv6_.get_status(result); + if (rv6 == DNSResolverStatus::OK) { + return DNSResolverStatus::OK; + } + auto rv4 = resolv4_.get_status(result); + if (rv4 == DNSResolverStatus::OK) { + return DNSResolverStatus::OK; + } + if (rv4 == DNSResolverStatus::RUNNING || rv6 == DNSResolverStatus::RUNNING) { + return DNSResolverStatus::RUNNING; + } + if (rv4 == DNSResolverStatus::ERROR || rv6 == DNSResolverStatus::ERROR) { + return DNSResolverStatus::ERROR; + } + return DNSResolverStatus::IDLE; +} + +} // namespace shrpx diff --git a/src/shrpx_dual_dns_resolver.h b/src/shrpx_dual_dns_resolver.h new file mode 100644 index 0000000..98065da --- /dev/null +++ b/src/shrpx_dual_dns_resolver.h @@ -0,0 +1,69 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_DUAL_DNS_RESOLVER_H +#define SHRPX_DUAL_DNS_RESOLVER_H + +#include "shrpx.h" + +#include + +#include "shrpx_dns_resolver.h" + +using namespace nghttp2; + +namespace shrpx { + +// DualDNSResolver performs name resolution for both A and AAAA +// records at the same time. The first successful return (or if we +// have both successful results, prefer to AAAA) is chosen. This is +// wrapper around 2 DNSResolver inside. resolve(), get_status(), and +// how CompleteCb is called have the same semantics with DNSResolver. +class DualDNSResolver { +public: + // |family| controls IP version preference. If |family| == + // AF_UNSPEC, bot A and AAAA lookups are performed. If |family| == + // AF_INET, only A lookup is performed. If |family| == AF_INET6, + // only AAAA lookup is performed. + DualDNSResolver(struct ev_loop *loop, int family); + + // Resolves |host|. |host| must be NULL-terminated string. + int resolve(const StringRef &host); + CompleteCb get_complete_cb() const; + void set_complete_cb(CompleteCb cb); + DNSResolverStatus get_status(Address *result) const; + +private: + // IP version preference. + int family_; + // For A record + DNSResolver resolv4_; + // For AAAA record + DNSResolver resolv6_; + CompleteCb complete_cb_; +}; + +} // namespace shrpx + +#endif // SHRPX_DUAL_DNS_RESOLVER_H diff --git a/src/shrpx_error.h b/src/shrpx_error.h new file mode 100644 index 0000000..c89611f --- /dev/null +++ b/src/shrpx_error.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_ERROR_H +#define SHRPX_ERROR_H + +#include "shrpx.h" + +namespace shrpx { + +// Deprecated, do not use. +enum ErrorCode { + SHRPX_ERR_SUCCESS = 0, + SHRPX_ERR_ERROR = -1, + SHRPX_ERR_NETWORK = -100, + SHRPX_ERR_EOF = -101, + SHRPX_ERR_INPROGRESS = -102, + SHRPX_ERR_DCONN_CANCELED = -103, + SHRPX_ERR_RETRY = -104, + SHRPX_ERR_TLS_REQUIRED = -105, + SHRPX_ERR_SEND_BLOCKED = -106, +}; + +} // namespace shrpx + +#endif // SHRPX_ERROR_H diff --git a/src/shrpx_exec.cc b/src/shrpx_exec.cc new file mode 100644 index 0000000..d5cd091 --- /dev/null +++ b/src/shrpx_exec.cc @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_exec.h" + +#include + +#include "shrpx_signal.h" +#include "shrpx_log.h" +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +// inspired by h2o_read_command function from h2o project: +// https://github.com/h2o/h2o +int exec_read_command(Process &proc, char *const argv[]) { + int rv; + int pfd[2]; + +#ifdef O_CLOEXEC + if (pipe2(pfd, O_CLOEXEC) == -1) { + return -1; + } +#else // !O_CLOEXEC + if (pipe(pfd) == -1) { + return -1; + } + util::make_socket_closeonexec(pfd[0]); + util::make_socket_closeonexec(pfd[1]); +#endif // !O_CLOEXEC + + auto closer = defer([&pfd]() { + if (pfd[0] != -1) { + close(pfd[0]); + } + + if (pfd[1] != -1) { + close(pfd[1]); + } + }); + + sigset_t oldset; + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: errno=" << error; + + return -1; + } + + auto pid = fork(); + + if (pid == 0) { + // This is multithreaded program, and we are allowed to use only + // async-signal-safe functions here. + + // child process + shrpx_signal_unset_worker_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + static constexpr char msg[] = "Unblocking all signals failed\n"; + while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR) + ; + nghttp2_Exit(EXIT_FAILURE); + } + + dup2(pfd[1], 1); + close(pfd[0]); + + rv = execv(argv[0], argv); + if (rv == -1) { + static constexpr char msg[] = "Could not execute command\n"; + while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR) + ; + nghttp2_Exit(EXIT_FAILURE); + } + // unreachable + } + + // parent process + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "Could not execute command: " << argv[0] + << ", fork() failed, errno=" << error; + } + + rv = shrpx_signal_set(&oldset); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring all signals failed: errno=" << error; + + nghttp2_Exit(EXIT_FAILURE); + } + + if (pid == -1) { + return -1; + } + + close(pfd[1]); + pfd[1] = -1; + + util::make_socket_nonblocking(pfd[0]); + + proc.pid = pid; + proc.rfd = pfd[0]; + + pfd[0] = -1; + + return 0; +} + +} // namespace shrpx diff --git a/src/shrpx_exec.h b/src/shrpx_exec.h new file mode 100644 index 0000000..2d8a770 --- /dev/null +++ b/src/shrpx_exec.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_EXEC_H +#define SHRPX_EXEC_H + +#include "unistd.h" + +namespace shrpx { + +struct Process { + pid_t pid; + // fd to read from process + int rfd; +}; + +// Executes command |argv| after forking current process. The command +// should not expect to read from stdin. Parent process can read the +// stdout from command using proc.rfd. On success, this function +// returns 0, and process information is stored in |proc|. Otherwise, +// returns -1. +int exec_read_command(Process &proc, char *const argv[]); + +} // namespace shrpx + +#endif // SHRPX_EXEC_H diff --git a/src/shrpx_health_monitor_downstream_connection.cc b/src/shrpx_health_monitor_downstream_connection.cc new file mode 100644 index 0000000..89e5396 --- /dev/null +++ b/src/shrpx_health_monitor_downstream_connection.cc @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_health_monitor_downstream_connection.h" + +#include "shrpx_client_handler.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +HealthMonitorDownstreamConnection::HealthMonitorDownstreamConnection() {} + +HealthMonitorDownstreamConnection::~HealthMonitorDownstreamConnection() {} + +int HealthMonitorDownstreamConnection::attach_downstream( + Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + + downstream_ = downstream; + + return 0; +} + +void HealthMonitorDownstreamConnection::detach_downstream( + Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + downstream_ = nullptr; +} + +int HealthMonitorDownstreamConnection::push_request_headers() { + downstream_->set_request_header_sent(true); + auto src = downstream_->get_blocked_request_buf(); + auto dest = downstream_->get_request_buf(); + src->remove(*dest); + + return 0; +} + +int HealthMonitorDownstreamConnection::push_upload_data_chunk( + const uint8_t *data, size_t datalen) { + return 0; +} + +int HealthMonitorDownstreamConnection::end_upload_data() { + auto upstream = downstream_->get_upstream(); + auto &resp = downstream_->response(); + + resp.http_status = 200; + + resp.fs.add_header_token(StringRef::from_lit("content-length"), + StringRef::from_lit("0"), false, + http2::HD_CONTENT_LENGTH); + + if (upstream->send_reply(downstream_, nullptr, 0) != 0) { + return -1; + } + + return 0; +} + +void HealthMonitorDownstreamConnection::pause_read(IOCtrlReason reason) {} + +int HealthMonitorDownstreamConnection::resume_read(IOCtrlReason reason, + size_t consumed) { + return 0; +} + +void HealthMonitorDownstreamConnection::force_resume_read() {} + +int HealthMonitorDownstreamConnection::on_read() { return 0; } + +int HealthMonitorDownstreamConnection::on_write() { return 0; } + +void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *upstream) { +} + +bool HealthMonitorDownstreamConnection::poolable() const { return false; } + +const std::shared_ptr & +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/src/shrpx_health_monitor_downstream_connection.h b/src/shrpx_health_monitor_downstream_connection.h new file mode 100644 index 0000000..c0eb633 --- /dev/null +++ b/src/shrpx_health_monitor_downstream_connection.h @@ -0,0 +1,64 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H +#define SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" + +namespace shrpx { + +class Worker; + +class HealthMonitorDownstreamConnection : public DownstreamConnection { +public: + HealthMonitorDownstreamConnection(); + virtual ~HealthMonitorDownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read(); + + virtual int on_read(); + virtual int on_write(); + + virtual void on_upstream_change(Upstream *upstream); + + // true if this object is poolable. + virtual bool poolable() const; + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; +}; + +} // namespace shrpx + +#endif // SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc new file mode 100644 index 0000000..ad32dc9 --- /dev/null +++ b/src/shrpx_http.cc @@ -0,0 +1,280 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http.h" + +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "http2.h" +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace http { + +StringRef create_error_html(BlockAllocator &balloc, unsigned int http_status) { + auto &httpconf = get_config()->http; + + const auto &error_pages = httpconf.error_pages; + for (const auto &page : error_pages) { + if (page.http_status == 0 || page.http_status == http_status) { + return StringRef{std::begin(page.content), std::end(page.content)}; + } + } + + auto status_string = http2::stringify_status(balloc, http_status); + auto reason_phrase = http2::get_reason_phrase(http_status); + + return concat_string_ref( + balloc, StringRef::from_lit(R"()"), + 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/src/shrpx_http.h b/src/shrpx_http.h new file mode 100644 index 0000000..18935d8 --- /dev/null +++ b/src/shrpx_http.h @@ -0,0 +1,96 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP_H +#define SHRPX_HTTP_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc new file mode 100644 index 0000000..ee48799 --- /dev/null +++ b/src/shrpx_http2_downstream_connection.cc @@ -0,0 +1,621 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http2_downstream_connection.h" + +#ifdef HAVE_UNISTD_H +# include +#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 defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto conn = handler->get_connection(); + + if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) { + nva.push_back(http2::make_nv_ll("early-data", "1")); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL + + auto fwd = + fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED); + + if (fwdconf.params) { + auto params = fwdconf.params; + + if (config->http2_proxy || req.regular_connect_method()) { + params &= ~FORWARDED_PROTO; + } + + auto value = http::create_forwarded( + balloc, params, handler->get_forwarded_by(), + handler->get_forwarded_for(), req.authority, req.scheme); + + if (fwd || !value.empty()) { + if (fwd) { + if (value.empty()) { + value = fwd->value; + } else { + value = concat_string_ref(balloc, fwd->value, + StringRef::from_lit(", "), value); + } + } + + nva.push_back(http2::make_nv_ls_nocopy("forwarded", value)); + } + } else if (fwd) { + nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value)); + } + + auto xff = xffconf.strip_incoming ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_FOR); + + if (xffconf.add) { + StringRef xff_value; + const auto &addr = upstream->get_client_handler()->get_ipaddr(); + if (xff) { + xff_value = concat_string_ref(balloc, xff->value, + StringRef::from_lit(", "), addr); + } else { + xff_value = addr; + } + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value)); + } else if (xff) { + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value)); + } + + if (!config->http2_proxy && !req.regular_connect_method()) { + auto xfp = xfpconf.strip_incoming + ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_PROTO); + + if (xfpconf.add) { + StringRef xfp_value; + // We use same protocol with :scheme header field + if (xfp) { + xfp_value = concat_string_ref(balloc, xfp->value, + StringRef::from_lit(", "), req.scheme); + } else { + xfp_value = req.scheme; + } + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp_value)); + } else if (xfp) { + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp->value)); + } + } + + auto via = req.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); + } + } else { + size_t vialen = 16; + if (via) { + vialen += via->value.size() + 2; + } + + auto iov = make_byte_ref(balloc, vialen + 1); + auto p = iov.base; + + if (via) { + p = std::copy(std::begin(via->value), std::end(via->value), p); + p = util::copy_lit(p, ", "); + } + p = http::create_via_header_value(p, req.http_major, req.http_minor); + *p = '\0'; + + nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p})); + } + + auto te = req.fs.header(http2::HD_TE); + // HTTP/1 upstream request can contain keyword other than + // "trailers". We just forward "trailers". + // TODO more strict handling required here. + if (te && http2::contains_trailers(te->value)) { + nva.push_back(http2::make_nv_ll("te", "trailers")); + } + + for (auto &p : httpconf.add_request_headers) { + nva.push_back(http2::make_nv_nocopy(p.name, p.value)); + } + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + if (util::streq_l("authorization", nv.name, nv.namelen)) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST + << ": \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/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h new file mode 100644 index 0000000..0fc7d91 --- /dev/null +++ b/src/shrpx_http2_downstream_connection.h @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H +#define SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc new file mode 100644 index 0000000..f58ed2f --- /dev/null +++ b/src/shrpx_http2_session.cc @@ -0,0 +1,2426 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http2_session.h" + +#include +#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; + + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + + if (!next_proto) { + downstream_failure(addr_, raddr_); + return -1; + } + + auto proto = StringRef{next_proto, next_proto_len}; + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Negotiated next protocol: " << proto; + } + if (!util::check_h2_is_selected(proto)) { + downstream_failure(addr_, raddr_); + return -1; + } + } + + auto config = get_config(); + auto &http2conf = config->http2; + + rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks, + this, http2conf.downstream.option); + + if (rv != 0) { + return -1; + } + + std::array 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/src/shrpx_http2_session.h b/src/shrpx_http2_session.h new file mode 100644 index 0000000..31b2545 --- /dev/null +++ b/src/shrpx_http2_session.h @@ -0,0 +1,296 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP2_SESSION_H +#define SHRPX_HTTP2_SESSION_H + +#include "shrpx.h" + +#include +#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/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc new file mode 100644 index 0000000..c9f8a8c --- /dev/null +++ b/src/shrpx_http2_upstream.cc @@ -0,0 +1,2404 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http2_upstream.h" + +#include +#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/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h new file mode 100644 index 0000000..739c6ff --- /dev/null +++ b/src/shrpx_http2_upstream.h @@ -0,0 +1,150 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP2_UPSTREAM_H +#define SHRPX_HTTP2_UPSTREAM_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc new file mode 100644 index 0000000..f8ae1ce --- /dev/null +++ b/src/shrpx_http3_upstream.cc @@ -0,0 +1,2906 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http3_upstream.h" + +#include +#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->http_shutdown_stream_read(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_shutdown_stream_read(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_shutdown_stream_read: " + << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(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/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h new file mode 100644 index 0000000..89dfc17 --- /dev/null +++ b/src/shrpx_http3_upstream.h @@ -0,0 +1,193 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP3_UPSTREAM_H +#define SHRPX_HTTP3_UPSTREAM_H + +#include "shrpx.h" + +#include +#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); + 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/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc new file mode 100644 index 0000000..4164b8b --- /dev/null +++ b/src/shrpx_http_downstream_connection.cc @@ -0,0 +1,1617 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http_downstream_connection.h" + +#include + +#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 defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto conn = handler->get_connection(); + + if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) { + buf->append("Early-Data: 1\r\n"); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_BORINGSSL + + auto fwd = + fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED); + + if (fwdconf.params) { + auto params = fwdconf.params; + + if (config->http2_proxy || connect_method) { + params &= ~FORWARDED_PROTO; + } + + auto value = http::create_forwarded( + balloc, params, handler->get_forwarded_by(), + handler->get_forwarded_for(), req.authority, req.scheme); + + if (fwd || !value.empty()) { + buf->append("Forwarded: "); + if (fwd) { + buf->append(fwd->value); + + if (!value.empty()) { + buf->append(", "); + } + } + buf->append(value); + buf->append("\r\n"); + } + } else if (fwd) { + buf->append("Forwarded: "); + buf->append(fwd->value); + buf->append("\r\n"); + } + + auto xff = xffconf.strip_incoming ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_FOR); + + if (xffconf.add) { + buf->append("X-Forwarded-For: "); + if (xff) { + buf->append((*xff).value); + buf->append(", "); + } + buf->append(client_handler_->get_ipaddr()); + buf->append("\r\n"); + } else if (xff) { + buf->append("X-Forwarded-For: "); + buf->append((*xff).value); + buf->append("\r\n"); + } + if (!config->http2_proxy && !connect_method) { + auto xfp = xfpconf.strip_incoming + ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_PROTO); + + if (xfpconf.add) { + buf->append("X-Forwarded-Proto: "); + if (xfp) { + buf->append((*xfp).value); + buf->append(", "); + } + assert(!req.scheme.empty()); + buf->append(req.scheme); + buf->append("\r\n"); + } else if (xfp) { + buf->append("X-Forwarded-Proto: "); + buf->append((*xfp).value); + buf->append("\r\n"); + } + } + auto via = req.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + buf->append("Via: "); + buf->append((*via).value); + buf->append("\r\n"); + } + } else { + buf->append("Via: "); + if (via) { + buf->append((*via).value); + buf->append(", "); + } + std::array 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/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h new file mode 100644 index 0000000..a453f0d --- /dev/null +++ b/src/shrpx_http_downstream_connection.h @@ -0,0 +1,124 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP_DOWNSTREAM_CONNECTION_H +#define SHRPX_HTTP_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include "llhttp.h" + +#include "shrpx_downstream_connection.h" +#include "shrpx_io_control.h" +#include "shrpx_connection.h" + +namespace shrpx { + +class DownstreamConnectionPool; +class Worker; +struct DownstreamAddrGroup; +struct DownstreamAddr; +struct DNSQuery; + +class HttpDownstreamConnection : public DownstreamConnection { +public: + HttpDownstreamConnection(const std::shared_ptr &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/src/shrpx_http_test.cc b/src/shrpx_http_test.cc new file mode 100644 index 0000000..3ace870 --- /dev/null +++ b/src/shrpx_http_test.cc @@ -0,0 +1,168 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_http_test.h" + +#ifdef HAVE_UNISTD_H +# include +#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/src/shrpx_http_test.h b/src/shrpx_http_test.h new file mode 100644 index 0000000..d50ab53 --- /dev/null +++ b/src/shrpx_http_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTP_TEST_H +#define SHRPX_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_http_create_forwarded(void); +void test_shrpx_http_create_via_header_value(void); +void test_shrpx_http_create_affinity_cookie(void); +void test_shrpx_http_create_altsvc_header_value(void); +void test_shrpx_http_check_http_scheme(void); + +} // namespace shrpx + +#endif // SHRPX_HTTP_TEST_H diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc new file mode 100644 index 0000000..49d2088 --- /dev/null +++ b/src/shrpx_https_upstream.cc @@ -0,0 +1,1582 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_https_upstream.h" + +#include +#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/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h new file mode 100644 index 0000000..d85d2de --- /dev/null +++ b/src/shrpx_https_upstream.h @@ -0,0 +1,113 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_HTTPS_UPSTREAM_H +#define SHRPX_HTTPS_UPSTREAM_H + +#include "shrpx.h" + +#include +#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/src/shrpx_io_control.cc b/src/shrpx_io_control.cc new file mode 100644 index 0000000..f43a257 --- /dev/null +++ b/src/shrpx_io_control.cc @@ -0,0 +1,66 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_io_control.h" + +#include + +#include "shrpx_rate_limit.h" +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {} + +IOControl::~IOControl() {} + +void IOControl::pause_read(IOCtrlReason reason) { + rdbits_ |= reason; + if (lim_) { + lim_->stopw(); + } +} + +bool IOControl::resume_read(IOCtrlReason reason) { + rdbits_ &= ~reason; + if (rdbits_ == 0) { + if (lim_) { + lim_->startw(); + } + return true; + } + + return false; +} + +void IOControl::force_resume_read() { + rdbits_ = 0; + if (lim_) { + lim_->startw(); + } +} + +} // namespace shrpx diff --git a/src/shrpx_io_control.h b/src/shrpx_io_control.h new file mode 100644 index 0000000..d427fcb --- /dev/null +++ b/src/shrpx_io_control.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_IO_CONTROL_H +#define SHRPX_IO_CONTROL_H + +#include "shrpx.h" + +#include +#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/src/shrpx_live_check.cc b/src/shrpx_live_check.cc new file mode 100644 index 0000000..9b932cd --- /dev/null +++ b/src/shrpx_live_check.cc @@ -0,0 +1,792 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_live_check.h" +#include "shrpx_worker.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_tls.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +constexpr size_t MAX_BUFFER_SIZE = 4_k; +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(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; + + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + + auto proto = StringRef{next_proto, next_proto_len}; + + switch (addr_->proto) { + case Proto::HTTP1: + if (proto.empty() || proto == StringRef::from_lit("http/1.1")) { + break; + } + return -1; + case Proto::HTTP2: + if (util::check_h2_is_selected(proto)) { + // For HTTP/2, we try to read SETTINGS ACK from server to make + // sure it is really alive, and serving HTTP/2. + read_ = &LiveCheck::read_tls; + write_ = &LiveCheck::write_tls; + + if (connection_made() != 0) { + return -1; + } + + return 0; + } + return -1; + default: + break; + } + + on_success(); + + return 0; +} + +int LiveCheck::read_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array 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/src/shrpx_live_check.h b/src/shrpx_live_check.h new file mode 100644 index 0000000..b65ecdc --- /dev/null +++ b/src/shrpx_live_check.h @@ -0,0 +1,125 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_LIVE_CHECK_H +#define SHRPX_LIVE_CHECK_H + +#include "shrpx.h" + +#include +#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/src/shrpx_log.cc b/src/shrpx_log.cc new file mode 100644 index 0000000..de5e09f --- /dev/null +++ b/src/shrpx_log.cc @@ -0,0 +1,1008 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_log.h" + +#ifdef HAVE_SYSLOG_H +# include +#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/src/shrpx_log.h b/src/shrpx_log.h new file mode 100644 index 0000000..bc30097 --- /dev/null +++ b/src/shrpx_log.h @@ -0,0 +1,318 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_LOG_H +#define SHRPX_LOG_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_log_config.cc b/src/shrpx_log_config.cc new file mode 100644 index 0000000..92eb055 --- /dev/null +++ b/src/shrpx_log_config.cc @@ -0,0 +1,127 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_log_config.h" + +#include + +#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/src/shrpx_log_config.h b/src/shrpx_log_config.h new file mode 100644 index 0000000..76fa78b --- /dev/null +++ b/src/shrpx_log_config.h @@ -0,0 +1,79 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_LOG_CONFIG_H +#define SHRPX_LOG_CONFIG_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_memcached_connection.cc b/src/shrpx_memcached_connection.cc new file mode 100644 index 0000000..f72cb11 --- /dev/null +++ b/src/shrpx_memcached_connection.cc @@ -0,0 +1,777 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_memcached_connection.h" + +#include +#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/src/shrpx_memcached_connection.h b/src/shrpx_memcached_connection.h new file mode 100644 index 0000000..2e097e1 --- /dev/null +++ b/src/shrpx_memcached_connection.h @@ -0,0 +1,155 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MEMCACHED_CONNECTION_H +#define SHRPX_MEMCACHED_CONNECTION_H + +#include "shrpx.h" + +#include +#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/src/shrpx_memcached_dispatcher.cc b/src/shrpx_memcached_dispatcher.cc new file mode 100644 index 0000000..024bd5a --- /dev/null +++ b/src/shrpx_memcached_dispatcher.cc @@ -0,0 +1,53 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_memcached_dispatcher.h" + +#include "shrpx_memcached_request.h" +#include "shrpx_memcached_connection.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +MemcachedDispatcher::MemcachedDispatcher(const Address *addr, + struct ev_loop *loop, SSL_CTX *ssl_ctx, + const StringRef &sni_name, + MemchunkPool *mcpool, + std::mt19937 &gen) + : loop_(loop), + mconn_(std::make_unique(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/src/shrpx_memcached_dispatcher.h b/src/shrpx_memcached_dispatcher.h new file mode 100644 index 0000000..de1af80 --- /dev/null +++ b/src/shrpx_memcached_dispatcher.h @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MEMCACHED_DISPATCHER_H +#define SHRPX_MEMCACHED_DISPATCHER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_memcached_request.h b/src/shrpx_memcached_request.h new file mode 100644 index 0000000..3696983 --- /dev/null +++ b/src/shrpx_memcached_request.h @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MEMCACHED_REQUEST_H +#define SHRPX_MEMCACHED_REQUEST_H + +#include "shrpx.h" + +#include +#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/src/shrpx_memcached_result.h b/src/shrpx_memcached_result.h new file mode 100644 index 0000000..60a87af --- /dev/null +++ b/src/shrpx_memcached_result.h @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MEMCACHED_RESULT_H +#define SHRPX_MEMCACHED_RESULT_H + +#include "shrpx.h" + +#include + +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/src/shrpx_mruby.cc b/src/shrpx_mruby.cc new file mode 100644 index 0000000..b5c6ed3 --- /dev/null +++ b/src/shrpx_mruby.cc @@ -0,0 +1,238 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_mruby.h" + +#include +#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/src/shrpx_mruby.h b/src/shrpx_mruby.h new file mode 100644 index 0000000..518063d --- /dev/null +++ b/src/shrpx_mruby.h @@ -0,0 +1,89 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MRUBY_H +#define SHRPX_MRUBY_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_mruby_module.cc b/src/shrpx_mruby_module.cc new file mode 100644 index 0000000..27b7769 --- /dev/null +++ b/src/shrpx_mruby_module.cc @@ -0,0 +1,113 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_mruby_module.h" + +#include + +#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/src/shrpx_mruby_module.h b/src/shrpx_mruby_module.h new file mode 100644 index 0000000..a426bea --- /dev/null +++ b/src/shrpx_mruby_module.h @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MRUBY_MODULE_H +#define SHRPX_MRUBY_MODULE_H + +#include "shrpx.h" + +#include + +#include "http2.h" + +using namespace nghttp2; + +namespace shrpx { + +class Downstream; + +namespace mruby { + +mrb_value init_module(mrb_state *mrb); + +void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream); + +mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_H diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc new file mode 100644 index 0000000..5ebd9c0 --- /dev/null +++ b/src/shrpx_mruby_module_env.cc @@ -0,0 +1,500 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_mruby_module_env.h" + +#include +#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/src/shrpx_mruby_module_env.h b/src/shrpx_mruby_module_env.h new file mode 100644 index 0000000..0884678 --- /dev/null +++ b/src/shrpx_mruby_module_env.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MRUBY_MODULE_ENV_H +#define SHRPX_MRUBY_MODULE_ENV_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_env_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_ENV_H diff --git a/src/shrpx_mruby_module_request.cc b/src/shrpx_mruby_module_request.cc new file mode 100644 index 0000000..1ed3aa9 --- /dev/null +++ b/src/shrpx_mruby_module_request.cc @@ -0,0 +1,367 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_mruby_module_request.h" + +#include +#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/src/shrpx_mruby_module_request.h b/src/shrpx_mruby_module_request.h new file mode 100644 index 0000000..e74f335 --- /dev/null +++ b/src/shrpx_mruby_module_request.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MRUBY_MODULE_REQUEST_H +#define SHRPX_MRUBY_MODULE_REQUEST_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_request_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_REQUEST_H diff --git a/src/shrpx_mruby_module_response.cc b/src/shrpx_mruby_module_response.cc new file mode 100644 index 0000000..1de1d5f --- /dev/null +++ b/src/shrpx_mruby_module_response.cc @@ -0,0 +1,398 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_mruby_module_response.h" + +#include +#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/src/shrpx_mruby_module_response.h b/src/shrpx_mruby_module_response.h new file mode 100644 index 0000000..a35b42b --- /dev/null +++ b/src/shrpx_mruby_module_response.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_MRUBY_MODULE_RESPONSE_H +#define SHRPX_MRUBY_MODULE_RESPONSE_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_response_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_RESPONSE_H diff --git a/src/shrpx_null_downstream_connection.cc b/src/shrpx_null_downstream_connection.cc new file mode 100644 index 0000000..cd81c8a --- /dev/null +++ b/src/shrpx_null_downstream_connection.cc @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_null_downstream_connection.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +NullDownstreamConnection::NullDownstreamConnection( + const std::shared_ptr &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/src/shrpx_null_downstream_connection.h b/src/shrpx_null_downstream_connection.h new file mode 100644 index 0000000..7defcc3 --- /dev/null +++ b/src/shrpx_null_downstream_connection.h @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_NULL_DOWNSTREAM_CONNECTION_H +#define SHRPX_NULL_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class NullDownstreamConnection : public DownstreamConnection { +public: + NullDownstreamConnection(const std::shared_ptr &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/src/shrpx_process.h b/src/shrpx_process.h new file mode 100644 index 0000000..d35461b --- /dev/null +++ b/src/shrpx_process.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_PROCESS_H +#define SHRPX_PROCESS_H + +#include "shrpx.h" + +namespace shrpx { + +constexpr uint8_t SHRPX_IPC_REOPEN_LOG = 1; +constexpr uint8_t SHRPX_IPC_GRACEFUL_SHUTDOWN = 2; + +} // namespace shrpx + +#endif // SHRPX_PROCESS_H diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc new file mode 100644 index 0000000..2d4de59 --- /dev/null +++ b/src/shrpx_quic.cc @@ -0,0 +1,393 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic.h" + +#include +#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/src/shrpx_quic.h b/src/shrpx_quic.h new file mode 100644 index 0000000..b2f6087 --- /dev/null +++ b/src/shrpx_quic.h @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_H +#define SHRPX_QUIC_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc new file mode 100644 index 0000000..6287971 --- /dev/null +++ b/src/shrpx_quic_connection_handler.cc @@ -0,0 +1,761 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic_connection_handler.h" + +#include + +#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; + } + +#ifdef NGHTTP2_GENUINE_OPENSSL + assert(SSL_is_quic(ssl)); +#endif // NGHTTP2_GENUINE_OPENSSL + + SSL_set_accept_state(ssl); + + auto config = get_config(); + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data) { +#ifdef NGHTTP2_GENUINE_OPENSSL + SSL_set_quic_early_data_enabled(ssl, 1); +#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + SSL_set_early_data_enabled(ssl, 1); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + } + + // Disable TLS session ticket if we don't have working ticket + // keys. + if (!worker_->get_ticket_keys()) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + + auto handler = std::make_unique( + 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/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h new file mode 100644 index 0000000..29e73a4 --- /dev/null +++ b/src/shrpx_quic_connection_handler.h @@ -0,0 +1,142 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_CONNECTION_HANDLER_H +#define SHRPX_QUIC_CONNECTION_HANDLER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_quic_listener.cc b/src/shrpx_quic_listener.cc new file mode 100644 index 0000000..9b9f120 --- /dev/null +++ b/src/shrpx_quic_listener.cc @@ -0,0 +1,132 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_quic_listener.h" +#include "shrpx_worker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revent) { + auto l = static_cast(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/src/shrpx_quic_listener.h b/src/shrpx_quic_listener.h new file mode 100644 index 0000000..3d70921 --- /dev/null +++ b/src/shrpx_quic_listener.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_QUIC_LISTENER_H +#define SHRPX_QUIC_LISTENER_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +struct UpstreamAddr; +class Worker; + +class QUICListener { +public: + QUICListener(const UpstreamAddr *faddr, Worker *worker); + ~QUICListener(); + void on_read(); + +private: + const UpstreamAddr *faddr_; + Worker *worker_; + ev_io rev_; +}; + +} // namespace shrpx + +#endif // SHRPX_QUIC_LISTENER_H diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc new file mode 100644 index 0000000..0d4f921 --- /dev/null +++ b/src/shrpx_rate_limit.cc @@ -0,0 +1,123 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_rate_limit.h" + +#include + +#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/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h new file mode 100644 index 0000000..7502a27 --- /dev/null +++ b/src/shrpx_rate_limit.h @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_RATE_LIMIT_H +#define SHRPX_RATE_LIMIT_H + +#include "shrpx.h" + +#include + +#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/src/shrpx_router.cc b/src/shrpx_router.cc new file mode 100644 index 0000000..d3565db --- /dev/null +++ b/src/shrpx_router.cc @@ -0,0 +1,420 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_router.h" + +#include + +#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/src/shrpx_router.h b/src/shrpx_router.h new file mode 100644 index 0000000..295db7e --- /dev/null +++ b/src/shrpx_router.h @@ -0,0 +1,110 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_ROUTER_H +#define SHRPX_ROUTER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_router_test.cc b/src/shrpx_router_test.cc new file mode 100644 index 0000000..21c2f51 --- /dev/null +++ b/src/shrpx_router_test.cc @@ -0,0 +1,184 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_router_test.h" + +#include + +#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/src/shrpx_router_test.h b/src/shrpx_router_test.h new file mode 100644 index 0000000..d39cb87 --- /dev/null +++ b/src/shrpx_router_test.h @@ -0,0 +1,40 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_ROUTER_TEST_H +#define SHRPX_ROUTER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_router_match(void); +void test_shrpx_router_match_wildcard(void); +void test_shrpx_router_match_prefix(void); + +} // namespace shrpx + +#endif // SHRPX_ROUTER_TEST_H diff --git a/src/shrpx_signal.cc b/src/shrpx_signal.cc new file mode 100644 index 0000000..63fcc07 --- /dev/null +++ b/src/shrpx_signal.cc @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_signal.h" + +#include + +#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/src/shrpx_signal.h b/src/shrpx_signal.h new file mode 100644 index 0000000..152ca36 --- /dev/null +++ b/src/shrpx_signal.h @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_SIGNAL_H +#define SHRPX_SIGNAL_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +constexpr int REOPEN_LOG_SIGNAL = SIGUSR1; +constexpr int EXEC_BINARY_SIGNAL = SIGUSR2; +constexpr int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT; +constexpr int RELOAD_SIGNAL = SIGHUP; + +// Blocks all signals. The previous signal mask is stored into +// |oldset| if it is not nullptr. This function returns 0 if it +// succeeds, or -1. The errno will indicate the error. +int shrpx_signal_block_all(sigset_t *oldset); + +// Unblocks all signals. This function returns 0 if it succeeds, or +// -1. The errno will indicate the error. +int shrpx_signal_unblock_all(); + +// Sets signal mask |set|. This function returns 0 if it succeeds, or +// -1. The errno will indicate the error. +int shrpx_signal_set(sigset_t *set); + +int shrpx_signal_set_main_proc_ign_handler(); +int shrpx_signal_unset_main_proc_ign_handler(); + +int shrpx_signal_set_worker_proc_ign_handler(); +int shrpx_signal_unset_worker_proc_ign_handler(); + +} // namespace shrpx + +#endif // SHRPX_SIGNAL_H diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc new file mode 100644 index 0000000..aa0c9f2 --- /dev/null +++ b/src/shrpx_tls.cc @@ -0,0 +1,2465 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_tls.h" + +#ifdef HAVE_SYS_SOCKET_H +# include +#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 { + +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()); + +#ifdef NGHTTP2_GENUINE_OPENSSL + auto num_sigalgs = + SSL_get_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + + for (idx = 0; idx < num_sigalgs; ++idx) { + int signhash; + + SSL_get_sigalgs(ssl, idx, nullptr, nullptr, &signhash, nullptr, nullptr); + switch (signhash) { + case NID_ecdsa_with_SHA256: + case NID_ecdsa_with_SHA384: + case NID_ecdsa_with_SHA512: + break; + default: + continue; + } + + break; + } + + if (idx == num_sigalgs) { + SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]); + + return SSL_TLSEXT_ERR_OK; + } + + auto num_shared_curves = SSL_get_shared_curve(ssl, -1); + + for (auto i = 0; i < num_shared_curves; ++i) { + auto shared_curve = SSL_get_shared_curve(ssl, i); +# if OPENSSL_3_0_0_API + // It looks like only short name is defined in OpenSSL. No idea + // which one to use because it is unknown that which one + // EVP_PKEY_get_utf8_string_param("group") returns. + auto shared_curve_name = OBJ_nid2sn(shared_curve); + if (shared_curve_name == nullptr) { + continue; + } +# endif // OPENSSL_3_0_0_API + + for (auto ssl_ctx : ssl_ctx_list) { + auto cert = SSL_CTX_get0_certificate(ssl_ctx); + auto pubkey = X509_get0_pubkey(cert); + + if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) { + continue; + } + +# if OPENSSL_3_0_0_API + std::array curve_name; + if (!EVP_PKEY_get_utf8_string_param(pubkey, "group", curve_name.data(), + curve_name.size(), nullptr)) { + continue; + } + + if (strcmp(shared_curve_name, curve_name.data()) == 0) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } +# else // !OPENSSL_3_0_0_API + auto eckey = EVP_PKEY_get0_EC_KEY(pubkey); + if (eckey == nullptr) { + continue; + } + + auto ecgroup = EC_KEY_get0_group(eckey); + auto cert_curve = EC_GROUP_get_curve_name(ecgroup); + + if (shared_curve == cert_curve) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } +# endif // !OPENSSL_3_0_0_API + } + } +#endif // NGHTTP2_GENUINE_OPENSSL + + SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]); + + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL +namespace { +std::shared_ptr> +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, const unsigned char *id, 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 + +namespace { +int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + // We assume that get_config()->alpn_list contains ALPN protocol + // identifier sorted by preference order. So we just break when we + // found the first overlap. + for (const auto &target_proto_id : get_config()->tls.alpn_list) { + for (auto p = in, end = in + inlen; p < end;) { + auto proto_id = p + 1; + auto proto_len = *p; + + if (proto_id + proto_len <= end && + util::streq(target_proto_id, StringRef{proto_id, proto_len})) { + + *out = reinterpret_cast(proto_id); + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + constexpr StringRef alpnlist[] = { + StringRef::from_lit("h3"), + StringRef::from_lit("h3-29"), + }; + + for (auto &alpn : alpnlist) { + for (auto p = in, end = in + inlen; p < end;) { + auto proto_id = p + 1; + auto proto_len = *p; + + if (alpn.size() == proto_len && + memcmp(alpn.byte(), proto_id, alpn.size()) == 0) { + *out = proto_id; + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace +#endif // ENABLE_HTTP3 + +#ifdef NGHTTP2_GENUINE_OPENSSL +namespace { +int sct_add_cb(SSL *ssl, unsigned int ext_type, unsigned int context, + const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) { + assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp); + + auto conn = static_cast(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 + +#endif // NGHTTP2_GENUINE_OPENSSL + +#ifndef OPENSSL_NO_PSK +namespace { +unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk, + unsigned int max_psk_len) { + auto config = get_config(); + auto &tlsconf = config->tls; + + auto it = tlsconf.psk_secrets.find(StringRef{identity}); + if (it == std::end(tlsconf.psk_secrets)) { + return 0; + } + + auto &secret = (*it).second; + if (secret.size() > max_psk_len) { + LOG(ERROR) << "The size of PSK secret is " << secret.size() + << ", but the acceptable maximum size is" << max_psk_len; + return 0; + } + + std::copy(std::begin(secret), std::end(secret), psk); + + return static_cast(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 +#ifdef NGHTTP2_GENUINE_OPENSSL + // The reason for disabling built-in anti-replay in + // OpenSSL is that it only works if client gets back + // to the same server. The freshness check + // described in + // https://tools.ietf.org/html/rfc8446#section-8.3 + // is still performed. + | SSL_OP_NO_ANTI_REPLAY +#endif // NGHTTP2_GENUINE_OPENSSL + ; + + auto config = mod_config(); + auto &tlsconf = config->tls; + +#ifdef SSL_OP_ENABLE_KTLS + if (tlsconf.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version"; + DIE(); + } + + const unsigned char sid_ctx[] = "shrpx"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); + + if (!tlsconf.session_cache.memcached.host.empty()) { + SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb); + SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb); + } + + SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count()); + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL + +#ifndef OPENSSL_NO_EC + if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { + LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves + << " failed"; + DIE(); + } +#endif // OPENSSL_NO_EC + + if (!tlsconf.dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + LOG(FATAL) << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + LOG(FATAL) << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (dh == nullptr) { + LOG(FATAL) << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +#endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!tlsconf.cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(), + nullptr) != 1) { + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.private_key_passwd.empty()) { + SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); + } + +#ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); + DIE(); + } +#endif // HAVE_NEVERBLEED + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (tlsconf.client_verify.enabled) { + if (!tlsconf.client_verify.cacert.empty()) { + if (SSL_CTX_load_verify_locations( + ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + // It is heard that SSL_CTX_load_verify_locations() may leave + // error even though it returns success. See + // http://forum.nginx.org/read.php?29,242540 + ERR_clear_error(); + auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str()); + if (!list) { + LOG(FATAL) << "Could not load ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_client_CA_list(ssl_ctx, list); + } + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); +#if OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb); +#else // !OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); +#endif // !OPENSSL_3_0_0_API +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_info_callback(ssl_ctx, info_callback); + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_early_data_enabled(ssl_ctx, 1); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr); + + auto tls_ctx_data = new TLSContextData(); + tls_ctx_data->cert_file = cert_file; + tls_ctx_data->sct_data = sct_data; + + SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data); + +#ifdef NGHTTP2_GENUINE_OPENSSL + // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) + // returns 1, which means OpenSSL internally handles it. But + // OpenSSL handles signed_certificate_timestamp extension specially, + // and it lets custom handler to process the extension. + if (!sct_data.empty()) { + // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is + // required here. sct_parse_cb is called without + // SSL_EXT_CLIENT_HELLO being set. But the passed context value + // is SSL_EXT_CLIENT_HELLO. + if (SSL_CTX_add_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | + SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION, + sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } +#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls_ctx_data->sct_data.empty() && + SSL_CTX_set_signed_cert_timestamp_list( + ssl_ctx, tls_ctx_data->sct_data.data(), + tls_ctx_data->sct_data.size()) != 1) { + LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +#ifdef NGHTTP2_GENUINE_OPENSSL + if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_set_recv_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) { + LOG(FATAL) << "SSL_CTX_set_recv_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // NGHTTP2_GENUINE_OPENSSL + +#ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); +#endif // !LIBRESSL_NO_PSK + + return ssl_ctx; +} + +#ifdef ENABLE_HTTP3 +SSL_CTX *create_quic_ssl_context(const char *private_key_file, + const char *cert_file, + const std::vector &sct_data +# ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +# endif // HAVE_NEVERBLEED +) { + auto ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + constexpr auto ssl_opts = + (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | + SSL_OP_SINGLE_DH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE +# ifdef NGHTTP2_GENUINE_OPENSSL + // The reason for disabling built-in anti-replay in OpenSSL is + // that it only works if client gets back to the same server. + // The freshness check described in + // https://tools.ietf.org/html/rfc8446#section-8.3 is still + // performed. + | SSL_OP_NO_ANTI_REPLAY +# endif // NGHTTP2_GENUINE_OPENSSL + ; + + auto config = mod_config(); + auto &tlsconf = config->tls; + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS + if (ngtcp2_crypto_quictls_configure_server_context(ssl_ctx) != 0) { + LOG(FATAL) << "ngtcp2_crypto_quictls_configure_server_context failed"; + DIE(); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx) != 0) { + LOG(FATAL) << "ngtcp2_crypto_boringssl_configure_server_context failed"; + DIE(); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + + const unsigned char sid_ctx[] = "shrpx"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF); + + SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count()); + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +# if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL + +# ifndef OPENSSL_NO_EC + if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { + LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves + << " failed"; + DIE(); + } +# endif // OPENSSL_NO_EC + + if (!tlsconf.dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + LOG(FATAL) << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + LOG(FATAL) << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (dh == nullptr) { + LOG(FATAL) << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +# endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!tlsconf.cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(), + nullptr) != 1) { + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.private_key_passwd.empty()) { + SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); + } + +# ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); + DIE(); + } +# endif // HAVE_NEVERBLEED + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (tlsconf.client_verify.enabled) { + if (!tlsconf.client_verify.cacert.empty()) { + if (SSL_CTX_load_verify_locations( + ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + // It is heard that SSL_CTX_load_verify_locations() may leave + // error even though it returns success. See + // http://forum.nginx.org/read.php?29,242540 + ERR_clear_error(); + auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str()); + if (!list) { + LOG(FATAL) << "Could not load ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_client_CA_list(ssl_ctx, list); + } + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); +# if OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb); +# else // !OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); +# endif // !OPENSSL_3_0_0_API +# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); +# endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); + + auto tls_ctx_data = new TLSContextData(); + tls_ctx_data->cert_file = cert_file; + tls_ctx_data->sct_data = sct_data; + + SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data); + +# ifdef NGHTTP2_GENUINE_OPENSSL + // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) + // returns 1, which means OpenSSL internally handles it. But + // OpenSSL handles signed_certificate_timestamp extension specially, + // and it lets custom handler to process the extension. + if (!sct_data.empty()) { + // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is + // required here. sct_parse_cb is called without + // SSL_EXT_CLIENT_HELLO being set. But the passed context value + // is SSL_EXT_CLIENT_HELLO. + if (SSL_CTX_add_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | + SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION, + sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } +# elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls_ctx_data->sct_data.empty() && + SSL_CTX_set_signed_cert_timestamp_list( + ssl_ctx, tls_ctx_data->sct_data.data(), + tls_ctx_data->sct_data.size()) != 1) { + LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +# ifdef NGHTTP2_GENUINE_OPENSSL + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data && + SSL_CTX_set_max_early_data(ssl_ctx, + std::numeric_limits::max()) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // NGHTTP2_GENUINE_OPENSSL + +# ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); +# endif // !LIBRESSL_NO_PSK + + return ssl_ctx; +} +#endif // ENABLE_HTTP3 + +SSL_CTX *create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +#endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file) { + auto ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + + auto &tlsconf = get_config()->tls; + +#ifdef SSL_OP_ENABLE_KTLS + if (tlsconf.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL_STORE); + SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_client_new_cb); + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version"; + DIE(); + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) == + 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // NGHTTP2_GENUINE_OPENSSL || NGHTTP2_OPENSSL_IS_LIBRESSL + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " << cacert + << ": " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.insecure) { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); + } + + if (!cert_file.empty()) { + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file.c_str()) != 1) { + + LOG(FATAL) << "Could not load client certificate from " << cert_file + << ": " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!private_key_file.empty()) { +#ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file.c_str(), + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "Could not load client private key from " + << private_key_file << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file.c_str(), + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file: could not load client " + "private key from " + << private_key_file << ": " << errbuf.data(); + DIE(); + } +#endif // HAVE_NEVERBLEED + } + +#ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_client_callback(ssl_ctx, psk_client_cb); +#endif // !OPENSSL_NO_PSK + + return ssl_ctx; +} + +SSL *create_ssl(SSL_CTX *ssl_ctx) { + auto ssl = SSL_new(ssl_ctx); + if (!ssl) { + LOG(ERROR) << "SSL_new() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return nullptr; + } + + return ssl; +} + +ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, + int addrlen, const UpstreamAddr *faddr) { + std::array 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; + + auto cert = SSL_CTX_get0_certificate(ssl_ctx); + 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); +} + +void setup_downstream_http2_alpn(SSL *ssl) { + // ALPN advertisement + auto alpn = util::get_default_alpn(); + SSL_set_alpn_protos(ssl, alpn.data(), alpn.size()); +} + +void setup_downstream_http1_alpn(SSL *ssl) { + // ALPN advertisement + SSL_set_alpn_protos(ssl, NGHTTP2_H1_1_ALPN.byte(), NGHTTP2_H1_1_ALPN.size()); +} + +std::unique_ptr 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) { +#ifndef OPENSSL_NO_OCSP + int rv; + + STACK_OF(X509) * chain_certs; + SSL_CTX_get0_chain_certs(ssl_ctx, &chain_certs); + + auto resp = d2i_OCSP_RESPONSE(nullptr, &ocsp_resp, ocsp_resplen); + if (resp == nullptr) { + LOG(ERROR) << "d2i_OCSP_RESPONSE failed"; + return -1; + } + auto resp_deleter = defer(OCSP_RESPONSE_free, resp); + + if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + LOG(ERROR) << "OCSP response status is not successful"; + return -1; + } + + ERR_clear_error(); + + auto bs = OCSP_response_get1_basic(resp); + if (bs == nullptr) { + LOG(ERROR) << "OCSP_response_get1_basic failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + auto bs_deleter = defer(OCSP_BASICRESP_free, bs); + + auto store = SSL_CTX_get_cert_store(ssl_ctx); + + ERR_clear_error(); + + rv = OCSP_basic_verify(bs, chain_certs, store, 0); + + if (rv != 1) { + LOG(ERROR) << "OCSP_basic_verify failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + + auto sresp = OCSP_resp_get0(bs, 0); + if (sresp == nullptr) { + LOG(ERROR) << "OCSP response verification failed: no single response"; + return -1; + } + + auto certid = OCSP_SINGLERESP_get0_id(sresp); + assert(certid != nullptr); + + ASN1_INTEGER *serial; + rv = OCSP_id_get0_info(nullptr, nullptr, nullptr, &serial, + const_cast(certid)); + if (rv != 1) { + LOG(ERROR) << "OCSP_id_get0_info failed"; + return -1; + } + + if (serial == nullptr) { + LOG(ERROR) << "OCSP response does not contain serial number"; + return -1; + } + + auto cert = SSL_CTX_get0_certificate(ssl_ctx); + auto cert_serial = X509_get_serialNumber(cert); + + if (ASN1_INTEGER_cmp(cert_serial, serial)) { + LOG(ERROR) << "OCSP verification serial numbers do not match"; + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "OCSP verification succeeded"; + } +#endif // !OPENSSL_NO_OCSP + + return 0; +} + +ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x, + const EVP_MD *md) { + unsigned int len = dstlen; + if (X509_digest(x, md, dst, &len) != 1) { + return -1; + } + return len; +} + +namespace { +StringRef get_x509_name(BlockAllocator &balloc, X509_NAME *nm) { + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return StringRef{}; + } + + auto b_deleter = defer(BIO_free, b); + + // Not documented, but it seems that X509_NAME_print_ex returns the + // number of bytes written into b. + auto slen = X509_NAME_print_ex(b, nm, 0, XN_FLAG_RFC2253); + if (slen <= 0) { + return StringRef{}; + } + + auto iov = make_byte_ref(balloc, slen + 1); + BIO_read(b, iov.base, slen); + iov.base[slen] = '\0'; + return StringRef{iov.base, static_cast(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 defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + struct tm tm; + rv = ASN1_TIME_to_tm(at, &tm); + if (rv != 1) { + return -1; + } + + t = nghttp2_timegm(&tm); +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return -1; + } + + auto bio_deleter = defer(BIO_free, b); + + rv = ASN1_TIME_print(b, at); + if (rv != 1) { + return -1; + } + +# ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + char *s; +# else + unsigned char *s; +# endif + auto slen = BIO_get_mem_data(b, &s); + auto tt = util::parse_openssl_asn1_time_print( + StringRef{s, static_cast(slen)}); + if (tt == 0) { + return -1; + } + + t = tt; +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL + + return 0; +} +} // namespace + +int get_x509_not_before(time_t &t, X509 *x) { + auto at = X509_get0_notBefore(x); + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +int get_x509_not_after(time_t &t, X509 *x) { + auto at = X509_get0_notAfter(x); + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +} // namespace tls + +} // namespace shrpx diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h new file mode 100644 index 0000000..f740507 --- /dev/null +++ b/src/shrpx_tls.h @@ -0,0 +1,321 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_TLS_H +#define SHRPX_TLS_H + +#include "shrpx.h" + +#include +#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. +SSL_CTX *create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +#endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file); + +ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, + int addrlen, const UpstreamAddr *faddr); + +// Check peer's certificate against given |address| and |host|. +int check_cert(SSL *ssl, const Address *addr, const StringRef &host); +// Check peer's certificate against given host name described in +// |addr| and numeric address in |raddr|. Note that |raddr| might not +// point to &addr->addr. +int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr); + +// Verify |cert| using numeric IP address. |hostname| and |addr| +// should contain the same numeric IP address. This function returns +// 0 if it succeeds, or -1. +int verify_numeric_hostname(X509 *cert, const StringRef &hostname, + const Address *addr); + +// Verify |cert| using DNS name hostname. This function returns 0 if +// it succeeds, or -1. +int verify_dns_hostname(X509 *cert, const StringRef &hostname); + +struct WildcardRevPrefix { + WildcardRevPrefix(const StringRef &prefix, size_t idx) + : prefix(std::begin(prefix), std::end(prefix)), idx(idx) {} + + // "Prefix" of wildcard pattern. It is reversed from original form. + // For example, if the original wildcard is "test*.nghttp2.org", + // prefix would be "tset". + ImmutableString prefix; + // The index of SSL_CTX. See ConnectionHandler::get_ssl_ctx(). + size_t idx; +}; + +struct WildcardPattern { + // Wildcard host sharing only suffix is probably rare, so we just do + // linear search. + std::vector 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/src/shrpx_tls_test.cc b/src/shrpx_tls_test.cc new file mode 100644 index 0000000..02fb168 --- /dev/null +++ b/src/shrpx_tls_test.cc @@ -0,0 +1,339 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_tls_test.h" + +#include + +#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/src/shrpx_tls_test.h b/src/shrpx_tls_test.h new file mode 100644 index 0000000..7edc742 --- /dev/null +++ b/src/shrpx_tls_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_TLS_TEST_H +#define SHRPX_TLS_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_tls_create_lookup_tree(void); +void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void); +void test_shrpx_tls_tls_hostname_match(void); +void test_shrpx_tls_verify_numeric_hostname(void); +void test_shrpx_tls_verify_dns_hostname(void); + +} // namespace shrpx + +#endif // SHRPX_TLS_TEST_H diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h new file mode 100644 index 0000000..3da62c9 --- /dev/null +++ b/src/shrpx_upstream.h @@ -0,0 +1,112 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_UPSTREAM_H +#define SHRPX_UPSTREAM_H + +#include "shrpx.h" +#include "shrpx_io_control.h" +#include "memchunk.h" + +using namespace nghttp2; + +namespace shrpx { + +class ClientHandler; +class Downstream; +class DownstreamConnection; + +class Upstream { +public: + virtual ~Upstream() {} + virtual int on_read() = 0; + virtual int on_write() = 0; + virtual int on_timeout(Downstream *downstream) { return 0; }; + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) = 0; + // Called when the current request is aborted without forwarding it + // to backend, and it should be redirected to https URI. + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0; + virtual int downstream_read(DownstreamConnection *dconn) = 0; + virtual int downstream_write(DownstreamConnection *dconn) = 0; + virtual int downstream_eof(DownstreamConnection *dconn) = 0; + virtual int downstream_error(DownstreamConnection *dconn, int events) = 0; + virtual ClientHandler *get_client_handler() const = 0; + + virtual int on_downstream_header_complete(Downstream *downstream) = 0; + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush) = 0; + virtual int on_downstream_body_complete(Downstream *downstream) = 0; + + virtual void on_handler_delete() = 0; + // Called when downstream connection for |downstream| is reset. + // Currently this is only used by Http2Session. If |no_retry| is + // true, another connection attempt using new DownstreamConnection + // is not allowed. + virtual int on_downstream_reset(Downstream *downstream, bool no_retry) = 0; + + virtual void pause_read(IOCtrlReason reason) = 0; + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) = 0; + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) = 0; + + // Starts server push. The |downstream| is an associated stream for + // the pushed resource. This function returns 0 if it succeeds, + // otherwise -1. + virtual int initiate_push(Downstream *downstream, const StringRef &uri) = 0; + + // Fills response data in |iov| whose capacity is |iovcnt|. Returns + // the number of iovs filled. + virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0; + virtual void response_drain(size_t n) = 0; + virtual bool response_empty() const = 0; + + // Called when PUSH_PROMISE was started in downstream. The + // associated downstream is given as |downstream|. The promised + // stream ID is given as |promised_stream_id|. If upstream supports + // server push for the corresponding upstream connection, it should + // return Downstream object for pushed stream. Otherwise, returns + // nullptr. + virtual Downstream * + on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) = 0; + // Called when PUSH_PROMISE frame was completely received in + // downstream. The associated downstream is given as |downstream|. + // This function returns 0 if it succeeds, or -1. + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream) = 0; + // Returns true if server push is enabled in upstream connection. + virtual bool push_enabled() const = 0; + // Cancels promised downstream. This function is called when + // PUSH_PROMISE for |promised_downstream| is not submitted to + // upstream session. + virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0; +}; + +} // namespace shrpx + +#endif // SHRPX_UPSTREAM_H diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc new file mode 100644 index 0000000..4c069db --- /dev/null +++ b/src/shrpx_worker.cc @@ -0,0 +1,1347 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_worker.h" + +#ifdef HAVE_UNISTD_H +# include +#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 = std::move(shared_addr); + + addr_groups_indexer.emplace(std::move(dkey), i); + } else { + auto &g = *(std::begin(downstream_addr_groups_) + (*it).second); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << dst->pattern << " shares the same backend group with " + << g->pattern; + } + dst->shared_addr = g->shared_addr; + } + } +} + +Worker::~Worker() { + ev_async_stop(loop_, &w_); + ev_timer_stop(loop_, &mcpool_clear_timer_); + ev_timer_stop(loop_, &proc_wev_timer_); +} + +void Worker::schedule_clear_mcpool() { + // libev manual says: "If the watcher is already active nothing will + // happen." Since we don't change any timeout here, we don't have + // to worry about querying ev_is_active. + ev_timer_start(loop_, &mcpool_clear_timer_); +} + +void Worker::wait() { +#ifndef NOTHREADS + fut_.get(); +#endif // !NOTHREADS +} + +void Worker::run_async() { +#ifndef NOTHREADS + fut_ = std::async(std::launch::async, [this] { + (void)reopen_log_files(get_config()->logging); + ev_run(loop_); + delete_log_config(); + }); +#endif // !NOTHREADS +} + +void Worker::send(WorkerEvent event) { + { + std::lock_guard 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/src/shrpx_worker.h b/src/shrpx_worker.h new file mode 100644 index 0000000..3cc7b57 --- /dev/null +++ b/src/shrpx_worker.h @@ -0,0 +1,480 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_WORKER_H +#define SHRPX_WORKER_H + +#include "shrpx.h" + +#include +#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/src/shrpx_worker_process.cc b/src/shrpx_worker_process.cc new file mode 100644 index 0000000..05eac02 --- /dev/null +++ b/src/shrpx_worker_process.cc @@ -0,0 +1,701 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_worker_process.h" + +#include +#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 exited; 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/src/shrpx_worker_process.h b/src/shrpx_worker_process.h new file mode 100644 index 0000000..f432503 --- /dev/null +++ b/src/shrpx_worker_process.h @@ -0,0 +1,67 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_WORKER_PROCESS_H +#define SHRPX_WORKER_PROCESS_H + +#include "shrpx.h" + +#include +#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/src/shrpx_worker_test.cc b/src/shrpx_worker_test.cc new file mode 100644 index 0000000..7c5c329 --- /dev/null +++ b/src/shrpx_worker_test.cc @@ -0,0 +1,247 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_worker_test.h" + +#ifdef HAVE_UNISTD_H +# include +#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/src/shrpx_worker_test.h b/src/shrpx_worker_test.h new file mode 100644 index 0000000..8ffa2f1 --- /dev/null +++ b/src/shrpx_worker_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_WORKER_TEST_H +#define SHRPX_WORKER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_worker_match_downstream_addr_group(void); + +} // namespace shrpx + +#endif // SHRPX_WORKER_TEST_H diff --git a/src/ssl_compat.h b/src/ssl_compat.h new file mode 100644 index 0000000..f691104 --- /dev/null +++ b/src/ssl_compat.h @@ -0,0 +1,48 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef OPENSSL_COMPAT_H + +# include + +# ifdef LIBRESSL_VERSION_NUMBER +# define NGHTTP2_OPENSSL_IS_LIBRESSL +# endif // !LIBRESSL_VERSION_NUMBER + +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +# define NGHTTP2_OPENSSL_IS_BORINGSSL +# endif // OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC + +# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && \ + !defined(NGHTTP2_OPENSSL_IS_LIBRESSL) +# define NGHTTP2_GENUINE_OPENSSL +# endif // !NGHTTP2_OPENSSL_IS_BORINGSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL + +# ifdef NGHTTP2_GENUINE_OPENSSL +# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L) +# else // !NGHTTP2_GENUINE_OPENSSL +# define OPENSSL_3_0_0_API 0 +# endif // !NGHTTP2_GENUINE_OPENSSL + +#endif // OPENSSL_COMPAT_H diff --git a/src/template.h b/src/template.h new file mode 100644 index 0000000..530a1d1 --- /dev/null +++ b/src/template.h @@ -0,0 +1,548 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include "nghttp2_config.h" + +#include +#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/src/template_test.cc b/src/template_test.cc new file mode 100644 index 0000000..4a77315 --- /dev/null +++ b/src/template_test.cc @@ -0,0 +1,204 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "template_test.h" + +#include +#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/src/template_test.h b/src/template_test.h new file mode 100644 index 0000000..2c1448f --- /dev/null +++ b/src/template_test.h @@ -0,0 +1,39 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TEMPLATE_TEST_H +#define TEMPLATE_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_template_immutable_string(void); +void test_template_string_ref(void); + +} // namespace nghttp2 + +#endif // TEMPLATE_TEST_H diff --git a/src/test.example.com-key.pem b/src/test.example.com-key.pem new file mode 100644 index 0000000..6d5515c --- /dev/null +++ b/src/test.example.com-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2FroLDwqVUYRlxp +U/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpowUxvTDv/GNIHH +XK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmIsYsO0Y4iBtwB +M/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvcVzvSgZIzMLqS +zvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7G/ZbX/4kUU5b +PabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABAoIBAQCjUL69iFOs7muK +CZGFe/iU1uxQM6XuGPN7mso3z15W07UxdlS3o6ZUSoLTONWcBxP/N4knulHJjZSY +0LivbDYz3htic3t0kdGmxOxPVnLKRXN6ncbQTaeAE8tlBMAcWd/UH2Tlylz+Ac// +6cV3gNJMShwUmhb3l4v3Rml0nZ6PO1pFc/Chk5L9REAV8G6rNtc9bzgmgoFucRO/ +8ce/uJrENt1Pu3vBvmz42DTGfG48v5RZ0OY4qEPawZJ7p+QYiTf6h3Eilss/AllW +PPfQ0thdyB+yrZ3p6qb+ZUYphpGxgg6YlQxLfDKAikuo+EXwjPBPfeHhTO4kAj+h +opDCroZhAoGBANyVFbagCWqwguE6nVPmnCaiNQUIh8b7L2CnkkLfdbPQr/KvyIjg +Ua125bTJhe9Uk+ZBWsobQkjA0Baidiylx51pWYaxPVn5araVmkh2dqMluk2QE82X +AWemBgKhAqCLLLMVXbrRYlxpKUm1Fc/lJ8Ig2R/MJSntTMpQhJtIejUbAoGBAMnt +XMvlFABCoFbI9GMcteI/KkvNGQUy3OKEln/QCssnE4/XIu7LCxy6P+1lycbFy/mQ +0bnp525sPEIIkMpi6LeAbSzYN2O3BRjNrjPcbx6Khz9DweNhRIo5qTFRszZ+pHbV +N+9Oc9JVenwPw6EuW7uZRFKFhCHtsBFdUrWLJoSVAoGAQ3ytdwGBwA2fDW/UgL32 +mm9YT2DrwbpKJYU/X4xkw44ett6HOTGAa9ULtINPogi7c2AdeeZbIk0znSk5hLF3 +4DZCOM5zWdrQhmpBGNh9ta6uUFq7ZFRGDsMh5Z4DYsER/PyVf7neIS3ffviTYtbW +kjNgmrTnzesXanK2D5heI28CgYEAhl+qjRTYhoPP53C7EOmeL/0QzHij2c3LKAJL +lKqBREewwNvNp1L/BhL7T6OY7unZny48IpgBJn5oaxkAIW5IpzSTcnBAC99TSPo2 +ntRmLdDJx9PzRrkHv2Q3r1ZLCEymbV3eZyWx9ZpkdAKZkL0k1mZcDP5Eu79Ml4Ge +9Kiw7TECgYEAh+nTKwrCUFGbe4RIVCj/QG7FVPbq5PdxJ3gILZ3/1XkhPcNRFKJS +u5qPfA02tYEALz9KXATK1uRB/GlBM7Eap/g2GFiHpVxrw6wPpybLywJmNyNTwqiq +eJxQ0FRzW9Kwwn1ThPY38LdFe/wvXZFOcNvGD8hHCLQRdlBR4zuTsBk= +-----END RSA PRIVATE KEY----- diff --git a/src/test.example.com.csr b/src/test.example.com.csr new file mode 100644 index 0000000..8163935 --- /dev/null +++ b/src/test.example.com.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAxMQdGVz +dC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK39 +lAbBIf8cHf+FmX1RX+bDMlnJ7P9YnRdha6Cw8KlVGEZcaVPym6yBvF/L98OHFT/8 +Tl3dcvUPlvTiytmEnAHh3hPe5h2V23KaMFMb0w7/xjSBx1yv8r2DPEdhBJ2Ue9al +3aF7AmpL/2htnsJJLwNdPHhG5NLu0cTZiLGLDtGOIgbcATPzEpAGD31na3R4WPc9 +JeDPBgMlDrmXNLeo9M8N385I6plYBhCb3Fc70oGSMzC6ks7wvPb4l7Y9acs0IzC4 +Jrg27NJeO7kR6WV4DBlchd1LkIqT874Tuxv2W1/+JFFOWz2m3bbz2447u3ROT3xq +yjk4TOh0vd2O0L2E1LcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBMAUqwty7R +/YWRrC8NuvrbSsW0r7Z7FXWxny5w5ImONCgVffc2wVydtBVQ0rfd3pDZLyu0P4sx +4bJ/KBz67t2MsOKbCMDS7SJuFwHu9AUzaYNh455HeBOVwb6LemJDNnCtMG9DgcRv +2BpwKqekUVTGDuUQmLibjE8qwDHw/p9k4gjQBxlfJe2sIZGs6oA/JGFJUU6ZIn8Y +M6aazrbjWexbWCnjhiXkNa8kfKiSHzU+2ct+GY5QxI221+63bXRiAi2/LK0gaY+p ++3vYu75F7+8oPZOfsGmYEyPz7c1jPqcwPgVDk+sdvl1MO1TGFRaFNIlRP1DhpHkj +fuJ/id6oUHhj +-----END CERTIFICATE REQUEST----- diff --git a/src/test.example.com.csr.json b/src/test.example.com.csr.json new file mode 100644 index 0000000..5cd3e1e --- /dev/null +++ b/src/test.example.com.csr.json @@ -0,0 +1,14 @@ +{ + "CN": "test.example.com", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/src/test.example.com.pem b/src/test.example.com.pem new file mode 100644 index 0000000..1c7e71e --- /dev/null +++ b/src/test.example.com.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwTCCAqmgAwIBAgIUDhKNhGRUq1TSHD6aG2k4TRR8iA0wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzNzAwWhcNMjYwNjIzMDkzNzAwWjBgMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2Fr +oLDwqVUYRlxpU/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpow +UxvTDv/GNIHHXK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmI +sYsO0Y4iBtwBM/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvc +VzvSgZIzMLqSzvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7 +G/ZbX/4kUU5bPabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABo3UwczAO +BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw +ADAdBgNVHQ4EFgQUm8jn1FICope9qUce6ORQ0CtbmhYwHwYDVR0jBBgwFoAU0DnF +VHVlxrFEkv1ULqnO4ua924YwDQYJKoZIhvcNAQELBQADggEBAD7RPz/5rAnS1MNP +JfAj1TXZSBwlYgtmJL65yaFB6a1SNSTo15deAm/1Vl10LbmYdV4sVnGKeZjhKNk+ +bvVzetUSUS7Rh1fHtxlivJFkG1VrvPu9b416l2aKftBiaNyAWXbyjqXwLYli6Ehk +uu6jZd0040Ggh7bY+KMSnDFDrp7Rar7OvGu9Iovs+sPdkc/iEbvwEiXdMjf3gwkT +Wqx6br1VDLzhD83HAsFA9tt5fv6KTf91UgJnCmOi81Uo6fSEJG84g32T25gwwmCK +q4U049aGF/f4u3QuWDsfYqNePycurAg3m5PC0wCoqvpY2u/q+PGbjWMi2PfZsF8U +imgl/L0= +-----END CERTIFICATE----- diff --git a/src/test.nghttp2.org-key.pem b/src/test.nghttp2.org-key.pem new file mode 100644 index 0000000..2532895 --- /dev/null +++ b/src/test.nghttp2.org-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA7p6KKa3ctS+Sr/nf2uTKNtTshuDVzTsBTbaGydj8q0YDmT3n +CnOPWXvvG1N+jJv5pcAXN2ZnV9UpGh3N5g/CaRcFTgQQ8o+NlCXYBdPIXAJ+Kkbx +limDw3n9xIXfeL6+V2QuPNrqh6n23xwDg5boKaNkpf7X5OrjT1Ph57SEfX1op3GX +bwkAP2+3WlxxYYs0htRq2gH97q9J4MlhHPDapi+uKGs+2b1y6Uxgf4nD5jEWdPmy +VqeKs+fT4ja2n+3gujpdOo2lg504p50gL4zP8zhAlcqlQCmeJGL1xFzCtm2wHQo7 +6XHSWca4pJ7rxf2oIdtE7ikvgFlTVXnG1T3TEQIDAQABAoIBAQDlX/UD96MPcDmb +e6EZ85AGgUsUpJAhBjVMlMagxTqtEVJoPj8XptoHdMD2DZ66XzztfedTU9bHcZpf +BoNkQYXqKzzoL7Ry1leML4ymnVweRi8tSKD2bdXBVEUCYoXctc6WhzCDQxTrcBBl +i7I9DhUB4ZTglEbIQJpdKQ8hAj/Rt55KWSxc+8X7ItSdtMrq+uz+pqg4PkysVAFS +3aDybOqiI/2hzOvwQU4HaB48uUQwpOU6EGidt0C5nAdWOQMbS8kkCJz6UODiUfdM +mLIyA4ygkQ45QthzrddKauUMhUd/y1SAFJOambR4ZiyA+bItomlbNq018sFx3FDr +Uvg7nz2ZAoGBAPC6aO4W0U7vsWyL/lgC7ybFbtPJ4emj4wK/W87qx3LW1/dRgl7Q +h6oblZTFK/oV6xA7J2/Foocz/s1ntnyIdxrtrZUYAIiBXlrWhDg9+MnnmErfXx7H +CkRyWH6i9JbTRUeiWRNBGQ9yMkwQPc2Ckytxrh7w9M+RVCpyzUh8lX+XAoGBAP3B +4V8cF3bVEUOk0tHshR5m2kcJ22qmUv8WUG+IdRUDb4tRpzVFC8HcKedEjk3jxkXR +UInRSD+hLhx0HIhjZKWqJffSZI/G3U8AqoKu9+eh/xHZCah/8KW1YWNsn4rROcyZ +5XFRiMn7psWTjLEZ17zQS4rk9g65SKc9u1wtTxeXAoGAIY3qOF2n2Tfh5D5zOnNW +QHI+q3i1a6qzZtujgWkKWgCGY+vRn0Oz1Us5A16kbZyGgmGscpD6wZvGxXzSW/Nt +nqxIiMKquFxH+aNzFJ/WwNXuTWlrSc/2p2nE2gn+y9MxEfYYMm3df2CskBuncbDk +sKaM3bU6eoBIWg5cfOEYuYsCgYACB2bR59uYK6PzsoGtBAMcdx4Pq1iBxcqsF3WV +LrYg8OIXbxOzLVYmuqfrHXU10jhnnoDSWUYGnDdOKu9/d6v6Vx3umVQMgj6Kvyqd +2OBKjdUIQ3/8ROmbqZOZw+iSp5GavTBEc65wTv7KXZ+mWtqKu++esK32+CxIignR +dttHCQKBgQDZUt94wj9s5p7H5LH6hxyqNr6P9JYEuYPao5l/3mOJ8N330wKuN2G+ +GUg7p/AhtQHwdoyErlsQavKZa791oCvfJiOURYa8gYU03sYsyI1tV45UexCwl40f +oS+VQYgU16UdYo9B2petecEPNpM+mgpne3qzVUwJ5NUNURgmWpyiQw== +-----END RSA PRIVATE KEY----- diff --git a/src/test.nghttp2.org.csr b/src/test.nghttp2.org.csr new file mode 100644 index 0000000..dc4bb10 --- /dev/null +++ b/src/test.nghttp2.org.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDATCCAekCAQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAxMUbm90 +LXVzZWQubmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDunooprdy1L5Kv+d/a5Mo21OyG4NXNOwFNtobJ2PyrRgOZPecKc49Ze+8bU36M +m/mlwBc3ZmdX1SkaHc3mD8JpFwVOBBDyj42UJdgF08hcAn4qRvGWKYPDef3Ehd94 +vr5XZC482uqHqfbfHAODlugpo2Sl/tfk6uNPU+HntIR9fWincZdvCQA/b7daXHFh +izSG1GraAf3ur0ngyWEc8NqmL64oaz7ZvXLpTGB/icPmMRZ0+bJWp4qz59PiNraf +7eC6Ol06jaWDnTinnSAvjM/zOECVyqVAKZ4kYvXEXMK2bbAdCjvpcdJZxriknuvF +/agh20TuKS+AWVNVecbVPdMRAgMBAAGgWDBWBgkqhkiG9w0BCQ4xSTBHMEUGA1Ud +EQQ+MDyCEFRFU1QuTkdIVFRQMi5PUkeCEioudGVzdC5uZ2h0dHAyLm9yZ4IUdyp3 +LnRlc3QubmdodHRwMi5vcmcwDQYJKoZIhvcNAQELBQADggEBAIAEwnoM5moRwO5U +eaeVCuzpxw1qQsB769GyQu+ey1aa+2BYflirv/FW+8x/uzQpCWGEgHqd5w+MXyXA +PsyucHgKh5Ia6MUW6xxlHkkOtVtmZiH7lXWv90RNtdfHHGWnBzw8iGsk5WfEaNho +NlPiuYLiFqA7W6jR/c4kOg3zziDlwTXaH6SWLCuDzLTb7E7nGcrWkN6moYj+QlSx +viA4GsqDBoFgXT7cSfUzS8ZwIjrqbx7C1xkzPEt5jAiCD/UBX9ot0G+lEgCv3UQj +Q1KkY+TO3bzMkt/kQSX2Q6plKj8D77tlDfFCjd77VC2lL3Qmzaz+M6T7uF+wyl9W +AQJvoUg= +-----END CERTIFICATE REQUEST----- diff --git a/src/test.nghttp2.org.csr.json b/src/test.nghttp2.org.csr.json new file mode 100644 index 0000000..5ee3069 --- /dev/null +++ b/src/test.nghttp2.org.csr.json @@ -0,0 +1,19 @@ +{ + "CN": "not-used.nghttp2.org", + "hosts": [ + "TEST.NGHTTP2.ORG", + "*.test.nghttp2.org", + "w*w.test.nghttp2.org" + ], + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/src/test.nghttp2.org.pem b/src/test.nghttp2.org.pem new file mode 100644 index 0000000..0c386fc --- /dev/null +++ b/src/test.nghttp2.org.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDjCCAvagAwIBAgIUQBCY8Nre85JT1c7P+HbXUF9yzg8wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBkMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMR0wGwYDVQQDExRub3QtdXNlZC5uZ2h0dHAyLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6eiimt3LUvkq/539rkyjbU7Ibg1c07 +AU22hsnY/KtGA5k95wpzj1l77xtTfoyb+aXAFzdmZ1fVKRodzeYPwmkXBU4EEPKP +jZQl2AXTyFwCfipG8ZYpg8N5/cSF33i+vldkLjza6oep9t8cA4OW6CmjZKX+1+Tq +409T4ee0hH19aKdxl28JAD9vt1pccWGLNIbUatoB/e6vSeDJYRzw2qYvrihrPtm9 +culMYH+Jw+YxFnT5slanirPn0+I2tp/t4Lo6XTqNpYOdOKedIC+Mz/M4QJXKpUAp +niRi9cRcwrZtsB0KO+lx0lnGuKSe68X9qCHbRO4pL4BZU1V5xtU90xECAwEAAaOB +vTCBujAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T +AQH/BAIwADAdBgNVHQ4EFgQUGlxgxowH6jrQiyyFpCbwPkCXXIYwHwYDVR0jBBgw +FoAU0DnFVHVlxrFEkv1ULqnO4ua924YwRQYDVR0RBD4wPIIQVEVTVC5OR0hUVFAy +Lk9SR4ISKi50ZXN0Lm5naHR0cDIub3JnghR3KncudGVzdC5uZ2h0dHAyLm9yZzAN +BgkqhkiG9w0BAQsFAAOCAQEANCqM6ocfqOpgDEHYOOQTGFHJIptQhS3kRYAdTIo2 +G8XvGCoy+CDYe1GAUWbxE090+a1I1rsYMHcWKJnjKaCBZid7KMhyayIvrmgEsOCh +L8iLf3bxkCoyIAmCpxJwa3LMxm2QQLtRx8AoMXWf+N8are4HY6MLNn6aP4zaTrTZ +H+WkjKIh7WjSHtW/ro666PCXJDCCdRXljOf8v/fff3bYiLg8o70RBp7OFM0HaPtK +wCfcLLxBeoVIncWswB6GtVUFhLeGjepDzWpuDHOdw6DtpghwSXvWFu9bRtl+x02m +LAGfJ0kJrpYGfr9UB51NFX3aM/D3p2zxrjKwR2b59vJEcA== +-----END CERTIFICATE----- diff --git a/src/testdata/Makefile.am b/src/testdata/Makefile.am new file mode 100644 index 0000000..b1cb575 --- /dev/null +++ b/src/testdata/Makefile.am @@ -0,0 +1,27 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2023 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = \ + ipaddr.crt \ + nosan.crt \ + nosan_ip.crt \ + verify_hostname.crt diff --git a/src/testdata/ipaddr.crt b/src/testdata/ipaddr.crt new file mode 100644 index 0000000..cdacdf0 --- /dev/null +++ b/src/testdata/ipaddr.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBfDCCASKgAwIBAgIBATAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwsxOTIuMTY4 +LjAuMTAeFw0yMzAzMTUxMjQ5MDBaFw0zMzAxMjExMjQ5MDBaMBYxFDASBgNVBAMT +CzE5Mi4xNjguMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDh3hNne6xGM +fOrf7ln5EFnlLpk98qadBx3MKjG5gAfMYHzf/S7v19G608sH1LtabubV+Tvjllon +K56G2Gk0+6NhMF8wDgYDVR0PAQH/BAQDAgeAME0GA1UdEQRGMESCE25naHR0cDIu +ZXhhbXBsZS5jb22CFSoubmdodHRwMi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA +AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiEA3jZzO49MYccR5mYS08qVUCdh +HsEAC8GhRXFwL6zvf2ACIFAJrca2zTU4QRjV6V+LGRHc2ZocE2e7wFTLobblmDfB +-----END CERTIFICATE----- diff --git a/src/testdata/nosan.crt b/src/testdata/nosan.crt new file mode 100644 index 0000000..ebbb5c0 --- /dev/null +++ b/src/testdata/nosan.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBKDCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCWxvY2FsaG9z +dDAeFw0yMzAzMTUxMjQzMzhaFw0zMzAxMjExMjQzMzhaMBQxEjAQBgNVBAMTCWxv +Y2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIEpWYgtXtcx0uJ2oFPK +RiII93iw5ITMrhMfBXQ0SzCfkUdvCJ0gNW+3isTBu4Jt0URpgP37eGwiJf2wPApq +KpajEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNIADBFAiEA4IYil4G4 +cMxaVkcAnMGgiSdn7/qIgdhFB0Vx5AOd+EUCIGubRPhsXAJXvG//cK25mmxi3Wax +r7AgRKuDtWxn2bCO +-----END CERTIFICATE----- diff --git a/src/testdata/nosan_ip.crt b/src/testdata/nosan_ip.crt new file mode 100644 index 0000000..717a1bd --- /dev/null +++ b/src/testdata/nosan_ip.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBJzCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCTEyNy4wLjAu +MTAeFw0yMzAzMTUxMjQ1MTVaFw0zMzAxMjExMjQ1MTVaMBQxEjAQBgNVBAMTCTEy +Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOXGPfSzXoeD7jszmAQO +qAhak5HQMTmj32Q/xqO9WmCnXRQ+T06701o6q1hjotrC/HdMk9kabsKHc9V7Bk4O +zkGjEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNHADBEAiAI3fKrkNTN +IEo9qI8bd/pZ6on4d9vLcnHtqYhcuWZGTwIgW2zYMwASLUw4H1k/prBtTEEJOahJ +bvFs3oMbJEprQ+g= +-----END CERTIFICATE----- diff --git a/src/testdata/verify_hostname.crt b/src/testdata/verify_hostname.crt new file mode 100644 index 0000000..a327616 --- /dev/null +++ b/src/testdata/verify_hostname.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBeTCCAR6gAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlsb2NhbGhv +c3QwHhcNMjMwMzE1MTIzNzU1WhcNMzMwMTIxMTIzNzU1WjAUMRIwEAYDVQQDEwls +b2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMHcWmb55fi0KHNDwM +cYzVTAOfzJf44AqrqC+Pq2zW/ig8tPZbXf3eA/Vvp07Di+yWmuo3fGatUcY4nsx+ +Jd62o2EwXzAOBgNVHQ8BAf8EBAMCB4AwTQYDVR0RBEYwRIITbmdodHRwMi5leGFt +cGxlLmNvbYIVKi5uZ2h0dHAyLmV4YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAA +AAAAAAABMAoGCCqGSM49BAMCA0kAMEYCIQDQJFRJ3Ah4cGy7bwpkzVYeTgG+NhDa +55F4dPtJp9dS8wIhALQ9qf379lke1jVHg2t84iZLo3bL23RgICMezEYvqO3K +-----END CERTIFICATE----- diff --git a/src/timegm.c b/src/timegm.c new file mode 100644 index 0000000..3a671b1 --- /dev/null +++ b/src/timegm.c @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "timegm.h" + +#include + +/* Counter the number of leap year in the range [0, y). The |y| is the + year, including century (e.g., 2012) */ +static int count_leap_year(int y) { + y -= 1; + return y / 4 - y / 100 + y / 400; +} + +/* Based on the algorithm of Python 2.7 calendar.timegm. */ +time_t nghttp2_timegm(struct tm *tm) { + int days; + int num_leap_year; + int64_t t; + if (tm->tm_mon > 11) { + return -1; + } + num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970); + days = (tm->tm_year - 70) * 365 + num_leap_year + tm->tm_yday; + t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec; + + if (sizeof(time_t) == 4) { + if (t < INT32_MIN || t > INT32_MAX) { + return -1; + } + } + + return (time_t)t; +} + +/* Returns nonzero if the |y| is the leap year. The |y| is the year, + including century (e.g., 2012) */ +static int is_leap_year(int y) { + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); +} + +/* The number of days before ith month begins */ +static int daysum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + +time_t nghttp2_timegm_without_yday(struct tm *tm) { + int days; + int num_leap_year; + int64_t t; + if (tm->tm_mon > 11) { + return -1; + } + num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970); + days = (tm->tm_year - 70) * 365 + num_leap_year + daysum[tm->tm_mon] + + tm->tm_mday - 1; + if (tm->tm_mon >= 2 && is_leap_year(tm->tm_year + 1900)) { + ++days; + } + t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec; + + if (sizeof(time_t) == 4) { + if (t < INT32_MIN || t > INT32_MAX) { + return -1; + } + } + + return (time_t)t; +} diff --git a/src/timegm.h b/src/timegm.h new file mode 100644 index 0000000..152917c --- /dev/null +++ b/src/timegm.h @@ -0,0 +1,49 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TIMEGM_H +#define TIMEGM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +time_t nghttp2_timegm(struct tm *tm); + +/* Just like nghttp2_timegm, but without using tm->tm_yday. This is + useful if we use tm from strptime, since some platforms do not + calculate tm_yday with that call. */ +time_t nghttp2_timegm_without_yday(struct tm *tm); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* TIMEGM_H */ diff --git a/src/tls.cc b/src/tls.cc new file mode 100644 index 0000000..e88dc56 --- /dev/null +++ b/src/tls.cc @@ -0,0 +1,125 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls.h" + +#include +#include +#include +#include + +#include +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +const char *get_tls_protocol(SSL *ssl) { + switch (SSL_version(ssl)) { + case SSL2_VERSION: + return "SSLv2"; + case SSL3_VERSION: + return "SSLv3"; +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: + return "TLSv1.3"; +#endif // TLS1_3_VERSION + case TLS1_2_VERSION: + return "TLSv1.2"; + case TLS1_1_VERSION: + return "TLSv1.1"; + case TLS1_VERSION: + return "TLSv1"; + default: + return "unknown"; + } +} + +TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) { + if (!ssl) { + return nullptr; + } + + auto session = SSL_get_session(ssl); + if (!session) { + return nullptr; + } + + tls_info->cipher = SSL_get_cipher_name(ssl); + tls_info->protocol = get_tls_protocol(ssl); + tls_info->session_reused = SSL_session_reused(ssl); + + unsigned int session_id_length; + tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length); + tls_info->session_id_length = session_id_length; + + return tls_info; +} + +/* Conditional logic w/ lookup tables to check if id is one of the + the block listed cipher suites for HTTP/2 described in RFC 7540. + https://github.com/jay/http2_blacklisted_ciphers +*/ +#define IS_CIPHER_BANNED_METHOD2(id) \ + ((0x0000 <= id && id <= 0x00FF && \ + "\xFF\xFF\xFF\xCF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x80\x3F\x00\x00" \ + "\xF0\xFF\xFF\x3F\xF3\xF3\xFF\xFF\x3F\x00\x00\x00\x00\x00\x00\x80" \ + [(id & 0xFF) / 8] & \ + (1 << (id % 8))) || \ + (0xC000 <= id && id <= 0xC0FF && \ + "\xFE\xFF\xFF\xFF\xFF\x67\xFE\xFF\xFF\xFF\x33\xCF\xFC\xCF\xFF\xCF" \ + "\x3C\xF3\xFC\x3F\x33\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + [(id & 0xFF) / 8] & \ + (1 << (id % 8)))) + +bool check_http2_cipher_block_list(SSL *ssl) { + int id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl)) & 0xFFFFFF; + + return IS_CIPHER_BANNED_METHOD2(id); +} + +bool check_http2_tls_version(SSL *ssl) { + auto tls_ver = SSL_version(ssl); + + return tls_ver >= TLS1_2_VERSION; +} + +bool check_http2_requirement(SSL *ssl) { + return check_http2_tls_version(ssl) && !check_http2_cipher_block_list(ssl); +} + +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { + if (SSL_CTX_set_min_proto_version(ssl_ctx, min) != 1 || + SSL_CTX_set_max_proto_version(ssl_ctx, max) != 1) { + return -1; + } + return 0; +} + +} // namespace tls + +} // namespace nghttp2 diff --git a/src/tls.h b/src/tls.h new file mode 100644 index 0000000..59e2ccd --- /dev/null +++ b/src/tls.h @@ -0,0 +1,104 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_H +#define TLS_H + +#include "nghttp2_config.h" + +#include + +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +// Recommended general purpose "Intermediate compatibility" cipher +// suites for TLSv1.2 by mozilla. +// +// https://wiki.mozilla.org/Security/Server_Side_TLS +constexpr char DEFAULT_CIPHER_LIST[] = + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-" + "AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-" + "POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-" + "AES256-GCM-SHA384"; + +// Recommended general purpose "Modern compatibility" cipher suites +// for TLSv1.3 by mozilla. +// +// https://wiki.mozilla.org/Security/Server_Side_TLS +constexpr char DEFAULT_TLS13_CIPHER_LIST[] = +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_LIBRESSL) + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL + "" +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_LIBRESSL + ; + +constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION; +#ifdef TLS1_3_VERSION +constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_3_VERSION; +#else // !TLS1_3_VERSION +constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_2_VERSION; +#endif // !TLS1_3_VERSION + +const char *get_tls_protocol(SSL *ssl); + +struct TLSSessionInfo { + const char *cipher; + const char *protocol; + const uint8_t *session_id; + bool session_reused; + size_t session_id_length; +}; + +TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl); + +// Returns true iff the negotiated protocol is TLSv1.2. +bool check_http2_tls_version(SSL *ssl); + +// Returns true iff the negotiated cipher suite is in HTTP/2 cipher +// block list. +bool check_http2_cipher_block_list(SSL *ssl); + +// Returns true if SSL/TLS requirement for HTTP/2 is fulfilled. +// To fulfill the requirement, the following 2 terms must be hold: +// +// 1. The negotiated protocol must be TLSv1.2. +// 2. The negotiated cipher cuite is not listed in the block list +// described in RFC 7540. +bool check_http2_requirement(SSL *ssl); + +// Sets TLS min and max versions to |ssl_ctx|. This function returns +// 0 if it succeeds, or -1. +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max); + +} // namespace tls + +} // namespace nghttp2 + +#endif // TLS_H diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 0000000..0996c0a --- /dev/null +++ b/src/util.cc @@ -0,0 +1,1796 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#include +#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 + +#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 candidates. + return; + } + ++prefix_match; + cands.emplace_back(0, options[i].name); + continue; + } + // Use cost 0 for suffix match, but match at least 3 characters + if (unkoptlen >= 3 && + iends_with(options[i].name, options[i].name + optnamelen, unkopt, + unkopt + unkoptlen)) { + cands.emplace_back(0, options[i].name); + continue; + } + // cost values are borrowed from git, help.c. + int sim = + levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3); + cands.emplace_back(sim, options[i].name); + } + if (prefix_match == 1 || cands.empty()) { + return; + } + std::sort(std::begin(cands), std::end(cands)); + int threshold = cands[0].first; + // threshold value is a magic value. + if (threshold > 6) { + return; + } + std::cerr << "\nDid you mean:\n"; + for (auto &item : cands) { + if (item.first > threshold) { + break; + } + std::cerr << "\t--" << item.second << "\n"; + } +} + +bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) { + return u.field_set & (1 << field); +} + +bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2, http_parser_url_fields field) { + if (!has_uri_field(u1, field)) { + if (!has_uri_field(u2, field)) { + return true; + } else { + return false; + } + } else if (!has_uri_field(u2, field)) { + return false; + } + if (u1.field_data[field].len != u2.field_data[field].len) { + return false; + } + return memcmp(uri1 + u1.field_data[field].off, + uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0; +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const char *t) { + return fieldeq(uri, u, field, StringRef{t}); +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const StringRef &t) { + if (!has_uri_field(u, field)) { + return t.empty(); + } + auto &f = u.field_data[field]; + return StringRef{uri + f.off, f.len} == t; +} + +StringRef get_uri_field(const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (!util::has_uri_field(u, field)) { + return StringRef{}; + } + + return StringRef{uri + u.field_data[field].off, u.field_data[field].len}; +} + +uint16_t get_default_port(const char *uri, const http_parser_url &u) { + if (util::fieldeq(uri, u, UF_SCHEMA, "https")) { + return 443; + } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) { + return 80; + } else { + return 443; + } +} + +bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2) { + uint16_t port1, port2; + port1 = + util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1); + port2 = + util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2); + return port1 == port2; +} + +void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (util::has_uri_field(u, field)) { + o.write(uri + u.field_data[field].off, u.field_data[field].len); + } +} + +bool numeric_host(const char *hostname) { + return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6); +} + +bool numeric_host(const char *hostname, int family) { + int rv; + std::array 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; +} + +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/src/util.h b/src/util.h new file mode 100644 index 0000000..d818bf2 --- /dev/null +++ b/src/util.h @@ -0,0 +1,971 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_H +#define UTIL_H + +#include "nghttp2_config.h" + +#ifdef HAVE_UNISTD_H +# include +#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/src/util_test.cc b/src/util_test.cc new file mode 100644 index 0000000..0ac0f1d --- /dev/null +++ b/src/util_test.cc @@ -0,0 +1,707 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util_test.h" + +#include +#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/src/util_test.h b/src/util_test.h new file mode 100644 index 0000000..48925ab --- /dev/null +++ b/src/util_test.h @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_TEST_H +#define UTIL_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_util_streq(void); +void test_util_strieq(void); +void test_util_inp_strlower(void); +void test_util_to_base64(void); +void test_util_to_token68(void); +void test_util_percent_encode_token(void); +void test_util_percent_decode(void); +void test_util_quote_string(void); +void test_util_utox(void); +void test_util_http_date(void); +void test_util_select_h2(void); +void test_util_ipv6_numeric_addr(void); +void test_util_utos(void); +void test_util_make_string_ref_uint(void); +void test_util_utos_unit(void); +void test_util_utos_funit(void); +void test_util_parse_uint_with_unit(void); +void test_util_parse_uint(void); +void test_util_parse_duration_with_unit(void); +void test_util_duration_str(void); +void test_util_format_duration(void); +void test_util_starts_with(void); +void test_util_ends_with(void); +void test_util_parse_http_date(void); +void test_util_localtime_date(void); +void test_util_get_uint64(void); +void test_util_parse_config_str_list(void); +void test_util_make_http_hostport(void); +void test_util_make_hostport(void); +void test_util_strifind(void); +void test_util_random_alpha_digit(void); +void test_util_format_hex(void); +void test_util_is_hex_string(void); +void test_util_decode_hex(void); +void test_util_extract_host(void); +void test_util_split_hostport(void); +void test_util_split_str(void); +void test_util_rstrip(void); + +} // namespace shrpx + +#endif // UTIL_TEST_H diff --git a/src/xsi_strerror.c b/src/xsi_strerror.c new file mode 100644 index 0000000..d008d4e --- /dev/null +++ b/src/xsi_strerror.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "xsi_strerror.h" + +/* Make sure that we get XSI-compliant version of strerror_r */ +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif /* _POSIX_C_SOURCE */ + +#ifdef _GNU_SOURCE +# undef _GNU_SOURCE +#endif /* _GNU_SOURCE */ + +#include + +char *xsi_strerror(int errnum, char *buf, size_t buflen) { + int rv; + + rv = strerror_r(errnum, buf, buflen); + + if (rv != 0) { + if (buflen > 0) { + buf[0] = '\0'; + } + } + + return buf; +} diff --git a/src/xsi_strerror.h b/src/xsi_strerror.h new file mode 100644 index 0000000..32cadc3 --- /dev/null +++ b/src/xsi_strerror.h @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef XSI_STRERROR_H +#define XSI_STRERROR_H + +#ifdef HAVE_CONFIG_H +# include +#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/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..42dfe63 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +# tests +failmalloc +main diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b20ccf7 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,55 @@ +# XXX testdata/: EXTRA_DIST = cacert.pem index.html privkey.pem +if(HAVE_CUNIT) + string(REPLACE " " ";" c_flags "${WARNCFLAGS}") + add_compile_options(${c_flags}) + + include_directories( + "${CMAKE_SOURCE_DIR}/lib/includes" + "${CMAKE_SOURCE_DIR}/lib" + "${CMAKE_BINARY_DIR}/lib/includes" + ${CUNIT_INCLUDE_DIRS} + ) + + set(MAIN_SOURCES + main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c + nghttp2_test_helper.c + nghttp2_frame_test.c + nghttp2_stream_test.c + nghttp2_session_test.c + nghttp2_hd_test.c + nghttp2_alpn_test.c + nghttp2_helper_test.c + nghttp2_buf_test.c + nghttp2_http_test.c + nghttp2_extpri_test.c + nghttp2_ratelim_test.c + ) + + add_executable(main EXCLUDE_FROM_ALL + ${MAIN_SOURCES} + ) + target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS}) + target_link_libraries(main + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(main main) + add_dependencies(check main) + + if(ENABLE_FAILMALLOC) + set(FAILMALLOC_SOURCES + failmalloc.c failmalloc_test.c + malloc_wrapper.c + nghttp2_test_helper.c + ) + add_executable(failmalloc EXCLUDE_FROM_ALL + ${FAILMALLOC_SOURCES} + ) + target_link_libraries(failmalloc + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(failmalloc failmalloc) + add_dependencies(check failmalloc) + endif() +endif() diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..1c506d8 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,93 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = testdata + +EXTRA_DIST = CMakeLists.txt + +if HAVE_CUNIT + +check_PROGRAMS = main + +if ENABLE_FAILMALLOC +check_PROGRAMS += failmalloc +endif # ENABLE_FAILMALLOC + +OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ + nghttp2_test_helper.c \ + nghttp2_frame_test.c \ + nghttp2_stream_test.c \ + nghttp2_session_test.c \ + nghttp2_hd_test.c \ + nghttp2_alpn_test.c \ + nghttp2_helper_test.c \ + nghttp2_buf_test.c \ + nghttp2_http_test.c \ + nghttp2_extpri_test.c \ + nghttp2_ratelim_test.c + +HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ + nghttp2_session_test.h \ + nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ + nghttp2_alpn_test.h nghttp2_helper_test.h \ + nghttp2_test_helper.h \ + nghttp2_buf_test.h \ + nghttp2_http_test.h \ + nghttp2_extpri_test.h \ + nghttp2_ratelim_test.h + +main_SOURCES = $(HFILES) $(OBJECTS) + +if ENABLE_STATIC +main_LDADD = ${top_builddir}/lib/libnghttp2.la +else +# With static lib disabled and symbol hiding enabled, we have to link object +# files directly because the tests use symbols not included in public API. +main_LDADD = ${top_builddir}/lib/.libs/*.o +endif + +main_LDADD += @CUNIT_LIBS@ @TESTLDADD@ +main_LDFLAGS = -static + +if ENABLE_FAILMALLOC +failmalloc_SOURCES = failmalloc.c failmalloc_test.c failmalloc_test.h \ + malloc_wrapper.c malloc_wrapper.h \ + nghttp2_test_helper.c nghttp2_test_helper.h +failmalloc_LDADD = $(main_LDADD) +failmalloc_LDFLAGS = $(main_LDFLAGS) +endif # ENABLE_FAILMALLOC + +AM_CFLAGS = $(WARNCFLAGS) \ + -I${top_srcdir}/lib \ + -I${top_srcdir}/lib/includes \ + -I${top_builddir}/lib/includes \ + -DBUILDING_NGHTTP2 \ + -DNGHTTP2_STATICLIB \ + @CUNIT_CFLAGS@ @DEFS@ + +TESTS = main + +if ENABLE_FAILMALLOC +TESTS += failmalloc +endif # ENABLE_FAILMALLOC + +endif # HAVE_CUNIT diff --git a/tests/failmalloc.c b/tests/failmalloc.c new file mode 100644 index 0000000..6294cff --- /dev/null +++ b/tests/failmalloc.c @@ -0,0 +1,79 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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/tests/failmalloc_test.c b/tests/failmalloc_test.c new file mode 100644 index 0000000..594bc72 --- /dev/null +++ b/tests/failmalloc_test.c @@ -0,0 +1,576 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "failmalloc_test.h" + +#include +#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/tests/failmalloc_test.h b/tests/failmalloc_test.h new file mode 100644 index 0000000..576932a --- /dev/null +++ b/tests/failmalloc_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef FAILMALLOC_TEST_H +#define FAILMALLOC_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_send(void); +void test_nghttp2_session_send_server(void); +void test_nghttp2_session_recv(void); +void test_nghttp2_frame(void); +void test_nghttp2_hd(void); + +#endif /* FAILMALLOC_TEST_H */ diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..6827daf --- /dev/null +++ b/tests/main.c @@ -0,0 +1,473 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include +#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_alpn_test.h" +#include "nghttp2_helper_test.h" +#include "nghttp2_buf_test.h" +#include "nghttp2_http_test.h" +#include "nghttp2_extpri_test.h" +#include "nghttp2_ratelim_test.h" + +extern int nghttp2_enable_strict_preface; + +static int init_suite1(void) { return 0; } + +static int clean_suite1(void) { return 0; } + +int main(void) { + CU_pSuite pSuite = NULL; + unsigned int num_tests_failed; + + nghttp2_enable_strict_preface = 0; + + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return (int)CU_get_error(); + + /* add a suite to the registry */ + pSuite = CU_add_suite("libnghttp2_TestSuite", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return (int)CU_get_error(); + } + + /* add the tests to the suite */ + if (!CU_add_test(pSuite, "pq", test_nghttp2_pq) || + !CU_add_test(pSuite, "pq_update", test_nghttp2_pq_update) || + !CU_add_test(pSuite, "pq_remove", test_nghttp2_pq_remove) || + !CU_add_test(pSuite, "map", test_nghttp2_map) || + !CU_add_test(pSuite, "map_functional", test_nghttp2_map_functional) || + !CU_add_test(pSuite, "map_each_free", test_nghttp2_map_each_free) || + !CU_add_test(pSuite, "queue", test_nghttp2_queue) || + !CU_add_test(pSuite, "alpn", test_nghttp2_alpn) || + !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || + !CU_add_test(pSuite, "session_recv_invalid_stream_id", + test_nghttp2_session_recv_invalid_stream_id) || + !CU_add_test(pSuite, "session_recv_invalid_frame", + test_nghttp2_session_recv_invalid_frame) || + !CU_add_test(pSuite, "session_recv_eof", test_nghttp2_session_recv_eof) || + !CU_add_test(pSuite, "session_recv_data", + test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_data_no_auto_flow_control", + test_nghttp2_session_recv_data_no_auto_flow_control) || + !CU_add_test(pSuite, "session_recv_continuation", + test_nghttp2_session_recv_continuation) || + !CU_add_test(pSuite, "session_recv_headers_with_priority", + test_nghttp2_session_recv_headers_with_priority) || + !CU_add_test(pSuite, "session_recv_headers_with_padding", + test_nghttp2_session_recv_headers_with_padding) || + !CU_add_test(pSuite, "session_recv_headers_early_response", + test_nghttp2_session_recv_headers_early_response) || + !CU_add_test(pSuite, "session_recv_headers_for_closed_stream", + test_nghttp2_session_recv_headers_for_closed_stream) || + !CU_add_test(pSuite, "session_recv_headers_with_extpri", + test_nghttp2_session_recv_headers_with_extpri) || + !CU_add_test(pSuite, "session_server_recv_push_response", + test_nghttp2_session_server_recv_push_response) || + !CU_add_test(pSuite, "session_recv_premature_headers", + test_nghttp2_session_recv_premature_headers) || + !CU_add_test(pSuite, "session_recv_unknown_frame", + test_nghttp2_session_recv_unknown_frame) || + !CU_add_test(pSuite, "session_recv_unexpected_continuation", + test_nghttp2_session_recv_unexpected_continuation) || + !CU_add_test(pSuite, "session_recv_settings_header_table_size", + test_nghttp2_session_recv_settings_header_table_size) || + !CU_add_test(pSuite, "session_recv_too_large_frame_length", + test_nghttp2_session_recv_too_large_frame_length) || + !CU_add_test(pSuite, "session_recv_extension", + test_nghttp2_session_recv_extension) || + !CU_add_test(pSuite, "session_recv_altsvc", + test_nghttp2_session_recv_altsvc) || + !CU_add_test(pSuite, "session_recv_origin", + test_nghttp2_session_recv_origin) || + !CU_add_test(pSuite, "session_recv_priority_update", + test_nghttp2_session_recv_priority_update) || + !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || + !CU_add_test(pSuite, "session_add_frame", + test_nghttp2_session_add_frame) || + !CU_add_test(pSuite, "session_on_request_headers_received", + test_nghttp2_session_on_request_headers_received) || + !CU_add_test(pSuite, "session_on_response_headers_received", + test_nghttp2_session_on_response_headers_received) || + !CU_add_test(pSuite, "session_on_headers_received", + test_nghttp2_session_on_headers_received) || + !CU_add_test(pSuite, "session_on_push_response_headers_received", + test_nghttp2_session_on_push_response_headers_received) || + !CU_add_test(pSuite, "session_on_priority_received", + test_nghttp2_session_on_priority_received) || + !CU_add_test(pSuite, "session_on_rst_stream_received", + test_nghttp2_session_on_rst_stream_received) || + !CU_add_test(pSuite, "session_on_settings_received", + test_nghttp2_session_on_settings_received) || + !CU_add_test(pSuite, "session_on_push_promise_received", + test_nghttp2_session_on_push_promise_received) || + !CU_add_test(pSuite, "session_on_ping_received", + test_nghttp2_session_on_ping_received) || + !CU_add_test(pSuite, "session_on_goaway_received", + test_nghttp2_session_on_goaway_received) || + !CU_add_test(pSuite, "session_on_window_update_received", + test_nghttp2_session_on_window_update_received) || + !CU_add_test(pSuite, "session_on_data_received", + test_nghttp2_session_on_data_received) || + !CU_add_test(pSuite, "session_on_data_received_fail_fast", + test_nghttp2_session_on_data_received_fail_fast) || + !CU_add_test(pSuite, "session_on_altsvc_received", + test_nghttp2_session_on_altsvc_received) || + !CU_add_test(pSuite, "session_send_headers_start_stream", + test_nghttp2_session_send_headers_start_stream) || + !CU_add_test(pSuite, "session_send_headers_reply", + test_nghttp2_session_send_headers_reply) || + !CU_add_test(pSuite, "session_send_headers_frame_size_error", + test_nghttp2_session_send_headers_frame_size_error) || + !CU_add_test(pSuite, "session_send_headers_push_reply", + test_nghttp2_session_send_headers_push_reply) || + !CU_add_test(pSuite, "session_send_rst_stream", + test_nghttp2_session_send_rst_stream) || + !CU_add_test(pSuite, "session_send_push_promise", + test_nghttp2_session_send_push_promise) || + !CU_add_test(pSuite, "session_is_my_stream_id", + test_nghttp2_session_is_my_stream_id) || + !CU_add_test(pSuite, "session_upgrade2", test_nghttp2_session_upgrade2) || + !CU_add_test(pSuite, "session_reprioritize_stream", + test_nghttp2_session_reprioritize_stream) || + !CU_add_test( + pSuite, "session_reprioritize_stream_with_idle_stream_dep", + test_nghttp2_session_reprioritize_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) || + !CU_add_test(pSuite, "submit_data_read_length_too_large", + test_nghttp2_submit_data_read_length_too_large) || + !CU_add_test(pSuite, "submit_data_read_length_smallest", + test_nghttp2_submit_data_read_length_smallest) || + !CU_add_test(pSuite, "submit_data_twice", + test_nghttp2_submit_data_twice) || + !CU_add_test(pSuite, "submit_request_with_data", + test_nghttp2_submit_request_with_data) || + !CU_add_test(pSuite, "submit_request_without_data", + test_nghttp2_submit_request_without_data) || + !CU_add_test(pSuite, "submit_response_with_data", + test_nghttp2_submit_response_with_data) || + !CU_add_test(pSuite, "submit_response_without_data", + test_nghttp2_submit_response_without_data) || + !CU_add_test(pSuite, "Submit_response_push_response", + test_nghttp2_submit_response_push_response) || + !CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) || + !CU_add_test(pSuite, "submit_headers_start_stream", + test_nghttp2_submit_headers_start_stream) || + !CU_add_test(pSuite, "submit_headers_reply", + test_nghttp2_submit_headers_reply) || + !CU_add_test(pSuite, "submit_headers_push_reply", + test_nghttp2_submit_headers_push_reply) || + !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || + !CU_add_test(pSuite, "submit_headers_continuation", + test_nghttp2_submit_headers_continuation) || + !CU_add_test(pSuite, "submit_headers_continuation_extra_large", + test_nghttp2_submit_headers_continuation_extra_large) || + !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || + !CU_add_test(pSuite, "session_submit_settings", + test_nghttp2_submit_settings) || + !CU_add_test(pSuite, "session_submit_settings_update_local_window_size", + test_nghttp2_submit_settings_update_local_window_size) || + !CU_add_test(pSuite, "session_submit_settings_multiple_times", + test_nghttp2_submit_settings_multiple_times) || + !CU_add_test(pSuite, "session_submit_push_promise", + test_nghttp2_submit_push_promise) || + !CU_add_test(pSuite, "submit_window_update", + test_nghttp2_submit_window_update) || + !CU_add_test(pSuite, "submit_window_update_local_window_size", + test_nghttp2_submit_window_update_local_window_size) || + !CU_add_test(pSuite, "submit_shutdown_notice", + test_nghttp2_submit_shutdown_notice) || + !CU_add_test(pSuite, "submit_invalid_nv", + test_nghttp2_submit_invalid_nv) || + !CU_add_test(pSuite, "submit_extension", test_nghttp2_submit_extension) || + !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) || + !CU_add_test(pSuite, "submit_origin", test_nghttp2_submit_origin) || + !CU_add_test(pSuite, "submit_priority_update", + test_nghttp2_submit_priority_update) || + !CU_add_test(pSuite, "submit_rst_stream", + test_nghttp2_submit_rst_stream) || + !CU_add_test(pSuite, "session_open_stream", + test_nghttp2_session_open_stream) || + !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", + test_nghttp2_session_open_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "session_get_next_ob_item", + test_nghttp2_session_get_next_ob_item) || + !CU_add_test(pSuite, "session_pop_next_ob_item", + test_nghttp2_session_pop_next_ob_item) || + !CU_add_test(pSuite, "session_reply_fail", + test_nghttp2_session_reply_fail) || + !CU_add_test(pSuite, "session_max_concurrent_streams", + test_nghttp2_session_max_concurrent_streams) || + !CU_add_test(pSuite, "session_stop_data_with_rst_stream", + test_nghttp2_session_stop_data_with_rst_stream) || + !CU_add_test(pSuite, "session_defer_data", + test_nghttp2_session_defer_data) || + !CU_add_test(pSuite, "session_flow_control", + test_nghttp2_session_flow_control) || + !CU_add_test(pSuite, "session_flow_control_data_recv", + test_nghttp2_session_flow_control_data_recv) || + !CU_add_test(pSuite, "session_flow_control_data_with_padding_recv", + test_nghttp2_session_flow_control_data_with_padding_recv) || + !CU_add_test(pSuite, "session_data_read_temporal_failure", + test_nghttp2_session_data_read_temporal_failure) || + !CU_add_test(pSuite, "session_on_stream_close", + test_nghttp2_session_on_stream_close) || + !CU_add_test(pSuite, "session_on_ctrl_not_send", + test_nghttp2_session_on_ctrl_not_send) || + !CU_add_test(pSuite, "session_get_outbound_queue_size", + test_nghttp2_session_get_outbound_queue_size) || + !CU_add_test(pSuite, "session_get_effective_local_window_size", + test_nghttp2_session_get_effective_local_window_size) || + !CU_add_test(pSuite, "session_set_option", + test_nghttp2_session_set_option) || + !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame", + test_nghttp2_session_data_backoff_by_high_pri_frame) || + !CU_add_test(pSuite, "session_pack_data_with_padding", + test_nghttp2_session_pack_data_with_padding) || + !CU_add_test(pSuite, "session_pack_headers_with_padding", + test_nghttp2_session_pack_headers_with_padding) || + !CU_add_test(pSuite, "pack_settings_payload", + test_nghttp2_pack_settings_payload) || + !CU_add_test(pSuite, "session_stream_dep_add", + test_nghttp2_session_stream_dep_add) || + !CU_add_test(pSuite, "session_stream_dep_remove", + test_nghttp2_session_stream_dep_remove) || + !CU_add_test(pSuite, "session_stream_dep_add_subtree", + test_nghttp2_session_stream_dep_add_subtree) || + !CU_add_test(pSuite, "session_stream_dep_remove_subtree", + test_nghttp2_session_stream_dep_remove_subtree) || + !CU_add_test( + pSuite, "session_stream_dep_all_your_stream_are_belong_to_us", + test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us) || + !CU_add_test(pSuite, "session_stream_attach_item", + test_nghttp2_session_stream_attach_item) || + !CU_add_test(pSuite, "session_stream_attach_item_subtree", + test_nghttp2_session_stream_attach_item_subtree) || + !CU_add_test(pSuite, "session_stream_get_state", + test_nghttp2_session_stream_get_state) || + !CU_add_test(pSuite, "session_stream_get_something", + test_nghttp2_session_stream_get_something) || + !CU_add_test(pSuite, "session_find_stream", + test_nghttp2_session_find_stream) || + !CU_add_test(pSuite, "session_keep_closed_stream", + test_nghttp2_session_keep_closed_stream) || + !CU_add_test(pSuite, "session_keep_idle_stream", + test_nghttp2_session_keep_idle_stream) || + !CU_add_test(pSuite, "session_detach_idle_stream", + test_nghttp2_session_detach_idle_stream) || + !CU_add_test(pSuite, "session_large_dep_tree", + test_nghttp2_session_large_dep_tree) || + !CU_add_test(pSuite, "session_graceful_shutdown", + test_nghttp2_session_graceful_shutdown) || + !CU_add_test(pSuite, "session_on_header_temporal_failure", + test_nghttp2_session_on_header_temporal_failure) || + !CU_add_test(pSuite, "session_recv_client_magic", + test_nghttp2_session_recv_client_magic) || + !CU_add_test(pSuite, "session_delete_data_item", + test_nghttp2_session_delete_data_item) || + !CU_add_test(pSuite, "session_open_idle_stream", + test_nghttp2_session_open_idle_stream) || + !CU_add_test(pSuite, "session_cancel_reserved_remote", + test_nghttp2_session_cancel_reserved_remote) || + !CU_add_test(pSuite, "session_reset_pending_headers", + test_nghttp2_session_reset_pending_headers) || + !CU_add_test(pSuite, "session_send_data_callback", + test_nghttp2_session_send_data_callback) || + !CU_add_test(pSuite, "session_on_begin_headers_temporal_failure", + test_nghttp2_session_on_begin_headers_temporal_failure) || + !CU_add_test(pSuite, "session_defer_then_close", + test_nghttp2_session_defer_then_close) || + !CU_add_test(pSuite, "session_detach_item_from_closed_stream", + test_nghttp2_session_detach_item_from_closed_stream) || + !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) || + !CU_add_test(pSuite, "session_change_stream_priority", + test_nghttp2_session_change_stream_priority) || + !CU_add_test(pSuite, "session_change_extpri_stream_priority", + test_nghttp2_session_change_extpri_stream_priority) || + !CU_add_test(pSuite, "session_create_idle_stream", + test_nghttp2_session_create_idle_stream) || + !CU_add_test(pSuite, "session_repeated_priority_change", + test_nghttp2_session_repeated_priority_change) || + !CU_add_test(pSuite, "session_repeated_priority_submission", + test_nghttp2_session_repeated_priority_submission) || + !CU_add_test(pSuite, "session_set_local_window_size", + test_nghttp2_session_set_local_window_size) || + !CU_add_test(pSuite, "session_cancel_from_before_frame_send", + test_nghttp2_session_cancel_from_before_frame_send) || + !CU_add_test(pSuite, "session_too_many_settings", + test_nghttp2_session_too_many_settings) || + !CU_add_test(pSuite, "session_removed_closed_stream", + test_nghttp2_session_removed_closed_stream) || + !CU_add_test(pSuite, "session_pause_data", + test_nghttp2_session_pause_data) || + !CU_add_test(pSuite, "session_no_closed_streams", + test_nghttp2_session_no_closed_streams) || + !CU_add_test(pSuite, "session_set_stream_user_data", + test_nghttp2_session_set_stream_user_data) || + !CU_add_test(pSuite, "session_no_rfc7540_priorities", + test_nghttp2_session_no_rfc7540_priorities) || + !CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities", + test_nghttp2_session_server_fallback_rfc7540_priorities) || + !CU_add_test(pSuite, "session_stream_reset_ratelim", + test_nghttp2_session_stream_reset_ratelim) || + !CU_add_test(pSuite, "http_mandatory_headers", + test_nghttp2_http_mandatory_headers) || + !CU_add_test(pSuite, "http_content_length", + test_nghttp2_http_content_length) || + !CU_add_test(pSuite, "http_content_length_mismatch", + test_nghttp2_http_content_length_mismatch) || + !CU_add_test(pSuite, "http_non_final_response", + test_nghttp2_http_non_final_response) || + !CU_add_test(pSuite, "http_trailer_headers", + test_nghttp2_http_trailer_headers) || + !CU_add_test(pSuite, "http_ignore_regular_header", + test_nghttp2_http_ignore_regular_header) || + !CU_add_test(pSuite, "http_ignore_content_length", + test_nghttp2_http_ignore_content_length) || + !CU_add_test(pSuite, "http_record_request_method", + test_nghttp2_http_record_request_method) || + !CU_add_test(pSuite, "http_push_promise", + test_nghttp2_http_push_promise) || + !CU_add_test(pSuite, "http_head_method_upgrade_workaround", + test_nghttp2_http_head_method_upgrade_workaround) || + !CU_add_test( + pSuite, "http_no_rfc9113_leading_and_trailing_ws_validation", + test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation) || + !CU_add_test(pSuite, "frame_pack_headers", + test_nghttp2_frame_pack_headers) || + !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", + test_nghttp2_frame_pack_headers_frame_too_large) || + !CU_add_test(pSuite, "frame_pack_priority", + test_nghttp2_frame_pack_priority) || + !CU_add_test(pSuite, "frame_pack_rst_stream", + test_nghttp2_frame_pack_rst_stream) || + !CU_add_test(pSuite, "frame_pack_settings", + test_nghttp2_frame_pack_settings) || + !CU_add_test(pSuite, "frame_pack_push_promise", + test_nghttp2_frame_pack_push_promise) || + !CU_add_test(pSuite, "frame_pack_ping", test_nghttp2_frame_pack_ping) || + !CU_add_test(pSuite, "frame_pack_goaway", + test_nghttp2_frame_pack_goaway) || + !CU_add_test(pSuite, "frame_pack_window_update", + test_nghttp2_frame_pack_window_update) || + !CU_add_test(pSuite, "frame_pack_altsvc", + test_nghttp2_frame_pack_altsvc) || + !CU_add_test(pSuite, "frame_pack_origin", + test_nghttp2_frame_pack_origin) || + !CU_add_test(pSuite, "frame_pack_priority_update", + test_nghttp2_frame_pack_priority_update) || + !CU_add_test(pSuite, "nv_array_copy", test_nghttp2_nv_array_copy) || + !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) || + !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || + !CU_add_test(pSuite, "hd_deflate_same_indexed_repr", + test_nghttp2_hd_deflate_same_indexed_repr) || + !CU_add_test(pSuite, "hd_inflate_indexed", + test_nghttp2_hd_inflate_indexed) || + !CU_add_test(pSuite, "hd_inflate_indname_noinc", + test_nghttp2_hd_inflate_indname_noinc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc", + test_nghttp2_hd_inflate_indname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", + test_nghttp2_hd_inflate_indname_inc_eviction) || + !CU_add_test(pSuite, "hd_inflate_newname_noinc", + test_nghttp2_hd_inflate_newname_noinc) || + !CU_add_test(pSuite, "hd_inflate_newname_inc", + test_nghttp2_hd_inflate_newname_inc) || + !CU_add_test(pSuite, "hd_inflate_clearall_inc", + test_nghttp2_hd_inflate_clearall_inc) || + !CU_add_test(pSuite, "hd_inflate_zero_length_huffman", + test_nghttp2_hd_inflate_zero_length_huffman) || + !CU_add_test(pSuite, "hd_inflate_expect_table_size_update", + test_nghttp2_hd_inflate_expect_table_size_update) || + !CU_add_test(pSuite, "hd_inflate_unexpected_table_size_update", + test_nghttp2_hd_inflate_unexpected_table_size_update) || + !CU_add_test(pSuite, "hd_ringbuf_reserve", + test_nghttp2_hd_ringbuf_reserve) || + !CU_add_test(pSuite, "hd_change_table_size", + test_nghttp2_hd_change_table_size) || + !CU_add_test(pSuite, "hd_deflate_inflate", + test_nghttp2_hd_deflate_inflate) || + !CU_add_test(pSuite, "hd_no_index", test_nghttp2_hd_no_index) || + !CU_add_test(pSuite, "hd_deflate_bound", test_nghttp2_hd_deflate_bound) || + !CU_add_test(pSuite, "hd_public_api", test_nghttp2_hd_public_api) || + !CU_add_test(pSuite, "hd_deflate_hd_vec", + test_nghttp2_hd_deflate_hd_vec) || + !CU_add_test(pSuite, "hd_decode_length", test_nghttp2_hd_decode_length) || + !CU_add_test(pSuite, "hd_huff_encode", test_nghttp2_hd_huff_encode) || + !CU_add_test(pSuite, "hd_huff_decode", test_nghttp2_hd_huff_decode) || + !CU_add_test(pSuite, "adjust_local_window_size", + test_nghttp2_adjust_local_window_size) || + !CU_add_test(pSuite, "check_header_name", + test_nghttp2_check_header_name) || + !CU_add_test(pSuite, "check_header_value", + test_nghttp2_check_header_value) || + !CU_add_test(pSuite, "check_header_value_rfc9113", + test_nghttp2_check_header_value_rfc9113) || + !CU_add_test(pSuite, "bufs_add", test_nghttp2_bufs_add) || + !CU_add_test(pSuite, "bufs_add_stack_buffer_overflow_bug", + test_nghttp2_bufs_add_stack_buffer_overflow_bug) || + !CU_add_test(pSuite, "bufs_addb", test_nghttp2_bufs_addb) || + !CU_add_test(pSuite, "bufs_orb", test_nghttp2_bufs_orb) || + !CU_add_test(pSuite, "bufs_remove", test_nghttp2_bufs_remove) || + !CU_add_test(pSuite, "bufs_reset", test_nghttp2_bufs_reset) || + !CU_add_test(pSuite, "bufs_advance", test_nghttp2_bufs_advance) || + !CU_add_test(pSuite, "bufs_next_present", + test_nghttp2_bufs_next_present) || + !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc) || + !CU_add_test(pSuite, "http_parse_priority", + test_nghttp2_http_parse_priority) || + !CU_add_test(pSuite, "extpri_to_uint8", test_nghttp2_extpri_to_uint8) || + !CU_add_test(pSuite, "ratelim_update", test_nghttp2_ratelim_update) || + !CU_add_test(pSuite, "ratelim_drain", test_nghttp2_ratelim_drain)) { + CU_cleanup_registry(); + return (int)CU_get_error(); + } + + /* Run all tests using the CUnit Basic interface */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_tests_failed = CU_get_number_of_tests_failed(); + CU_cleanup_registry(); + if (CU_get_error() == CUE_SUCCESS) { + return (int)num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return (int)CU_get_error(); + } +} diff --git a/tests/malloc_wrapper.c b/tests/malloc_wrapper.c new file mode 100644 index 0000000..f814c3d --- /dev/null +++ b/tests/malloc_wrapper.c @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "malloc_wrapper.h" + +int nghttp2_failmalloc = 0; +int nghttp2_failstart = 0; +int nghttp2_countmalloc = 1; +int nghttp2_nmalloc = 0; + +#define CHECK_PREREQ \ + do { \ + if (nghttp2_failmalloc && nghttp2_nmalloc >= nghttp2_failstart) { \ + return NULL; \ + } \ + if (nghttp2_countmalloc) { \ + ++nghttp2_nmalloc; \ + } \ + } while (0) + +static void *my_malloc(size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return malloc(size); +} + +static void my_free(void *ptr, void *mud) { + (void)mud; + + free(ptr); +} + +static void *my_calloc(size_t nmemb, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return calloc(nmemb, size); +} + +static void *my_realloc(void *ptr, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return realloc(ptr, size); +} + +static nghttp2_mem mem = {NULL, my_malloc, my_free, my_calloc, my_realloc}; + +nghttp2_mem *nghttp2_mem_fm(void) { return &mem; } + +static int failmalloc_bk, countmalloc_bk; + +void nghttp2_failmalloc_pause(void) { + failmalloc_bk = nghttp2_failmalloc; + countmalloc_bk = nghttp2_countmalloc; + nghttp2_failmalloc = 0; + nghttp2_countmalloc = 0; +} + +void nghttp2_failmalloc_unpause(void) { + nghttp2_failmalloc = failmalloc_bk; + nghttp2_countmalloc = countmalloc_bk; +} diff --git a/tests/malloc_wrapper.h b/tests/malloc_wrapper.h new file mode 100644 index 0000000..a3a3dd7 --- /dev/null +++ b/tests/malloc_wrapper.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef MALLOC_WRAPPER_H +#define MALLOC_WRAPPER_H + +#ifdef HAVE_CONFIG_H +# include +#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/tests/nghttp2_alpn_test.c b/tests/nghttp2_alpn_test.c new file mode 100644 index 0000000..d471d5f --- /dev/null +++ b/tests/nghttp2_alpn_test.c @@ -0,0 +1,95 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_alpn_test.h" + +#include +#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; + const unsigned char *out; + CU_ASSERT(1 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen, + p, sizeof(p))); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0); + + outlen = 0; + out = NULL; + + CU_ASSERT(1 == nghttp2_select_alpn(&out, &outlen, p, sizeof(p))); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0); +} + +static void http11(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + unsigned char outlen; + const unsigned char *out; + CU_ASSERT(0 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen, + spdy, sizeof(spdy))); + CU_ASSERT(8 == outlen); + CU_ASSERT(memcmp("http/1.1", out, outlen) == 0); + + outlen = 0; + out = NULL; + + CU_ASSERT(0 == nghttp2_select_alpn(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(8 == outlen); + CU_ASSERT(memcmp("http/1.1", out, outlen) == 0); +} + +static void no_overlap(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '0', + }; + unsigned char outlen = 0; + const unsigned char *out = NULL; + CU_ASSERT(-1 == nghttp2_select_next_protocol((unsigned char **)&out, &outlen, + spdy, sizeof(spdy))); + CU_ASSERT(0 == outlen); + CU_ASSERT(NULL == out); + + outlen = 0; + out = NULL; + + CU_ASSERT(-1 == nghttp2_select_alpn(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(0 == outlen); + CU_ASSERT(NULL == out); +} + +void test_nghttp2_alpn(void) { + http2(); + http11(); + no_overlap(); +} diff --git a/tests/nghttp2_alpn_test.h b/tests/nghttp2_alpn_test.h new file mode 100644 index 0000000..f797f9e --- /dev/null +++ b/tests/nghttp2_alpn_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_ALPN_TEST_H +#define NGHTTP2_ALPN_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_alpn(void); + +#endif /* NGHTTP2_ALPN_TEST_H */ diff --git a/tests/nghttp2_buf_test.c b/tests/nghttp2_buf_test.c new file mode 100644 index 0000000..e3e8a14 --- /dev/null +++ b/tests/nghttp2_buf_test.c @@ -0,0 +1,344 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_buf_test.h" + +#include + +#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/tests/nghttp2_buf_test.h b/tests/nghttp2_buf_test.h new file mode 100644 index 0000000..714b89f --- /dev/null +++ b/tests/nghttp2_buf_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_BUF_TEST_H +#define NGHTTP2_BUF_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_bufs_add(void); +void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void); +void test_nghttp2_bufs_addb(void); +void test_nghttp2_bufs_orb(void); +void test_nghttp2_bufs_remove(void); +void test_nghttp2_bufs_reset(void); +void test_nghttp2_bufs_advance(void); +void test_nghttp2_bufs_next_present(void); +void test_nghttp2_bufs_realloc(void); + +#endif /* NGHTTP2_BUF_TEST_H */ diff --git a/tests/nghttp2_extpri_test.c b/tests/nghttp2_extpri_test.c new file mode 100644 index 0000000..0ef59b7 --- /dev/null +++ b/tests/nghttp2_extpri_test.c @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_extpri_test.h" + +#include + +#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/tests/nghttp2_extpri_test.h b/tests/nghttp2_extpri_test.h new file mode 100644 index 0000000..a8a93b9 --- /dev/null +++ b/tests/nghttp2_extpri_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_EXTPRI_TEST_H +#define NGHTTP2_EXTPRI_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_extpri_to_uint8(void); + +#endif /* NGHTTP2_EXTPRI_TEST_H */ diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c new file mode 100644 index 0000000..7ce98dd --- /dev/null +++ b/tests/nghttp2_frame_test.c @@ -0,0 +1,735 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_frame_test.h" + +#include +#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/tests/nghttp2_frame_test.h b/tests/nghttp2_frame_test.h new file mode 100644 index 0000000..dc07625 --- /dev/null +++ b/tests/nghttp2_frame_test.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_FRAME_TEST_H +#define NGHTTP2_FRAME_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_frame_pack_headers(void); +void test_nghttp2_frame_pack_headers_frame_too_large(void); +void test_nghttp2_frame_pack_priority(void); +void test_nghttp2_frame_pack_rst_stream(void); +void test_nghttp2_frame_pack_settings(void); +void test_nghttp2_frame_pack_push_promise(void); +void test_nghttp2_frame_pack_ping(void); +void test_nghttp2_frame_pack_goaway(void); +void test_nghttp2_frame_pack_window_update(void); +void test_nghttp2_frame_pack_altsvc(void); +void test_nghttp2_frame_pack_origin(void); +void test_nghttp2_frame_pack_priority_update(void); +void test_nghttp2_nv_array_copy(void); +void test_nghttp2_iv_check(void); + +#endif /* NGHTTP2_FRAME_TEST_H */ diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c new file mode 100644 index 0000000..657d895 --- /dev/null +++ b/tests/nghttp2_hd_test.c @@ -0,0 +1,1577 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_hd_test.h" + +#include +#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/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h new file mode 100644 index 0000000..ab0117c --- /dev/null +++ b/tests/nghttp2_hd_test.h @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HD_TEST_H +#define NGHTTP2_HD_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_hd_deflate(void); +void test_nghttp2_hd_deflate_same_indexed_repr(void); +void test_nghttp2_hd_inflate_indexed(void); +void test_nghttp2_hd_inflate_indname_noinc(void); +void test_nghttp2_hd_inflate_indname_inc(void); +void test_nghttp2_hd_inflate_indname_inc_eviction(void); +void test_nghttp2_hd_inflate_newname_noinc(void); +void test_nghttp2_hd_inflate_newname_inc(void); +void test_nghttp2_hd_inflate_clearall_inc(void); +void test_nghttp2_hd_inflate_zero_length_huffman(void); +void test_nghttp2_hd_inflate_expect_table_size_update(void); +void test_nghttp2_hd_inflate_unexpected_table_size_update(void); +void test_nghttp2_hd_ringbuf_reserve(void); +void test_nghttp2_hd_change_table_size(void); +void test_nghttp2_hd_deflate_inflate(void); +void test_nghttp2_hd_no_index(void); +void test_nghttp2_hd_deflate_bound(void); +void test_nghttp2_hd_public_api(void); +void test_nghttp2_hd_deflate_hd_vec(void); +void test_nghttp2_hd_decode_length(void); +void test_nghttp2_hd_huff_encode(void); +void test_nghttp2_hd_huff_decode(void); + +#endif /* NGHTTP2_HD_TEST_H */ diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c new file mode 100644 index 0000000..377f49d --- /dev/null +++ b/tests/nghttp2_helper_test.c @@ -0,0 +1,195 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_helper_test.h" + +#include + +#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/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h new file mode 100644 index 0000000..8790dcf --- /dev/null +++ b/tests/nghttp2_helper_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HELPER_TEST_H +#define NGHTTP2_HELPER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_adjust_local_window_size(void); +void test_nghttp2_check_header_name(void); +void test_nghttp2_check_header_value(void); +void test_nghttp2_check_header_value_rfc9113(void); + +#endif /* NGHTTP2_HELPER_TEST_H */ diff --git a/tests/nghttp2_http_test.c b/tests/nghttp2_http_test.c new file mode 100644 index 0000000..19f345b --- /dev/null +++ b/tests/nghttp2_http_test.c @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_http_test.h" + +#include +#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/tests/nghttp2_http_test.h b/tests/nghttp2_http_test.h new file mode 100644 index 0000000..e616cdc --- /dev/null +++ b/tests/nghttp2_http_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HTTP_TEST_H +#define NGHTTP2_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_http_parse_priority(void); + +#endif /* NGHTTP2_HTTP_TEST_H */ diff --git a/tests/nghttp2_map_test.c b/tests/nghttp2_map_test.c new file mode 100644 index 0000000..7ba9bd6 --- /dev/null +++ b/tests/nghttp2_map_test.c @@ -0,0 +1,208 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_map_test.h" + +#include + +#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/tests/nghttp2_map_test.h b/tests/nghttp2_map_test.h new file mode 100644 index 0000000..235624d --- /dev/null +++ b/tests/nghttp2_map_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_MAP_TEST_H +#define NGHTTP2_MAP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_map(void); +void test_nghttp2_map_functional(void); +void test_nghttp2_map_each_free(void); +void test_nghttp2_map_clear(void); + +#endif /* NGHTTP2_MAP_TEST_H */ diff --git a/tests/nghttp2_pq_test.c b/tests/nghttp2_pq_test.c new file mode 100644 index 0000000..90db26d --- /dev/null +++ b/tests/nghttp2_pq_test.c @@ -0,0 +1,228 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_pq_test.h" + +#include + +#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/tests/nghttp2_pq_test.h b/tests/nghttp2_pq_test.h new file mode 100644 index 0000000..969662a --- /dev/null +++ b/tests/nghttp2_pq_test.h @@ -0,0 +1,36 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_PQ_TEST_H +#define NGHTTP2_PQ_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_pq(void); +void test_nghttp2_pq_update(void); +void test_nghttp2_pq_remove(void); + +#endif /* NGHTTP2_PQ_TEST_H */ diff --git a/tests/nghttp2_queue_test.c b/tests/nghttp2_queue_test.c new file mode 100644 index 0000000..cb993a8 --- /dev/null +++ b/tests/nghttp2_queue_test.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_queue_test.h" + +#include + +#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/tests/nghttp2_queue_test.h b/tests/nghttp2_queue_test.h new file mode 100644 index 0000000..64f8ce8 --- /dev/null +++ b/tests/nghttp2_queue_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_QUEUE_TEST_H +#define NGHTTP2_QUEUE_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_queue(void); + +#endif /* NGHTTP2_QUEUE_TEST_H */ diff --git a/tests/nghttp2_ratelim_test.c b/tests/nghttp2_ratelim_test.c new file mode 100644 index 0000000..6abece9 --- /dev/null +++ b/tests/nghttp2_ratelim_test.c @@ -0,0 +1,101 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_ratelim_test.h" + +#include + +#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/tests/nghttp2_ratelim_test.h b/tests/nghttp2_ratelim_test.h new file mode 100644 index 0000000..02b2f2b --- /dev/null +++ b/tests/nghttp2_ratelim_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_RATELIM_TEST_H +#define NGHTTP2_RATELIM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_ratelim_update(void); +void test_nghttp2_ratelim_drain(void); + +#endif /* NGHTTP2_RATELIM_TEST_H */ diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c new file mode 100644 index 0000000..9f6a667 --- /dev/null +++ b/tests/nghttp2_session_test.c @@ -0,0 +1,13438 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_session_test.h" + +#include +#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/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h new file mode 100644 index 0000000..be4fdf8 --- /dev/null +++ b/tests/nghttp2_session_test.h @@ -0,0 +1,184 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_SESSION_TEST_H +#define NGHTTP2_SESSION_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_recv(void); +void test_nghttp2_session_recv_invalid_stream_id(void); +void test_nghttp2_session_recv_invalid_frame(void); +void test_nghttp2_session_recv_eof(void); +void test_nghttp2_session_recv_data(void); +void test_nghttp2_session_recv_data_no_auto_flow_control(void); +void test_nghttp2_session_recv_continuation(void); +void test_nghttp2_session_recv_headers_with_priority(void); +void test_nghttp2_session_recv_headers_with_padding(void); +void test_nghttp2_session_recv_headers_early_response(void); +void test_nghttp2_session_recv_headers_for_closed_stream(void); +void test_nghttp2_session_recv_headers_with_extpri(void); +void test_nghttp2_session_server_recv_push_response(void); +void test_nghttp2_session_recv_premature_headers(void); +void test_nghttp2_session_recv_unknown_frame(void); +void test_nghttp2_session_recv_unexpected_continuation(void); +void test_nghttp2_session_recv_settings_header_table_size(void); +void test_nghttp2_session_recv_too_large_frame_length(void); +void test_nghttp2_session_recv_extension(void); +void test_nghttp2_session_recv_altsvc(void); +void test_nghttp2_session_recv_origin(void); +void test_nghttp2_session_recv_priority_update(void); +void test_nghttp2_session_continue(void); +void test_nghttp2_session_add_frame(void); +void test_nghttp2_session_on_request_headers_received(void); +void test_nghttp2_session_on_response_headers_received(void); +void test_nghttp2_session_on_headers_received(void); +void test_nghttp2_session_on_push_response_headers_received(void); +void test_nghttp2_session_on_priority_received(void); +void test_nghttp2_session_on_rst_stream_received(void); +void test_nghttp2_session_on_settings_received(void); +void test_nghttp2_session_on_push_promise_received(void); +void test_nghttp2_session_on_ping_received(void); +void test_nghttp2_session_on_goaway_received(void); +void test_nghttp2_session_on_window_update_received(void); +void test_nghttp2_session_on_data_received(void); +void test_nghttp2_session_on_data_received_fail_fast(void); +void test_nghttp2_session_on_altsvc_received(void); +void test_nghttp2_session_send_headers_start_stream(void); +void test_nghttp2_session_send_headers_reply(void); +void test_nghttp2_session_send_headers_frame_size_error(void); +void test_nghttp2_session_send_headers_push_reply(void); +void test_nghttp2_session_send_rst_stream(void); +void test_nghttp2_session_send_push_promise(void); +void test_nghttp2_session_is_my_stream_id(void); +void test_nghttp2_session_upgrade2(void); +void test_nghttp2_session_reprioritize_stream(void); +void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void); +void test_nghttp2_submit_data(void); +void test_nghttp2_submit_data_read_length_too_large(void); +void test_nghttp2_submit_data_read_length_smallest(void); +void test_nghttp2_submit_data_twice(void); +void test_nghttp2_submit_request_with_data(void); +void test_nghttp2_submit_request_without_data(void); +void test_nghttp2_submit_response_with_data(void); +void test_nghttp2_submit_response_without_data(void); +void test_nghttp2_submit_response_push_response(void); +void test_nghttp2_submit_trailer(void); +void test_nghttp2_submit_headers_start_stream(void); +void test_nghttp2_submit_headers_reply(void); +void test_nghttp2_submit_headers_push_reply(void); +void test_nghttp2_submit_headers(void); +void test_nghttp2_submit_headers_continuation(void); +void test_nghttp2_submit_headers_continuation_extra_large(void); +void test_nghttp2_submit_priority(void); +void test_nghttp2_submit_settings(void); +void test_nghttp2_submit_settings_update_local_window_size(void); +void test_nghttp2_submit_settings_multiple_times(void); +void test_nghttp2_submit_push_promise(void); +void test_nghttp2_submit_window_update(void); +void test_nghttp2_submit_window_update_local_window_size(void); +void test_nghttp2_submit_shutdown_notice(void); +void test_nghttp2_submit_invalid_nv(void); +void test_nghttp2_submit_extension(void); +void test_nghttp2_submit_altsvc(void); +void test_nghttp2_submit_origin(void); +void test_nghttp2_submit_priority_update(void); +void test_nghttp2_submit_rst_stream(void); +void test_nghttp2_session_open_stream(void); +void test_nghttp2_session_open_stream_with_idle_stream_dep(void); +void test_nghttp2_session_get_next_ob_item(void); +void test_nghttp2_session_pop_next_ob_item(void); +void test_nghttp2_session_reply_fail(void); +void test_nghttp2_session_max_concurrent_streams(void); +void test_nghttp2_session_stop_data_with_rst_stream(void); +void test_nghttp2_session_defer_data(void); +void test_nghttp2_session_flow_control(void); +void test_nghttp2_session_flow_control_data_recv(void); +void test_nghttp2_session_flow_control_data_with_padding_recv(void); +void test_nghttp2_session_data_read_temporal_failure(void); +void test_nghttp2_session_on_stream_close(void); +void test_nghttp2_session_on_ctrl_not_send(void); +void test_nghttp2_session_get_outbound_queue_size(void); +void test_nghttp2_session_get_effective_local_window_size(void); +void test_nghttp2_session_set_option(void); +void test_nghttp2_session_data_backoff_by_high_pri_frame(void); +void test_nghttp2_session_pack_data_with_padding(void); +void test_nghttp2_session_pack_headers_with_padding(void); +void test_nghttp2_pack_settings_payload(void); +void test_nghttp2_session_stream_dep_add(void); +void test_nghttp2_session_stream_dep_remove(void); +void test_nghttp2_session_stream_dep_add_subtree(void); +void test_nghttp2_session_stream_dep_remove_subtree(void); +void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void); +void test_nghttp2_session_stream_attach_item(void); +void test_nghttp2_session_stream_attach_item_subtree(void); +void test_nghttp2_session_stream_get_state(void); +void test_nghttp2_session_stream_get_something(void); +void test_nghttp2_session_find_stream(void); +void test_nghttp2_session_keep_closed_stream(void); +void test_nghttp2_session_keep_idle_stream(void); +void test_nghttp2_session_detach_idle_stream(void); +void test_nghttp2_session_large_dep_tree(void); +void test_nghttp2_session_graceful_shutdown(void); +void test_nghttp2_session_on_header_temporal_failure(void); +void test_nghttp2_session_recv_client_magic(void); +void test_nghttp2_session_delete_data_item(void); +void test_nghttp2_session_open_idle_stream(void); +void test_nghttp2_session_cancel_reserved_remote(void); +void test_nghttp2_session_reset_pending_headers(void); +void test_nghttp2_session_send_data_callback(void); +void test_nghttp2_session_on_begin_headers_temporal_failure(void); +void test_nghttp2_session_defer_then_close(void); +void test_nghttp2_session_detach_item_from_closed_stream(void); +void test_nghttp2_session_flooding(void); +void test_nghttp2_session_change_stream_priority(void); +void test_nghttp2_session_change_extpri_stream_priority(void); +void test_nghttp2_session_create_idle_stream(void); +void test_nghttp2_session_repeated_priority_change(void); +void test_nghttp2_session_repeated_priority_submission(void); +void test_nghttp2_session_set_local_window_size(void); +void test_nghttp2_session_cancel_from_before_frame_send(void); +void test_nghttp2_session_too_many_settings(void); +void test_nghttp2_session_removed_closed_stream(void); +void test_nghttp2_session_pause_data(void); +void test_nghttp2_session_no_closed_streams(void); +void test_nghttp2_session_set_stream_user_data(void); +void test_nghttp2_session_no_rfc7540_priorities(void); +void test_nghttp2_session_server_fallback_rfc7540_priorities(void); +void test_nghttp2_session_stream_reset_ratelim(void); +void test_nghttp2_http_mandatory_headers(void); +void test_nghttp2_http_content_length(void); +void test_nghttp2_http_content_length_mismatch(void); +void test_nghttp2_http_non_final_response(void); +void test_nghttp2_http_trailer_headers(void); +void test_nghttp2_http_ignore_regular_header(void); +void test_nghttp2_http_ignore_content_length(void); +void test_nghttp2_http_record_request_method(void); +void test_nghttp2_http_push_promise(void); +void test_nghttp2_http_head_method_upgrade_workaround(void); +void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void); + +#endif /* NGHTTP2_SESSION_TEST_H */ diff --git a/tests/nghttp2_stream_test.c b/tests/nghttp2_stream_test.c new file mode 100644 index 0000000..75b4d68 --- /dev/null +++ b/tests/nghttp2_stream_test.c @@ -0,0 +1,31 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_stream_test.h" + +#include + +#include + +#include "nghttp2_stream.h" diff --git a/tests/nghttp2_stream_test.h b/tests/nghttp2_stream_test.h new file mode 100644 index 0000000..ad7be64 --- /dev/null +++ b/tests/nghttp2_stream_test.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_STREAM_TEST_H +#define NGHTTP2_STREAM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#endif /* NGHTTP2_STREAM_TEST_H */ diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c new file mode 100644 index 0000000..1bd4a63 --- /dev/null +++ b/tests/nghttp2_test_helper.c @@ -0,0 +1,435 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_test_helper.h" + +#include +#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/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h new file mode 100644 index 0000000..c66298a --- /dev/null +++ b/tests/nghttp2_test_helper.h @@ -0,0 +1,158 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_TEST_HELPER_H +#define NGHTTP2_TEST_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include "nghttp2_frame.h" +#include "nghttp2_hd.h" +#include "nghttp2_session.h" + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)(NAME), (uint8_t *)(VALUE), sizeof((NAME)) - 1, \ + sizeof((VALUE)) - 1, NGHTTP2_NV_FLAG_NONE \ + } +#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0])) + +#define assert_nv_equal(A, B, len, mem) \ + do { \ + size_t alloclen = sizeof(nghttp2_nv) * len; \ + const nghttp2_nv *sa = A, *sb = B; \ + nghttp2_nv *a = mem->malloc(alloclen, NULL); \ + nghttp2_nv *b = mem->malloc(alloclen, NULL); \ + ssize_t i_; \ + memcpy(a, sa, alloclen); \ + memcpy(b, sb, alloclen); \ + nghttp2_nv_array_sort(a, len); \ + nghttp2_nv_array_sort(b, len); \ + for (i_ = 0; i_ < (ssize_t)len; ++i_) { \ + CU_ASSERT(nghttp2_nv_equal(&a[i_], &b[i_])); \ + } \ + mem->free(b, NULL); \ + mem->free(a, NULL); \ + } while (0); + +int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs); + +int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len); + +int strmemeq(const char *a, const uint8_t *b, size_t bn); + +int nvnameeq(const char *a, nghttp2_nv *nv); + +int nvvalueeq(const char *a, nghttp2_nv *nv); + +typedef struct { + nghttp2_nv nva[256]; + size_t nvlen; +} nva_out; + +void nva_out_init(nva_out *out); +void nva_out_reset(nva_out *out, nghttp2_mem *mem); + +void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem); + +ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, + nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem); + +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, + int32_t promised_stream_id, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int frame_pack_bufs_init(nghttp2_bufs *bufs); + +void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size); + +nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, int32_t weight, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_outbound_item *create_data_ob_item(nghttp2_mem *mem); + +/* Opens stream. This stream is assumed to be sent from |session|, + and session->last_sent_stream_id and session->next_stream_id will + be adjusted accordingly. */ +nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_sent_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +/* Opens stream. This stream is assumed to be received by |session|, + and session->last_recv_stream_id will be adjusted accordingly. */ +nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_recv_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +#endif /* NGHTTP2_TEST_HELPER_H */ diff --git a/tests/testdata/Makefile.am b/tests/testdata/Makefile.am new file mode 100644 index 0000000..ee38113 --- /dev/null +++ b/tests/testdata/Makefile.am @@ -0,0 +1,23 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = cacert.pem index.html privkey.pem diff --git a/tests/testdata/cacert.pem b/tests/testdata/cacert.pem new file mode 100644 index 0000000..e462790 --- /dev/null +++ b/tests/testdata/cacert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKTCCAdOgAwIBAgIJAIsolheWrwMZMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEQ2l0eTESMBAGA1UECgwJU3Bk +eSBUZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnNwZHlA +bG9jYWxob3N0MB4XDTEyMDMwMTE5MTI0NVoXDTIzMDUxOTE5MTI0NVowcDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARDaXR5MRIwEAYDVQQKDAlT +cGR5IFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOc3Bk +eUBsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAw/2MgzAdlJDm29qH +ZlAibgs9mH+8keOtsRrb4B1PiCcZoHvN9eCVZ4WnzT+0zhHF+nO3YfwVFVC3w7TF +7fLB3QIDAQABo1AwTjAdBgNVHQ4EFgQUVP2Jw9RX6BB76aV5x2qk5qsrAIQwHwYD +VR0jBBgwFoAUVP2Jw9RX6BB76aV5x2qk5qsrAIQwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQUFAANBAKd9M5FzQLEZW1KPe9/XNZlgxZ2g3EC5Krxo5I4Ul3MnIYS9 +u4K8t/iprhgOzjFH6+8LVk9v0Za+gU+K43CpUo4= +-----END CERTIFICATE----- diff --git a/tests/testdata/index.html b/tests/testdata/index.html new file mode 100644 index 0000000..cdd75fc --- /dev/null +++ b/tests/testdata/index.html @@ -0,0 +1 @@ +small diff --git a/tests/testdata/privkey.pem b/tests/testdata/privkey.pem new file mode 100644 index 0000000..0ab825b --- /dev/null +++ b/tests/testdata/privkey.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAMP9jIMwHZSQ5tvah2ZQIm4LPZh/vJHjrbEa2+AdT4gnGaB7zfXg +lWeFp80/tM4Rxfpzt2H8FRVQt8O0xe3ywd0CAwEAAQJBAIQ8PGP/QNYOdlT8OsLj +aneJCgQsm1Rro7ONBbFO1WxslvA6+uJsx4Rs8zLiS8cyqmJ/lmGa7zhwYSOvFQPa +XgECIQDgIcgM/2C67peTm1diKKIoGVVKFCfdRi+Dje6mTl2TQQIhAN/bcFWbG73j +cUVlIsr9Wk1dJzjPPWKeyirF1qd/WbOdAiEApTsCOeLCssxV3jF02B5QfPNAFx6I +zO2C9Z7awque/IECIGCHW3VOoTPMs7dc2Rf3D810cclJdArmtf6juOAZRjDxAiBS +AC+H685IBJ99N5nCbF9NWYIVSkuiKVQ8POYVZX+0Jg== +-----END RSA PRIVATE KEY----- diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt new file mode 100644 index 0000000..b0ef060 --- /dev/null +++ b/third-party/CMakeLists.txt @@ -0,0 +1,81 @@ +if(ENABLE_THIRD_PARTY) + set(LIBLLHTTP_SOURCES + llhttp/src/api.c + llhttp/src/http.c + llhttp/src/llhttp.c + ) + add_library(llhttp OBJECT ${LIBLLHTTP_SOURCES}) + target_include_directories(llhttp PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/llhttp/include" + ) + set_target_properties(llhttp PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) + + set(LIBURL_PARSER_SOURCES + url-parser/url_parser.c + ) + add_library(url-parser OBJECT ${LIBURL_PARSER_SOURCES}) + set_target_properties(url-parser PROPERTIES + POSITION_INDEPENDENT_CODE ON) + + if(HAVE_NEVERBLEED) + set(NEVERBLEED_SOURCES + neverbleed/neverbleed.c + ) + add_library(neverbleed ${NEVERBLEED_SOURCES}) + target_include_directories(neverbleed PRIVATE ${OPENSSL_INCLUDE_DIRS}) + target_include_directories(neverbleed INTERFACE + "${CMAKE_SOURCE_DIR}/third-party/neverbleed" + ) + target_link_libraries(neverbleed ${OPENSSL_LIBRARIES}) + endif() + + if(HAVE_MRUBY) + # EXTRA_DIST = build_config.rb mruby/* + + set(MRUBY_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/mruby/build") + set(MRUBY_LIBRARY + "${CMAKE_STATIC_LIBRARY_PREFIX}mruby${CMAKE_STATIC_LIBRARY_SUFFIX}" + ) + + # The mruby build needs some env vars. Alternatively, look at cmake -P + if(CMAKE_VERSION VERSION_LESS "3.1") + # XXX works only for Unixes? + set(ENV_COMMAND env) + else() + set(ENV_COMMAND ${CMAKE_COMMAND} -E env) + endif() + # Required for the Ninja generator. For older CMake, you first have to + # invoke 'ninja mruby' before building dependents. + if(CMAKE_VERSION VERSION_LESS "3.2") + set(_byproducts) + else() + set(_byproducts BYPRODUCTS "mruby/build/lib/${MRUBY_LIBRARY}") + endif() + add_custom_target(mruby + COMMAND ${ENV_COMMAND} + "MRUBY_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/build_config.rb" + "BUILD_DIR=${MRUBY_BUILD_DIR}" + "INSTALL_DIR=${MRUBY_BUILD_DIR}/install/bin" + "MRUBY_CC=${CMAKE_C_COMPILER}" "MRUBY_CXX=${CMAKE_CXX_COMPILER}" + "${CMAKE_CURRENT_SOURCE_DIR}/mruby/minirake" + -f "${CMAKE_CURRENT_SOURCE_DIR}/mruby/Rakefile" + ${_byproducts} + VERBATIM + ) + + # Make the mruby library available to others in this project without them + # having to worry about include dirs and the mruby location. + add_library(mruby-lib STATIC IMPORTED GLOBAL) + set_target_properties(mruby-lib PROPERTIES + IMPORTED_LOCATION "${MRUBY_BUILD_DIR}/lib/${MRUBY_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/mruby/include" + ) + add_dependencies(mruby-lib mruby) + + set_directory_properties(PROPERTIES + ADDITIONAL_MAKE_CLEAN_FILES mruby/build + ) + endif() +endif() diff --git a/third-party/Makefile.am b/third-party/Makefile.am new file mode 100644 index 0000000..8b47532 --- /dev/null +++ b/third-party/Makefile.am @@ -0,0 +1,593 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2014 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AM_CPPFLAGS = @DEFS@ + +EXTRA_DIST = CMakeLists.txt build_config.rb + +# Enumerate all mruby files with the following command: +# find mruby -type f ! -ipath 'mruby/.*' | awk '{print "\t"$0" \\"}' +EXTRA_DIST += \ + mruby/AUTHORS \ + mruby/codespell.txt \ + mruby/CONTRIBUTING.md \ + mruby/NEWS \ + mruby/CODEOWNERS \ + mruby/appveyor.yml \ + mruby/benchmark/plot.gpl \ + mruby/benchmark/bm_ao_render.rb \ + mruby/benchmark/bm_fib.rb \ + mruby/benchmark/bm_so_lists.rb \ + mruby/benchmark/bm_app_lc_fizzbuzz.rb \ + mruby/Makefile \ + mruby/LEGAL \ + mruby/tasks/libmruby.rake \ + mruby/tasks/core.rake \ + mruby/tasks/toolchains/visualcpp.rake \ + mruby/tasks/toolchains/openwrt.rake \ + mruby/tasks/toolchains/android.rake \ + mruby/tasks/toolchains/gcc.rake \ + mruby/tasks/toolchains/clang.rake \ + mruby/tasks/doc.rake \ + mruby/tasks/benchmark.rake \ + mruby/tasks/mrbgems.rake \ + mruby/tasks/presym.rake \ + mruby/tasks/bin.rake \ + mruby/tasks/test.rake \ + mruby/tasks/mrblib.rake \ + mruby/README.md \ + mruby/oss-fuzz/ruby.proto \ + mruby/oss-fuzz/proto_to_ruby.cpp \ + mruby/oss-fuzz/mruby_proto_fuzzer.cpp \ + mruby/oss-fuzz/config/mruby.dict \ + mruby/oss-fuzz/config/mruby_proto_fuzzer.options \ + mruby/oss-fuzz/config/mruby_fuzzer.options \ + mruby/oss-fuzz/mruby_fuzzer.c \ + mruby/oss-fuzz/proto_to_ruby.h \ + mruby/build_config/cross-mingw-winetest.rb \ + mruby/build_config/host-f32.rb \ + mruby/build_config/chipKITMax32.rb \ + mruby/build_config/gameboyadvance.rb \ + mruby/build_config/minimal.rb \ + mruby/build_config/host-m32.rb \ + mruby/build_config/host-shared.rb \ + mruby/build_config/host-debug.rb \ + mruby/build_config/bench.rb \ + mruby/build_config/host-cxx.rb \ + mruby/build_config/mrbc.rb \ + mruby/build_config/ArduinoDue.rb \ + mruby/build_config/IntelEdison.rb \ + mruby/build_config/boxing.rb \ + mruby/build_config/IntelGalileo.rb \ + mruby/build_config/ci/msvc.rb \ + mruby/build_config/ci/gcc-clang.rb \ + mruby/build_config/nintendo_switch.rb \ + mruby/build_config/serenity.rb \ + mruby/build_config/dreamcast_shelf.rb \ + mruby/build_config/default.rb \ + mruby/build_config/android_armeabi_v7a_neon_hard.rb \ + mruby/build_config/host-gprof.rb \ + mruby/build_config/clang-asan.rb \ + mruby/build_config/RX630.rb \ + mruby/build_config/host-nofloat.rb \ + mruby/build_config/android_armeabi.rb \ + mruby/build_config/android_arm64_v8a.rb \ + mruby/build_config/cross-32bit.rb \ + mruby/build_config/cross-mingw.rb \ + mruby/build_config/helpers/wine_runner.rb \ + mruby/lib/mruby/build/command.rb \ + mruby/lib/mruby/build/load_gems.rb \ + mruby/lib/mruby/lockfile.rb \ + mruby/lib/mruby/build.rb \ + mruby/lib/mruby/core_ext.rb \ + mruby/lib/mruby/doc.rb \ + mruby/lib/mruby/gem.rb \ + mruby/lib/mruby/presym.rb \ + mruby/lib/mruby/source.rb \ + mruby/examples/mrbgems/ruby_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/ruby_extension_example/README.md \ + mruby/examples/mrbgems/ruby_extension_example/test/example.rb \ + mruby/examples/mrbgems/ruby_extension_example/mrblib/example.rb \ + mruby/examples/mrbgems/cdata_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/cdata_extension_example/README.md \ + mruby/examples/mrbgems/cdata_extension_example/test/example.c \ + mruby/examples/mrbgems/cdata_extension_example/src/example.c \ + mruby/examples/mrbgems/c_and_ruby_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/c_and_ruby_extension_example/README.md \ + mruby/examples/mrbgems/c_and_ruby_extension_example/test/example.rb \ + mruby/examples/mrbgems/c_and_ruby_extension_example/mrblib/example.rb \ + mruby/examples/mrbgems/c_and_ruby_extension_example/src/example.c \ + mruby/examples/mrbgems/c_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/c_extension_example/README.md \ + mruby/examples/mrbgems/c_extension_example/test/example.rb \ + mruby/examples/mrbgems/c_extension_example/test/example.c \ + mruby/examples/mrbgems/c_extension_example/src/example.c \ + mruby/examples/mrbgems/mruby-YOUR-bigint/TODO-HINT.md \ + mruby/examples/mrbgems/mruby-YOUR-bigint/core/bigint.c \ + mruby/examples/mrbgems/mruby-YOUR-bigint/mrbgem.rake \ + mruby/test/bintest.rb \ + mruby/test/t/enumerable.rb \ + mruby/test/t/comparable.rb \ + mruby/test/t/false.rb \ + mruby/test/t/exception.rb \ + mruby/test/t/rangeerror.rb \ + mruby/test/t/bs_literal.rb \ + mruby/test/t/regexperror.rb \ + mruby/test/t/class.rb \ + mruby/test/t/iterations.rb \ + mruby/test/t/true.rb \ + mruby/test/t/lang.rb \ + mruby/test/t/range.rb \ + mruby/test/t/kernel.rb \ + mruby/test/t/unicode.rb \ + mruby/test/t/localjumperror.rb \ + mruby/test/t/hash.rb \ + mruby/test/t/syntax.rb \ + mruby/test/t/nameerror.rb \ + mruby/test/t/module.rb \ + mruby/test/t/argumenterror.rb \ + mruby/test/t/methods.rb \ + mruby/test/t/ensure.rb \ + mruby/test/t/indexerror.rb \ + mruby/test/t/runtimeerror.rb \ + mruby/test/t/gc.rb \ + mruby/test/t/array.rb \ + mruby/test/t/string.rb \ + mruby/test/t/proc.rb \ + mruby/test/t/basicobject.rb \ + mruby/test/t/integer.rb \ + mruby/test/t/nomethoderror.rb \ + mruby/test/t/codegen.rb \ + mruby/test/t/literals.rb \ + mruby/test/t/nil.rb \ + mruby/test/t/typeerror.rb \ + mruby/test/t/symbol.rb \ + mruby/test/t/object.rb \ + mruby/test/t/vformat.rb \ + mruby/test/t/numeric.rb \ + mruby/test/t/bs_block.rb \ + mruby/test/t/float.rb \ + mruby/test/t/standarderror.rb \ + mruby/test/t/superclass.rb \ + mruby/test/assert.rb \ + mruby/super-linter.report/.keep \ + mruby/TODO.md \ + mruby/Doxyfile \ + mruby/mrblib/00kernel.rb \ + mruby/mrblib/range.rb \ + mruby/mrblib/kernel.rb \ + mruby/mrblib/hash.rb \ + mruby/mrblib/00class.rb \ + mruby/mrblib/10error.rb \ + mruby/mrblib/array.rb \ + mruby/mrblib/string.rb \ + mruby/mrblib/compar.rb \ + mruby/mrblib/symbol.rb \ + mruby/mrblib/enum.rb \ + mruby/mrblib/numeric.rb \ + mruby/build_config.rb \ + mruby/LICENSE \ + mruby/src/etc.c \ + mruby/src/variable.c \ + mruby/src/cdump.c \ + mruby/src/init.c \ + mruby/src/opcode.h \ + mruby/src/gc.c \ + mruby/src/load.c \ + mruby/src/codedump.c \ + mruby/src/error.c \ + mruby/src/readfloat.c \ + mruby/src/state.c \ + mruby/src/string.c \ + mruby/src/numeric.c \ + mruby/src/readnum.c \ + mruby/src/enum.c \ + mruby/src/print.c \ + mruby/src/readint.c \ + mruby/src/numops.c \ + mruby/src/hash.c \ + mruby/src/version.c \ + mruby/src/backtrace.c \ + mruby/src/fmt_fp.c \ + mruby/src/range.c \ + mruby/src/vm.c \ + mruby/src/debug.c \ + mruby/src/symbol.c \ + mruby/src/error.h \ + mruby/src/kernel.c \ + mruby/src/object.c \ + mruby/src/proc.c \ + mruby/src/array.c \ + mruby/src/dump.c \ + mruby/src/class.c \ + mruby/src/compar.c \ + mruby/src/value_array.h \ + mruby/src/pool.c \ + mruby/mruby-source.gemspec \ + mruby/mrbgems/mruby-eval/mrbgem.rake \ + mruby/mrbgems/mruby-eval/test/eval.rb \ + mruby/mrbgems/mruby-eval/src/eval.c \ + mruby/mrbgems/stdlib.gembox \ + mruby/mrbgems/mruby-string-ext/mrbgem.rake \ + mruby/mrbgems/mruby-string-ext/test/range.rb \ + mruby/mrbgems/mruby-string-ext/test/string.rb \ + mruby/mrbgems/mruby-string-ext/test/numeric.rb \ + mruby/mrbgems/mruby-string-ext/mrblib/string.rb \ + mruby/mrbgems/mruby-string-ext/src/string.c \ + mruby/mrbgems/default-no-stdio.gembox \ + mruby/mrbgems/mruby-set/mrbgem.rake \ + mruby/mrbgems/mruby-set/README.md \ + mruby/mrbgems/mruby-set/test/set.rb \ + mruby/mrbgems/mruby-set/mrblib/set.rb \ + mruby/mrbgems/mruby-set/LICENSE \ + mruby/mrbgems/mruby-set/mruby-set.gem \ + mruby/mrbgems/mruby-method/mrbgem.rake \ + mruby/mrbgems/mruby-method/README.md \ + mruby/mrbgems/mruby-method/test/method.rb \ + mruby/mrbgems/mruby-method/mrblib/kernel.rb \ + mruby/mrbgems/mruby-method/mrblib/method.rb \ + mruby/mrbgems/mruby-method/src/method.c \ + mruby/mrbgems/mruby-random/mrbgem.rake \ + mruby/mrbgems/mruby-random/test/random.rb \ + mruby/mrbgems/mruby-random/src/random.c \ + mruby/mrbgems/mruby-catch/mrbgem.rake \ + mruby/mrbgems/mruby-catch/test/catch.rb \ + mruby/mrbgems/mruby-catch/mrblib/catch.rb \ + mruby/mrbgems/mruby-catch/src/catch.c \ + mruby/mrbgems/mruby-cmath/mrbgem.rake \ + mruby/mrbgems/mruby-cmath/test/cmath.rb \ + mruby/mrbgems/mruby-cmath/src/cmath.c \ + mruby/mrbgems/mruby-compar-ext/mrbgem.rake \ + mruby/mrbgems/mruby-compar-ext/mrblib/compar.rb \ + mruby/mrbgems/mruby-pack/mrbgem.rake \ + mruby/mrbgems/mruby-pack/README.md \ + mruby/mrbgems/mruby-pack/test/pack.rb \ + mruby/mrbgems/mruby-pack/src/pack.c \ + mruby/mrbgems/mruby-struct/mrbgem.rake \ + mruby/mrbgems/mruby-struct/test/struct.rb \ + mruby/mrbgems/mruby-struct/mrblib/struct.rb \ + mruby/mrbgems/mruby-struct/src/struct.c \ + mruby/mrbgems/mruby-bigint/core/bigint.c \ + mruby/mrbgems/mruby-bigint/core/bigint.h \ + mruby/mrbgems/mruby-bigint/mrbgem.rake \ + mruby/mrbgems/mruby-bigint/README.md \ + mruby/mrbgems/mruby-bigint/test/bigint.rb \ + mruby/mrbgems/mruby-bigint/README-fgmp.md \ + mruby/mrbgems/mruby-symbol-ext/mrbgem.rake \ + mruby/mrbgems/mruby-symbol-ext/test/symbol.rb \ + mruby/mrbgems/mruby-symbol-ext/mrblib/symbol.rb \ + mruby/mrbgems/mruby-symbol-ext/src/symbol.c \ + mruby/mrbgems/mruby-io/mrbgem.rake \ + mruby/mrbgems/mruby-io/README.md \ + mruby/mrbgems/mruby-io/test/mruby_io_test.c \ + mruby/mrbgems/mruby-io/test/io.rb \ + mruby/mrbgems/mruby-io/test/file.rb \ + mruby/mrbgems/mruby-io/test/file_test.rb \ + mruby/mrbgems/mruby-io/mrblib/io.rb \ + mruby/mrbgems/mruby-io/mrblib/file.rb \ + mruby/mrbgems/mruby-io/mrblib/file_constants.rb \ + mruby/mrbgems/mruby-io/mrblib/kernel.rb \ + mruby/mrbgems/mruby-io/src/mruby_io_gem.c \ + mruby/mrbgems/mruby-io/src/file_test.c \ + mruby/mrbgems/mruby-io/src/io.c \ + mruby/mrbgems/mruby-io/src/file.c \ + mruby/mrbgems/mruby-io/include/mruby/ext/io.h \ + mruby/mrbgems/mruby-compiler/core/parse.y \ + mruby/mrbgems/mruby-compiler/core/y.tab.c \ + mruby/mrbgems/mruby-compiler/core/node.h \ + mruby/mrbgems/mruby-compiler/core/keywords \ + mruby/mrbgems/mruby-compiler/core/codegen.c \ + mruby/mrbgems/mruby-compiler/core/lex.def \ + mruby/mrbgems/mruby-compiler/mrbgem.rake \ + mruby/mrbgems/mruby-bin-config/mrbgem.rake \ + mruby/mrbgems/mruby-bin-config/mruby-config \ + mruby/mrbgems/mruby-bin-config/mruby-config.bat \ + mruby/mrbgems/mruby-proc-ext/mrbgem.rake \ + mruby/mrbgems/mruby-proc-ext/test/proc.rb \ + mruby/mrbgems/mruby-proc-ext/test/proc.c \ + mruby/mrbgems/mruby-proc-ext/mrblib/proc.rb \ + mruby/mrbgems/mruby-proc-ext/src/proc.c \ + mruby/mrbgems/mruby-data/mrbgem.rake \ + mruby/mrbgems/mruby-data/test/data.rb \ + mruby/mrbgems/mruby-data/src/data.c \ + mruby/mrbgems/mruby-dir/mrbgem.rake \ + mruby/mrbgems/mruby-dir/README.md \ + mruby/mrbgems/mruby-dir/test/dir.rb \ + mruby/mrbgems/mruby-dir/test/dirtest.c \ + mruby/mrbgems/mruby-dir/mrblib/dir.rb \ + mruby/mrbgems/mruby-dir/src/Win/dirent.c \ + mruby/mrbgems/mruby-dir/src/dir.c \ + mruby/mrbgems/mruby-object-ext/mrbgem.rake \ + mruby/mrbgems/mruby-object-ext/test/nil.rb \ + mruby/mrbgems/mruby-object-ext/test/object.rb \ + mruby/mrbgems/mruby-object-ext/mrblib/object.rb \ + mruby/mrbgems/mruby-object-ext/src/object.c \ + mruby/mrbgems/mruby-kernel-ext/mrbgem.rake \ + mruby/mrbgems/mruby-kernel-ext/test/kernel.rb \ + mruby/mrbgems/mruby-kernel-ext/src/kernel.c \ + mruby/mrbgems/mruby-class-ext/mrbgem.rake \ + mruby/mrbgems/mruby-class-ext/test/class.rb \ + mruby/mrbgems/mruby-class-ext/test/module.rb \ + mruby/mrbgems/mruby-class-ext/mrblib/module.rb \ + mruby/mrbgems/mruby-class-ext/src/class.c \ + mruby/mrbgems/mruby-binding/mrbgem.rake \ + mruby/mrbgems/mruby-binding/test/binding.rb \ + mruby/mrbgems/mruby-binding/test/binding.c \ + mruby/mrbgems/mruby-binding/src/binding.c \ + mruby/mrbgems/mruby-print/mrbgem.rake \ + mruby/mrbgems/mruby-print/mrblib/print.rb \ + mruby/mrbgems/mruby-print/src/print.c \ + mruby/mrbgems/mruby-hash-ext/mrbgem.rake \ + mruby/mrbgems/mruby-hash-ext/test/hash.rb \ + mruby/mrbgems/mruby-hash-ext/mrblib/hash.rb \ + mruby/mrbgems/mruby-hash-ext/src/hash-ext.c \ + mruby/mrbgems/mruby-sleep/example/sleep.rb \ + mruby/mrbgems/mruby-sleep/mrbgem.rake \ + mruby/mrbgems/mruby-sleep/README.md \ + mruby/mrbgems/mruby-sleep/test/sleep_test.rb \ + mruby/mrbgems/mruby-sleep/src/sleep.c \ + mruby/mrbgems/mruby-sprintf/mrbgem.rake \ + mruby/mrbgems/mruby-sprintf/test/sprintf.rb \ + mruby/mrbgems/mruby-sprintf/mrblib/string.rb \ + mruby/mrbgems/mruby-sprintf/src/sprintf.c \ + mruby/mrbgems/mruby-enum-chain/mrbgem.rake \ + mruby/mrbgems/mruby-enum-chain/test/enum_chain.rb \ + mruby/mrbgems/mruby-enum-chain/mrblib/chain.rb \ + mruby/mrbgems/mruby-range-ext/mrbgem.rake \ + mruby/mrbgems/mruby-range-ext/test/range.rb \ + mruby/mrbgems/mruby-range-ext/mrblib/range.rb \ + mruby/mrbgems/mruby-range-ext/src/range.c \ + mruby/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c \ + mruby/mrbgems/mruby-bin-mirb/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mirb/bintest/mirb.rb \ + mruby/mrbgems/mruby-metaprog/mrbgem.rake \ + mruby/mrbgems/mruby-metaprog/test/metaprog.rb \ + mruby/mrbgems/mruby-metaprog/src/metaprog.c \ + mruby/mrbgems/mruby-test-inline-struct/mrbgem.rake \ + mruby/mrbgems/mruby-test-inline-struct/test/inline.c \ + mruby/mrbgems/mruby-test-inline-struct/test/inline.rb \ + mruby/mrbgems/mruby-time/mrbgem.rake \ + mruby/mrbgems/mruby-time/test/time.rb \ + mruby/mrbgems/mruby-time/mrblib/time.rb \ + mruby/mrbgems/mruby-time/src/time.c \ + mruby/mrbgems/mruby-time/include/mruby/time.h \ + mruby/mrbgems/mruby-proc-binding/mrbgem.rake \ + mruby/mrbgems/mruby-proc-binding/test/proc-binding.c \ + mruby/mrbgems/mruby-proc-binding/test/proc-binding.rb \ + mruby/mrbgems/mruby-proc-binding/src/proc-binding.c \ + mruby/mrbgems/stdlib-ext.gembox \ + mruby/mrbgems/metaprog.gembox \ + mruby/mrbgems/mruby-enumerator/mrbgem.rake \ + mruby/mrbgems/mruby-enumerator/test/enumerator.rb \ + mruby/mrbgems/mruby-enumerator/mrblib/enumerator.rb \ + mruby/mrbgems/math.gembox \ + mruby/mrbgems/mruby-socket/mrbgem.rake \ + mruby/mrbgems/mruby-socket/README.md \ + mruby/mrbgems/mruby-socket/test/socket.rb \ + mruby/mrbgems/mruby-socket/test/udpsocket.rb \ + mruby/mrbgems/mruby-socket/test/tcpsocket.rb \ + mruby/mrbgems/mruby-socket/test/sockettest.c \ + mruby/mrbgems/mruby-socket/test/basicsocket.rb \ + mruby/mrbgems/mruby-socket/test/addrinfo.rb \ + mruby/mrbgems/mruby-socket/test/unix.rb \ + mruby/mrbgems/mruby-socket/test/ipsocket.rb \ + mruby/mrbgems/mruby-socket/mrblib/socket.rb \ + mruby/mrbgems/mruby-socket/src/socket.c \ + mruby/mrbgems/mruby-socket/src/gen.rb \ + mruby/mrbgems/mruby-socket/src/const.cstub \ + mruby/mrbgems/mruby-socket/src/const.def \ + mruby/mrbgems/mruby-objectspace/mrbgem.rake \ + mruby/mrbgems/mruby-objectspace/test/objectspace.rb \ + mruby/mrbgems/mruby-objectspace/src/mruby_objectspace.c \ + mruby/mrbgems/full-core.gembox \ + mruby/mrbgems/mruby-rational/mrbgem.rake \ + mruby/mrbgems/mruby-rational/test/rational.rb \ + mruby/mrbgems/mruby-rational/mrblib/rational.rb \ + mruby/mrbgems/mruby-rational/src/rational.c \ + mruby/mrbgems/mruby-numeric-ext/mrbgem.rake \ + mruby/mrbgems/mruby-numeric-ext/test/numeric.rb \ + mruby/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb \ + mruby/mrbgems/mruby-numeric-ext/src/numeric_ext.c \ + mruby/mrbgems/mruby-binding-core/mrbgem.rake \ + mruby/mrbgems/mruby-binding-core/test/binding-core.rb \ + mruby/mrbgems/mruby-binding-core/src/binding-core.c \ + mruby/mrbgems/mruby-fiber/mrbgem.rake \ + mruby/mrbgems/mruby-fiber/test/fiber.rb \ + mruby/mrbgems/mruby-fiber/src/fiber.c \ + mruby/mrbgems/mruby-complex/mrbgem.rake \ + mruby/mrbgems/mruby-complex/test/complex.rb \ + mruby/mrbgems/mruby-complex/mrblib/complex.rb \ + mruby/mrbgems/mruby-complex/src/complex.c \ + mruby/mrbgems/mruby-exit/mrbgem.rake \ + mruby/mrbgems/mruby-exit/src/mruby-exit.c \ + mruby/mrbgems/mruby-error/mrbgem.rake \ + mruby/mrbgems/mruby-error/test/exception.rb \ + mruby/mrbgems/mruby-error/test/exception.c \ + mruby/mrbgems/mruby-error/src/exception.c \ + mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c \ + mruby/mrbgems/mruby-bin-mruby/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mruby/bintest/mruby.rb \ + mruby/mrbgems/mruby-test/mrbgem.rake \ + mruby/mrbgems/mruby-test/vformat.c \ + mruby/mrbgems/mruby-test/README.md \ + mruby/mrbgems/mruby-test/driver.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c \ + mruby/mrbgems/mruby-bin-debugger/mrbgem.rake \ + mruby/mrbgems/mruby-bin-debugger/bintest/mrdb.rb \ + mruby/mrbgems/mruby-bin-debugger/bintest/print.rb \ + mruby/mrbgems/mruby-array-ext/mrbgem.rake \ + mruby/mrbgems/mruby-array-ext/test/array.rb \ + mruby/mrbgems/mruby-array-ext/mrblib/array.rb \ + mruby/mrbgems/mruby-array-ext/src/array.c \ + mruby/mrbgems/mruby-enum-lazy/mrbgem.rake \ + mruby/mrbgems/mruby-enum-lazy/test/lazy.rb \ + mruby/mrbgems/mruby-enum-lazy/mrblib/lazy.rb \ + mruby/mrbgems/mruby-bin-strip/tools/mruby-strip/mruby-strip.c \ + mruby/mrbgems/mruby-bin-strip/mrbgem.rake \ + mruby/mrbgems/mruby-bin-strip/bintest/mruby-strip.rb \ + mruby/mrbgems/mruby-errno/mrbgem.rake \ + mruby/mrbgems/mruby-errno/README.md \ + mruby/mrbgems/mruby-errno/test/errno.rb \ + mruby/mrbgems/mruby-errno/mrblib/errno.rb \ + mruby/mrbgems/mruby-errno/src/gen.rb \ + mruby/mrbgems/mruby-errno/src/known_errors_def.cstub \ + mruby/mrbgems/mruby-errno/src/known_errors.def \ + mruby/mrbgems/mruby-errno/src/errno.c \ + mruby/mrbgems/mruby-toplevel-ext/mrbgem.rake \ + mruby/mrbgems/mruby-toplevel-ext/test/toplevel.rb \ + mruby/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb \ + mruby/mrbgems/mruby-math/mrbgem.rake \ + mruby/mrbgems/mruby-math/test/math.rb \ + mruby/mrbgems/mruby-math/src/math.c \ + mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c \ + mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c \ + mruby/mrbgems/mruby-bin-mrbc/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb \ + mruby/mrbgems/stdlib-io.gembox \ + mruby/mrbgems/default.gembox \ + mruby/mrbgems/mruby-os-memsize/mrbgem.rake \ + mruby/mrbgems/mruby-os-memsize/test/memsize.rb \ + mruby/mrbgems/mruby-os-memsize/src/memsize.c \ + mruby/mrbgems/default-no-fpu.gembox \ + mruby/mrbgems/mruby-enum-ext/mrbgem.rake \ + mruby/mrbgems/mruby-enum-ext/test/enum.rb \ + mruby/mrbgems/mruby-enum-ext/mrblib/enum.rb \ + mruby/Rakefile \ + mruby/SECURITY.md \ + mruby/doc/mruby_logo_red_icon.png \ + mruby/doc/mruby3.0.md \ + mruby/doc/mruby3.1.md \ + mruby/doc/internal/opcode.md \ + mruby/doc/internal/boxing.md \ + mruby/doc/limitations.md \ + mruby/doc/mruby3.2.md \ + mruby/doc/guides/compile.md \ + mruby/doc/guides/mrbgems.md \ + mruby/doc/guides/debugger.md \ + mruby/doc/guides/link.md \ + mruby/doc/guides/symbol.md \ + mruby/doc/guides/gc-arena-howto.md \ + mruby/doc/guides/mrbconf.md \ + mruby/minirake \ + mruby/include/mruby/opcode.h \ + mruby/include/mruby/re.h \ + mruby/include/mruby/hash.h \ + mruby/include/mruby/string.h \ + mruby/include/mruby/presym.h \ + mruby/include/mruby/object.h \ + mruby/include/mruby/class.h \ + mruby/include/mruby/ops.h \ + mruby/include/mruby/irep.h \ + mruby/include/mruby/compile.h \ + mruby/include/mruby/array.h \ + mruby/include/mruby/range.h \ + mruby/include/mruby/throw.h \ + mruby/include/mruby/variable.h \ + mruby/include/mruby/boxing_word.h \ + mruby/include/mruby/istruct.h \ + mruby/include/mruby/data.h \ + mruby/include/mruby/common.h \ + mruby/include/mruby/error.h \ + mruby/include/mruby/debug.h \ + mruby/include/mruby/boxing_no.h \ + mruby/include/mruby/numeric.h \ + mruby/include/mruby/boxing_nan.h \ + mruby/include/mruby/value.h \ + mruby/include/mruby/endian.h \ + mruby/include/mruby/internal.h \ + mruby/include/mruby/dump.h \ + mruby/include/mruby/version.h \ + mruby/include/mruby/khash.h \ + mruby/include/mruby/gc.h \ + mruby/include/mruby/presym/enable.h \ + mruby/include/mruby/presym/disable.h \ + mruby/include/mruby/presym/scanning.h \ + mruby/include/mruby/proc.h \ + mruby/include/mrbconf.h \ + mruby/include/mruby.h + +if ENABLE_THIRD_PARTY + +noinst_LTLIBRARIES = liburl-parser.la +liburl_parser_la_SOURCES = \ + url-parser/url_parser.c \ + url-parser/url_parser.h + +noinst_LTLIBRARIES += libllhttp.la +libllhttp_la_SOURCES = \ + llhttp/src/api.c \ + llhttp/src/http.c \ + llhttp/src/llhttp.c \ + llhttp/include/llhttp.h +libllhttp_la_CPPFLAGS = -I${srcdir}/llhttp/include + +if HAVE_NEVERBLEED +noinst_LTLIBRARIES += libneverbleed.la +libneverbleed_la_CPPFLAGS = @OPENSSL_CFLAGS@ +libneverbleed_la_LIBADD = @OPENSSL_LIBS@ +libneverbleed_la_SOURCES = neverbleed/neverbleed.c neverbleed/neverbleed.h +endif # HAVE_NEVERBLEED + +if HAVE_MRUBY + +.PHONY: all-local clean mruby + +mruby: + mkdir -p "${abs_builddir}/mruby/build" + diff "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build/build_config.rb" >& /dev/null || \ + cp "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build" + MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \ + BUILD_DIR="${abs_builddir}/mruby/build" \ + INSTALL_DIR="${abs_builddir}/mruby/build/install/bin" \ + MRUBY_CC="${CC}" MRUBY_CXX="$(firstword $(CXX))" MRUBY_LD="${LD}" \ + MRUBY_AR="${AR}" \ + HOST="${host}" BUILD="${build}" \ + "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile" + +all-local: mruby + +clean-local: + [ ! -f "${abs_builddir}/mruby/build/build_config.rb" ] || \ + MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \ + BUILD_DIR="${abs_builddir}/mruby/build" \ + MRUBY_CC="${CC}" \ + "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile" clean + +endif # HAVE_MRUBY + +endif # ENABLE_THIRD_PARTY diff --git a/third-party/build_config.rb b/third-party/build_config.rb new file mode 100644 index 0000000..e06a4cc --- /dev/null +++ b/third-party/build_config.rb @@ -0,0 +1,34 @@ +def config(conf) + toolchain :clang if ENV['MRUBY_CC'].include? "clang" + toolchain :gcc if ENV['MRUBY_CC'].include? "gcc" + + conf.cc.command = ENV['MRUBY_CC'] + conf.cxx.command = ENV['MRUBY_CXX'] + + if ENV['MRUBY_LD'] + conf.linker.command = ENV['MRUBY_LD'] + end + if ENV['MRUBY_AR'] + conf.archiver.command = ENV['MRUBY_AR'] + end + + # C++ project needs this. Without this, mruby exception does not + # properly destroy C++ object allocated on stack. + conf.enable_cxx_exception + + conf.build_dir = ENV['BUILD_DIR'] + + # include the default GEMs + conf.gembox 'default' + conf.gem :core => 'mruby-eval' +end + +if ENV['BUILD'] == ENV['HOST'] then + MRuby::Build.new do |conf| + config(conf) + end +else + MRuby::CrossBuild.new(ENV['HOST']) do |conf| + config(conf) + end +end diff --git a/third-party/llhttp/LICENSE-MIT b/third-party/llhttp/LICENSE-MIT new file mode 100644 index 0000000..6c1512d --- /dev/null +++ b/third-party/llhttp/LICENSE-MIT @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third-party/llhttp/README.md b/third-party/llhttp/README.md new file mode 100644 index 0000000..e982ed0 --- /dev/null +++ b/third-party/llhttp/README.md @@ -0,0 +1,482 @@ +# llhttp +[![CI](https://github.com/nodejs/llhttp/workflows/CI/badge.svg)](https://github.com/nodejs/llhttp/actions?query=workflow%3ACI) + +Port of [http_parser][0] to [llparse][1]. + +## Why? + +Let's face it, [http_parser][0] is practically unmaintainable. Even +introduction of a single new method results in a significant code churn. + +This project aims to: + +* Make it maintainable +* Verifiable +* Improving benchmarks where possible + +More details in [Fedor Indutny's talk at JSConf EU 2019](https://youtu.be/x3k_5Mi66sY) + +## How? + +Over time, different approaches for improving [http_parser][0]'s code base +were tried. However, all of them failed due to resulting significant performance +degradation. + +This project is a port of [http_parser][0] to TypeScript. [llparse][1] is used +to generate the output C source file, which could be compiled and +linked with the embedder's program (like [Node.js][7]). + +## Performance + +So far llhttp outperforms http_parser: + +| | input size | bandwidth | reqs/sec | time | +|:----------------|-----------:|-------------:|-----------:|--------:| +| **llhttp** | 8192.00 mb | 1777.24 mb/s | 3583799.39 req/sec | 4.61 s | +| **http_parser** | 8192.00 mb | 694.66 mb/s | 1406180.33 req/sec | 11.79 s | + +llhttp is faster by approximately **156%**. + +## Maintenance + +llhttp project has about 1400 lines of TypeScript code describing the parser +itself and around 450 lines of C code and headers providing the helper methods. +The whole [http_parser][0] is implemented in approximately 2500 lines of C, and +436 lines of headers. + +All optimizations and multi-character matching in llhttp are generated +automatically, and thus doesn't add any extra maintenance cost. On the contrary, +most of http_parser's code is hand-optimized and unrolled. Instead describing +"how" it should parse the HTTP requests/responses, a maintainer should +implement the new features in [http_parser][0] cautiously, considering +possible performance degradation and manually optimizing the new code. + +## Verification + +The state machine graph is encoded explicitly in llhttp. The [llparse][1] +automatically checks the graph for absence of loops and correct reporting of the +input ranges (spans) like header names and values. In the future, additional +checks could be performed to get even stricter verification of the llhttp. + +## Usage + +```C +#include "stdio.h" +#include "llhttp.h" +#include "string.h" + +int handle_on_message_complete(llhttp_t* parser) { + fprintf(stdout, "Message completed!\n"); + return 0; +} + +int main() { + llhttp_t parser; + llhttp_settings_t settings; + + /*Initialize user callbacks and settings */ + llhttp_settings_init(&settings); + + /*Set user callback */ + settings.on_message_complete = handle_on_message_complete; + + /*Initialize the parser in HTTP_BOTH mode, meaning that it will select between + *HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first + *input. + */ + llhttp_init(&parser, HTTP_BOTH, &settings); + + /*Parse request! */ + const char* request = "GET / HTTP/1.1\r\n\r\n"; + int request_len = strlen(request); + + enum llhttp_errno err = llhttp_execute(&parser, request, request_len); + if (err == HPE_OK) { + fprintf(stdout, "Successfully parsed!\n"); + } else { + fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); + } +} +``` +For more information on API usage, please refer to [src/native/api.h](https://github.com/nodejs/llhttp/blob/main/src/native/api.h). + +## API + +### llhttp_settings_t + +The settings object contains a list of callbacks that the parser will invoke. + +The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_PAUSED` (pause the parser): + +* `on_message_begin`: Invoked when a new request/response starts. +* `on_message_complete`: Invoked when a request/response has been completedly parsed. +* `on_url_complete`: Invoked after the URL has been parsed. +* `on_method_complete`: Invoked after the HTTP method has been parsed. +* `on_version_complete`: Invoked after the HTTP version has been parsed. +* `on_status_complete`: Invoked after the status code has been parsed. +* `on_header_field_complete`: Invoked after a header name has been parsed. +* `on_header_value_complete`: Invoked after a header value has been parsed. +* `on_chunk_header`: Invoked after a new chunk is started. The current chunk length is stored in `parser->content_length`. +* `on_chunk_extension_name_complete`: Invoked after a chunk extension name is started. +* `on_chunk_extension_value_complete`: Invoked after a chunk extension value is started. +* `on_chunk_complete`: Invoked after a new chunk is received. +* `on_reset`: Invoked after `on_message_complete` and before `on_message_begin` when a new message + is received on the same parser. This is not invoked for the first message of the parser. + +The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_USER` (error from the callback): + +* `on_url`: Invoked when another character of the URL is received. +* `on_status`: Invoked when another character of the status is received. +* `on_method`: Invoked when another character of the method is received. + When parser is created with `HTTP_BOTH` and the input is a response, this also invoked for the sequence `HTTP/` + of the first message. +* `on_version`: Invoked when another character of the version is received. +* `on_header_field`: Invoked when another character of a header name is received. +* `on_header_value`: Invoked when another character of a header value is received. +* `on_chunk_extension_name`: Invoked when another character of a chunk extension name is received. +* `on_chunk_extension_value`: Invoked when another character of a extension value is received. + +The callback `on_headers_complete`, invoked when headers are completed, can return: + +* `0`: Proceed normally. +* `1`: Assume that request/response has no body, and proceed to parsing the next message. +* `2`: Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE`. +* `-1`: Error +* `HPE_PAUSED`: Pause the parser. + +### `void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)` + +Initialize the parser with specific type and user settings. + +### `uint8_t llhttp_get_type(llhttp_t* parser)` + +Returns the type of the parser. + +### `uint8_t llhttp_get_http_major(llhttp_t* parser)` + +Returns the major version of the HTTP protocol of the current request/response. + +### `uint8_t llhttp_get_http_minor(llhttp_t* parser)` + +Returns the minor version of the HTTP protocol of the current request/response. + +### `uint8_t llhttp_get_method(llhttp_t* parser)` + +Returns the method of the current request. + +### `int llhttp_get_status_code(llhttp_t* parser)` + +Returns the method of the current response. + +### `uint8_t llhttp_get_upgrade(llhttp_t* parser)` + +Returns `1` if request includes the `Connection: upgrade` header. + +### `void llhttp_reset(llhttp_t* parser)` + +Reset an already initialized parser back to the start state, preserving the +existing parser type, callback settings, user data, and lenient flags. + +### `void llhttp_settings_init(llhttp_settings_t* settings)` + +Initialize the settings object. + +### `llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)` + +Parse full or partial request/response, invoking user callbacks along the way. + +If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing interrupts, +and such errno is returned from `llhttp_execute()`. If `HPE_PAUSED` was used as a errno, +the execution can be resumed with `llhttp_resume()` call. + +In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` is returned +after fully parsing the request/response. If the user wishes to continue parsing, +they need to invoke `llhttp_resume_after_upgrade()`. + +**if this function ever returns a non-pause type error, it will continue to return +the same error upon each successive call up until `llhttp_init()` is called.** + +### `llhttp_errno_t llhttp_finish(llhttp_t* parser)` + +This method should be called when the other side has no further bytes to +send (e.g. shutdown of readable side of the TCP connection.) + +Requests without `Content-Length` and other messages might require treating +all incoming bytes as the part of the body, up to the last byte of the +connection. + +This method will invoke `on_message_complete()` callback if the +request was terminated safely. Otherwise a error code would be returned. + + +### `int llhttp_message_needs_eof(const llhttp_t* parser)` + +Returns `1` if the incoming message is parsed until the last byte, and has to be completed by calling `llhttp_finish()` on EOF. + +### `int llhttp_should_keep_alive(const llhttp_t* parser)` + +Returns `1` if there might be any other messages following the last that was +successfully parsed. + +### `void llhttp_pause(llhttp_t* parser)` + +Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set +appropriate error reason. + +**Do not call this from user callbacks! User callbacks must return +`HPE_PAUSED` if pausing is required.** + +### `void llhttp_resume(llhttp_t* parser)` + +Might be called to resume the execution after the pause in user's callback. + +See `llhttp_execute()` above for details. + +**Call this only if `llhttp_execute()` returns `HPE_PAUSED`.** + +### `void llhttp_resume_after_upgrade(llhttp_t* parser)` + +Might be called to resume the execution after the pause in user's callback. +See `llhttp_execute()` above for details. + +**Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`** + +### `llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)` + +Returns the latest error. + +### `const char* llhttp_get_error_reason(const llhttp_t* parser)` + +Returns the verbal explanation of the latest returned error. + +**User callback should set error reason when returning the error. See +`llhttp_set_error_reason()` for details.** + +### `void llhttp_set_error_reason(llhttp_t* parser, const char* reason)` + +Assign verbal description to the returned error. Must be called in user +callbacks right before returning the errno. + +**`HPE_USER` error code might be useful in user callbacks.** + +### `const char* llhttp_get_error_pos(const llhttp_t* parser)` + +Returns the pointer to the last parsed byte before the returned error. The +pointer is relative to the `data` argument of `llhttp_execute()`. + +**This method might be useful for counting the number of parsed bytes.** + +### `const char* llhttp_errno_name(llhttp_errno_t err)` + +Returns textual name of error code. + +### `const char* llhttp_method_name(llhttp_method_t method)` + +Returns textual name of HTTP method. + +### `const char* llhttp_status_name(llhttp_status_t status)` + +Returns textual name of HTTP status. + +### `void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)` + +Enables/disables lenient header value parsing (disabled by default). +Lenient parsing disables header value token checks, extending llhttp's +protocol support to highly non-compliant clients/server. + +No `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when +lenient parsing is "on". + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of conflicting `Transfer-Encoding` and +`Content-Length` headers (disabled by default). + +Normally `llhttp` would error when `Transfer-Encoding` is present in +conjunction with `Content-Length`. + +This error is important to prevent HTTP request smuggling, but may be less desirable +for small number of cases involving legacy servers. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of `Connection: close` and HTTP/1.0 +requests responses. + +Normally `llhttp` would error the HTTP request/response +after the request/response with `Connection: close` and `Content-Length`. + +This is important to prevent cache poisoning attacks, +but might interact badly with outdated and insecure clients. + +With this flag the extra request/response will be parsed normally. + +**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of `Transfer-Encoding` header. + +Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value +and another value after it (either in a single header or in multiple +headers whose value are internally joined using `, `). + +This is mandated by the spec to reliably determine request body size and thus +avoid request smuggling. + +With this flag the extra value will be parsed normally. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_version(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of HTTP version. + +Normally `llhttp` would error when the HTTP version in the request or status line +is not `0.9`, `1.0`, `1.1` or `2.0`. +With this flag the extra value will be parsed normally. + +**Enabling this flag can pose a security issue since you will allow unsupported HTTP versions. USE WITH CAUTION!** + +### `void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of additional data received after a message ends +and keep-alive is disabled. + +Normally `llhttp` would error when additional unexpected data is received if the message +contains the `Connection` header with `close` value. +With this flag the extra data will discarded without throwing an error. + +**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of incomplete CRLF sequences. + +Normally `llhttp` would error when a CR is not followed by LF when terminating the +request line, the status line, the headers or a chunk header. +With this flag only a CR is required to terminate such sections. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of chunks not separated via CRLF. + +Normally `llhttp` would error when after a chunk data a CRLF is missing before +starting a new chunk. +With this flag the new chunk can start immediately after the previous one. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +## Build Instructions + +Make sure you have [Node.js](https://nodejs.org/), npm and npx installed. Then under project directory run: + +```sh +npm install +make +``` + +--- + +### Bindings to other languages + +* Lua: [MunifTanjim/llhttp.lua][11] +* Python: [pallas/pyllhttp][8] +* Ruby: [metabahn/llhttp][9] +* Rust: [JackLiar/rust-llhttp][10] + +### Using with CMake + +If you want to use this library in a CMake project as a shared library, you can use the snippet below. + +``` +FetchContent_Declare(llhttp + URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz") + +FetchContent_MakeAvailable(llhttp) + +# Link with the llhttp_shared target +target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_shared ${PROJECT_NAME}) +``` + +If you want to use this library in a CMake project as a static library, you can set some cache variables first. + +``` +FetchContent_Declare(llhttp + URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz") + +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") +set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") +FetchContent_MakeAvailable(llhttp) + +# Link with the llhttp_static target +target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_static ${PROJECT_NAME}) +``` + +_Note that using the git repo directly (e.g., via a git repo url and tag) will not work with FetchContent_Declare because [CMakeLists.txt](./CMakeLists.txt) requires string replacements (e.g., `_RELEASE_`) before it will build._ + +## Building on Windows + +### Installation + +* `choco install git` +* `choco install node` +* `choco install llvm` (or install the `C++ Clang tools for Windows` optional package from the Visual Studio 2019 installer) +* `choco install make` (or if you have MinGW, it comes bundled) + +1. Ensure that `Clang` and `make` are in your system path. +2. Using Git Bash, clone the repo to your preferred location. +3. Cd into the cloned directory and run `npm install` +5. Run `make` +6. Your `repo/build` directory should now have `libllhttp.a` and `libllhttp.so` static and dynamic libraries. +7. When building your executable, you can link to these libraries. Make sure to set the build folder as an include path when building so you can reference the declarations in `repo/build/llhttp.h`. + +### A simple example on linking with the library: + +Assuming you have an executable `main.cpp` in your current working directory, you would run: `clang++ -Os -g3 -Wall -Wextra -Wno-unused-parameter -I/path/to/llhttp/build main.cpp /path/to/llhttp/build/libllhttp.a -o main.exe`. + +If you are getting `unresolved external symbol` linker errors you are likely attempting to build `llhttp.c` without linking it with object files from `api.c` and `http.c`. + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +[0]: https://github.com/nodejs/http-parser +[1]: https://github.com/nodejs/llparse +[2]: https://en.wikipedia.org/wiki/Register_allocation#Spilling +[3]: https://en.wikipedia.org/wiki/Tail_call +[4]: https://llvm.org/docs/LangRef.html +[5]: https://llvm.org/docs/LangRef.html#call-instruction +[6]: https://clang.llvm.org/ +[7]: https://github.com/nodejs/node +[8]: https://github.com/pallas/pyllhttp +[9]: https://github.com/metabahn/llhttp +[10]: https://github.com/JackLiar/rust-llhttp +[11]: https://github.com/MunifTanjim/llhttp.lua diff --git a/third-party/llhttp/common.gypi b/third-party/llhttp/common.gypi new file mode 100644 index 0000000..ef7549f --- /dev/null +++ b/third-party/llhttp/common.gypi @@ -0,0 +1,46 @@ +{ + 'target_defaults': { + 'default_configuration': 'Debug', + 'configurations': { + # TODO: hoist these out and put them somewhere common, because + # RuntimeLibrary MUST MATCH across the entire project + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 1, # static debug + }, + }, + }, + 'Release': { + 'defines': [ 'NDEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O3' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 0, # static release + }, + }, + } + }, + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. llhttp.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + 'VCLibrarianTool': { + }, + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + }, + }, + 'conditions': [ + ['OS == "win"', { + 'defines': [ + 'WIN32' + ], + }] + ], + }, +} diff --git a/third-party/llhttp/include/llhttp.h b/third-party/llhttp/include/llhttp.h new file mode 100644 index 0000000..6588ae5 --- /dev/null +++ b/third-party/llhttp/include/llhttp.h @@ -0,0 +1,871 @@ + +#ifndef INCLUDE_LLHTTP_H_ +#define INCLUDE_LLHTTP_H_ + +#define LLHTTP_VERSION_MAJOR 9 +#define LLHTTP_VERSION_MINOR 0 +#define LLHTTP_VERSION_PATCH 1 + +#ifndef INCLUDE_LLHTTP_ITSELF_H_ +#define INCLUDE_LLHTTP_ITSELF_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +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/third-party/llhttp/llhttp.gyp b/third-party/llhttp/llhttp.gyp new file mode 100644 index 0000000..c7b8800 --- /dev/null +++ b/third-party/llhttp/llhttp.gyp @@ -0,0 +1,22 @@ +{ + 'variables': { + 'llhttp_sources': [ + 'src/llhttp.c', + 'src/api.c', + 'src/http.c', + ] + }, + 'targets': [ + { + 'target_name': 'llhttp', + 'type': 'static_library', + 'include_dirs': [ '.', 'include' ], + 'direct_dependent_settings': { + 'include_dirs': [ 'include' ], + }, + 'sources': [ + '<@(llhttp_sources)', + ], + }, + ] +} diff --git a/third-party/llhttp/src/api.c b/third-party/llhttp/src/api.c new file mode 100644 index 0000000..e0d0385 --- /dev/null +++ b/third-party/llhttp/src/api.c @@ -0,0 +1,494 @@ +#include +#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/third-party/llhttp/src/http.c b/third-party/llhttp/src/http.c new file mode 100644 index 0000000..3a66044 --- /dev/null +++ b/third-party/llhttp/src/http.c @@ -0,0 +1,150 @@ +#include +#ifndef LLHTTP__TEST +# include "llhttp.h" +#else +# define llhttp_t llparse_t +#endif /* */ + +int llhttp_message_needs_eof(const llhttp_t* parser); +int llhttp_should_keep_alive(const llhttp_t* parser); + +int llhttp__before_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + /* Set this here so that on_headers_complete() callbacks can see it */ + if ((parser->flags & F_UPGRADE) && + (parser->flags & F_CONNECTION_UPGRADE)) { + /* For responses, "Upgrade: foo" and "Connection: upgrade" are + * mandatory only when it is a 101 Switching Protocols response, + * otherwise it is purely informational, to announce support. + */ + parser->upgrade = + (parser->type == HTTP_REQUEST || parser->status_code == 101); + } else { + parser->upgrade = (parser->method == HTTP_CONNECT); + } + return 0; +} + + +/* Return values: + * 0 - No body, `restart`, message_complete + * 1 - CONNECT request, `restart`, message_complete, and pause + * 2 - chunk_size_start + * 3 - body_identity + * 4 - body_identity_eof + * 5 - invalid transfer-encoding for request + */ +int llhttp__after_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + int hasBody; + + hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + return 1; + } + + if (parser->flags & F_SKIPBODY) { + return 0; + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header, prepare for a chunk */ + return 2; + } else if (parser->flags & F_TRANSFER_ENCODING) { + if (parser->type == HTTP_REQUEST && + (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && + (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + return 5; + } else { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + return 4; + } + } else { + if (!(parser->flags & F_CONTENT_LENGTH)) { + if (!llhttp_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + return 0; + } else { + /* Read body until EOF */ + return 4; + } + } else if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + return 0; + } else { + /* Content-Length header given and non-zero */ + return 3; + } + } +} + + +int llhttp__after_message_complete(llhttp_t* parser, const char* p, + const char* endp) { + int should_keep_alive; + + should_keep_alive = llhttp_should_keep_alive(parser); + parser->finish = HTTP_FINISH_SAFE; + parser->flags = 0; + + /* NOTE: this is ignored in loose parsing mode */ + return should_keep_alive; +} + + +int llhttp_message_needs_eof(const llhttp_t* parser) { + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ + return 0; + } + + /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ + if ((parser->flags & F_TRANSFER_ENCODING) && + (parser->flags & F_CHUNKED) == 0) { + return 1; + } + + if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { + return 0; + } + + return 1; +} + + +int llhttp_should_keep_alive(const llhttp_t* parser) { + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !llhttp_message_needs_eof(parser); +} diff --git a/third-party/llhttp/src/llhttp.c b/third-party/llhttp/src/llhttp.c new file mode 100644 index 0000000..03f6d57 --- /dev/null +++ b/third-party/llhttp/src/llhttp.c @@ -0,0 +1,9537 @@ +#include +#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/third-party/url-parser/.gitignore b/third-party/url-parser/.gitignore new file mode 100644 index 0000000..c83c013 --- /dev/null +++ b/third-party/url-parser/.gitignore @@ -0,0 +1 @@ +/.dirstamp diff --git a/third-party/url-parser/AUTHORS b/third-party/url-parser/AUTHORS new file mode 100644 index 0000000..5323b68 --- /dev/null +++ b/third-party/url-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl +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/third-party/url-parser/LICENSE-MIT b/third-party/url-parser/LICENSE-MIT new file mode 100644 index 0000000..1ec0ab4 --- /dev/null +++ b/third-party/url-parser/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/third-party/url-parser/url_parser.c b/third-party/url-parser/url_parser.c new file mode 100644 index 0000000..4912ee2 --- /dev/null +++ b/third-party/url-parser/url_parser.c @@ -0,0 +1,652 @@ +/* Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "url_parser.h" +#include +#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/third-party/url-parser/url_parser.h b/third-party/url-parser/url_parser.h new file mode 100644 index 0000000..78b3096 --- /dev/null +++ b/third-party/url-parser/url_parser.h @@ -0,0 +1,94 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef url_parser_h +#define url_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 9 +#define HTTP_PARSER_VERSION_PATCH 1 + +#include +#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 -- cgit v1.2.3