From f75da71d579a71978616098d33d545c5e2effd80 Mon Sep 17 00:00:00 2001 From: peizhe Date: Fri, 16 Jun 2023 16:17:30 +0800 Subject: [PATCH] sync ylong_http from ylong_rust_sync repo Signed-off-by: peizhe --- LICENSE | 201 ++ OAT.xml | 71 + README.md | 55 +- ylong_http/Cargo.toml | 30 + ylong_http/README.md | 85 + ylong_http/examples/mimebody_multi.rs | 203 ++ .../mimebody_multi_then_async_data.rs | 130 ++ ylong_http/src/body/chunk.rs | 2064 +++++++++++++++++ ylong_http/src/body/empty.rs | 145 ++ ylong_http/src/body/mime/common/headers.rs | 489 ++++ ylong_http/src/body/mime/common/mix.rs | 478 ++++ ylong_http/src/body/mime/common/mod.rs | 262 +++ ylong_http/src/body/mime/common/multi.rs | 563 +++++ ylong_http/src/body/mime/common/part.rs | 622 +++++ ylong_http/src/body/mime/decode/mod.rs | 29 + ylong_http/src/body/mime/decode/multi.rs | 987 ++++++++ ylong_http/src/body/mime/decode/part.rs | 363 +++ ylong_http/src/body/mime/encode/mod.rs | 20 + ylong_http/src/body/mime/encode/multi.rs | 603 +++++ ylong_http/src/body/mime/encode/part.rs | 445 ++++ ylong_http/src/body/mime/mime_test_macro.rs | 134 ++ ylong_http/src/body/mime/mimetype.rs | 993 ++++++++ ylong_http/src/body/mime/mod.rs | 561 +++++ ylong_http/src/body/mod.rs | 551 +++++ ylong_http/src/body/text.rs | 657 ++++++ ylong_http/src/error.rs | 89 + ylong_http/src/h1/error.rs | 24 + ylong_http/src/h1/mod.rs | 25 + ylong_http/src/h1/request/encoder.rs | 774 +++++++ ylong_http/src/h1/request/mod.rs | 22 + ylong_http/src/h1/response/decoder.rs | 952 ++++++++ ylong_http/src/h1/response/mod.rs | 22 + ylong_http/src/h2/decoder.rs | 1797 ++++++++++++++ ylong_http/src/h2/encoder.rs | 1722 ++++++++++++++ ylong_http/src/h2/error.rs | 160 ++ ylong_http/src/h2/frame.rs | 689 ++++++ ylong_http/src/h2/hpack/decoder.rs | 591 +++++ ylong_http/src/h2/hpack/encoder.rs | 248 ++ ylong_http/src/h2/hpack/integer.rs | 239 ++ ylong_http/src/h2/hpack/mod.rs | 55 + .../src/h2/hpack/representation/decoder.rs | 845 +++++++ .../src/h2/hpack/representation/encoder.rs | 611 +++++ ylong_http/src/h2/hpack/representation/mod.rs | 329 +++ ylong_http/src/h2/hpack/table.rs | 560 +++++ ylong_http/src/h2/mod.rs | 73 + ylong_http/src/h2/parts.rs | 74 + ylong_http/src/h2/pseudo.rs | 508 ++++ ylong_http/src/h3/mod.rs | 16 + ylong_http/src/headers.rs | 1247 ++++++++++ ylong_http/src/huffman/consts.rs | 1628 +++++++++++++ ylong_http/src/huffman/mod.rs | 412 ++++ ylong_http/src/lib.rs | 48 + ylong_http/src/request/method.rs | 263 +++ ylong_http/src/request/mod.rs | 872 +++++++ ylong_http/src/request/uri.rs | 1536 ++++++++++++ ylong_http/src/response/mod.rs | 256 ++ ylong_http/src/response/status.rs | 661 ++++++ ylong_http/src/test_util.rs | 31 + ylong_http/src/version.rs | 118 + ylong_http_client/Cargo.toml | 100 + ylong_http_client/README.md | 40 + ylong_http_client/build.rs | 37 + ylong_http_client/examples/async_http.rs | 35 + ylong_http_client/examples/async_http2.rs | 67 + .../examples/async_http2_multi.rs | 82 + .../examples/async_https_outside.rs | 42 + .../examples/async_proxy_http.rs | 36 + .../examples/async_redirect_http.rs | 36 + ylong_http_client/examples/sync_http.rs | 34 + ylong_http_client/examples/sync_proxy_http.rs | 38 + .../examples/sync_redirect_http.rs | 38 + ylong_http_client/src/async_impl/adapter.rs | 185 ++ ylong_http_client/src/async_impl/client.rs | 647 ++++++ .../src/async_impl/conn/http1.rs | 144 ++ .../src/async_impl/conn/http2.rs | 489 ++++ ylong_http_client/src/async_impl/conn/mod.rs | 52 + ylong_http_client/src/async_impl/connector.rs | 240 ++ .../src/async_impl/downloader/builder.rs | 222 ++ .../src/async_impl/downloader/mod.rs | 264 +++ .../src/async_impl/downloader/operator.rs | 154 ++ ylong_http_client/src/async_impl/http_body.rs | 486 ++++ ylong_http_client/src/async_impl/mod.rs | 60 + ylong_http_client/src/async_impl/pool.rs | 159 ++ .../src/async_impl/ssl_stream/c_ssl_stream.rs | 155 ++ .../src/async_impl/ssl_stream/mix.rs | 77 + .../src/async_impl/ssl_stream/mod.rs | 26 + .../src/async_impl/ssl_stream/wrapper.rs | 86 + ylong_http_client/src/async_impl/timeout.rs | 55 + .../src/async_impl/uploader/builder.rs | 214 ++ .../src/async_impl/uploader/mod.rs | 196 ++ .../src/async_impl/uploader/operator.rs | 123 + ylong_http_client/src/error.rs | 200 ++ ylong_http_client/src/lib.rs | 62 + ylong_http_client/src/sync_impl/client.rs | 515 ++++ ylong_http_client/src/sync_impl/conn/http1.rs | 140 ++ ylong_http_client/src/sync_impl/conn/mod.rs | 46 + ylong_http_client/src/sync_impl/connector.rs | 201 ++ ylong_http_client/src/sync_impl/http_body.rs | 435 ++++ ylong_http_client/src/sync_impl/mod.rs | 51 + ylong_http_client/src/sync_impl/pool.rs | 104 + ylong_http_client/src/sync_impl/reader.rs | 251 ++ ylong_http_client/src/sync_impl/ssl_stream.rs | 56 + ylong_http_client/src/util/base64.rs | 70 + .../src/util/c_openssl/adapter.rs | 676 ++++++ ylong_http_client/src/util/c_openssl/bio.rs | 277 +++ ylong_http_client/src/util/c_openssl/error.rs | 269 +++ .../src/util/c_openssl/ffi/bio.rs | 79 + .../src/util/c_openssl/ffi/err.rs | 52 + .../src/util/c_openssl/ffi/mod.rs | 31 + .../src/util/c_openssl/ffi/pem.rs | 37 + .../src/util/c_openssl/ffi/ssl.rs | 149 ++ .../src/util/c_openssl/ffi/x509.rs | 46 + .../src/util/c_openssl/foreign.rs | 142 ++ ylong_http_client/src/util/c_openssl/mod.rs | 82 + .../src/util/c_openssl/ssl/ctx.rs | 342 +++ .../src/util/c_openssl/ssl/error.rs | 174 ++ .../src/util/c_openssl/ssl/filetype.rs | 29 + .../src/util/c_openssl/ssl/method.rs | 27 + .../src/util/c_openssl/ssl/mod.rs | 30 + .../src/util/c_openssl/ssl/ssl_base.rs | 126 + .../src/util/c_openssl/ssl/stream.rs | 237 ++ .../src/util/c_openssl/ssl/version.rs | 37 + ylong_http_client/src/util/c_openssl/x509.rs | 133 ++ ylong_http_client/src/util/config/client.rs | 44 + .../src/util/config/connector.rs | 26 + ylong_http_client/src/util/config/http.rs | 195 ++ ylong_http_client/src/util/config/mod.rs | 36 + ylong_http_client/src/util/config/settings.rs | 638 +++++ .../src/util/config/tls/alpn/mod.rs | 242 ++ ylong_http_client/src/util/config/tls/mod.rs | 17 + ylong_http_client/src/util/dispatcher.rs | 1427 ++++++++++++ ylong_http_client/src/util/mod.rs | 50 + ylong_http_client/src/util/normalizer.rs | 109 + ylong_http_client/src/util/pool.rs | 81 + ylong_http_client/src/util/proxy.rs | 305 +++ ylong_http_client/src/util/redirect.rs | 397 ++++ ylong_http_client/tests/common/mod.rs | 472 ++++ ylong_http_client/tests/file/cert.pem | 19 + ylong_http_client/tests/file/key.pem | 28 + ylong_http_client/tests/file/root-ca.pem | 21 + ylong_http_client/tests/helper.rs | 387 ++++ ylong_http_client/tests/sdv_client.rs | 278 +++ ylong_http_client/tests/sdv_https_c_ssl.rs | 257 ++ 143 files changed, 42965 insertions(+), 27 deletions(-) create mode 100644 LICENSE create mode 100644 OAT.xml create mode 100644 ylong_http/Cargo.toml create mode 100644 ylong_http/README.md create mode 100644 ylong_http/examples/mimebody_multi.rs create mode 100644 ylong_http/examples/mimebody_multi_then_async_data.rs create mode 100644 ylong_http/src/body/chunk.rs create mode 100644 ylong_http/src/body/empty.rs create mode 100644 ylong_http/src/body/mime/common/headers.rs create mode 100644 ylong_http/src/body/mime/common/mix.rs create mode 100644 ylong_http/src/body/mime/common/mod.rs create mode 100644 ylong_http/src/body/mime/common/multi.rs create mode 100644 ylong_http/src/body/mime/common/part.rs create mode 100644 ylong_http/src/body/mime/decode/mod.rs create mode 100644 ylong_http/src/body/mime/decode/multi.rs create mode 100644 ylong_http/src/body/mime/decode/part.rs create mode 100644 ylong_http/src/body/mime/encode/mod.rs create mode 100644 ylong_http/src/body/mime/encode/multi.rs create mode 100644 ylong_http/src/body/mime/encode/part.rs create mode 100644 ylong_http/src/body/mime/mime_test_macro.rs create mode 100644 ylong_http/src/body/mime/mimetype.rs create mode 100644 ylong_http/src/body/mime/mod.rs create mode 100644 ylong_http/src/body/mod.rs create mode 100644 ylong_http/src/body/text.rs create mode 100644 ylong_http/src/error.rs create mode 100644 ylong_http/src/h1/error.rs create mode 100644 ylong_http/src/h1/mod.rs create mode 100644 ylong_http/src/h1/request/encoder.rs create mode 100644 ylong_http/src/h1/request/mod.rs create mode 100644 ylong_http/src/h1/response/decoder.rs create mode 100644 ylong_http/src/h1/response/mod.rs create mode 100644 ylong_http/src/h2/decoder.rs create mode 100644 ylong_http/src/h2/encoder.rs create mode 100644 ylong_http/src/h2/error.rs create mode 100644 ylong_http/src/h2/frame.rs create mode 100644 ylong_http/src/h2/hpack/decoder.rs create mode 100644 ylong_http/src/h2/hpack/encoder.rs create mode 100644 ylong_http/src/h2/hpack/integer.rs create mode 100644 ylong_http/src/h2/hpack/mod.rs create mode 100644 ylong_http/src/h2/hpack/representation/decoder.rs create mode 100644 ylong_http/src/h2/hpack/representation/encoder.rs create mode 100644 ylong_http/src/h2/hpack/representation/mod.rs create mode 100644 ylong_http/src/h2/hpack/table.rs create mode 100644 ylong_http/src/h2/mod.rs create mode 100644 ylong_http/src/h2/parts.rs create mode 100644 ylong_http/src/h2/pseudo.rs create mode 100644 ylong_http/src/h3/mod.rs create mode 100644 ylong_http/src/headers.rs create mode 100644 ylong_http/src/huffman/consts.rs create mode 100644 ylong_http/src/huffman/mod.rs create mode 100644 ylong_http/src/lib.rs create mode 100644 ylong_http/src/request/method.rs create mode 100644 ylong_http/src/request/mod.rs create mode 100644 ylong_http/src/request/uri.rs create mode 100644 ylong_http/src/response/mod.rs create mode 100644 ylong_http/src/response/status.rs create mode 100644 ylong_http/src/test_util.rs create mode 100644 ylong_http/src/version.rs create mode 100644 ylong_http_client/Cargo.toml create mode 100644 ylong_http_client/README.md create mode 100644 ylong_http_client/build.rs create mode 100644 ylong_http_client/examples/async_http.rs create mode 100644 ylong_http_client/examples/async_http2.rs create mode 100644 ylong_http_client/examples/async_http2_multi.rs create mode 100644 ylong_http_client/examples/async_https_outside.rs create mode 100644 ylong_http_client/examples/async_proxy_http.rs create mode 100644 ylong_http_client/examples/async_redirect_http.rs create mode 100644 ylong_http_client/examples/sync_http.rs create mode 100644 ylong_http_client/examples/sync_proxy_http.rs create mode 100644 ylong_http_client/examples/sync_redirect_http.rs create mode 100644 ylong_http_client/src/async_impl/adapter.rs create mode 100644 ylong_http_client/src/async_impl/client.rs create mode 100644 ylong_http_client/src/async_impl/conn/http1.rs create mode 100644 ylong_http_client/src/async_impl/conn/http2.rs create mode 100644 ylong_http_client/src/async_impl/conn/mod.rs create mode 100644 ylong_http_client/src/async_impl/connector.rs create mode 100644 ylong_http_client/src/async_impl/downloader/builder.rs create mode 100644 ylong_http_client/src/async_impl/downloader/mod.rs create mode 100644 ylong_http_client/src/async_impl/downloader/operator.rs create mode 100644 ylong_http_client/src/async_impl/http_body.rs create mode 100644 ylong_http_client/src/async_impl/mod.rs create mode 100644 ylong_http_client/src/async_impl/pool.rs create mode 100644 ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs create mode 100644 ylong_http_client/src/async_impl/ssl_stream/mix.rs create mode 100644 ylong_http_client/src/async_impl/ssl_stream/mod.rs create mode 100644 ylong_http_client/src/async_impl/ssl_stream/wrapper.rs create mode 100644 ylong_http_client/src/async_impl/timeout.rs create mode 100644 ylong_http_client/src/async_impl/uploader/builder.rs create mode 100644 ylong_http_client/src/async_impl/uploader/mod.rs create mode 100644 ylong_http_client/src/async_impl/uploader/operator.rs create mode 100644 ylong_http_client/src/error.rs create mode 100644 ylong_http_client/src/lib.rs create mode 100644 ylong_http_client/src/sync_impl/client.rs create mode 100644 ylong_http_client/src/sync_impl/conn/http1.rs create mode 100644 ylong_http_client/src/sync_impl/conn/mod.rs create mode 100644 ylong_http_client/src/sync_impl/connector.rs create mode 100644 ylong_http_client/src/sync_impl/http_body.rs create mode 100644 ylong_http_client/src/sync_impl/mod.rs create mode 100644 ylong_http_client/src/sync_impl/pool.rs create mode 100644 ylong_http_client/src/sync_impl/reader.rs create mode 100644 ylong_http_client/src/sync_impl/ssl_stream.rs create mode 100644 ylong_http_client/src/util/base64.rs create mode 100644 ylong_http_client/src/util/c_openssl/adapter.rs create mode 100644 ylong_http_client/src/util/c_openssl/bio.rs create mode 100644 ylong_http_client/src/util/c_openssl/error.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/bio.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/err.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/mod.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/pem.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/ssl.rs create mode 100644 ylong_http_client/src/util/c_openssl/ffi/x509.rs create mode 100644 ylong_http_client/src/util/c_openssl/foreign.rs create mode 100644 ylong_http_client/src/util/c_openssl/mod.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/ctx.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/error.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/filetype.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/method.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/mod.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/stream.rs create mode 100644 ylong_http_client/src/util/c_openssl/ssl/version.rs create mode 100644 ylong_http_client/src/util/c_openssl/x509.rs create mode 100644 ylong_http_client/src/util/config/client.rs create mode 100644 ylong_http_client/src/util/config/connector.rs create mode 100644 ylong_http_client/src/util/config/http.rs create mode 100644 ylong_http_client/src/util/config/mod.rs create mode 100644 ylong_http_client/src/util/config/settings.rs create mode 100644 ylong_http_client/src/util/config/tls/alpn/mod.rs create mode 100644 ylong_http_client/src/util/config/tls/mod.rs create mode 100644 ylong_http_client/src/util/dispatcher.rs create mode 100644 ylong_http_client/src/util/mod.rs create mode 100644 ylong_http_client/src/util/normalizer.rs create mode 100644 ylong_http_client/src/util/pool.rs create mode 100644 ylong_http_client/src/util/proxy.rs create mode 100644 ylong_http_client/src/util/redirect.rs create mode 100644 ylong_http_client/tests/common/mod.rs create mode 100644 ylong_http_client/tests/file/cert.pem create mode 100644 ylong_http_client/tests/file/key.pem create mode 100644 ylong_http_client/tests/file/root-ca.pem create mode 100644 ylong_http_client/tests/helper.rs create mode 100644 ylong_http_client/tests/sdv_client.rs create mode 100644 ylong_http_client/tests/sdv_https_c_ssl.rs diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000..d09d3be --- /dev/null +++ b/OAT.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index b4a4d89..92fce44 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,40 @@ -# commonlibrary_rust_ylong_http +# ylong_http_client -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +### 简介 -#### 软件架构 -软件架构说明 +ylong_http_client 支持用户构建 HTTP 客户端,支持用户使用该客户端向服务器发送请求,接收并解析服务器返回的响应。 +##### Client -#### 安装教程 +ylong_http_client 支持用户创建同步或者异步 HTTP 客户端,用户可以使用 mod 区分两种客户端。 -1. xxxx -2. xxxx -3. xxxx +- `sync_impl::Client`:同步 HTTP 客户端,整体流程使用同步接口。 -#### 使用说明 +- `async_impl::Client`:异步 HTTP 客户端,整体流程使用异步接口。 -1. xxxx -2. xxxx -3. xxxx +不论是同步还是异步客户端,都具有相同的功能,例如:连接复用、自动重定向、自动重试、设置代理等功能。 -#### 参与贡献 +ylong_http_client 创建的客户端支持以下 HTTP 版本: -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +- `HTTP/1.1` +- `HTTP/2` -#### 特技 +- `HTTP/3` -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +##### Request 和 Response + +ylong_http_client 使用 ylong_http 库提供的 `Request` 结构,支持用户自定义请求内容。 + +在使用客户端发送完请求后,接收到的响应会以 ylong_http 库提供的 `Response` + `HttpBody` 的结构返回。 + +用户可以使用 `Response` 提供的接口来获取请求信息,并且可以使用 ylong_http 提供的 `Body` trait 读取响应的内容。用户也可以使用 ylong_http_client 提供的 `BodyReader` 读取内容。 + +### 编译构建 + +在 ```Cargo.toml``` 下添加依赖。添加后使用 ```cargo``` 进行编译和构建: + +```toml +[dependencies] +ylong_http_client = "1.9.0" +``` \ No newline at end of file diff --git a/ylong_http/Cargo.toml b/ylong_http/Cargo.toml new file mode 100644 index 0000000..ae15713 --- /dev/null +++ b/ylong_http/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ylong_http" +version = "1.9.0" +edition = "2018" +description = "HTTP implementation" +readme = "README.md" +license = "Apache-2.0" +repository = "https://open.codehub.huawei.com/innersource/Ylong_Rust/ylong_rs/files?ref=master&filePath=src%2Fweb%2Fylong_http" +keywords = ["ylong", "http"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["http1_1"] +full = [ + "http1_1", + "http2", + "http3", + "huffman", +] +http1_1 = [] # Uses HTTP/1.1. +http2 = [] # Uses HTTP/2. +http3 = [] # Uses HTTP/3. +huffman = [] # Uses Huffman encoding in `Hpack` and `Qpack`. + +[dependencies] +tokio = { version = "1.20.1", features = ["io-util"] } + +[dev-dependencies] +tokio = { version = "1.20.1", features = ["io-util", "rt-multi-thread", "macros"] } \ No newline at end of file diff --git a/ylong_http/README.md b/ylong_http/README.md new file mode 100644 index 0000000..c067b05 --- /dev/null +++ b/ylong_http/README.md @@ -0,0 +1,85 @@ +# ylong_http + +### 简介 + +ylong_http 提供了 HTTP 各个版本下的协议所需的各种基础组件和扩展组件,方便用户组织所需的 HTTP 结构。 + +ylong_http 包含以下核心功能: + +##### Request 和 Response + +ylong_http 使用 `Request` 和 `Response` 结构来表示 HTTP 最基础的请求和响应: + +- `Request`:HTTP 请求,包含 `Method`、`Uri`、`Headers`、`Body` 等。 + +- `Response`:HTTP 响应,包含 `StatusCode`、`Version`、`Headers`、`Body `等。 + +用户可以使用 `Request` 和 `Response` 提供的相关方法来获取相关信息,或是自定义请求和响应。 + + + +##### Body + +对于`Request`和`Response`的 Body 部分,ylong_http 提供了 `Body` trait,方便用户自定义想要的 Body 结构。 + +为了区分 Body 结构所处在同步还是异步上下文,ylong_http 声明了 `sync_impl::Body` trait 和 `async_impl::Body` trait,使用 mod 进行隔离: + +- `sync_impl::Body` :该 trait 使用同步方法,可以在同步上下文使用。 +- `async_impl::Body`:该 trait 使用异步方法,可以在异步上下文使用。 + +用户可以根据自身需要,选择合适的 `Body` trait 进行实现,也可以两种都实现。 + +ylong_http 也提供默认的 Body 结构供用户使用: + +- `EmptyBody`:空的 Body 结构。 +- `TextBody`:明文的 Body 结构,支持用户传入内存数据或是 IO 数据。 +- `ChunkBody`:分段的 Body 结构,支持用户自定义 Chunk 大小,支持传入内存数据或是 IO 数据。 +- `MimeBody`:MIME 格式的 Body 结构,支持用户使用 MIME 格式设置 Body,支持传入内存数据或是 IO 数据。 + +对应的,也提供了几种 Body 类型的读取器: + +- `TextBodyDecoder`:用于解析明文 Body。 +- `ChunkBodyDecoder`:用于解析分段 Body。 +- `MimeBodyDecoder`:用于解析 MIME 格式的 Body。 + + + +##### 其他组件 + +ylong_http 提供了以下几个 HTTP 版本的相关组件: + +- HTTP/1.1:提供了 `RequestEncoder`、`ResponseDecoder` 等。 +- HTTP/2:`DynamicTable`、`StaticTable`、`FrameDecoder`、`FrameEncoder` 等。 +- HTTP/3:`FrameDecoder`、`FrameEncoder` 等。 + + + +### 编译构建 + +在 ```Cargo.toml``` 下添加依赖。添加后使用 ```cargo``` 进行编译和构建: + +```toml +[dependencies] +ylong_http = "1.9.0" +``` + + + +### 目录 + +``` +ylong_http +├── examples # ylong_http 代码示例 +├── src +│ ├── body # Body trait 定义和扩展 Body 类型。 +│ ├── h1 # HTTP/1.1 相关组件实现。 +│ ├── h2 # HTTP/2 相关组件实现。 +│ ├── h2 # HTTP/3 相关组件实现。 +│ ├── huffman # Huffman 编解码实现。 +│ ├── request # Request 定义和实现。 +│ └── response # Response 定义和实现。 +└── tests # 测试目录 +``` + + + diff --git a/ylong_http/examples/mimebody_multi.rs b/ylong_http/examples/mimebody_multi.rs new file mode 100644 index 0000000..7d57f71 --- /dev/null +++ b/ylong_http/examples/mimebody_multi.rs @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Tests nested `MimeMulti`, for its building and synchronous encoding. + +use ylong_http::body::{sync_impl, MimeMulti, MimeMultiEncoder, MimePart}; + +// A multi example in [`RFC2049`] +// [`RFC2049`]: https://www.rfc-editor.org/rfc/rfc2049 +// +// MIME-Version: 1.0 +// From: Nathaniel Borenstein +// To: Ned Freed +// Date: Fri, 07 Oct 1994 16:15:05 -0700 (PDT) +// Subject: A multipart example +// Content-Type: multipart/mixed; +// boundary=unique-boundary-1 +// +// This is the preamble area of a multipart message. +// Mail readers that understand multipart format +// should ignore this preamble. +// +// If you are reading this text, you might want to +// consider changing to a mail reader that understands +// how to properly display multipart messages. +// +// --unique-boundary-1 +// +// ... Some text appears here ... +// +// [Note that the blank between the boundary and the start +// of the text in this part means no header fields were +// given and this is text in the US-ASCII character set. +// It could have been done with explicit typing as in the +// next part.] +// +// --unique-boundary-1 +// Content-type: text/plain; charset=US-ASCII +// +// This could have been part of the previous part, but +// illustrates explicit versus implicit typing of body +// parts. +// +// --unique-boundary-1 +// Content-Type: multipart/parallel; boundary=unique-boundary-2 +// +// --unique-boundary-2 +// Content-Type: audio/basic +// Content-Transfer-Encoding: base64 +// +// ... base64-encoded 8000 Hz single-channel +// mu-law-format audio data goes here ... +// +// --unique-boundary-2 +// Content-Type: image/jpeg +// Content-Transfer-Encoding: base64 +// +// ... base64-encoded image data goes here ... +// +// --unique-boundary-2-- +// +// --unique-boundary-1 +// Content-type: text/enriched +// +// This is enriched. +// as defined in RFC 1896 +// +// Isn't it +// cool? +// +// --unique-boundary-1 +// Content-Type: message/rfc822 +// +// From: (mailbox in US-ASCII) +// To: (address in US-ASCII) +// Subject: (subject in US-ASCII) +// Content-Type: Text/plain; charset=ISO-8859-1 +// Content-Transfer-Encoding: Quoted-printable +// +// ... Additional text in ISO-8859-1 goes here ... +// +// --unique-boundary-1-- + +fn main() { + let body_text1 = "\ + This could have been part of the previous part, but \ + illustrates explicit versus implicit typing of body parts.\r\n" + .as_bytes(); + let body_text2 = "\ + ... base64-encoded 8000 Hz single-channel \ + mu-law-format audio data goes here ...\r\n" + .as_bytes(); + let body_text3 = "... base64-encoded image data goes here ...\r\n".as_bytes(); + + let body_text4 = "\ +This is enriched. +as defined in RFC 1896 + +Isn't it +cool? +" + .as_bytes(); + + let body_text5 = "\ +From: (mailbox in US-ASCII) +To: (address in US-ASCII) +Subject: (subject in US-ASCII) +Content-Type: Text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: Quoted-printable + +... Additional text in ISO-8859-1 goes here ... +" + .as_bytes(); + + let multi = MimeMulti::builder() + .set_content_type(b"multipart/mixed", b"unique-boundary-1".to_vec()) + .add_part( + MimePart::builder() + .body_from_bytes("... Some text appears here ...\r\n".as_bytes()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "text/plain; charset=US-ASCII") + .body_from_bytes(body_text1) + .build() + .unwrap(), + ) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/parallel", b"unique-boundary-2".to_vec()) + .add_part( + MimePart::builder() + .header("Content-type", "audio/basic") + .header("Content-Transfer-Encoding", "base64") + .body_from_bytes(body_text2) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "image/jpeg") + .header("Content-Transfer-Encoding", "base64") + .body_from_bytes(body_text3) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "text/enriched") + .body_from_reader(body_text4) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "message/rfc822") + .body_from_reader(body_text5) + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 50]; + let mut v_size = vec![]; + let mut v_str = vec![]; + + loop { + let len = sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap(); + if len == 0 { + break; + } + v_size.push(len); + v_str.extend_from_slice(&buf[..len]); + } + assert_eq!( + v_size, + vec![ + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 35 + ] + ); + // Headers is a HashMap, so that sequence of iter is different. + println!("{}", core::str::from_utf8(&v_str).unwrap()); +} diff --git a/ylong_http/examples/mimebody_multi_then_async_data.rs b/ylong_http/examples/mimebody_multi_then_async_data.rs new file mode 100644 index 0000000..84c894e --- /dev/null +++ b/ylong_http/examples/mimebody_multi_then_async_data.rs @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Tests nested `MimeMulti`, for its building and asynchronous encoding. + +use ylong_http::body::{async_impl, MimeMulti, MimeMultiEncoder, MimePart}; + +#[tokio::main] +async fn main() { + let body_text1 = "\ + This could have been part of the previous part, but \ + illustrates explicit versus implicit typing of body parts.\r\n" + .as_bytes(); + let body_text2 = "\ + ... base64-encoded 8000 Hz single-channel \ + mu-law-format audio data goes here ...\r\n" + .as_bytes(); + let body_text3 = "... base64-encoded image data goes here ...\r\n".as_bytes(); + + let body_text4 = "\ +This is enriched. +as defined in RFC 1896 + +Isn't it +cool? +" + .as_bytes(); + + let body_text5 = "\ +From: (mailbox in US-ASCII) +To: (address in US-ASCII) +Subject: (subject in US-ASCII) +Content-Type: Text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: Quoted-printable + +... Additional text in ISO-8859-1 goes here ... +" + .as_bytes(); + + let multi = MimeMulti::builder() + .set_content_type(b"multipart/mixed", b"unique-boundary-1".to_vec()) + .add_part( + MimePart::builder() + .body_from_bytes("... Some text appears here ...\r\n".as_bytes()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "text/plain; charset=US-ASCII") + .body_from_bytes(body_text1) + .build() + .unwrap(), + ) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/parallel", b"unique-boundary-2".to_vec()) + .add_part( + MimePart::builder() + .header("Content-type", "audio/basic") + .header("Content-Transfer-Encoding", "base64") + .body_from_bytes(body_text2) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "image/jpeg") + .header("Content-Transfer-Encoding", "base64") + .body_from_bytes(body_text3) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "text/enriched") + .body_from_async_reader(body_text4) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("Content-type", "message/rfc822") + .body_from_async_reader(body_text5) + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 50]; + let mut v_size = vec![]; + let mut v_str = vec![]; + + loop { + let len = async_impl::Body::data(&mut multi_encoder, &mut buf) + .await + .unwrap(); + if len == 0 { + break; + } + v_size.push(len); + v_str.extend_from_slice(&buf[..len]); + } + assert_eq!( + v_size, + vec![ + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 35 + ] + ); + // Headers is a HashMap, so that sequence of iter is different. + println!("{}", core::str::from_utf8(&v_str).unwrap()); +} diff --git a/ylong_http/src/body/chunk.rs b/ylong_http/src/body/chunk.rs new file mode 100644 index 0000000..25c9368 --- /dev/null +++ b/ylong_http/src/body/chunk.rs @@ -0,0 +1,2064 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::origin::{FromAsyncReader, FromBytes, FromReader}; +use super::{async_impl, sync_impl}; +use crate::body::origin::FromAsyncBody; +use crate::error::{ErrorKind, HttpError}; +use crate::headers::{Header, HeaderName, HeaderValue, Headers}; +use core::convert::Infallible; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::str::from_utf8_unchecked; +use core::task::{Context, Poll}; +use std::any::Any; +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::future::Future; +use std::io::{Error, Read}; +use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf}; + +/// A chunk body is used to encode body to send message by chunk in `HTTP/1.1` format. +/// +/// This chunk body encoder supports you to use the chunk encode method multiple times to output +/// the result in multiple bytes slices. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::ChunkBody; +/// use ylong_http::body::sync_impl::Body; +/// +/// let content = "aaaaa bbbbb ccccc ddddd"; +/// // Gets `ChunkBody` +/// let mut task = ChunkBody::from_bytes(content.as_bytes()); +/// let mut user_slice = [0_u8; 10]; +/// let mut output_vec = vec![]; +/// +/// // First encoding, user_slice is filled. +/// let size = task.data(user_slice.as_mut_slice()).unwrap(); +/// assert_eq!(&user_slice[..size], "17\r\naaaaa ".as_bytes()); +/// output_vec.extend_from_slice(user_slice.as_mut_slice()); +/// +/// // Second encoding, user_slice is filled. +/// let size = task.data(user_slice.as_mut_slice()).unwrap(); +/// assert_eq!(&user_slice[..size], "bbbbb cccc".as_bytes()); +/// output_vec.extend_from_slice(user_slice.as_mut_slice()); +/// +/// // Third encoding, user_slice is filled. +/// let size = task.data(user_slice.as_mut_slice()).unwrap(); +/// assert_eq!(&user_slice[..size], "c ddddd\r\n0".as_bytes()); +/// output_vec.extend_from_slice(user_slice.as_mut_slice()); +/// +/// // Fourth encoding, part of user_slice is filled, this indicates that encoding has ended. +/// let size = task.data(user_slice.as_mut_slice()).unwrap(); +/// assert_eq!(&user_slice[..size], "\r\n\r\n".as_bytes()); +/// output_vec.extend_from_slice(&user_slice[..size]); +/// +/// // We can assemble temporary data into a complete data. +/// let result = "17\r\naaaaa bbbbb ccccc ddddd\r\n0\r\n\r\n"; +/// assert_eq!(output_vec.as_slice(), result.as_bytes()); +/// ``` +pub struct ChunkBody { + from: T, + trailer_value: Vec, + chunk_data: ChunkData, + data_status: DataState, + encode_status: EncodeStatus, + trailer: EncodeTrailer, +} + +const CHUNK_SIZE: usize = 1024; + +struct StatusVar { + cnt: usize, + data_status: DataState, +} + +// Data encoding status +enum DataState { + Partial, // Data encode is processing + Complete, // Data encode is completed + Finish, // Data encode is finished and return result +} + +// Component encoding status +enum TokenStatus { + Complete(T), // The current component is completely encoded. + Partial(E), // The current component is partially encoded. +} + +type Token = TokenStatus; + +impl<'a> ChunkBody> { + /// Creates a new `ChunkBody` by `bytes`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::ChunkBody; + /// + /// let task = ChunkBody::from_bytes("".as_bytes()); + /// ``` + pub fn from_bytes(bytes: &'a [u8]) -> Self { + ChunkBody { + from: FromBytes::new(bytes), + trailer_value: vec![], + chunk_data: ChunkData::new(vec![]), + data_status: DataState::Partial, + encode_status: EncodeStatus::new(), + trailer: EncodeTrailer::new(), + } + } + + fn chunk_encode(&mut self, src: &[u8], dst: &mut [u8]) -> usize { + self.encode_status.chunk_last = self.chunk_data.chunk_last; + let (output_size, var) = self.encode_status.encode(src, dst); + + if let Some(v) = var { + self.chunk_data.chunk_count = v.cnt; + self.data_status = v.data_status; + } + output_size + } +} + +impl ChunkBody> { + /// Creates a new `ChunkBody` by `reader`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::ChunkBody; + /// + /// let task = ChunkBody::from_reader("".as_bytes()); + /// ``` + pub fn from_reader(reader: T) -> Self { + ChunkBody { + from: FromReader::new(reader), + trailer_value: vec![], + chunk_data: ChunkData::new(vec![0; CHUNK_SIZE]), + data_status: DataState::Partial, + encode_status: EncodeStatus::new(), + trailer: EncodeTrailer::new(), + } + } + + fn chunk_encode(&mut self, dst: &mut [u8]) -> usize { + self.chunk_encode_reader(dst) + } +} + +impl ChunkBody> { + /// Creates a new `ChunkBody` by `async reader`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::ChunkBody; + /// + /// let task = ChunkBody::from_async_reader("".as_bytes()); + /// ``` + pub fn from_async_reader(reader: T) -> Self { + ChunkBody { + from: FromAsyncReader::new(reader), + trailer_value: vec![], + chunk_data: ChunkData::new(vec![0; CHUNK_SIZE]), + data_status: DataState::Partial, + encode_status: EncodeStatus::new(), + trailer: EncodeTrailer::new(), + } + } + + fn chunk_encode(&mut self, dst: &mut [u8]) -> usize { + self.chunk_encode_reader(dst) + } +} + +impl ChunkBody> { + /// Creates a new `ChunkBody` by `async body`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::ChunkBody; + /// + /// let task = ChunkBody::from_async_body("".as_bytes()); + /// ``` + pub fn from_async_body(body: T) -> Self { + ChunkBody { + from: FromAsyncBody::new(body), + trailer_value: vec![], + chunk_data: ChunkData::new(vec![0; CHUNK_SIZE]), + data_status: DataState::Partial, + encode_status: EncodeStatus::new(), + trailer: EncodeTrailer::new(), + } + } + + fn chunk_encode(&mut self, dst: &mut [u8]) -> usize { + self.chunk_encode_reader(dst) + } +} + +impl<'a> sync_impl::Body for ChunkBody> { + type Error = Infallible; + + fn data(&mut self, buf: &mut [u8]) -> Result { + let mut count = 0; + while count != buf.len() { + let encode_size = match self.data_status { + DataState::Partial => self.bytes_encode(&mut buf[count..]), + DataState::Complete => self.trailer_encode(&mut buf[count..]), + DataState::Finish => return Ok(count), + }; + count += encode_size; + } + Ok(buf.len()) + } +} + +impl sync_impl::Body for ChunkBody> { + type Error = Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + let mut count = 0; + while count != buf.len() { + let encode_size = match self.data_status { + DataState::Partial => { + if !self.encode_status.get_flag() { + self.encode_status.set_flag(true); + self.encode_status.set_chunk_idx(0); + self.chunk_data.chunk_last = + (*self.from).read(&mut self.chunk_data.chunk_buf).unwrap(); + } + self.chunk_encode(&mut buf[count..]) + } + DataState::Complete => self.trailer_encode(&mut buf[count..]), + DataState::Finish => { + return Ok(count); + } + }; + count += encode_size; + } + Ok(buf.len()) + } +} + +impl<'c> async_impl::Body for ChunkBody> { + type Error = Error; + + fn poll_data( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut count = 0; + while count != buf.len() { + let encode_size: Poll = match self.data_status { + DataState::Partial => Poll::Ready(self.bytes_encode(&mut buf[count..])), + DataState::Complete => Poll::Ready(self.trailer_encode(&mut buf[count..])), + DataState::Finish => return Poll::Ready(Ok(count)), + }; + match encode_size { + Poll::Ready(size) => { + count += size; + } + Poll::Pending => return Poll::Pending, + } + } + Poll::Ready(Ok(buf.len())) + } +} + +impl async_impl::Body for ChunkBody> { + type Error = Error; + + fn poll_data( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let chunk_body = self.get_mut(); + let mut count = 0; + while count != buf.len() { + let encode_size = match chunk_body.data_status { + DataState::Partial => { + if !chunk_body.encode_status.get_flag() { + let mut read_buf = ReadBuf::new(&mut chunk_body.chunk_data.chunk_buf); + + match Pin::new(&mut *chunk_body.from).poll_read(_cx, &mut read_buf) { + Poll::Ready(Ok(())) => { + let size = read_buf.filled().len(); + chunk_body.encode_status.set_flag(true); + // chunk idx reset zero + chunk_body.encode_status.set_chunk_idx(0); + chunk_body.chunk_data.chunk_last = size; + let data_size = chunk_body.chunk_encode(&mut buf[count..]); + Poll::Ready(data_size) + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } else { + Poll::Ready(chunk_body.chunk_encode(&mut buf[count..])) + } + } + DataState::Complete => Poll::Ready(chunk_body.trailer_encode(&mut buf[count..])), + DataState::Finish => { + return Poll::Ready(Ok(count)); + } + }; + + match encode_size { + Poll::Ready(size) => { + count += size; + } + Poll::Pending => { + if count != 0 { + return Poll::Ready(Ok(count)); + } + return Poll::Pending; + } + } + } + Poll::Ready(Ok(buf.len())) + } +} + +impl async_impl::Body for ChunkBody> { + type Error = T::Error; + + fn poll_data( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let chunk_body = self.get_mut(); + let mut count = 0; + while count != buf.len() { + let encode_size = match chunk_body.data_status { + DataState::Partial => { + if !chunk_body.encode_status.get_flag() { + match Pin::new(&mut *chunk_body.from) + .poll_data(_cx, &mut chunk_body.chunk_data.chunk_buf) + { + Poll::Ready(Ok(size)) => { + chunk_body.encode_status.set_flag(true); + // chunk idx reset zero + chunk_body.encode_status.set_chunk_idx(0); + chunk_body.chunk_data.chunk_last = size; + let data_size = chunk_body.chunk_encode(&mut buf[count..]); + Poll::Ready(data_size) + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } else { + Poll::Ready(chunk_body.chunk_encode(&mut buf[count..])) + } + } + DataState::Complete => Poll::Ready(chunk_body.trailer_encode(&mut buf[count..])), + DataState::Finish => { + return Poll::Ready(Ok(count)); + } + }; + + match encode_size { + Poll::Ready(size) => { + count += size; + } + Poll::Pending => { + if count != 0 { + return Poll::Ready(Ok(count)); + } + return Poll::Pending; + } + } + } + Poll::Ready(Ok(buf.len())) + } +} + +impl<'a> ChunkBody> { + fn bytes_encode(&mut self, dst: &mut [u8]) -> usize { + if !self.encode_status.get_flag() { + self.encode_status.set_flag(true); + self.encode_status.set_chunk_idx(0); + let data_left = self.from.len() - self.chunk_data.chunk_count * CHUNK_SIZE; + self.chunk_data.chunk_last = if data_left < CHUNK_SIZE { + data_left + } else { + CHUNK_SIZE + }; + } + let src = &self.from[self.chunk_data.chunk_count * CHUNK_SIZE + ..(self.chunk_data.chunk_count * CHUNK_SIZE + self.chunk_data.chunk_last)]; + self.chunk_encode(src, dst) + } +} + +impl ChunkBody { + /// Creates a new `Trailer` by `set_trailer`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::ChunkBody; + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// let _ = headers.insert("accept", "text/html"); + /// let mut task = ChunkBody::from_bytes("".as_bytes()).set_trailer(headers); + /// ``` + pub fn set_trailer(mut self, trailer_headers: Headers) -> Self { + let mut trailer_vec = vec![]; + for (name, value) in trailer_headers.into_iter() { + // Operate on each `HeaderName` and `HeaderValue` pair. + trailer_vec.extend_from_slice(name.as_bytes()); + trailer_vec.extend_from_slice(b":"); + // to_string will not return err, so can use unwrap directly + trailer_vec.extend_from_slice(value.to_str().unwrap().as_bytes()); + trailer_vec.extend_from_slice(b"\r\n"); + } + self.trailer_value = trailer_vec; + self + } + fn chunk_encode_reader(&mut self, dst: &mut [u8]) -> usize { + self.encode_status.chunk_last = self.chunk_data.chunk_last; + let (output_size, var) = self.encode_status.encode( + &self.chunk_data.chunk_buf[..self.chunk_data.chunk_last], + dst, + ); + if let Some(v) = var { + self.chunk_data.chunk_count = v.cnt; + self.data_status = v.data_status; + } + output_size + } + + fn trailer_encode(&mut self, dst: &mut [u8]) -> usize { + let mut src = b"0\r\n".to_vec(); + if self.trailer_value.is_empty() { + src.extend_from_slice(b"\r\n"); + } else { + src.extend_from_slice(self.trailer_value.as_slice()); + src.extend_from_slice(b"\r\n"); + }; + match self.trailer.encode(src.as_slice(), dst) { + TokenStatus::Complete(output_size) => { + self.data_status = DataState::Finish; + output_size + } + TokenStatus::Partial(output_size) => output_size, + } + } +} + +struct ChunkData { + chunk_buf: Vec, + chunk_count: usize, + chunk_last: usize, +} + +impl ChunkData { + fn new(buf: Vec) -> Self { + ChunkData { + chunk_buf: buf, + chunk_count: 0, + chunk_last: 0, + } + } +} + +struct EncodeStatus { + chunk_size: usize, + chunk_last: usize, + chunk_idx: usize, + read_flag: bool, + src_idx: usize, + chunk_status: ChunkState, + meta_crlf: EncodeCrlf, + data_crlf: EncodeCrlf, + finish_crlf: EncodeCrlf, + hex: EncodeHex, + hex_last: EncodeHex, +} + +impl EncodeStatus { + fn new() -> Self { + EncodeStatus { + chunk_size: CHUNK_SIZE, + chunk_last: 0, + chunk_idx: 0, + read_flag: false, + src_idx: 0, + chunk_status: ChunkState::MetaSize, + meta_crlf: EncodeCrlf::new(), + data_crlf: EncodeCrlf::new(), + finish_crlf: EncodeCrlf::new(), + hex: EncodeHex::new(format!("{CHUNK_SIZE:x}")), + hex_last: EncodeHex::new("".to_string()), + } + } + + fn encode(&mut self, src: &[u8], dst: &mut [u8]) -> (usize, Option) { + match self.chunk_status { + ChunkState::MetaSize => (self.meta_size_encode(dst), None), + ChunkState::MetaExt => (0, None), + ChunkState::MetaCrlf => (self.meta_crlf_encode(dst), None), + ChunkState::Data => { + if self.chunk_last != CHUNK_SIZE { + self.tail_encode(src, dst) + } else { + self.data_encode(src, dst) + } + } + ChunkState::DataCrlf => (self.data_crlf_encode(dst), None), + ChunkState::Finish => self.finish_encode(dst), + } + } + + fn meta_size_encode(&mut self, dst: &mut [u8]) -> usize { + if self.chunk_last == CHUNK_SIZE { + match self.hex.encode(dst) { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::MetaCrlf; + self.hex.src_idx = 0; + output_size + } + TokenStatus::Partial(output_size) => output_size, + } + } else { + self.hex_last = EncodeHex::new(format!("{last:x}", last = self.chunk_last)); + match self.hex_last.encode(dst) { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::MetaCrlf; + self.hex_last.src_idx = 0; + output_size + } + TokenStatus::Partial(output_size) => output_size, + } + } + } + + fn meta_crlf_encode(&mut self, dst: &mut [u8]) -> usize { + match self.meta_crlf.encode(dst) { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::Data; + self.meta_crlf.src_idx = 0; + output_size + } + TokenStatus::Partial(output_size) => output_size, + } + } + + fn data_crlf_encode(&mut self, dst: &mut [u8]) -> usize { + match self.data_crlf.encode(dst) { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::MetaSize; + self.data_crlf.src_idx = 0; + output_size + } + TokenStatus::Partial(output_size) => output_size, + } + } + + fn finish_encode(&mut self, dst: &mut [u8]) -> (usize, Option) { + match self.finish_crlf.encode(dst) { + TokenStatus::Complete(output_size) => { + self.meta_crlf.src_idx = 0; + let var = StatusVar { + cnt: 0, + data_status: DataState::Complete, + }; + (output_size, Some(var)) + } + TokenStatus::Partial(output_size) => (output_size, None), + } + } + + fn data_encode(&mut self, src: &[u8], dst: &mut [u8]) -> (usize, Option) { + let mut task = WriteData::new(src, &mut self.chunk_idx, dst); + + match task.write() { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::DataCrlf; + self.read_flag = false; + let var = StatusVar { + cnt: 1, + data_status: DataState::Partial, + }; + (output_size, Some(var)) + } + TokenStatus::Partial(output_size) => (output_size, None), + } + } + + fn tail_encode(&mut self, src: &[u8], dst: &mut [u8]) -> (usize, Option) { + let mut task = WriteData::new(src, &mut self.chunk_idx, dst); + match task.write() { + TokenStatus::Complete(output_size) => { + self.chunk_status = ChunkState::Finish; + self.read_flag = false; + let var = StatusVar { + cnt: 0, + data_status: DataState::Partial, + }; + (output_size, Some(var)) + } + TokenStatus::Partial(output_size) => (output_size, None), + } + } + + fn get_flag(&mut self) -> bool { + self.read_flag + } + + fn set_flag(&mut self, flag: bool) { + self.read_flag = flag; + } + + fn set_chunk_idx(&mut self, num: usize) { + self.chunk_idx = num; + } +} + +struct EncodeHex { + inner: String, + src_idx: usize, +} + +impl EncodeHex { + fn new(hex: String) -> Self { + Self { + inner: hex, + src_idx: 0, + } + } + + fn encode(&mut self, buf: &mut [u8]) -> Token { + let hex = self.inner.as_bytes(); + let mut task = WriteData::new(hex, &mut self.src_idx, buf); + task.write() + } +} + +struct EncodeCrlf { + src_idx: usize, +} + +impl EncodeCrlf { + fn new() -> Self { + Self { src_idx: 0 } + } + + fn encode(&mut self, buf: &mut [u8]) -> Token { + let crlf = "\r\n".as_bytes(); + let mut task = WriteData::new(crlf, &mut self.src_idx, buf); + task.write() + } +} + +struct EncodeTrailer { + src_idx: usize, +} + +impl EncodeTrailer { + fn new() -> Self { + Self { src_idx: 0 } + } + + fn encode(&mut self, src: &[u8], buf: &mut [u8]) -> Token { + let mut task = WriteData::new(src, &mut self.src_idx, buf); + task.write() + } +} + +struct WriteData<'a> { + src: &'a [u8], + src_idx: &'a mut usize, + dst: &'a mut [u8], +} + +impl<'a> WriteData<'a> { + fn new(src: &'a [u8], src_idx: &'a mut usize, dst: &'a mut [u8]) -> Self { + WriteData { src, src_idx, dst } + } + + fn write(&mut self) -> Token { + let src_idx = *self.src_idx; + let input_len = self.src.len() - src_idx; + let output_len = self.dst.len(); + let num = std::io::Read::read(&mut &self.src[src_idx..], self.dst).unwrap(); + if output_len >= input_len { + *self.src_idx += num; + return TokenStatus::Complete(num); + } + *self.src_idx += num; + TokenStatus::Partial(num) + } +} + +// Stage of decode chunks, The elements of the chunk-body are as follows: +// |======================================================================== +// | chunked-body = *chunk | +// | last-chunk | +// | trailer-section | +// | CRLF | +// | | +// | chunk = chunk-size [ chunk-ext ] CRLF | +// | chunk-data CRLF | +// | chunk-size = 1*HEXDIG | +// | last-chunk = 1*("0") [ chunk-ext ] CRLF | +// | | +// | chunk-data = 1*OCTET ; a sequence of chunk-size octets | +// | | +// | chunk-ext = *( BWS ";" BWS chunk-ext-name | +// | [ BWS "=" BWS chunk-ext-val ] ) | +// | | +// | chunk-ext-name = token | +// | chunk-ext-val = token / quoted-string | +// |======================================================================== +enum Stage { + Size, + Extension, + SizeEnd, + Data, + DataEnd, + TrailerCrlf, + TrailerData, + TrailerEndCrlf, +} + +/// Chunk-ext part of a chunk, +/// Currently, the `ChunkBodyDecoder` does not decode the chunk-ext part. +/// Therefore, the chunk-ext key-value pair cannot be inserted or extracted. +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ChunkExt { + map: HashMap, +} + +impl ChunkExt { + /// Constructor of `ChunkExt` + pub fn new() -> Self { + ChunkExt { + map: HashMap::new(), + } + } +} + +/// Decode state of the chunk buffer. +/// When chunks in the buffer end in different elements, `ChunkBodyDecoder` returns different `ChunkState`, as shown in the following figure: +/// > ```trust +/// > Meta: `chunk-size [ chunk-ext ] CRLF` +/// > Partial: `chunk-size [ chunk-ext ] CRLF chunk-data` +/// > Complete: `chunk-size [ chunk-ext ] CRLF chunk-data CRLF` +/// > ``` +#[derive(Debug, Eq, PartialEq)] +pub enum ChunkState { + /// State of `chunk-size` + MetaSize, + /// State of `chunk-ext` + MetaExt, + /// CRLF + MetaCrlf, + /// State of `chunk-data` + Data, + /// CRLF + DataCrlf, + /// End + Finish, +} + +/// Decode result of the chunk buffer, contains all chunks in a buffer. +#[derive(Debug, Eq, PartialEq)] +pub struct Chunks<'a> { + chunks: Vec>, +} + +/// An iterator of `Chunks`. +pub struct ChunksIter<'a> { + iter: core::slice::Iter<'a, Chunk<'a>>, +} + +/// An iterator that moves out of a `Chunks`. +pub struct ChunksIntoIter<'a> { + into_iter: std::vec::IntoIter>, +} + +impl ChunksIter<'_> { + fn new<'a>(iter: core::slice::Iter<'a, Chunk<'a>>) -> ChunksIter<'a> { + ChunksIter { iter } + } +} + +impl<'a> Deref for ChunksIter<'a> { + type Target = core::slice::Iter<'a, Chunk<'a>>; + + fn deref(&self) -> &Self::Target { + &self.iter + } +} + +impl<'a> DerefMut for ChunksIter<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.iter + } +} + +impl ChunksIntoIter<'_> { + fn new(into_iter: std::vec::IntoIter) -> ChunksIntoIter { + ChunksIntoIter { into_iter } + } +} + +impl<'a> Iterator for ChunksIntoIter<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + self.into_iter.next() + } +} + +impl<'a> Deref for ChunksIntoIter<'a> { + type Target = std::vec::IntoIter>; + + fn deref(&self) -> &Self::Target { + &self.into_iter + } +} + +impl<'b> Chunks<'b> { + /// Returns an `ChunksIter` + pub fn iter(&self) -> ChunksIter { + ChunksIter::new(self.chunks.iter()) + } + + fn new() -> Self { + Chunks { chunks: vec![] } + } + + fn push<'a: 'b>(&mut self, chunk: Chunk<'a>) { + self.chunks.push(chunk) + } +} + +impl<'a> IntoIterator for Chunks<'a> { + type Item = Chunk<'a>; + type IntoIter = ChunksIntoIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + ChunksIntoIter::new(self.chunks.into_iter()) + } +} + +/// Chunk instance, Indicates a chunk. +/// After a decode, the `ChunkBodyDecoder` returns a `Chunk` regardless of whether a chunk is completely decoded. +/// The decode status is recorded by the `state` variable. +/// +#[derive(Debug, Eq, PartialEq)] +pub struct Chunk<'a> { + id: usize, + state: ChunkState, + size: usize, + extension: ChunkExt, + data: &'a [u8], + trailer: Option<&'a [u8]>, +} + +impl Chunk<'_> { + fn set_id(&mut self, id: usize) { + self.id = id; + } + + fn is_complete(&self) -> bool { + matches!(self.state, ChunkState::Finish) + } + + /// Get the id of chunk-data. + pub fn id(&self) -> usize { + self.id + } + + /// Get the immutable reference of a state. + pub fn state(&self) -> &ChunkState { + &self.state + } + + /// Get the size of chunk-data, + /// If the size part of a chunk is not completely decoded, the value of size is 0. + pub fn size(&self) -> usize { + self.size + } + + /// Get the immutable reference of chunk-ext. + /// Currently, only one empty ChunkExt is contained. + pub fn extension(&self) -> &ChunkExt { + &self.extension + } + + /// Get the chunk-data. + /// When the state is partial, only partial data is returned. + pub fn data(&self) -> &[u8] { + self.data + } + /// Get the trailer. + pub fn trailer(&self) -> Option<&[u8]> { + self.trailer + } +} + +/// Chunk decoder. +/// The decoder decode only all chunks and last-chunk in chunk-body and does not decode subsequent trailer-section. +/// The decoder maintains a state saving decode phase. +/// When a chunk is not completely decoded or a decoding exception occurs, the state is not reset. +pub struct ChunkBodyDecoder { + chunk_num: usize, + total_size: usize, + rest_size: usize, + size_vec: Vec, + cr_meet: bool, + is_last_chunk: bool, + is_chunk_trailer: bool, + is_trailer: bool, + is_trailer_crlf: bool, + stage: Stage, +} + +impl Default for ChunkBodyDecoder { + fn default() -> Self { + Self::new() + } +} + +impl ChunkBodyDecoder { + /// Constructor of `ChunkBodyDecoder` bytes decoder. + /// Initial stage is `Size` + pub fn new() -> ChunkBodyDecoder { + ChunkBodyDecoder { + chunk_num: 0, + total_size: 0, + rest_size: 0, + size_vec: vec![], + cr_meet: false, + is_last_chunk: false, + is_chunk_trailer: false, + is_trailer: false, + is_trailer_crlf: false, + stage: Stage::Size, + } + } + + /// Initial trailer settings for check whether body contain trailer. + pub fn contains_trailer(mut self, contain_trailer: bool) -> Self { + self.is_trailer = contain_trailer; + self + } + + /// Decode interface of the chunk decoder. + /// It transfers a u8 slice pointing to the chunk data and returns the data of a chunk and the remaining data. + /// When the data in the u8 slice is not completely decoded for a chunk, + /// An empty u8 slice is returned for the remaining data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::{Chunk, ChunkBodyDecoder, ChunkExt, ChunkState}; + /// let mut decoder = ChunkBodyDecoder::new(); + /// let chunk_body_bytes = "\ + /// 5\r\n\ + /// hello\r\n\ + /// 000; message = last\r\n\ + /// \r\n\ + /// " + /// .as_bytes(); + /// let (chunks, rest) = decoder.decode(chunk_body_bytes).unwrap(); + /// assert_eq!(chunks.iter().len(), 2); + /// let chunk = chunks.iter().next().unwrap(); + /// assert_eq!( + /// (chunk.id(), chunk.state(), chunk.size(), chunk.extension(), chunk.data()), + /// (0, &ChunkState::Finish, 5, &ChunkExt::new(), "hello".as_bytes()) + /// ); + /// ``` + pub fn decode<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunks<'a>, &'a [u8]), HttpError> { + let mut results = Chunks::new(); + let mut remains = buf; + loop { + let (mut chunk, rest) = match self.stage { + Stage::Size => self.decode_size(remains), + Stage::Extension => self.skip_extension(remains), + Stage::SizeEnd => self.skip_crlf(remains), + Stage::Data => self.decode_data(remains), + Stage::DataEnd => self.skip_last_crlf(&remains[..0], remains), + Stage::TrailerCrlf => self.skip_trailer_crlf(remains), + Stage::TrailerData => self.decode_trailer_data(remains), + Stage::TrailerEndCrlf => self.skip_trailer_last_crlf(&remains[..0], remains), + }?; + + chunk.set_id(self.chunk_num); + remains = rest; + match (chunk.is_complete(), self.is_last_chunk) { + (false, _) => { + if self.is_chunk_trailer && chunk.state == ChunkState::Data { + results.push(chunk); + self.chunk_num += 1; + if remains.is_empty() { + break; + } + } else { + results.push(chunk); + break; + } + } + (true, true) => { + results.push(chunk); + self.is_last_chunk = false; + self.chunk_num = 0; + break; + } + (true, false) => { + results.push(chunk); + self.chunk_num += 1; + if remains.is_empty() { + break; + } + } + } + } + Ok((results, remains)) + } + + fn decode_size<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::Size; + if buf.is_empty() { + return Ok(( + Chunk { + id: 0, + state: ChunkState::MetaSize, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: None, + }, + buf, + )); + } + for (i, &b) in buf.iter().enumerate() { + match b { + b'0' => { + if buf.len() <= i + 1 { + continue; + } + if buf[i + 1] != b';' && buf[i + 1] != b' ' && buf[i + 1] != b'\r' { + continue; + } + if self.is_trailer { + self.is_chunk_trailer = true; + return self.skip_extension(&buf[i..]); + } else { + continue; + } + } + b'1'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => {} + b' ' | b'\t' | b';' | b'\r' | b'\n' => { + if self.is_chunk_trailer { + return self.skip_trailer_crlf(&buf[i..]); + } else { + self.size_vec.extend_from_slice(&buf[..i]); + self.total_size = usize::from_str_radix( + unsafe { from_utf8_unchecked(self.size_vec.as_slice()) }, + 16, + ) + .map_err(|_| { + >::into(ErrorKind::InvalidInput) + })?; + self.size_vec.clear(); + // Decode to the last chunk + return if self.total_size == 0 { + self.is_last_chunk = true; + self.skip_extension(&buf[i..]) + } else { + self.rest_size = self.total_size; + self.skip_extension(&buf[i..]) + }; + } + } + _ => return Err(ErrorKind::InvalidInput.into()), + } + } + self.size_vec.extend_from_slice(buf); + Ok(( + Chunk { + id: 0, + state: ChunkState::MetaSize, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: None, + }, + &buf[buf.len()..], + )) + } + + fn skip_extension<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::Extension; + if self.is_chunk_trailer { + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + return self.skip_trailer_crlf(&buf[i + 1..]); + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + + return self.skip_trailer_crlf(&buf[i..]); + } + _ => {} + } + } + Ok(( + Chunk { + id: 0, + state: ChunkState::MetaExt, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: Some(&buf[..0]), + }, + &buf[buf.len()..], + )) + } else { + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + return self.skip_crlf(&buf[i + 1..]); + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + return self.skip_crlf(&buf[i..]); + } + _ => {} + } + } + Ok(( + Chunk { + id: 0, + state: ChunkState::MetaExt, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: None, + }, + &buf[buf.len()..], + )) + } + } + + fn skip_crlf<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::SizeEnd; + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + // TODO Check whether the state machine needs to be reused after the parsing fails and whether the state machine status needs to be adjusted. + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + return self.decode_data(&buf[i + 1..]); + } + _ => return Err(ErrorKind::InvalidInput.into()), + } + } + Ok(( + Chunk { + id: 0, + state: ChunkState::MetaCrlf, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: None, + }, + &buf[buf.len()..], + )) + } + + fn skip_trailer_crlf<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::TrailerCrlf; + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + return self.decode_trailer_data(&buf[i + 1..]); + } + _ => return Err(ErrorKind::InvalidInput.into()), + } + } + Ok(( + Chunk { + id: 0, + state: ChunkState::MetaCrlf, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: Some(&buf[..0]), + }, + &buf[buf.len()..], + )) + } + + fn decode_trailer_data<'a>( + &mut self, + buf: &'a [u8], + ) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::TrailerData; + if buf.is_empty() { + return Ok(( + Chunk { + id: 0, + state: ChunkState::Data, + size: 0, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: Some(&buf[..0]), + }, + &buf[buf.len()..], + )); + } + + if buf[0] == b'\r' { + self.is_last_chunk = true; + } + + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + return self.skip_trailer_last_crlf(&buf[..i], &buf[i + 1..]); + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + return self.skip_trailer_last_crlf(&buf[..i], &buf[i..]); + } + _ => {} + } + } + + Ok(( + Chunk { + id: 0, + state: ChunkState::Data, + size: 0, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: Some(buf), + }, + &buf[buf.len()..], + )) + } + + fn decode_data<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::Data; + if buf.is_empty() { + return Ok(( + Chunk { + id: 0, + state: ChunkState::Data, + size: self.total_size, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: None, + }, + &buf[buf.len()..], + )); + } + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + + if self.rest_size != i { + return Err(ErrorKind::InvalidInput.into()); + } + self.rest_size = 0; + return self.skip_last_crlf(&buf[..i], &buf[i + 1..]); + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + + if self.rest_size != i { + return Err(ErrorKind::InvalidInput.into()); + } + self.rest_size = 0; + return self.skip_last_crlf(&buf[..i], &buf[i..]); + } + _ => {} + } + } + if self.rest_size < buf.len() { + return Err(ErrorKind::InvalidInput.into()); + } + + self.rest_size -= buf.len(); + Ok(( + Chunk { + id: 0, + state: ChunkState::Data, + size: self.total_size, + extension: ChunkExt::new(), + data: buf, + trailer: None, + }, + &buf[buf.len()..], + )) + } + + fn skip_trailer_last_crlf<'a>( + &mut self, + data: &'a [u8], + buf: &'a [u8], + ) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::TrailerEndCrlf; + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + return if self.is_last_chunk { + self.stage = Stage::TrailerEndCrlf; + Ok(( + Chunk { + id: 0, + state: ChunkState::Finish, + size: 0, + extension: ChunkExt::new(), + data: &buf[..0], + trailer: Some(&buf[..0]), + }, + &buf[i + 1..], + )) + } else { + self.cr_meet = false; + self.stage = Stage::TrailerData; + let complete_chunk = Chunk { + id: 0, + state: ChunkState::DataCrlf, + size: 0, + extension: ChunkExt::new(), + data: &data[..0], + trailer: Some(data), + }; + return Ok((complete_chunk, &buf[i + 1..])); + }; + } + _ => return Err(ErrorKind::InvalidInput.into()), + } + } + + Ok(( + Chunk { + id: 0, + state: ChunkState::DataCrlf, + size: 0, + extension: ChunkExt::new(), + data: &data[..0], + trailer: Some(data), + }, + &buf[buf.len()..], + )) + } + + fn skip_last_crlf<'a>( + &mut self, + data: &'a [u8], + buf: &'a [u8], + ) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { + self.stage = Stage::DataEnd; + for (i, &b) in buf.iter().enumerate() { + match b { + b'\r' => { + if self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = true; + } + b'\n' => { + if !self.cr_meet { + return Err(ErrorKind::InvalidInput.into()); + } + self.cr_meet = false; + self.stage = Stage::Size; + let complete_chunk = Chunk { + id: 0, + state: ChunkState::Finish, + size: self.total_size, + extension: ChunkExt::new(), + data, + trailer: None, + }; + self.total_size = 0; + return Ok((complete_chunk, &buf[i + 1..])); + } + _ => return Err(ErrorKind::InvalidInput.into()), + } + } + Ok(( + Chunk { + id: 0, + state: ChunkState::DataCrlf, + size: self.total_size, + extension: ChunkExt::new(), + data, + trailer: None, + }, + &buf[buf.len()..], + )) + } +} + +#[cfg(test)] +mod ut_chunk { + use crate::body::chunk::ChunkBody; + use crate::body::sync_impl::Body; + use crate::body::{async_impl, Chunk, ChunkBodyDecoder, ChunkExt, ChunkState, Chunks}; + use crate::error::ErrorKind; + use crate::headers::Headers; + + fn data_message() -> Vec { + let mut vec = Vec::new(); + for i in 0..=10 { + vec.extend_from_slice(&[i % 10; 100]); + } + vec + } + + fn res_message() -> Vec { + let mut res = b"400\r\n".to_vec(); + for i in 0..=9 { + res.extend_from_slice(&[i % 10; 100]); + } + res.extend_from_slice(&[0; 24]); + res.extend_from_slice(b"\r\n4c\r\n"); + res.extend_from_slice(&[0; 76]); + res.extend_from_slice(b"\r\n0\r\n\r\n"); + res + } + fn res_trailer_message() -> Vec { + let mut res = b"400\r\n".to_vec(); + for i in 0..=9 { + res.extend_from_slice(&[i % 10; 100]); + } + res.extend_from_slice(&[0; 24]); + res.extend_from_slice(b"\r\n4c\r\n"); + res.extend_from_slice(&[0; 76]); + res.extend_from_slice(b"\r\n0\r\n"); + res.extend_from_slice(b"accept:text/html\r\n"); + res.extend_from_slice(b"\r\n"); + res + } + #[test] + fn ut_chunk_body_encode_trailer_0() { + let mut headers = Headers::new(); + let _ = headers.insert("accept", "text/html"); + let content = data_message(); + let mut task = ChunkBody::from_bytes(content.as_slice()).set_trailer(headers); + let mut user_slice = [0_u8; 20]; + let mut output_vec = vec![]; + let mut size = user_slice.len(); + while size == user_slice.len() { + size = task.data(user_slice.as_mut_slice()).unwrap(); + output_vec.extend_from_slice(&user_slice[..size]); + } + assert_eq!(output_vec, res_trailer_message()); + } + + /// UT test cases for `ChunkBody::data`. + /// + /// # Brief + /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_bytes`. + /// 2. Encodes chunk body by calling `ChunkBody::data` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_encode_0() { + let content = data_message(); + let mut task = ChunkBody::from_bytes(content.as_slice()); + let mut user_slice = [0_u8; 20]; + let mut output_vec = vec![]; + + let mut size = user_slice.len(); + while size == user_slice.len() { + size = task.data(user_slice.as_mut_slice()).unwrap(); + output_vec.extend_from_slice(&user_slice[..size]); + } + assert_eq!(output_vec, res_message()); + } + + /// UT test cases for `ChunkBody::data`. + /// + /// # Brief + /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_reader`. + /// 2. Encodes chunk body by calling `ChunkBody::data` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_encode_1() { + let content = data_message(); + let mut task = ChunkBody::from_reader(content.as_slice()); + let mut user_slice = [0_u8; 20]; + let mut output_vec = vec![]; + + let mut size = user_slice.len(); + while size == user_slice.len() { + size = task.data(user_slice.as_mut_slice()).unwrap(); + output_vec.extend_from_slice(&user_slice[..size]); + } + assert_eq!(output_vec, res_message()); + } + + /// UT test cases for `ChunkBody::data` in async condition. + /// + /// # Brief + /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_bytes`. + /// 2. Encodes chunk body by calling `async_impl::Body::data` + /// 3. Checks if the test result is correct. + #[tokio::test] + async fn ut_asnyc_chunk_body_encode_0() { + let content = data_message(); + let mut task = ChunkBody::from_bytes(content.as_slice()); + let mut user_slice = [0_u8; 20]; + let mut output_vec = vec![]; + + let mut size = user_slice.len(); + while size == user_slice.len() { + size = async_impl::Body::data(&mut task, user_slice.as_mut_slice()) + .await + .unwrap(); + output_vec.extend_from_slice(&user_slice[..size]); + } + assert_eq!(output_vec, res_message()); + } + + /// UT test cases for `ChunkBody::data` in async condition. + /// + /// # Brief + /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_async_reader`. + /// 2. Encodes chunk body by calling `async_impl::Body::data` + /// 3. Checks if the test result is correct. + #[tokio::test] + async fn ut_asnyc_chunk_body_encode_1() { + let content = data_message(); + let mut task = ChunkBody::from_async_reader(content.as_slice()); + let mut user_slice = [0_u8; 1024]; + let mut output_vec = vec![]; + + let mut size = user_slice.len(); + while size == user_slice.len() { + size = async_impl::Body::data(&mut task, user_slice.as_mut_slice()) + .await + .unwrap(); + output_vec.extend_from_slice(&user_slice[..size]); + } + assert_eq!(output_vec, res_message()); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_0() { + let mut decoder = ChunkBodyDecoder::new().contains_trailer(true); + let chunk_body_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + Trailer: value\r\n\ + another-trainer: another-value\r\n\ + \r\n\ + " + .as_bytes(); + let res = decoder.decode(&chunk_body_bytes[..1]); // 5 + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::MetaSize, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[1..2]); // 5\r + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::MetaCrlf, + size: 5, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[2..2]); // 5\r + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::MetaCrlf, + size: 5, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[2..3]); // 5\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::Data, + size: 5, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[3..5]); // 5\r\nhe + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::Data, + size: 5, + extension: ChunkExt::new(), + data: "he".as_bytes(), + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[5..9]); // 5\r\nhello\r + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::DataCrlf, + size: 5, + extension: ChunkExt::new(), + data: "llo".as_bytes(), + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[9..9]); // 5\r\nhello\r + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::DataCrlf, + size: 5, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[9..10]); // 5\r\nhello\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::Finish, + size: 5, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[10..13]); // 5\r\nhello\r\nC ; + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 1, + state: ChunkState::MetaExt, + size: 12, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[13..27]); // 5\r\nhello\r\nC ; type = text ; + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 1, + state: ChunkState::MetaExt, + size: 12, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[27..36]); // 5\r\nhello\r\nC ; type = text ;end = !\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 1, + state: ChunkState::Data, + size: 12, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[36..50]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 1, + state: ChunkState::Finish, + size: 12, + extension: ChunkExt::new(), + data: "hello world!".as_bytes(), + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[50..51]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n0 + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 2, + state: ChunkState::MetaSize, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[51..54]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n000; + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 2, + state: ChunkState::MetaExt, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some(&[] as &[u8]), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8],))); + + let res = decoder.decode(&chunk_body_bytes[54..71]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n000; message = last\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 2, + state: ChunkState::Data, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some(&[] as &[u8]), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&chunk_body_bytes[71..87]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n000; message = last\r\nTrailer: value\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 3, + state: ChunkState::DataCrlf, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some("Trailer: value".as_bytes()), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&chunk_body_bytes[87..119]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n000; message = last\r\nTrailer: value\r\n\another-trainer: another-value\r\n\ + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 3, + state: ChunkState::DataCrlf, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some("another-trainer: another-value".as_bytes()), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&chunk_body_bytes[119..121]); // 5\r\nhello\r\nC ; type = text ;end = !\r\nhello world!\r\n000; message = last\r\nTrailer: value\r\n\another-trainer: another-value\r\n\r\n\ + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 3, + state: ChunkState::Finish, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some(&[] as &[u8]), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_1() { + let mut decoder = ChunkBodyDecoder::new(); + let chunk_body_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + \r\n\ + " + .as_bytes(); + let (chunks, remaining) = decoder.decode(chunk_body_bytes).unwrap(); // 5 + let mut iter = chunks.iter(); + let chunk = Chunk { + id: 0, + state: ChunkState::Finish, + size: 5, + extension: ChunkExt::new(), + data: "hello".as_bytes(), + trailer: None, + }; + assert_eq!(iter.next(), Some(&chunk)); + let chunk = Chunk { + id: 1, + state: ChunkState::Finish, + size: 12, + extension: ChunkExt::new(), + data: "hello world!".as_bytes(), + trailer: None, + }; + assert_eq!(iter.next(), Some(&chunk)); + let chunk = Chunk { + id: 2, + state: ChunkState::Finish, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }; + assert_eq!(iter.next(), Some(&chunk)); + assert_eq!(iter.next(), None); + assert_eq!(remaining, "".as_bytes()); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_2() { + let mut decoder = ChunkBodyDecoder::new(); + let chunk_body_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + \r\n\ + " + .as_bytes(); + let (chunks, remaining) = decoder.decode(chunk_body_bytes).unwrap(); // 5 + let mut iter = chunks.into_iter(); + let chunk = Chunk { + id: 0, + state: ChunkState::Finish, + size: 5, + extension: ChunkExt::new(), + data: "hello".as_bytes(), + trailer: None, + }; + assert_eq!(iter.next(), Some(chunk)); + let chunk = Chunk { + id: 1, + state: ChunkState::Finish, + size: 12, + extension: ChunkExt::new(), + data: "hello world!".as_bytes(), + trailer: None, + }; + assert_eq!(iter.next(), Some(chunk)); + let chunk = Chunk { + id: 2, + state: ChunkState::Finish, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: None, + }; + assert_eq!(iter.next(), Some(chunk)); + assert_eq!(iter.next(), None); + assert_eq!(remaining, "".as_bytes()); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_3() { + let chunk_body_bytes = "\ + 5 ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + \r\n\ + " + .as_bytes(); + let mut decoder = ChunkBodyDecoder::new(); + let res = decoder.decode(chunk_body_bytes); // 5 + assert_eq!(res, Err(ErrorKind::InvalidInput.into())); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_4() { + let chunk_body_bytes = "\ + C ; type = text ;end = !\r\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + Trailer: value\r\n\ + another-trainer: another-value\r\n\ + \r\n\ + " + .as_bytes(); + let mut decoder = ChunkBodyDecoder::new(); + let res = decoder.decode(chunk_body_bytes); // 5 + assert_eq!(res, Err(ErrorKind::InvalidInput.into())); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_5() { + let chunk_body_bytes = " C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + Trailer: value\r\n\ + another-trainer: another-value\r\n\ + \r\n\ + " + .as_bytes(); + let mut decoder = ChunkBodyDecoder::new(); + let res = decoder.decode(chunk_body_bytes); // 5 + assert_eq!(res, Err(ErrorKind::InvalidInput.into())); + } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_6() { + let mut decoder = ChunkBodyDecoder::new(); + let chunk_body_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + \r\n\ + " + .as_bytes(); + let (chunks, _) = decoder.decode(chunk_body_bytes).unwrap(); // 5 + assert_eq!(chunks.iter().len(), 3); + let chunk = chunks.iter().next().unwrap(); + assert_eq!( + ( + chunk.id(), + chunk.state(), + chunk.size(), + chunk.extension(), + chunk.data() + ), + ( + 0, + &ChunkState::Finish, + 5, + &ChunkExt::new(), + "hello".as_bytes() + ) + ); + } +} diff --git a/ylong_http/src/body/empty.rs b/ylong_http/src/body/empty.rs new file mode 100644 index 0000000..2c5694c --- /dev/null +++ b/ylong_http/src/body/empty.rs @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::body::{async_impl, sync_impl}; +use core::convert::Infallible; +use core::pin::Pin; +use core::task::{Context, Poll}; + +/// An empty body, indicating that there is no body part in the message. +/// +/// `EmptyBody` both implements [`sync_impl::Body`] and [`async_impl::Body`]. Using +/// [`sync_impl::Body::data`] method or [`async_impl::Body::data`] method has no effect +/// on buf and always returns `Ok(0)`. +/// +/// [`sync_impl::Body`]: sync_impl::Body +/// [`async_impl::Body`]: async_impl::Body +/// [`sync_impl::Body::data`]: sync_impl::Body::data +/// [`async_impl::Body::data`]: async_impl::Body::data +/// +/// # Examples +/// +/// sync_impl: +/// +/// ``` +/// use ylong_http::body::EmptyBody; +/// use ylong_http::body::sync_impl::Body; +/// +/// let mut body = EmptyBody::new(); +/// let mut buf = [0u8; 1024]; +/// +/// // EmptyBody has no body data. +/// assert_eq!(body.data(&mut buf), Ok(0)); +/// ``` +/// +/// async_impl: +/// +/// ``` +/// use ylong_http::body::EmptyBody; +/// use ylong_http::body::async_impl::Body; +/// +/// # async fn read_empty_body() { +/// let mut body = EmptyBody::new(); +/// let mut buf = [0u8; 1024]; +/// +/// // EmptyBody has no body data. +/// assert_eq!(body.data(&mut buf).await, Ok(0)); +/// # } +/// ``` +pub struct EmptyBody; + +impl EmptyBody { + /// Creates a new `EmptyBody`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::EmptyBody; + /// + /// let body = EmptyBody::new(); + /// ``` + pub fn new() -> Self { + Self + } +} + +impl Default for EmptyBody { + fn default() -> Self { + Self::new() + } +} + +impl sync_impl::Body for EmptyBody { + type Error = Infallible; + + fn data(&mut self, _buf: &mut [u8]) -> Result { + Ok(0) + } +} + +impl async_impl::Body for EmptyBody { + type Error = Infallible; + + fn poll_data( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut [u8], + ) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +#[cfg(test)] +mod ut_empty { + use crate::body::empty::EmptyBody; + + /// UT test cases for `EmptyBody::new`. + /// + /// # Brief + /// 1. Calls `EmptyBody::new()` to create an `EmptyBody`. + #[test] + fn ut_empty_body_new() { + let _body = EmptyBody::new(); + // Success if no panic. + } + + /// UT test cases for `sync_impl::Body::data` of `EmptyBody`. + /// + /// # Brief + /// 1. Creates an `EmptyBody`. + /// 2. Calls its `sync_impl::Body::data` method and then checks the results. + #[test] + fn ut_empty_body_sync_impl_data() { + use crate::body::sync_impl::Body; + + let mut body = EmptyBody::new(); + let mut buf = [0u8; 1]; + assert_eq!(body.data(&mut buf), Ok(0)); + } + + /// UT test cases for `async_impl::Body::data` of `EmptyBody`. + /// + /// # Brief + /// 1. Creates an `EmptyBody`. + /// 2. Calls its `async_impl::Body::data` method and then checks the results. + #[tokio::test] + async fn ut_empty_body_async_impl_data() { + use crate::body::async_impl::Body; + + let mut body = EmptyBody::new(); + let mut buf = [0u8; 1]; + assert_eq!(body.data(&mut buf).await, Ok(0)); + } +} diff --git a/ylong_http/src/body/mime/common/headers.rs b/ylong_http/src/body/mime/common/headers.rs new file mode 100644 index 0000000..f4b87b9 --- /dev/null +++ b/ylong_http/src/body/mime/common/headers.rs @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + body::{ + mime::{ + common::{consume_crlf, data_copy, trim_front_lwsp, BytesResult, TokenResult}, + CR, LF, + }, + TokenStatus, + }, + error::{ErrorKind, HttpError}, + h1::response::decoder::{HEADER_NAME_BYTES, HEADER_VALUE_BYTES}, + headers::{HeaderName, HeaderValue, Headers}, +}; +use core::mem::take; +use std::collections::hash_map::IntoIter; + +#[derive(Debug, PartialEq)] +pub(crate) enum HeaderStatus { + Start, + Name, + Colon, + Value, + Crlf, + End, +} + +#[derive(Debug)] +pub(crate) struct EncodeHeaders { + pub(crate) stage: HeaderStatus, + pub(crate) into_iter: IntoIter, + pub(crate) value: Option, + pub(crate) src: Vec, + pub(crate) src_idx: usize, +} + +impl EncodeHeaders { + pub(crate) fn new(headers: Headers) -> Self { + EncodeHeaders { + stage: HeaderStatus::Start, + into_iter: headers.into_iter(), + value: None, + src: vec![], + src_idx: 0, + } + } + + // when the encode stage go to next + fn check_next(&mut self) { + match self.stage { + HeaderStatus::Start => match self.into_iter.next() { + Some((name, value)) => { + self.src = name.into_bytes(); + self.src_idx = 0; + self.value = Some(value); + self.stage = HeaderStatus::Name; + } + None => { + self.stage = HeaderStatus::End; + } + }, + HeaderStatus::Name => { + self.stage = HeaderStatus::Colon; + self.src = b":".to_vec(); + self.src_idx = 0; + } + HeaderStatus::Colon => { + self.stage = HeaderStatus::Value; + match self.value.take() { + Some(v) => { + self.src = v.to_vec(); + } + None => { + self.src = vec![]; + } + } + self.src_idx = 0; + } + HeaderStatus::Value => { + self.stage = HeaderStatus::Crlf; + self.src = b"\r\n".to_vec(); + self.src_idx = 0; + } + HeaderStatus::Crlf => { + self.stage = HeaderStatus::Start; + } + HeaderStatus::End => {} + } + } + + pub(crate) fn encode(&mut self, dst: &mut [u8]) -> TokenResult { + match self.stage { + HeaderStatus::Start => { + self.check_next(); + Ok(TokenStatus::Partial(0)) + } + HeaderStatus::Name | HeaderStatus::Colon | HeaderStatus::Value | HeaderStatus::Crlf => { + match data_copy(&self.src, &mut self.src_idx, dst)? { + TokenStatus::Partial(size) => Ok(TokenStatus::Partial(size)), + TokenStatus::Complete(size) => { + self.check_next(); + Ok(TokenStatus::Partial(size)) + } + } + } + HeaderStatus::End => Ok(TokenStatus::Complete(0)), + } + } +} + +#[derive(Debug, PartialEq)] +pub(crate) struct DecodeHeaders { + pub(crate) stage: HeaderStatus, + pub(crate) name_src: Vec, + pub(crate) src: Vec, + pub(crate) headers: Headers, +} + +impl DecodeHeaders { + pub(crate) fn new() -> Self { + DecodeHeaders { + stage: HeaderStatus::Start, + headers: Headers::new(), + name_src: vec![], + src: vec![], + } + } + + // when the decode stage go to next + fn check_next(&mut self) { + match self.stage { + HeaderStatus::Start => { + self.stage = HeaderStatus::Name; + } + HeaderStatus::Name => { + self.stage = HeaderStatus::Value; + } + HeaderStatus::Colon => {} + HeaderStatus::Value => { + self.stage = HeaderStatus::Crlf; + } + HeaderStatus::Crlf => { + self.stage = HeaderStatus::Start; + self.src = vec![]; + } + HeaderStatus::End => {} + } + } + + pub(crate) fn decode<'a>( + &mut self, + buf: &'a [u8], + ) -> Result<(TokenStatus, &'a [u8]), HttpError> { + if buf.is_empty() { + return Err(ErrorKind::InvalidInput.into()); + } + + let mut results = TokenStatus::Partial(()); + let mut remains = buf; + loop { + let rest = match self.stage { + HeaderStatus::Start => self.start_decode(remains), + HeaderStatus::Name => self.name_decode(remains), + HeaderStatus::Colon => Ok(remains), // not use + HeaderStatus::Value => self.value_decode(remains), + HeaderStatus::Crlf => self.crlf_decode(remains), + HeaderStatus::End => { + results = TokenStatus::Complete(take(&mut self.headers)); + break; + } + }?; + remains = rest; + if remains.is_empty() && self.stage != HeaderStatus::End { + break; + } + } + Ok((results, remains)) + } + + fn start_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let buf = if self.src.is_empty() { + trim_front_lwsp(buf) + } else { + buf + }; + + let cr_meet = self.is_cr_meet(); + match buf[0] { + CR => { + if cr_meet { + Err(ErrorKind::InvalidInput.into()) + } else if buf.len() == 1 { + self.src.push(CR); + Ok(&[]) + } else if buf[1] == LF { + self.stage = HeaderStatus::End; + Ok(&buf[2..]) + } else { + Err(ErrorKind::InvalidInput.into()) + } + } + LF => { + self.stage = HeaderStatus::End; + Ok(&buf[1..]) + } + _ => { + if cr_meet { + Err(ErrorKind::InvalidInput.into()) + } else { + self.check_next(); + Ok(buf) + } + } + } + } + + // check '\r' + fn is_cr_meet(&self) -> bool { + self.src.len() == 1 && self.src[0] == CR + } + + fn crlf_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let cr_meet = self.is_cr_meet(); + match consume_crlf(buf, cr_meet)? { + TokenStatus::Partial(_size) => Ok(&[]), + TokenStatus::Complete(unparsed) => { + self.check_next(); + Ok(unparsed) + } + } + } + + fn name_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let buf = if self.src.is_empty() { + trim_front_lwsp(buf) + } else { + buf + }; + + match Self::get_header_name(buf)? { + TokenStatus::Partial(unparsed) => { + self.src.extend_from_slice(unparsed); + Ok(&[]) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.src.extend_from_slice(src); + self.name_src = take(&mut self.src); + self.check_next(); + Ok(unparsed) + } + } + } + + fn value_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let buf = if self.src.is_empty() { + trim_front_lwsp(buf) + } else { + buf + }; + + match Self::get_header_value(buf)? { + TokenStatus::Partial(unparsed) => { + self.src.extend_from_slice(unparsed); + Ok(&[]) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.src.extend_from_slice(src); + let value = take(&mut self.src); + let name = take(&mut self.name_src); + + self.headers + .insert(trim_front_lwsp(&name), trim_front_lwsp(&value))?; + self.check_next(); + Ok(unparsed) + } + } + } + + // end with ":" + fn get_header_name(buf: &[u8]) -> BytesResult { + for (i, b) in buf.iter().enumerate() { + if *b == b':' { + return Ok(TokenStatus::Complete((&buf[..i], &buf[i + 1..]))); // match "k:v" or "k: v" + } else if !HEADER_NAME_BYTES[*b as usize] { + return Err(ErrorKind::InvalidInput.into()); + } + } + Ok(TokenStatus::Partial(buf)) + } + + // end with "\r" or "\n" or "\r\n" + fn get_header_value(buf: &[u8]) -> BytesResult { + for (i, b) in buf.iter().enumerate() { + if *b == CR || *b == LF { + return Ok(TokenStatus::Complete((&buf[..i], &buf[i..]))); + } else if !HEADER_VALUE_BYTES[*b as usize] { + return Err(ErrorKind::InvalidInput.into()); + } + } + Ok(TokenStatus::Partial(buf)) + } +} + +#[cfg(test)] +mod ut_decode_headers { + use crate::{ + body::{mime::common::headers::DecodeHeaders, TokenStatus}, + headers::Headers, + }; + + /// UT test cases for `DecodeHeaders::decode`. + /// + /// # Brief + /// 1. Creates a `DecodeHeaders` by `DecodeHeaders::new`. + /// 2. Uses `DecodeHeaders::decode` to decode headers. + /// 3. The headers is divided by "\r\n". + /// 4. Checks whether the result is correct. + #[test] + fn ut_decode_headers_new() { + let buf = b"\r\nabcd"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!(headers, TokenStatus::Complete(Headers::new())); + assert_eq!(rest, b"abcd"); + + // has LWSP + let buf = b" \r\nabcd"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!(headers, TokenStatus::Complete(Headers::new())); + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `DecodeHeaders::decode`. + /// + /// # Brief + /// 1. Creates a `DecodeHeaders` by `DecodeHeaders::new`. + /// 2. Uses `DecodeHeaders::decode` to decode headers. + /// 3. The headers is divided by "\n". + /// 4. Checks whether the result is correct. + #[test] + fn ut_decode_headers_new2() { + let buf = b"\nabcd"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!(headers, TokenStatus::Complete(Headers::new())); + assert_eq!(rest, b"abcd"); + + // has LWSP + let buf = b" \nabcd"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!(headers, TokenStatus::Complete(Headers::new())); + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `DecodeHeaders::decode`. + /// + /// # Brief + /// 1. Creates a `DecodeHeaders` by `DecodeHeaders::new`. + /// 2. Uses `DecodeHeaders::decode` to decode headers. + /// 3. The headers has *LWSP-char(b' ' or b'\t'). + /// 4. Checks whether the result is correct. + #[test] + fn ut_decode_headers_decode() { + // all use "\r\n" + let buf = b" name1: value1\r\n name2: value2\r\n\r\n"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!( + headers, + TokenStatus::Complete({ + let mut headers = Headers::new(); + headers.insert("name1", "value1").unwrap(); + headers.insert("name2", "value2").unwrap(); + headers + }) + ); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + // all use "\n" + let buf = b"name1:value1\nname2:value2\n\n"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!( + headers, + TokenStatus::Complete({ + let mut headers = Headers::new(); + headers.insert("name1", "value1").unwrap(); + headers.insert("name2", "value2").unwrap(); + headers + }) + ); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + // some use "\r\n" + let buf = b"name1:value1\nname2:value2\r\n\n"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!( + headers, + TokenStatus::Complete({ + let mut headers = Headers::new(); + headers.insert("name1", "value1").unwrap(); + headers.insert("name2", "value2").unwrap(); + headers + }) + ); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + } + + /// UT test cases for `DecodeHeaders::decode`. + /// + /// # Brief + /// 1. Creates a `DecodeHeaders` by `DecodeHeaders::new`. + /// 2. Uses `DecodeHeaders::decode` to decode headers. + /// 3. The headers is common. + /// 4. Checks whether the result is correct. + #[test] + fn ut_decode_headers_decode2() { + let buf = b"name1:value1\r\n\r\n\r\naaaa"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(buf).unwrap(); + assert_eq!( + headers, + TokenStatus::Complete({ + let mut headers = Headers::new(); + headers.insert("name1", "value1").unwrap(); + headers + }) + ); + assert_eq!(std::str::from_utf8(rest).unwrap(), "\r\naaaa"); + } + + /// UT test cases for `DecodeHeaders::decode`. + /// + /// # Brief + /// 1. Creates a `DecodeHeaders` by `DecodeHeaders::new`. + /// 2. Uses `DecodeHeaders::decode` to decode headers. + /// 3. The decode bytes are divided into several executions. + /// 4. Checks whether the result is correct. + #[test] + fn ut_decode_headers_decode3() { + let buf = b"name1:value1\r\nname2:value2\r\n\r\naaaa"; + let mut decoder = DecodeHeaders::new(); + let (headers, rest) = decoder.decode(&buf[0..3]).unwrap(); // nam + assert_eq!(headers, TokenStatus::Partial(())); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + let (headers, rest) = decoder.decode(&buf[3..13]).unwrap(); // e1:value1\r + assert_eq!(headers, TokenStatus::Partial(())); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + let (headers, rest) = decoder.decode(&buf[13..29]).unwrap(); // \nname2:value2\r\n\r + assert_eq!(headers, TokenStatus::Partial(())); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + let (headers, rest) = decoder.decode(&buf[29..30]).unwrap(); // \n + assert_eq!( + headers, + TokenStatus::Complete({ + let mut headers = Headers::new(); + headers.insert("name1", "value1").unwrap(); + headers.insert("name2", "value2").unwrap(); + headers + }) + ); + assert_eq!(std::str::from_utf8(rest).unwrap(), ""); + + let (headers, rest) = decoder.decode(&buf[30..34]).unwrap(); // aaaa + assert_eq!(headers, TokenStatus::Complete(Headers::new())); + assert_eq!(std::str::from_utf8(rest).unwrap(), "aaaa"); + } +} diff --git a/ylong_http/src/body/mime/common/mix.rs b/ylong_http/src/body/mime/common/mix.rs new file mode 100644 index 0000000..579c7bb --- /dev/null +++ b/ylong_http/src/body/mime/common/mix.rs @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::body::{ + async_impl::{self, DataFuture}, + mime::common::{data_copy, SizeResult, TokenStatus}, + sync_impl, +}; +use core::{ + fmt::Debug, + pin::Pin, + task::{Context, Poll}, +}; +use std::io::Read; +use tokio::io::{AsyncRead, ReadBuf}; + +// Uses Box so that it can be put into a list(like vec) with different T. +pub(crate) enum MixFrom<'a> { + Owned { bytes: Vec, index: usize }, // the read content is Vec + Slice { bytes: &'a [u8], index: usize }, // the read content is from memory + Reader(Box), // the read content is from a synchronous reader + AsyncReader(Box), // the read content is from an asynchronous reader +} + +impl<'a> MixFrom<'a> { + // Usually needs owned. + pub(crate) fn new() -> Self { + MixFrom::Owned { + bytes: vec![], + index: 0, + } + } + + pub(crate) fn new_as_bytes() -> Self { + MixFrom::Slice { + bytes: &[], + index: 0, + } + } + + pub(crate) fn is_empty(&self) -> bool { + match self { + MixFrom::Owned { bytes, index: _ } => bytes.is_empty(), + MixFrom::Slice { bytes, index: _ } => bytes.is_empty(), + _ => false, + } + } + + pub(crate) fn set_owned(&mut self, bytes: Vec) { + *self = MixFrom::Owned { bytes, index: 0 }; + } + + pub(crate) fn set_bytes(&mut self, bytes: &'a [u8]) { + *self = MixFrom::Slice { bytes, index: 0 }; + } + + pub(crate) fn set_reader(&mut self, data: T) + where + T: Read + Send + Sync + 'static, + { + *self = MixFrom::Reader(Box::new(data)); + } + + pub(crate) fn set_async_reader(&mut self, data: T) + where + T: AsyncRead + Send + Sync + Unpin + 'static, + { + *self = MixFrom::AsyncReader(Box::new(data)); + } + + fn owned_encode(&mut self, dst: &mut [u8]) -> SizeResult { + if let MixFrom::Owned { bytes, index } = self { + match data_copy(bytes, index, dst)? { + TokenStatus::Partial(size) => Ok(size), + TokenStatus::Complete(size) => Ok(size), + } + } else { + Ok(0) + } + } + + fn bytes_encode(&mut self, dst: &mut [u8]) -> SizeResult { + if let MixFrom::Slice { bytes, index } = self { + match data_copy(bytes, index, dst)? { + TokenStatus::Partial(size) => Ok(size), + TokenStatus::Complete(size) => Ok(size), + } + } else { + Ok(0) + } + } + + fn read_encode(&mut self, dst: &mut [u8]) -> SizeResult { + if let MixFrom::Reader(data) = self { + let size = data.read(dst)?; + Ok(size) + } else { + Ok(0) + } + } +} + +impl Default for MixFrom<'_> { + fn default() -> Self { + MixFrom::new() + } +} + +impl Debug for MixFrom<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Owned { bytes, index } => f + .debug_struct("Owned") + .field("bytes", bytes) + .field("index", index) + .finish(), + Self::Slice { bytes, index } => f + .debug_struct("Slice") + .field("bytes", bytes) + .field("index", index) + .finish(), + Self::Reader(_) => f.debug_tuple("Reader").finish(), + Self::AsyncReader(_) => f.debug_tuple("AsyncReader").finish(), + } + } +} + +// It is not a complete implementation, only implements for MixFrom::Owned && MixFrom::Slice. +impl PartialEq for MixFrom<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::Owned { + bytes: l_bytes, + index: l_index, + }, + Self::Owned { + bytes: r_bytes, + index: r_index, + }, + ) => l_bytes == r_bytes && l_index == r_index, + ( + Self::Slice { + bytes: l_bytes, + index: l_index, + }, + Self::Slice { + bytes: r_bytes, + index: r_index, + }, + ) => l_bytes == r_bytes && l_index == r_index, + // Dyn trait object can not impl PartialEq + (Self::Reader(_l0), Self::Reader(_r0)) => false, + (Self::AsyncReader(_l0), Self::AsyncReader(_r0)) => false, + _ => false, + } + } +} + +impl sync_impl::Body for MixFrom<'_> { + type Error = std::io::Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + match self { + MixFrom::Owned { bytes: _, index: _ } => self.owned_encode(buf), + MixFrom::Slice { bytes: _, index: _ } => self.bytes_encode(buf), + MixFrom::Reader(_) => self.read_encode(buf), + MixFrom::AsyncReader(_) => Ok(0), + } + } +} + +impl async_impl::Body for MixFrom<'_> { + type Error = std::io::Error; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + match &mut *self { + MixFrom::Owned { bytes: _, index: _ } => Poll::Ready(self.owned_encode(buf)), + MixFrom::Slice { bytes: _, index: _ } => Poll::Ready(self.bytes_encode(buf)), + MixFrom::Reader(_) => Poll::Ready(Ok(0)), + MixFrom::AsyncReader(data) => { + let mut buf = ReadBuf::new(buf); + match Pin::new(data).poll_read(cx, &mut buf) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(buf.filled().len())), + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } + } + } +} + +#[cfg(test)] +mod ut_mix { + use crate::body::{async_impl, mime::common::mix::MixFrom, sync_impl}; + + /// Builds a `MixFrom`. + macro_rules! mix_build { + ( + MixFrom: { + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + ) => { + { + #[allow(unused_mut)] + let mut mix = MixFrom::new_as_bytes(); + + $(mix.set_owned($body1);)? + $(mix.set_bytes($body2);)? + $(mix.set_reader($body3);)? + $(mix.set_async_reader($body4);)? + mix + } + } + } + + /// Builds a `Mimepart`, encodes it, Compares with result. + macro_rules! mix_encode_compare { + ( + MixFrom: { + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + $(BufSize: $size: expr,)? + $(ResultSize: $v_size: expr,)? + $(ResultData: $v_data: expr,)? + Sync, + ) => { + let mut mix = mix_build!( + MixFrom: { + $(BodyOwned: $body1,)? + $(BodySlice: $body2,)? + $(BodyReader: $body3,)? + $(BodyAsyncReader: $body4,)? + }, + ); + + #[allow(unused_assignments, unused_mut)] + let mut len = 1; // default 1 + + $(len = $size;)? + let mut buf = vec![0u8; len]; + let mut v_data = vec![]; + let mut v_size = vec![]; + + loop { + let size = sync_impl::Body::data(&mut mix, &mut buf).expect("MixFrom encode failed"); + if size == 0 { + break; + } + v_size.push(size); + v_data.extend_from_slice(&buf[..size]); + } + $(assert_eq!(v_size, $v_size);)? + $(assert_eq!(v_data, $v_data);)? + }; + + ( + MixFrom: { + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + $(BufSize: $size: expr,)? + $(ResultSize: $v_size: expr,)? + $(ResultData: $v_data: expr,)? + Async, + ) => { + let mut mix = mix_build!( + MixFrom: { + $(BodyOwned: $body1,)? + $(BodySlice: $body2,)? + $(BodyReader: $body3,)? + $(BodyAsyncReader: $body4,)? + }, + ); + + #[allow(unused_assignments, unused_mut)] + let mut len = 1; // default 1 + + $(len = $size;)? + let mut buf = vec![0u8; len]; + let mut v_data = vec![]; + let mut v_size = vec![]; + + loop { + let size = async_impl::Body::data(&mut mix, &mut buf).await.expect("MixFrom encode failed"); + if size == 0 { + break; + } + v_size.push(size); + v_data.extend_from_slice(&buf[..size]); + } + $(assert_eq!(v_size, $v_size);)? + $(assert_eq!(v_data, $v_data);)? + }; + } + + /// UT test cases for `MixFrom::new`. + /// + /// # Brief + /// 1. Creates a `MixFrom` by `MixFrom::new`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_mix_new() { + mix_encode_compare!( + MixFrom: { + }, + ResultData: b"", + Sync, + ); + } + + /// UT test cases for `MixFrom::set_owned`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from Vec by `MixFrom::set_owned`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_mix_set_owned() { + mix_encode_compare!( + MixFrom: { + BodyOwned: b"123456".to_vec(), + }, + BufSize: 5, + ResultSize: vec![5, 1], + ResultData: b"123456", + Sync, + ); + } + + /// UT test cases for `MixFrom::set_bytes`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from bytes by `MixFrom::set_bytes`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_mix_set_bytes() { + mix_encode_compare!( + MixFrom: { + BodySlice: b"123456", + }, + ResultData: b"123456", + Sync, + ); + } + + /// UT test cases for `MixFrom::set_reader`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from synchronous read content by `MixFrom::set_reader`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_mix_set_reader() { + mix_encode_compare!( + MixFrom: { + BodyReader: "12345678".as_bytes(), + }, + ResultData: b"12345678", + Sync, + ); + } + + /// UT test cases for `MixFrom::set_async_reader`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from asynchronous read content by `MixFrom::set_async_reader`. + /// 2. Encodes by synchronous encoding. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mix_set_async_reader_then_sync_data() { + mix_encode_compare!( + MixFrom: { + BodyAsyncReader: "123456".as_bytes(), + }, + ResultData: b"", + Sync, + ); + } + + /// UT test cases for `MixFrom::set_async_reader`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from asynchronous read content by `MixFrom::set_async_reader`. + /// 2. Encodes by asynchronous encoding. + /// 3. Checks whether the result is correct. + #[tokio::test] + async fn ut_mix_set_async_reader() { + mix_encode_compare!( + MixFrom: { + BodyAsyncReader: "12345678".as_bytes(), + }, + ResultData: b"12345678", + Async, + ); + } + + /// UT test cases for `MixFrom::set_reader`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from synchronous read content by `MixFrom::set_reader`. + /// 2. Encodes by asynchronous encoding. + /// 3. Checks whether the result is correct. + #[tokio::test] + async fn ut_mix_set_reader_then_async_data() { + mix_encode_compare!( + MixFrom: { + BodyReader: "12345678".as_bytes(), + }, + ResultData: b"", + Async, + ); + } + + /// UT test cases for `MixFrom::set_bytes`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from synchronous read content by `MixFrom::set_bytes`. + /// 2. Encodes by asynchronous encoding. + /// 3. Checks whether the result is correct. + #[tokio::test] + async fn ut_mix_set_bytes_then_async_data() { + mix_encode_compare!( + MixFrom: { + BodySlice: "12345678".as_bytes(), + }, + ResultData: b"12345678", + Async, + ); + } + + /// UT test cases for `MixFrom::set_owned`. + /// + /// # Brief + /// 1. Creates a `MixFrom` from synchronous read content by `MixFrom::set_owned`. + /// 2. Encodes by asynchronous encoding. + /// 3. Checks whether the result is correct. + #[tokio::test] + async fn ut_mix_set_owned_then_async_data() { + mix_encode_compare!( + MixFrom: { + BodyOwned: b"12345678".to_vec(), + }, + ResultData: b"12345678", + Async, + ); + } +} diff --git a/ylong_http/src/body/mime/common/mod.rs b/ylong_http/src/body/mime/common/mod.rs new file mode 100644 index 0000000..243c4c9 --- /dev/null +++ b/ylong_http/src/body/mime/common/mod.rs @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod headers; +mod mix; +mod multi; +mod part; + +pub use multi::{MimeMulti, MimeMultiBuilder, XPart}; +pub use part::{MimePart, MimePartBuilder}; + +pub(crate) use headers::{DecodeHeaders, EncodeHeaders, HeaderStatus}; +pub(crate) use mix::MixFrom; +pub(crate) use part::PartStatus; +pub(crate) type SizeResult = Result; +pub(crate) type TokenResult = Result, std::io::Error>; +pub(crate) type BytesResult<'a> = Result, HttpError>; + +use crate::{ + error::{ErrorKind, HttpError}, + headers::Headers, +}; +use core::mem::take; +use std::io::Read; + +// RFC5234 ABNF +pub(crate) const HTAB: u8 = b'\t'; // horizontal tab +pub(crate) const SP: u8 = b' '; // 0x20 space +pub(crate) const CR: u8 = b'\r'; // carriage return +pub(crate) const LF: u8 = b'\n'; // linefeed +pub(crate) const CRLF: &[u8] = b"\r\n"; + +/// Represents component encoding/decoding status. +#[derive(Debug, Eq, PartialEq)] +pub enum TokenStatus { + /// The current component is partially encoded. + Partial(E), + /// The current component is completely encoded. + Complete(T), +} + +impl TokenStatus { + /// Checks whether is TokenStatus::Complete(T). + pub fn is_complete(&self) -> bool { + match self { + TokenStatus::Partial(_) => false, + TokenStatus::Complete(_) => true, + } + } + + /// Gets the complete inner type. + pub fn get_complete_once(self) -> Option { + match self { + TokenStatus::Partial(_) => None, + TokenStatus::Complete(inner) => Some(inner), + } + } +} + +// Pulls some bytes from this src into the buf, returning how many bytes were read. +pub(crate) fn data_copy(src: &[u8], src_idx: &mut usize, buf: &mut [u8]) -> TokenResult { + let input_len = src.len() - *src_idx; + let output_len = buf.len(); + let num = (&src[*src_idx..]).read(buf)?; // sync + *src_idx += num; + if output_len >= input_len { + return Ok(TokenStatus::Complete(num)); + } + Ok(TokenStatus::Partial(num)) +} + +// removes front *LWSP-char(b' ' or b'\t') +pub(crate) fn trim_front_lwsp(buf: &[u8]) -> &[u8] { + let mut idx = 0; + for b in buf.iter() { + match *b { + SP | HTAB => idx += 1, + _ => break, + } + } + &buf[idx..] +} + +// removes back *LWSP-char(b' ' or b'\t') +fn trim_back_lwsp(buf: &[u8]) -> &[u8] { + let mut idx = 0; + for b in buf.iter().rev() { + match *b { + SP | HTAB => idx += 1, + _ => break, + } + } + &buf[..buf.len() - idx] +} + +/// buf is not empty, and end with '\n'. +/// removes back *LWSP-char(b' ' or b'\t') +pub(crate) fn trim_back_lwsp_if_end_with_lf(buf: &[u8]) -> &[u8] { + let mut temp = &buf[..buf.len() - 1]; + if temp.ends_with(&[CR]) { + temp = &temp[..temp.len() - 1]; + } + trim_back_lwsp(temp) +} + +// reduce "\n" or "\r\n" +pub(crate) fn consume_crlf( + buf: &[u8], + cr_meet: bool, //has "\r" +) -> Result, HttpError> { + if buf.is_empty() { + return Ok(TokenStatus::Partial(0)); + } + match buf[0] { + CR => { + if cr_meet { + Err(ErrorKind::InvalidInput.into()) + } else if buf.len() == 1 { + Ok(TokenStatus::Partial(1)) + } else if buf[1] == LF { + Ok(TokenStatus::Complete(&buf[2..])) + } else { + Err(ErrorKind::InvalidInput.into()) + } + } + LF => Ok(TokenStatus::Complete(&buf[1..])), + _ => Err(ErrorKind::InvalidInput.into()), + } +} + +// end with "\n" or "\r\n" +pub(crate) fn get_crlf_contain(buf: &[u8]) -> TokenStatus<(&[u8], &[u8]), &[u8]> { + for (i, b) in buf.iter().enumerate() { + if *b == LF { + return TokenStatus::Complete((&buf[..i + 1], &buf[i + 1..])); + } + } + TokenStatus::Partial(buf) +} + +// TODO: Replace with `[u8]::trim_ascii_start` when is stable. +fn trim_ascii_start(mut bytes: &[u8]) -> &[u8] { + // Note: A pattern matching based approach (instead of indexing) allows + // making the function const. + while let [first, rest @ ..] = bytes { + if first.is_ascii_whitespace() { + bytes = rest; + } else { + break; + } + } + bytes +} + +// TODO: Replace with `[u8]::trim_ascii_end` when is stable. +fn trim_ascii_end(mut bytes: &[u8]) -> &[u8] { + // Note: A pattern matching based approach (instead of indexing) allows + // making the function const. + while let [rest @ .., last] = bytes { + if last.is_ascii_whitespace() { + bytes = rest; + } else { + break; + } + } + bytes +} + +// TODO: Replace with `[u8]::trim_ascii` when is stable. +fn trim_ascii(bytes: &[u8]) -> &[u8] { + trim_ascii_end(trim_ascii_start(bytes)) +} + +// get multipart boundary +pub(crate) fn get_content_type_boundary(headers: &Headers) -> Option> { + let header_value = headers.get("Content-Type"); + if let Some(value) = header_value { + let str = value.to_vec(); + let str = trim_ascii(&str); + if !str.starts_with(b"multipart") { + return None; + } + + let boundary = str + .split(|b| *b == b';') + .map(trim_ascii) + .find(|s| s.starts_with(b"boundary=")); + + if let Some(boundary) = boundary { + let boundary = trim_ascii_start(&boundary[9..]); + if boundary.len() > 2 && boundary.starts_with(&[b'"']) && boundary.ends_with(&[b'"']) { + return Some(boundary[1..boundary.len() - 1].to_vec()); + } else if !boundary.is_empty() { + return Some(boundary[..].to_vec()); + } + } + } + None +} + +#[cfg(test)] +mod ut_common { + use crate::{body::mime::common::get_content_type_boundary, headers::Headers}; + + /// UT test cases for `get_content_type_boundary`. + /// + /// # Brief + /// 1. Creates a `Headers` and inserts key and value. + /// 2. Gets boundary from headers by `get_content_type_boundary`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_get_content_type_boundary() { + // common + let mut headers = Headers::new(); + headers + .insert( + "Content-Type", + "multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p", + ) + .unwrap(); + assert_eq!( + get_content_type_boundary(&headers), + Some(b"gc0p4Jq0M2Yt08j34c0p".to_vec()) + ); + // has LWSF + let mut headers = Headers::new(); + headers + .insert( + "Content-Type", + " multipart/mixed; boundary= gc0p4Jq0M2Yt08j34c0p ", + ) + .unwrap(); + assert_eq!( + get_content_type_boundary(&headers), + Some(b"gc0p4Jq0M2Yt08j34c0p".to_vec()) + ); + // has "" + let mut headers = Headers::new(); + headers + .insert( + "Content-Type", + r#"multipart/mixed; boundary="gc0pJq0M:08jU534c0p""#, + ) + .unwrap(); + assert_eq!( + get_content_type_boundary(&headers), + Some(b"gc0pJq0M:08jU534c0p".to_vec()) + ); + } +} diff --git a/ylong_http/src/body/mime/common/multi.rs b/ylong_http/src/body/mime/common/multi.rs new file mode 100644 index 0000000..58234d9 --- /dev/null +++ b/ylong_http/src/body/mime/common/multi.rs @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::headers::{HeaderName, HeaderValue}; +use crate::{ + body::mime::{DecodeHeaders, MimePart}, + error::{ErrorKind, HttpError}, + headers::{Header, Headers}, +}; +use core::{convert::TryFrom, mem::take}; + +/// `MimeMulti` is a Composite MIME body which is defined in [`RFC2046`]: \ +/// In the case of multipart entities, in which one or more different +/// sets of data are combined in a single body, a "multipart" media type +/// field must appear in the entity's header. The body must then contain +/// one or more body parts, each preceded by a boundary delimiter line, +/// and the last one followed by a closing boundary delimiter line. \ +/// +/// `MimeMulti` can be built by [`MimeMultiBuilder`], then can set headers and +/// boundary, and add part([`MimePart`] or `MimeMulti`). +/// +/// [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1 +/// [`MimeMultiBuilder`]: MimeMultiBuilder +/// [`MimePart`]: MimePart +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::{MimeMulti, MimePart}; +/// +/// let multi1 = MimeMulti::builder() +/// .set_content_type(b"multipart/mixed", b"abc".to_vec()) +/// .set_boundary(b"abc".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key1", "value1") +/// .body_from_reader("111111".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_part( +/// MimePart::builder() +/// .header("key2", "value2") +/// .body_from_reader("22222".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// +/// let multi = MimeMulti::builder() +/// .set_boundary(b"abcde".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key3", "value3") +/// .body_from_reader("33333".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_multi(multi1) +/// .add_part( +/// MimePart::builder() +/// .header("key4", "value4") +/// .body_from_async_reader("444444".as_bytes()) // nothing in sync +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// ``` +#[derive(Debug, Default, PartialEq)] +pub struct MimeMulti<'a> { + // [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1 + // + // |======================================================================== + // | dash-boundary := "--" boundary | + // | ; boundary taken from the value of | + // | ; boundary parameter of the | + // | ; Content-Type field. | + // | | + // | multipart-body := [preamble CRLF] | + // | dash-boundary transport-padding CRLF | + // | body-part *encapsulation | + // | close-delimiter transport-padding | + // | [CRLF epilogue] | + // | | + // | transport-padding := *LWSP-char | + // | ; Composers MUST NOT generate | + // | ; non-zero length transport | + // | ; padding, but receivers MUST | + // | ; be able to handle padding | + // | ; added by message transports. | + // | | + // | encapsulation := delimiter transport-padding | + // | CRLF body-part | + // | | + // | delimiter := CRLF dash-boundary | + // | | + // | close-delimiter := delimiter "--" | + // | | + // | preamble := discard-text | + // | | + // | epilogue := discard-text | + // | | + // | discard-text := *(*text CRLF) *text | + // | ; May be ignored or discarded. | + // | | + // | body-part := MIME-part-headers [CRLF *OCTET] | + // | ; Lines in a body-part must not start | + // | ; with the specified dash-boundary and | + // | ; the delimiter must not appear anywhere | + // | ; in the body part. Note that the | + // | ; semantics of a body-part differ from | + // | ; the semantics of a message, as | + // | ; described in the text. | + // | | + // | OCTET := | + // |======================================================================== + pub(crate) headers: Headers, + pub(crate) boundary: Vec, + pub(crate) list: Vec>, +} + +impl<'a> MimeMulti<'a> { + pub(crate) fn new() -> Self { + MimeMulti { + headers: Headers::new(), + + // In [`RFC 2046`] + // The simplest boundary delimiter line possible is something like "---", + // with a closing boundary delimiter line of "-----". + // [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 + boundary: b"-".to_vec(), + list: vec![], + } + } + + pub(crate) fn set_headers(&mut self, headers: Headers) { + self.headers = headers; + } + + pub(crate) fn insert_header( + &mut self, + name: N, + value: V, + ) -> Result<&mut Headers, HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.headers.insert(name, value)?; + Ok(self.headers_mut()) + } + + pub(crate) fn append_header( + &mut self, + name: N, + value: V, + ) -> Result<&mut Headers, HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.headers.append(name, value)?; + Ok(self.headers_mut()) + } + + pub(crate) fn set_boundary(&mut self, boundary: Vec) { + self.boundary = boundary; + } + + pub(crate) fn set_content_type( + &mut self, + content_type: &[u8], + boundary: Vec, + ) -> Result<&mut Headers, HttpError> { + let mut v = vec![]; + v.extend_from_slice(content_type); + v.extend_from_slice(b"; boundary="); + v.extend_from_slice(&boundary); + + self.boundary = boundary; + self.headers.insert("Content-Type", &v[..])?; + Ok(self.headers_mut()) + } + + pub(crate) fn add_part(&mut self, part: MimePart<'a>) { + self.list.push(XPart::Part(part)); + } + + pub(crate) fn add_multi(&mut self, multi: MimeMulti<'a>) { + self.list.push(XPart::Multi(multi)); + } + + pub(crate) fn add_xpart(&mut self, xpart: XPart<'a>) { + self.list.push(xpart); + } + + /// Creates a [`MimeMultiBuilder`]. + /// + /// [`MimeMultiBuilder`]: MimeMultiBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMulti; + /// + /// let multi = MimeMulti::builder().build().unwrap(); + /// ``` + pub fn builder() -> MimeMultiBuilder<'a> { + MimeMultiBuilder::new() + } + + /// Gets the reference of headers. + pub fn headers(&self) -> &Headers { + &self.headers + } + + /// Gets the mutable reference of headers. + pub fn headers_mut(&mut self) -> &mut Headers { + &mut self.headers + } + + /// Gets the ownership of headers by replacing it with an default value. + pub fn headers_once(&mut self) -> Headers { + take(&mut self.headers) + } + + /// Gets the reference of boundary. + pub fn boundary(&self) -> &[u8] { + &self.boundary + } + + /// Gets the mutable reference of boundary. + pub fn boundary_mut(&mut self) -> &mut [u8] { + &mut self.boundary + } + + /// Gets the ownership of boundary by replacing it with an default value. + pub fn boundary_once(&mut self) -> Vec { + take(&mut self.boundary) + } + + /// Gets the reference of part list. + pub fn list(&self) -> &Vec { + &self.list + } + + /// Gets the mutable reference of part list. + pub fn list_mut(&mut self) -> &mut Vec> { + &mut self.list + } + + /// Gets the ownership of part list by replacing it with an default value. + pub fn list_once(&mut self) -> Vec { + take(&mut self.list) + } +} + +/// `MimeMultiBuilder` can set set a Composite MIME body [`MimeMulti`]. \ +/// `MimeMultiBuilder` can set headers and boundary, and add part([`MimePart`] +/// or [`MimeMulti`]). +/// +/// [`MimePart`]: MimePart +/// [`MimeMulti`]: MimeMulti +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::{MimeMulti, MimePart}; +/// +/// let multi1 = MimeMulti::builder() +/// .set_content_type(b"multipart/mixed", b"abc".to_vec()) +/// .set_boundary(b"abc".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key1", "value1") +/// .body_from_reader("111111".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_part( +/// MimePart::builder() +/// .header("key2", "value2") +/// .body_from_reader("22222".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// +/// let multi = MimeMulti::builder() +/// .set_boundary(b"abcde".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key3", "value3") +/// .body_from_reader("33333".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_multi(multi1) +/// .add_part( +/// MimePart::builder() +/// .header("key4", "value4") +/// .body_from_async_reader("444444".as_bytes()) // nothing in sync +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// ``` +pub struct MimeMultiBuilder<'a> { + inner: Result, HttpError>, +} + +impl<'a> MimeMultiBuilder<'a> { + /// Creates a new [`MimeMultiBuilder`]. + /// + /// [`MimeMultiBuilder`]: MimeMultiBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMultiBuilder; + /// + /// let mut multi = MimeMultiBuilder::new().build().unwrap(); + /// ``` + pub fn new() -> Self { + MimeMultiBuilder { + inner: Ok(MimeMulti::new()), + } + } + + /// Sets headers to the Composite MIME body. \ + /// It is recommended to use [`set_content_type`] to set header 'Content-Type' + /// and set boundary simultaneously. + /// + /// [`set_content_type`]: MimeMultiBuilder::set_content_type + /// + /// # Examples + /// + /// ``` + /// use ylong_http::{ + /// body::MimeMultiBuilder, + /// headers::Headers, + /// }; + /// + /// let multi = MimeMultiBuilder::new() + /// .set_headers({ + /// let mut headers = Headers::new(); + /// headers + /// .append("Content-Type", "multipart/mixed; boundary=ab") + /// .unwrap(); + /// headers + /// }) + /// .set_boundary(b"ab".to_vec()); + /// ``` + pub fn set_headers(mut self, headers: Headers) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.set_headers(headers); + inner + }); + self + } + + /// Inserts header to the Composite MIME body. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMultiBuilder; + /// + /// let multi = MimeMultiBuilder::new() + /// .insert_header("accept", "text/html") + /// .insert_header("accept", "text/plain"); + /// ``` + pub fn insert_header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.inner = self.inner.and_then(move |mut inner| { + inner.insert_header(name, value)?; + Ok(inner) + }); + self + } + + /// Appends header to the Composite MIME body. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMultiBuilder; + /// + /// let multi = MimeMultiBuilder::new() + /// .append_header("accept", "text/html") + /// .append_header("accept", "text/plain"); + /// ``` + pub fn append_header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.inner = self.inner.and_then(move |mut inner| { + inner.append_header(name, value)?; + Ok(inner) + }); + self + } + + // TODO: boundary check + /// The boundary is defined in [`RFC2046`]. + /// The only mandatory global parameter for the "multipart" media type is + /// the boundary parameter, which consists of 1 to 70 characters from a + /// set of characters known to be very robust through mail gateways, and + /// NOT ending with white space. \ + /// It is recommended to use [`set_content_type`] to set header 'Content-Type' + /// and set boundary simultaneously. + /// + /// [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 + /// [`set_content_type`]: MimeMultiBuilder::set_content_type + pub fn set_boundary(mut self, boundary: Vec) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.set_boundary(boundary); + inner + }); + self + } + + /// It is recommended to use `set_content_type` to set header 'Content-Type' + /// and set boundary simultaneously. \ + /// The Content-Type field for multipart entities requires one parameter, + /// "boundary". A typical "multipart" Content-Type header field might look + /// like this In [`RFC2046`]: \ + /// *Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p* + /// + /// [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMultiBuilder; + /// + /// let mut multi = MimeMultiBuilder::new() + /// .set_content_type(b"multipart/mixed", b"ab".to_vec()); + /// ``` + pub fn set_content_type(mut self, content_type: &'a [u8], boundary: Vec) -> Self { + self.inner = self.inner.and_then(move |mut inner| { + inner.set_content_type(content_type, boundary)?; + Ok(inner) + }); + self + } + + /// Adds a single body part When it is a `MimePart`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimeMultiBuilder, MimePart}; + /// + /// let part = MimePart::builder() + /// .header("key1", "value1") + /// .body_from_reader("9876543210\r\n".as_bytes()) + /// .build() + /// .unwrap(); + /// let multi = MimeMultiBuilder::new().add_part(part); + /// ``` + pub fn add_part(mut self, part: MimePart<'a>) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.add_part(part); + inner + }); + self + } + + /// Adds a multi body part When it is a `MimeMulti`. + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimeMulti, MimeMultiBuilder}; + /// + /// let multi1 = MimeMulti::builder().build().unwrap(); + /// let multi = MimeMultiBuilder::new() + /// .add_multi(multi1); + /// ``` + pub fn add_multi(mut self, multi: MimeMulti<'a>) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.add_multi(multi); + inner + }); + self + } + + /// Adds a multi body part When it is a `XPart`. + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimeMulti, MimeMultiBuilder, XPart}; + /// + /// let multi1 = MimeMulti::builder().build().unwrap(); + /// let multi = MimeMultiBuilder::new() + /// .add_xpart(XPart::Multi(multi1)); + /// ``` + pub fn add_xpart(mut self, xpart: XPart<'a>) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.add_xpart(xpart); + inner + }); + self + } + + /// Builds a [`MimeMulti`]. + /// + /// [`MimeMulti`]: MimeMulti + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeMultiBuilder; + /// + /// let multi = MimeMultiBuilder::new().build().unwrap(); + /// ``` + pub fn build(self) -> Result, HttpError> { + self.inner + } +} + +impl<'a> Default for MimeMultiBuilder<'a> { + fn default() -> Self { + Self::new() + } +} + +/// `MimePart` or `MimeMulti` as a part. +#[derive(Debug, PartialEq)] +pub enum XPart<'a> { + /// `MimePart` + Part(MimePart<'a>), + /// `MimeMulti` + Multi(MimeMulti<'a>), +} diff --git a/ylong_http/src/body/mime/common/part.rs b/ylong_http/src/body/mime/common/part.rs new file mode 100644 index 0000000..9240f45 --- /dev/null +++ b/ylong_http/src/body/mime/common/part.rs @@ -0,0 +1,622 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::headers::{HeaderName, HeaderValue}; +use crate::{ + body::mime::{MixFrom, CR, LF}, + error::HttpError, + headers::{Header, Headers}, +}; +use core::{convert::TryFrom, mem::take}; +use std::io::Read; +use tokio::io::AsyncRead; + +/// `MimePart` is a body part of a Composite MIME body which is defined in [`RFC2046`]: \ +/// The body must then contain one or more body parts, each preceded by a boundary +/// delimiter line, and the last one followed by a closing boundary delimiter line. +/// Each body part then consists of a header area, a blank line, and a body area. \ +/// +/// `MimePart` can be built by [`MimePartBuilder`], then sets headers and body. +/// +/// [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1 +/// [`MimePartBuilder`]: MimePartBuilder +/// +/// # Examples +/// ``` +/// use ylong_http::body::MimePart; +/// +/// let part = MimePart::builder() +/// .header("accept", "text/html") +/// .body_from_bytes(b"9876543210\r\n") +/// .build() +/// .unwrap(); +/// ``` +#[derive(Debug, Default, PartialEq)] +pub struct MimePart<'a> { + // |======================================================================== + // | Headers [CRLF Field-body] | + // |======================================================================== + // if it has body, it has CRLF. + // + // [`RFC2046`]: https://www.rfc-editor.org/rfc/rfc2046#section-5.1 + // body-part := MIME-part-headers [CRLF *OCTET] + // ; Lines in a body-part must not start + // ; with the specified dash-boundary and + // ; the delimiter must not appear anywhere + // ; in the body part. Note that the + // ; semantics of a body-part differ from + // ; the semantics of a message, as + // ; described in the text. + // + // OCTET := + pub(crate) headers: Headers, + pub(crate) body: MixFrom<'a>, // all use for encode; owned use for decode +} + +impl<'a> MimePart<'a> { + pub(crate) fn new() -> Self { + Self { + headers: Headers::new(), + body: MixFrom::new(), + } + } + + pub(crate) fn set_headers(&mut self, headers: Headers) { + self.headers = headers; + } + + pub(crate) fn header(&mut self, name: N, value: V) -> Result<&mut Headers, HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.headers.insert(name, value)?; + Ok(self.headers_mut()) + } + + pub(crate) fn append_header( + &mut self, + name: N, + value: V, + ) -> Result<&mut Headers, HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.headers.append(name, value)?; + Ok(self.headers_mut()) + } + + pub(crate) fn body_from_owned(&mut self, data: Vec) { + self.body.set_owned(data); + } + + pub(crate) fn body_from_bytes(&mut self, data: &'a [u8]) { + self.body.set_bytes(data); + } + + pub(crate) fn body_from_reader(&mut self, data: T) + where + T: Read + Send + Sync + 'static, + { + self.body.set_reader(data); + } + + pub(crate) fn body_from_async_reader(&mut self, data: T) + where + T: AsyncRead + Send + Sync + Unpin + 'static, + { + self.body.set_async_reader(data); + } + + pub(crate) fn body_extend_from_slice(&mut self, src: &[u8]) { + if let MixFrom::Owned { bytes, index: _ } = &mut self.body { + bytes.extend_from_slice(src) + } + } + + /// Keeps the first len elements and discard the others. + pub(crate) fn body_truncate(&mut self, len: usize) { + if let MixFrom::Owned { bytes, index: _ } = &mut self.body { + bytes.truncate(len); + } + } + + /// Trims the back '\n' or '\r\n' once. + pub(crate) fn body_trim_crlf_once(&mut self) { + if let MixFrom::Owned { bytes, index: _ } = &mut self.body { + if bytes.ends_with(&[LF]) { + bytes.pop(); + } + if bytes.ends_with(&[CR]) { + bytes.pop(); + } + } + } + + /// Creates a [`MimePartBuilder`]. + /// + /// [`MimePartBuilder`]: MimePartBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePart; + /// + /// let part = MimePart::builder().build().unwrap(); + /// ``` + pub fn builder() -> MimePartBuilder<'a> { + MimePartBuilder::new() + } + + /// Gets the reference of headers. + pub fn headers(&self) -> &Headers { + &self.headers + } + + /// Gets the mutable reference of headers. + pub fn headers_mut(&mut self) -> &mut Headers { + &mut self.headers + } + + /// Gets the ownership of headers by replacing it with an default value. + pub fn headers_once(&mut self) -> Headers { + take(&mut self.headers) + } + + /// Gets the reference of body. + pub fn body(&self) -> Option<&Vec> { + match &self.body { + MixFrom::Owned { bytes, index: _ } => Some(bytes), + _ => None, + } + } + + /// Gets the mutable reference of body. + pub fn body_mut(&mut self) -> Option<&mut Vec> { + match &mut self.body { + MixFrom::Owned { bytes, index: _ } => Some(bytes), + _ => None, + } + } + + /// Gets the ownership of body by replacing it with an default value. + pub fn body_once(&mut self) -> Option> { + match &mut self.body { + MixFrom::Owned { bytes, index: _ } => Some(take(bytes)), + _ => None, + } + } +} + +/// `MimePartBuilder` can set a body part of a Composite MIME body [`MimePart`]. \ +/// `MimePartBuilder` can set headers and body, then builds a [`MimePart`]. \ +/// +/// [`MimePart`]: MimePart +/// +/// # Examples +/// ``` +/// use ylong_http::body::MimePartBuilder; +/// +/// let part = MimePartBuilder::new() +/// .header("accept", "text/html") +/// .body_from_bytes(b"9876543210\r\n") +/// .build() +/// .unwrap(); +/// ``` +pub struct MimePartBuilder<'a> { + inner: Result, HttpError>, +} + +impl<'a> MimePartBuilder<'a> { + /// Creates a new [`MimePartBuilder`]. + /// + /// [`MimePartBuilder`]: MimePartBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new(); + /// ``` + pub fn new() -> Self { + MimePartBuilder { + inner: Ok(MimePart::new()), + } + } + + /// Sets headers of [`MimePartBuilder`]. + /// + /// [`MimePartBuilder`]: MimePartBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::{body::MimePartBuilder, headers::Headers}; + /// + /// let part = MimePartBuilder::new().set_headers({ + /// let mut headers = Headers::new(); + /// headers.append("accept", "text/html").unwrap(); + /// headers.append("key1", "value1").unwrap(); + /// headers + /// }); + /// ``` + pub fn set_headers(mut self, headers: Headers) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.set_headers(headers); + inner + }); + self + } + + /// Inserts header to the MIME body part. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new() + /// .header("accept", "text/html") + /// .header("accept", "text/plain"); + /// ``` + pub fn header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.inner = self.inner.and_then(move |mut inner| { + inner.header(name, value)?; + Ok(inner) + }); + self + } + + /// Appends header to the MIME body part. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new() + /// .append_header("accept", "text/html") + /// .append_header("accept", "text/plain"); + /// ``` + pub fn append_header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.inner = self.inner.and_then(move |mut inner| { + inner.append_header(name, value)?; + Ok(inner) + }); + self + } + + /// Sets body to the MIME body part, the read content is from `Vec`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new().body_from_owned(b"123456".to_vec()); + /// ``` + pub fn body_from_owned(mut self, data: Vec) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.body_from_owned(data); + inner + }); + self + } + + /// Sets body to the MIME body part, the read content is from memory. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new().body_from_bytes("123456".as_bytes()); + /// ``` + pub fn body_from_bytes(mut self, data: &'a [u8]) -> Self { + self.inner = self.inner.map(move |mut inner| { + inner.body_from_bytes(data); + inner + }); + self + } + + /// Sets body to the MIME body part, the read content is from a synchronous reader. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new().body_from_reader("123456".as_bytes()); + /// ``` + pub fn body_from_reader(mut self, data: T) -> Self + where + T: Read + Send + Sync + 'static, + { + self.inner = self.inner.map(move |mut inner| { + inner.body_from_reader(data); + inner + }); + self + } + + /// Sets body to the MIME body part, the read content is from a asynchronous reader. + pub fn body_from_async_reader(mut self, data: T) -> Self + where + T: AsyncRead + Send + Sync + Unpin + 'static, + { + self.inner = self.inner.map(move |mut inner| { + inner.body_from_async_reader(data); + inner + }); + self + } + + /// Builds a [`MimePart`]. + /// + /// [`MimePart`]: MimePart + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimePartBuilder; + /// + /// let part = MimePartBuilder::new().build(); + /// ``` + pub fn build(self) -> Result, HttpError> { + self.inner + } +} + +impl<'a> Default for MimePartBuilder<'a> { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum PartStatus { + Start, + Headers, + Crlf, + Body, + End, +} + +#[cfg(test)] +mod ut_mime_part { + use crate::{ + body::{MimePart, MimePartBuilder}, + headers::Headers, + }; + + /// UT test cases for `MimePartBuilder::new`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePartBuilder::new`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_mime_part_builder_new() { + let part_builder = MimePartBuilder::new(); + assert!(part_builder.inner.is_ok()); + } + + /// UT test cases for `MimePartBuilder::set_headers`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder`. + /// 2. Sets headers. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mime_part_builder_set_headers() { + let part = MimePartBuilder::new().set_headers({ + let mut headers = Headers::new(); + headers.append("accept", "text/html").unwrap(); + headers.append("key1", "value1").unwrap(); + headers.append("key2", "value2").unwrap(); + headers.append("key3", "\"value3\"").unwrap(); + headers + }); + assert_eq!(part.inner.unwrap().headers, { + let mut headers = Headers::new(); + headers.append("accept", "text/html").unwrap(); + headers.append("key1", "value1").unwrap(); + headers.append("key2", "value2").unwrap(); + headers.append("key3", "\"value3\"").unwrap(); + headers + }); + } + + /// UT test cases for `MimePartBuilder::insert_header`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder`. + /// 2. Inserts header. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mime_part_builder_insert_header() { + let part = MimePartBuilder::new() + .header("accept", "text/html") + .header("accept", "text/plain"); + assert_eq!(part.inner.unwrap().headers, { + let mut headers = Headers::new(); + headers.append("accept", "text/plain").unwrap(); + headers + }); + } + + /// UT test cases for `MimePartBuilder::append_header`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder`. + /// 2. Appends header. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mime_part_builder_append_header() { + let part = MimePartBuilder::new() + .append_header("accept", "text/html") + .append_header("accept", "text/plain"); + let binding = part.inner.unwrap(); + let value = binding.headers.get("accept"); + assert_eq!(value.unwrap().to_str().unwrap(), "text/html, text/plain"); + } + + /// UT test cases for `MimePart::builder`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets headers. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mime_part_builder() { + let part = MimePart::builder() + .header("key1", "value1") + .build() + .unwrap(); + assert_eq!(part.headers().to_owned(), { + let mut headers = Headers::new(); + headers.insert("key1", "value1").unwrap(); + headers + }); + } + + /// UT test cases for `MimePart::headers`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets headers. + /// 3. Gets the reference of headers then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_headers() { + let part = MimePart::builder() + .header("key1", "value1") + .build() + .unwrap(); + assert_eq!(part.headers().to_owned(), { + let mut headers = Headers::new(); + headers.insert("key1", "value1").unwrap(); + headers + }); + } + + /// UT test cases for `MimePart::headers_mut`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets headers. + /// 3. Gets the mutable reference of headers then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_headers_mut() { + let part = MimePart::builder() + .header("key1", "value1") + .build() + .unwrap(); + assert_eq!(part.headers().to_owned(), { + let mut headers = Headers::new(); + headers.insert("key1", "value1").unwrap(); + headers + }); + } + + /// UT test cases for `MimePart::headers_once`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets headers. + /// 3. Gets the ownership of headers then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_headers_once() { + let mut part = MimePart::builder() + .header("key1", "value1") + .build() + .unwrap(); + assert_eq!(part.headers_once(), { + let mut headers = Headers::new(); + headers.insert("key1", "value1").unwrap(); + headers + }); + } + + /// UT test cases for `MimePart::body`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets body. + /// 3. Gets the reference of body then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_body() { + let part = MimePart::builder() + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(); + assert_eq!(part.body().unwrap(), b"1111"); + } + + /// UT test cases for `MimePart::body_mut`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets body. + /// 3. Gets the mutable reference of body then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_body_mut() { + let mut part = MimePart::builder() + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(); + part.body_mut().unwrap()[0] = b'2'; + assert_eq!(part.body().unwrap(), b"2111"); + } + + /// UT test cases for `MimePart::body_once`. + /// + /// # Brief + /// 1. Creates a `MimePartBuilder` by `MimePart::builder`. + /// 2. Sets body. + /// 3. Gets the ownership of body then compares. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_body_once() { + let mut part = MimePart::builder() + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(); + assert_eq!(part.body_once().unwrap(), b"1111".to_vec()); + assert_eq!(part.body().unwrap(), b""); + } +} diff --git a/ylong_http/src/body/mime/decode/mod.rs b/ylong_http/src/body/mime/decode/mod.rs new file mode 100644 index 0000000..17943df --- /dev/null +++ b/ylong_http/src/body/mime/decode/mod.rs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod multi; +mod part; + +pub use multi::MimeMultiDecoder; +pub(crate) use part::MimePartDecoder; + +// Marks which boundary is completed. +#[derive(Debug, PartialEq)] +pub(crate) enum BoundaryTag { + Init, + First, + Middle, + End, +} diff --git a/ylong_http/src/body/mime/decode/multi.rs b/ylong_http/src/body/mime/decode/multi.rs new file mode 100644 index 0000000..21fdf2b --- /dev/null +++ b/ylong_http/src/body/mime/decode/multi.rs @@ -0,0 +1,987 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + body::{ + mime::{ + common::{ + get_content_type_boundary, get_crlf_contain, trim_back_lwsp_if_end_with_lf, XPart, + }, + decode::BoundaryTag, + DecodeHeaders, MimeMulti, MimePartDecoder, + }, + TokenStatus, + }, + error::{ErrorKind, HttpError}, + headers::Headers, +}; +use core::mem::take; + +type ByteVec<'a> = Result, &'a [u8]), &'a [u8]>, HttpError>; + +// TODO: Increases compatibility for preamble and epilogue. + +/// `MimeMultiDecoder` can create a [`MimeMulti`] according to a serialized data. +/// +/// [`MimeMulti`]: MimeMulti +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::{MimeMulti, MimeMultiDecoder, MimePart, TokenStatus}; +/// +/// let buf = b"--abc\r\nkey1:value1\r\n\r\n1111\r\n--abc--\r\nabcd"; +/// let mut decoder = MimeMultiDecoder::new(); +/// let (elem, rest) = decoder.decode(buf).unwrap(); +/// assert!(elem.is_complete()); +/// let multi1 = MimeMulti::builder() +/// .set_boundary(b"abc".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key1", "value1") +/// .body_from_owned(b"1111".to_vec()) +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// if let TokenStatus::Complete(multi) = elem { +/// assert_eq!(multi.boundary(), multi1.boundary()); +/// assert_eq!(multi.headers(), multi1.headers()); +/// assert_eq!(multi, multi1); +/// } +/// assert_eq!(rest, b"abcd"); +/// ``` +#[derive(Debug, Default, PartialEq)] +pub struct MimeMultiDecoder { + stages: Vec, // stack of stage + multis: Vec>, // stack of multi +} + +impl MimeMultiDecoder { + /// Creates a new `MimeMultiDecoder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http:: body::MimeMultiDecoder; + /// + /// let mut decoder = MimeMultiDecoder::new(); + /// ``` + pub fn new() -> Self { + let mut decoder = MimeMultiDecoder { + stages: vec![], + multis: vec![], + }; + let multi = MimeMulti::new(); + decoder.multis.push(multi); + let data = DecodeData::new(); + decoder.stages.push(MultiStage::Multi(data)); + decoder + } + + /// Inputs datas, gets a [`MimeMulti`]. + /// + /// [`MimeMulti`]: MimeMulti + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimeMulti, MimeMultiDecoder, MimePart, TokenStatus}; + /// + /// let buf = b"--abc\r\n\r\n--abc--\r\nabcd"; + /// let mut decoder = MimeMultiDecoder::new(); + /// let (elem, rest) = decoder.decode(buf).unwrap(); + /// assert!(elem.is_complete()); + /// let multi1 = MimeMulti::builder() + /// .set_boundary(b"abc".to_vec()) + /// .add_part(MimePart::builder().build().unwrap()) + /// .build() + /// .unwrap(); + /// if let TokenStatus::Complete(multi) = elem { + /// assert_eq!(multi.boundary(), multi1.boundary()); + /// assert_eq!(multi, multi1); + /// } + /// assert_eq!(rest, b"abcd"); + /// ``` + pub fn decode<'a>( + &mut self, + buf: &'a [u8], + ) -> Result<(TokenStatus, &'a [u8]), HttpError> { + if buf.is_empty() { + return Err(ErrorKind::InvalidInput.into()); + } + + let mut results = TokenStatus::Partial(()); + let mut remains = buf; + + while let Some(stage) = self.stages.pop() { + let rest = match stage { + MultiStage::Unknown(headers_decoder) => { + self.unknown_decode(headers_decoder, remains) + } + MultiStage::Part(part_decoder) => self.part_decode(part_decoder, remains), + MultiStage::Multi(data) => self.multi_decode(data, remains), + MultiStage::End => { + results = match self.multis.pop() { + Some(multi) => TokenStatus::Complete(multi), + None => return Err(ErrorKind::InvalidInput.into()), + }; + break; + } + }?; + remains = rest; + // at least has the outermost multi stage, unless is replaced by MultiStage::End, so the last stage can uncheck. + if remains.is_empty() && !self.last_stage()?.is_end() { + break; + } + } + + Ok((results, remains)) + } + + fn unknown_decode<'a>( + &mut self, + mut headers_decoder: DecodeHeaders, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + let (elem, rest) = headers_decoder.decode(buf)?; + match elem { + TokenStatus::Partial(_) => self.stages.push(MultiStage::Unknown(headers_decoder)), + TokenStatus::Complete(headers) => { + match get_content_type_boundary(&headers) { + // is multi + Some(boundary) => self.push_new_multi_stage(headers, boundary), + // is part + None => self.push_new_part_stage(headers)?, + } + } + } + Ok(rest) + } + + fn push_new_unknown_stage(&mut self) { + let headers_decoder = DecodeHeaders::new(); + self.stages.push(MultiStage::Unknown(headers_decoder)); + } + + fn push_new_multi_stage(&mut self, headers: Headers, boundary: Vec) { + // push a new Multi stage + let data = DecodeData::new_as_part(boundary.clone()); + self.stages.push(MultiStage::Multi(data)); + + // push a new multi struct + let mut multi = MimeMulti::new(); + multi.set_headers(headers); + multi.set_boundary(boundary); + self.multis.push(multi); + } + + fn push_new_part_stage(&mut self, headers: Headers) -> Result<(), HttpError> { + let mut decoder = MimePartDecoder::new(); + // at least has the outermost multi, so the last multi can uncheck. + decoder.init_from_multi(headers, self.last_multi()?.boundary.clone()); + self.stages.push(MultiStage::Part(decoder)); + Ok(()) + } + + fn part_decode<'a>( + &mut self, + mut part_decoder: MimePartDecoder, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + let (elem, rest) = part_decoder.decode(buf)?; + match elem { + TokenStatus::Partial(_) => { + // push self + self.stages.push(MultiStage::Part(part_decoder)); + } + TokenStatus::Complete(part) => { + // at least has the outermost multi, so the last multi can uncheck. + self.last_multi_mut()?.add_part(part); + // now temp multi is end + if part_decoder.is_last_part() { + self.temp_multi_end(true)?; + } else { + self.push_new_unknown_stage(); + } + } + } + Ok(rest) + } + + fn multi_decode<'a>(&mut self, data: DecodeData, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + match data.is_finish_first_boundary { + true => self.middle_or_end_boundary_decode(data, buf), + false => self.first_boundary_decode(data, buf), + } + } + + /// find middle or end boundary for inner and outer multi + fn middle_or_end_boundary_decode<'a>( + &mut self, + mut data: DecodeData, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + match data.middle_or_end_boundary(buf) { + TokenStatus::Partial(rest) => { + self.stages.push(MultiStage::Multi(data)); + Ok(rest) + } + TokenStatus::Complete(rest) => match data.tag { + BoundaryTag::Middle => { + self.stages.push(MultiStage::Multi(data)); + self.push_new_unknown_stage(); + Ok(rest) + } + BoundaryTag::End => { + self.temp_multi_end(false)?; + Ok(rest) + } + // ensure not in this arm + _ => Ok(rest), + }, + } + } + + fn first_boundary_decode<'a>( + &mut self, + data: DecodeData, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + match data.is_outermost { + true => self.outermost_first_boundary_decode(data, buf), + false => self.inner_first_boundary_decode(data, buf), + } + } + + /// for outermost multi + fn outermost_first_boundary_decode<'a>( + &mut self, + mut data: DecodeData, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + match data.outermost_first_boundary(buf)? { + TokenStatus::Partial(rest) => { + // push self + self.stages.push(MultiStage::Multi(data)); + Ok(rest) + } + TokenStatus::Complete((boundary, rest)) => { + match self.multis.first_mut() { + Some(multi) => { + multi.set_boundary(boundary); + // push self + self.stages.push(MultiStage::Multi(data)); + self.push_new_unknown_stage(); + Ok(rest) + } + None => Err(ErrorKind::InvalidInput.into()), + } + } + } + } + + /// for inner multi + fn inner_first_boundary_decode<'a>( + &mut self, + mut data: DecodeData, + buf: &'a [u8], + ) -> Result<&'a [u8], HttpError> { + let rest = match data.inner_first_boundary(buf)? { + TokenStatus::Partial(rest) => rest, + TokenStatus::Complete(rest) => rest, + }; + // push self + self.stages.push(MultiStage::Multi(data)); + self.push_new_unknown_stage(); + Ok(rest) + } + + // when the last multi is end. + fn temp_multi_end(&mut self, by_part: bool) -> Result<(), HttpError> { + if self.multis.len() == 1 { + self.stages = vec![MultiStage::End]; + return Ok(()); + } + + if by_part { + // pop the nearly Multi stage + self.stages_pop()?; + } + let multi = self.multis_pop()?; + // at least has the outermost multi, so the last multi can uncheck. + self.last_multi_mut()?.add_multi(multi); + Ok(()) + } + + fn multis_pop(&mut self) -> Result, HttpError> { + match self.multis.pop() { + Some(multi) => Ok(multi), + None => Err(ErrorKind::InvalidInput.into()), + } + } + + fn stages_pop(&mut self) -> Result { + match self.stages.pop() { + Some(stage) => Ok(stage), + None => Err(ErrorKind::InvalidInput.into()), + } + } + + fn last_multi(&self) -> Result<&MimeMulti<'static>, HttpError> { + match self.multis.last() { + Some(multi) => Ok(multi), + None => Err(ErrorKind::InvalidInput.into()), + } + } + + fn last_multi_mut(&mut self) -> Result<&mut MimeMulti<'static>, HttpError> { + match self.multis.last_mut() { + Some(multi) => Ok(multi), + None => Err(ErrorKind::InvalidInput.into()), + } + } + + fn last_stage(&self) -> Result<&MultiStage, HttpError> { + match self.stages.last() { + Some(stage) => Ok(stage), + None => Err(ErrorKind::InvalidInput.into()), + } + } +} + +#[derive(Debug, PartialEq)] +enum MultiStage { + Unknown(DecodeHeaders), + Part(MimePartDecoder), + Multi(DecodeData), + End, +} + +impl MultiStage { + fn is_end(&self) -> bool { + matches!(self, MultiStage::End) + } +} + +#[derive(Debug, PartialEq)] +struct DecodeData { + is_finish_first_boundary: bool, // whether read first boundary completely + is_outermost: bool, // whether is outermost multi + boundary: Vec, + tag: BoundaryTag, // 1 is middle part; 2 is end part + src: Vec, // src which is need to encode + src_idx: usize, +} + +impl DecodeData { + /// is outermost multi + fn new() -> Self { + Self { + is_finish_first_boundary: false, + is_outermost: true, + boundary: vec![], + tag: BoundaryTag::Init, + src: vec![], + src_idx: 0, + } + } + + /// is a part of multi + fn new_as_part(boundary: Vec) -> Self { + Self { + is_finish_first_boundary: false, + is_outermost: false, + boundary, + tag: BoundaryTag::Init, + src: vec![], + src_idx: 0, + } + } + + fn set_boundary(&mut self, boundary: Vec) { + self.boundary = boundary; + } + + /// finds the first boundary of the outermost multi + fn outermost_first_boundary<'a>(&mut self, buf: &'a [u8]) -> ByteVec<'a> { + match get_crlf_contain(buf) { + TokenStatus::Partial(unparsed) => { + self.src.extend_from_slice(unparsed); + Ok(TokenStatus::Partial(&[])) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.src.extend_from_slice(src); + // safety: use for trim. + let s = unsafe { std::str::from_utf8_unchecked(&self.src).trim() }; + // implies s >= 2 + if let Some(s) = s.strip_prefix("--") { + let boundary = s.as_bytes().to_vec(); + self.set_boundary(boundary.clone()); + self.when_is_first_boundary(); + return Ok(TokenStatus::Complete((boundary, unparsed))); + } + + // is not end; need to find next '\n' + self.src = vec![]; + self.src_idx = 0; + Ok(TokenStatus::Partial(&[])) + } + } + } + + /// use to inner multi + fn inner_first_boundary<'a>( + &mut self, + buf: &'a [u8], + ) -> Result, HttpError> { + match get_crlf_contain(buf) { + TokenStatus::Partial(unparsed) => { + self.src.extend_from_slice(unparsed); + Ok(TokenStatus::Partial(&[])) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.src.extend_from_slice(src); + let line = &self.src[self.src_idx..]; + let trim_line = trim_back_lwsp_if_end_with_lf(line); + + // checks whether is first boundary + let mut deriv_boundary = b"--".to_vec(); + deriv_boundary.extend_from_slice(&self.boundary); + if trim_line == deriv_boundary { + self.when_is_first_boundary(); + return Ok(TokenStatus::Complete(unparsed)); + } + + // is not end + self.src_idx = self.src.len(); + Ok(TokenStatus::Partial(unparsed)) + } + } + } + + fn when_is_first_boundary(&mut self) { + self.is_finish_first_boundary = true; + self.tag = BoundaryTag::First; + self.src_init(); + } + + fn src_init(&mut self) { + // init + self.src = vec![]; + self.src_idx = 0; + } + + /// use to all multi + fn middle_or_end_boundary<'a>(&mut self, buf: &'a [u8]) -> TokenStatus<&'a [u8], &'a [u8]> { + match get_crlf_contain(buf) { + TokenStatus::Partial(unparsed) => { + self.src.extend_from_slice(unparsed); + TokenStatus::Partial(&[]) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.src.extend_from_slice(src); + let line = &self.src[self.src_idx..]; + let trim_line = trim_back_lwsp_if_end_with_lf(line); + + // checks whether is middle boundary + let mut deriv_boundary = b"--".to_vec(); + deriv_boundary.extend_from_slice(&self.boundary); + if trim_line == deriv_boundary { + self.tag = BoundaryTag::Middle; + self.src_init(); + return TokenStatus::Complete(unparsed); + } + // checks whether is end boundary + deriv_boundary.extend_from_slice(b"--"); + if trim_line == deriv_boundary { + self.tag = BoundaryTag::End; + self.src_init(); + return TokenStatus::Complete(unparsed); + } + + // is not end + self.src_idx = self.src.len(); + TokenStatus::Partial(unparsed) + } + } + } +} + +#[cfg(test)] +mod ut_mime_multi_decoder { + use crate::body::{MimeMulti, MimeMultiDecoder, MimePart, TokenStatus}; + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of a part. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_new() { + let buf = b"--abc\r\n\r\n--abc--\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part(MimePart::builder().build().unwrap()) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi.boundary(), multi1.boundary()); + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of a part. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_one_part() { + let buf = b"--abc\r\nkey1:value1\r\n\r\n1111\r\n--abc--\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi.boundary(), multi1.boundary()); + assert_eq!(multi.headers(), multi1.headers()); + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of a part. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_one_part_no_headers() { + let buf = b"--abc\r\n\r\n112233\r\n--abc--\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part( + MimePart::builder() + .body_from_owned(b"112233".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi.boundary(), multi1.boundary()); + assert_eq!(multi.headers(), multi1.headers()); + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of a part. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_one_part_no_body() { + let buf = b"--abc\r\nkey1:value1\r\n\r\n--abc--\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi.boundary(), multi1.boundary()); + assert_eq!(multi.headers(), multi1.headers()); + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of several parts. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_several_parts() { + let buf = + b"---\r\nkey1:value1\r\n\r\n1111\r\n---\r\nkey2: value2\r\n\r\n2222\r\n-----\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"-".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is composed of several parts, and the boundary has LWSP chars. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_several_parts_has_lwsp() { + let buf = + b"--- \r\nkey1:value1\r\n\r\n1111\r\n--- \r\nkey2: value2\r\n\r\n2222\r\n----- \r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"-".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is nesting. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_nest() { + let buf = "\ + --abc\r\nContent-Type:multipart/mix; boundary=aaa\r\n\r\n\ + --aaa\r\nkey1:value1\r\n\r\n1111\r\n--aaa--\r\n\r\n--abc--\r\nabcd" + .as_bytes(); + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/mix", b"aaa".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is nesting. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_nest2() { + let buf = "\ + --abc\r\nContent-Type:multipart/mix; boundary=aaa\r\n\r\n--aaa\r\nkey1:\ + value1\r\n\r\n1111\r\n--aaa--\r\n\r\n--abc\r\nkey2:value2\r\n\r\n2222\r\n--\ + abc--\r\nabcd" + .as_bytes(); + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/mix", b"aaa".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is nesting. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_nest3() { + let buf = "\ + --abc\r\nkey2:value2\r\n\r\n2222\r\n--abc\r\nContent-Type:multipart/mix; \ + boundary=aaa\r\n\r\n--aaa\r\nkey1:value1\r\n\r\n1111\r\n--aaa--\r\n\r\n\ + --abc--\r\nabcd" + .as_bytes(); + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/mix", b"aaa".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The `MimeMulti` is nesting. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_nest4() { + let buf = "\ + --abc\r\nkey2:value2\r\n\r\n2222\r\n--abc\r\nContent-Type:multipart/mix; \ + boundary=aaa\r\n\r\n--aaa\r\nkey1:value1\r\n\r\n1111\r\n--aaa\r\nkey3:value3\ + \r\n\r\n--aaa--\r\n\r\n--abc--\r\nabcd" + .as_bytes(); + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .add_multi( + MimeMulti::builder() + .set_content_type(b"multipart/mix", b"aaa".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key3", "value3") + .build() + .unwrap(), + ) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. The decode bytes are divided into several executions. + /// 4. Creates a `MimeMulti` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_times() { + let buf = + b"---\r\nkey1:value1\r\n\r\n1111\r\n---\r\nkey2: value2\r\n\r\n2222\r\n-----\r\nabcd"; + let mut decoder = MimeMultiDecoder::new(); + let (elem, rest) = decoder.decode(&buf[..3]).unwrap(); //--- + assert!(!elem.is_complete()); + assert_eq!(rest, b""); + let (elem, rest) = decoder.decode(&buf[3..16]).unwrap(); //\r\nkey1:value1 + assert!(!elem.is_complete()); + assert_eq!(rest, b""); + let (elem, rest) = decoder.decode(&buf[16..31]).unwrap(); //\r\n\r\n1111\r\n---\r\n + assert!(!elem.is_complete()); + assert_eq!(rest, b""); + let (elem, rest) = decoder.decode(&buf[31..60]).unwrap(); //key2: value2\r\n\r\n2222\r\n-----\r\n + assert!(elem.is_complete()); + let multi1 = MimeMulti::builder() + .set_boundary(b"-".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_owned(b"1111".to_vec()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_owned(b"2222".to_vec()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + if let TokenStatus::Complete(multi) = elem { + assert_eq!(multi, multi1); + } + assert_eq!(rest, b""); + let (_elem, rest) = decoder.decode(&buf[60..]).unwrap(); //abcd + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimeMultiDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimeMultiDecoder` by `MimeMultiDecoder::new`. + /// 2. Uses `MimeMultiDecoder::decode` to decode `MimeMulti`. + /// 3. Uses wrong input. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_multi_decoder_err() { + let mut buf = b"--abc\r\n\r\n--abc--\r\nabcd".to_vec(); + buf.insert(7, 200); + let mut decoder = MimeMultiDecoder::new(); + let res = decoder.decode(&buf); + assert!(res.is_err()); + } +} diff --git a/ylong_http/src/body/mime/decode/part.rs b/ylong_http/src/body/mime/decode/part.rs new file mode 100644 index 0000000..26abbd9 --- /dev/null +++ b/ylong_http/src/body/mime/decode/part.rs @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + body::{ + mime::{ + common::{get_crlf_contain, trim_back_lwsp_if_end_with_lf, trim_front_lwsp}, + decode::BoundaryTag, + DecodeHeaders, MimePart, PartStatus, + }, + TokenStatus, + }, + error::{ErrorKind, HttpError}, + headers::Headers, +}; +use core::mem::take; + +#[derive(Debug, PartialEq)] +pub(crate) struct MimePartDecoder { + stage: PartStatus, // Encode stage now + headers_decoder: DecodeHeaders, + part: MimePart<'static>, + boundary: Vec, // boundary + tag: BoundaryTag, + src_idx: usize, // marks last "\n" +} + +impl MimePartDecoder { + pub(crate) fn new() -> Self { + MimePartDecoder { + stage: PartStatus::Start, + headers_decoder: DecodeHeaders::new(), + part: MimePart::new(), + boundary: b"-".to_vec(), + tag: BoundaryTag::Init, + src_idx: 0, + } + } + + pub(crate) fn init(&mut self) { + self.stage = PartStatus::Start; + self.headers_decoder = DecodeHeaders::new(); + + self.part = MimePart::new(); + self.boundary = b"-".to_vec(); + self.tag = BoundaryTag::Init; + self.src_idx = 0; + } + + // by MimeMultiDecode + pub(crate) fn init_from_multi(&mut self, headers: Headers, boundary: Vec) { + self.init(); + self.part.set_headers(headers); + self.boundary = boundary; + self.stage = PartStatus::Body; + } + + pub(crate) fn set_boundary(&mut self, boundary: Vec) { + self.boundary = boundary; + } + + pub(crate) fn decode<'a>( + &mut self, + buf: &'a [u8], + ) -> Result<(TokenStatus, ()>, &'a [u8]), HttpError> { + // Option + if buf.is_empty() { + return Err(ErrorKind::InvalidInput.into()); + } + + let mut results = TokenStatus::Partial(()); + let mut remains = buf; + loop { + let rest = match self.stage { + PartStatus::Start => self.start_decode(remains), + PartStatus::Headers => self.headers_decode(remains), + PartStatus::Crlf => Ok(remains), // not use + PartStatus::Body => self.body_decode(remains), + PartStatus::End => { + results = TokenStatus::Complete(take(&mut self.part)); + break; + } + }?; + remains = rest; + if remains.is_empty() && self.stage != PartStatus::End { + break; + } + } + Ok((results, remains)) + } + + fn start_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let buf = trim_front_lwsp(buf); + self.stage = PartStatus::Headers; + Ok(buf) + } + + // Headers + Crlf + fn headers_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + let (elem, rest) = self.headers_decoder.decode(buf)?; + if let TokenStatus::Complete(headers) = elem { + self.part.set_headers(headers); + self.stage = PartStatus::Body; + } + + Ok(rest) + } + + // Writes to the part body and checks boundary. + fn body_decode<'a>(&mut self, buf: &'a [u8]) -> Result<&'a [u8], HttpError> { + match get_crlf_contain(buf) { + TokenStatus::Partial(unparsed) => { + self.part.body_extend_from_slice(unparsed); + Ok(&[]) + } + TokenStatus::Complete((src, unparsed)) => { + // clone in this. + self.part.body_extend_from_slice(src); + let body = match self.part.body() { + Some(vec) => vec, + None => return Err(ErrorKind::InvalidInput.into()), + }; + let line = &body[self.src_idx..]; + let trim_line = trim_back_lwsp_if_end_with_lf(line); + + // checks whether is middle boundary + let mut deriv_boundary = b"--".to_vec(); + deriv_boundary.extend_from_slice(&self.boundary); + if trim_line == deriv_boundary { + self.tag = BoundaryTag::Middle; + self.when_is_end(); + return Ok(unparsed); + } + // checks whether is end boundary + deriv_boundary.extend_from_slice(b"--"); + if trim_line == deriv_boundary { + self.tag = BoundaryTag::End; + self.when_is_end(); + return Ok(unparsed); + } + + // is not end + self.src_idx = body.len(); + Ok(unparsed) + } + } + } + + fn when_is_end(&mut self) { + self.stage = PartStatus::End; + self.part.body_truncate(self.src_idx); + self.part.body_trim_crlf_once(); + } + + // Checks whether is the last part of multi. + pub(crate) fn is_last_part(&self) -> bool { + self.tag == BoundaryTag::End + } +} + +#[cfg(test)] +mod ut_mime_part_decoder { + use crate::body::{ + mime::{MimePart, MimePartDecoder}, + TokenStatus, + }; + + /// UT test cases for `MimePartDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimePartDecoder` by `MimePartDecoder::new`. + /// 2. Uses `MimePartDecoder::decode` to decode `MimePart`. + /// 3. Creates a `MimePart` and sets the same parameters to compare. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_decoder_new_by_crlf() { + let part1 = MimePart::builder() + .body_from_owned(b"abcd".to_vec()) + .build() + .unwrap(); + + // common + let buf = b"\r\nabcd\r\n--abc\r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + + // has LWSP + let buf = b" \r\nabcd\r\n--abc\r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + + // has RFC LWSP + let buf = b"\r\nabcd\r\n--abc \r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimePartDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimePartDecoder` by `MimePartDecoder::new`. + /// 2. Uses `MimePartDecoder::decode` to decode `MimePart`. + /// 3. Creates a `MimePart` and sets the same parameters to compare. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_decoder_new_by_lf() { + let part1 = MimePart::builder() + .body_from_owned(b"abcd".to_vec()) + .build() + .unwrap(); + + // common + let buf = b"\nabcd\n--abc\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + + // has LWSP + let buf = b" \nabcd\n--abc\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + + // has RFC LWSP + let buf = b" \nabcd\n--abc \nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimePartDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimePartDecoder` by `MimePartDecoder::new`. + /// 2. Uses `MimePartDecoder::decode` to decode `MimePart`. + /// 3. Creates a `MimePart` and sets the same parameters to compare. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_decoder_decode() { + let buf = b" name1: value1\r\n name2: value2\r\n\r\nabcd\r\n--abc \r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let part1 = MimePart::builder() + .header("name1", "value1") + .header("name2", "value2") + .body_from_owned(b"abcd".to_vec()) + .build() + .unwrap(); + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimePartDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimePartDecoder` by `MimePartDecoder::new`. + /// 2. Uses `MimePartDecoder::decode` to decode `MimePart`. + /// 3. Creates a `MimePart` and sets the same parameters to compare. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_decoder_no_headers_no_body() { + let buf = b"\r\n--abc\r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(buf).unwrap(); + assert!(elem.is_complete()); + let part1 = MimePart::builder().build().unwrap(); + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + } + + /// UT test cases for `MimePartDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `MimePartDecoder` by `MimePartDecoder::new`. + /// 2. Uses `MimePartDecoder::decode` to decode `MimePart`. + /// 3. The decode bytes are divided into several executions. + /// 4. Creates a `MimePart` and sets the same parameters to compare. + /// 5. Checks whether the result is correct. + #[test] + fn ut_decode_headers_decode_times() { + let buf = b"name1:value1\r\nname2:value2\r\n\r\nabcd\r\n--abc\r\nabcd"; + let mut decoder = MimePartDecoder::new(); + decoder.set_boundary(b"abc".to_vec()); + let (elem, rest) = decoder.decode(&buf[0..3]).unwrap(); // nam + assert!(!elem.is_complete()); + assert_eq!(rest, b""); + + let (elem, rest) = decoder.decode(&buf[3..30]).unwrap(); // e1:value1\r\nname2:value2\r\n\r\n + assert!(!elem.is_complete()); + assert_eq!(rest, b""); + + let (elem, rest) = decoder.decode(&buf[30..]).unwrap(); // abcd\r\n--abc\r\nabcd + assert!(elem.is_complete()); + let part1 = MimePart::builder() + .header("name1", "value1") + .header("name2", "value2") + .body_from_owned(b"abcd".to_vec()) + .build() + .unwrap(); + if let TokenStatus::Complete(part) = elem { + assert_eq!(part, part1); + } + assert_eq!(rest, b"abcd"); + } +} diff --git a/ylong_http/src/body/mime/encode/mod.rs b/ylong_http/src/body/mime/encode/mod.rs new file mode 100644 index 0000000..4b2537a --- /dev/null +++ b/ylong_http/src/body/mime/encode/mod.rs @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod multi; +mod part; + +pub use multi::MimeMultiEncoder; +pub use part::MimePartEncoder; diff --git a/ylong_http/src/body/mime/encode/multi.rs b/ylong_http/src/body/mime/encode/multi.rs new file mode 100644 index 0000000..817e301 --- /dev/null +++ b/ylong_http/src/body/mime/encode/multi.rs @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + body::{ + async_impl, + mime::{ + common::{data_copy, SizeResult}, + EncodeHeaders, + }, + sync_impl, MimeMulti, MimePartEncoder, TokenStatus, XPart, + }, + headers::Headers, +}; +use core::{ + pin::Pin, + task::{Context, Poll}, +}; +use std::collections::VecDeque; + +/// `MimeMultiEncoder` can get a [`MimeMulti`] to encode into data that can be transmitted. +/// +/// [`MimeMulti`]: MimeMulti +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::{sync_impl, MimeMulti, MimeMultiEncoder, MimePart}; +/// +/// let multi1 = MimeMulti::builder() +/// .set_content_type(b"multipart/mixed", b"abc".to_vec()) +/// .set_boundary(b"abc".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key1", "value1") +/// .body_from_reader("111111".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_part( +/// MimePart::builder() +/// .header("key2", "value2") +/// .body_from_reader("22222".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// +/// let multi = MimeMulti::builder() +/// .set_boundary(b"abcde".to_vec()) +/// .add_part( +/// MimePart::builder() +/// .header("key3", "value3") +/// .body_from_reader("33333".as_bytes()) +/// .build() +/// .unwrap(), +/// ) +/// .add_multi(multi1) +/// .add_part( +/// MimePart::builder() +/// .header("key4", "value4") +/// .body_from_async_reader("444444".as_bytes()) // nothing in sync +/// .build() +/// .unwrap(), +/// ) +/// .build() +/// .unwrap(); +/// +/// let mut multi_encoder = MimeMultiEncoder::from_multi(multi); +/// let mut buf = vec![0u8; 30]; +/// let mut v_size = vec![]; +/// let mut v_str = vec![]; +/// +/// loop { +/// let len = sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap(); +/// if len == 0 { +/// break; +/// } +/// v_size.push(len); +/// v_str.extend_from_slice(&buf[..len]); +/// } +/// assert_eq!(v_size, vec![30, 30, 30, 30, 30, 30, 13]); +/// assert_eq!(v_str, +/// b"--abcde\r\nkey3:value3\r\n\r\n33333\r\n--abcde\r\n\ +/// content-type:multipart/mixed; boundary=abc\r\n\r\n--abc\r\n\ +/// key1:value1\r\n\r\n111111\r\n--abc\r\nkey2:value2\r\n\r\n22222\r\n\ +/// --abc--\r\n\r\n--abcde\r\nkey4:value4\r\n\r\n\r\n--abcde--\r\n" +/// ); +/// ``` +#[derive(Debug)] +pub struct MimeMultiEncoder<'a> { + stages: VecDeque>, + headers: Option, // default is not part, don't encode headers + src_idx: usize, + src: Vec, +} + +impl<'a> MimeMultiEncoder<'a> { + /// Creates a `MimeMultiEncoder` by a [`MimeMulti`]. + /// + /// [`MimeMulti`]: MimeMulti + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimeMulti, MimeMultiEncoder}; + /// + /// let multi = MimeMulti::builder() + /// .build() + /// .unwrap(); + /// let multi_encoder = MimeMultiEncoder::from_multi(multi); + /// ``` + pub fn from_multi(multi: MimeMulti<'a>) -> Self { + let mut encoder = Self::new(); + encoder.headers = Some(multi.headers); + encoder.push_list_to_stages(multi.boundary, multi.list); + encoder + } + + fn new() -> Self { + MimeMultiEncoder { + stages: VecDeque::new(), + headers: None, + src: vec![], + src_idx: 0, + } + } + + fn push_list_to_stages(&mut self, boundary: Vec, list: Vec>) { + // dash-boundary := "--" boundary + self.stages.push_back(MultiStage::Dash); + self.stages + .push_back(MultiStage::Boundary(boundary.clone())); + self.stages.push_back(MultiStage::Crlf); + + if list.is_empty() { + // close-delimiter CRLF "--" boundary "--" CRLF + self.push_end_boundary(boundary); + return; + } + + let len = list.len(); + + for (idx, xpart) in list.into_iter().enumerate() { + match xpart { + XPart::Part(part) => { + let part_encoder = MimePartEncoder::from_part(part); + self.stages.push_back(MultiStage::MimePart(part_encoder)); + } + XPart::Multi(multi) => self.push_multi_as_part(multi), + } + if idx == len - 1 { + self.push_end_boundary(boundary.clone()); + } else { + self.push_middle_boundary(boundary.clone()); + } + } + } + + fn push_multi_as_part(&mut self, multi: MimeMulti<'a>) { + let headers_encoder = EncodeHeaders::new(multi.headers); + self.stages.push_back(MultiStage::Headers(headers_encoder)); + self.stages.push_back(MultiStage::Crlf); + self.push_list_to_stages(multi.boundary, multi.list); + } + + // "--" boundary CRLF + fn push_first_boundary(&mut self, boundary: Vec) { + // dash-boundary := "--" boundary + self.stages.push_back(MultiStage::Dash); + self.stages.push_back(MultiStage::Boundary(boundary)); + // end CRLF + self.stages.push_back(MultiStage::Crlf); + } + + // CRLF "--" boundary CRLF + fn push_middle_boundary(&mut self, boundary: Vec) { + // delimiter := CRLF dash-boundary + self.stages.push_back(MultiStage::Crlf); + self.push_first_boundary(boundary); + } + + // CRLF "--" boundary "--" CRLF + fn push_end_boundary(&mut self, boundary: Vec) { + // close-delimiter: = CRLF "--" boundary "--" + self.stages.push_back(MultiStage::Crlf); + self.stages.push_back(MultiStage::Dash); + self.stages.push_back(MultiStage::Boundary(boundary)); + self.stages.push_back(MultiStage::Dash); + // end CRLF + self.stages.push_back(MultiStage::Crlf); + } + + fn init_src(&mut self) { + self.src = vec![]; + self.src_idx = 0; + } + + fn temp_src(&mut self, stage: &MultiStage) { + match stage { + MultiStage::Crlf => self.src = b"\r\n".to_vec(), + MultiStage::Dash => self.src = b"--".to_vec(), + _ => {} + } + } + + //"\r\n" + fn crlf_encode(&mut self, dst: &mut [u8]) -> SizeResult { + match data_copy(&self.src, &mut self.src_idx, dst)? { + TokenStatus::Partial(size) => { + self.stages.push_front(MultiStage::Crlf); + Ok(size) + } + TokenStatus::Complete(size) => { + self.init_src(); + Ok(size) + } + } + } + + // "--" + fn dash_encode(&mut self, dst: &mut [u8]) -> SizeResult { + match data_copy(&self.src, &mut self.src_idx, dst)? { + TokenStatus::Partial(size) => { + self.stages.push_front(MultiStage::Dash); + Ok(size) + } + TokenStatus::Complete(size) => { + self.init_src(); + Ok(size) + } + } + } + + fn boundary_encode(&mut self, dst: &mut [u8], boundary: Vec) -> SizeResult { + match data_copy(&boundary, &mut self.src_idx, dst)? { + TokenStatus::Partial(size) => { + self.stages.push_front(MultiStage::Boundary(boundary)); + Ok(size) + } + TokenStatus::Complete(size) => { + self.init_src(); + Ok(size) + } + } + } + + fn headers_encode(&mut self, dst: &mut [u8], mut headers_encoder: EncodeHeaders) -> SizeResult { + match headers_encoder.encode(dst)? { + TokenStatus::Partial(size) => { + self.stages.push_front(MultiStage::Headers(headers_encoder)); + Ok(size) + } + TokenStatus::Complete(size) => { + self.init_src(); + Ok(size) + } + } + } + + fn sync_mimepart_encode( + &mut self, + dst: &mut [u8], + mut part_encoder: MimePartEncoder<'a>, + ) -> SizeResult { + let size = sync_impl::Body::data(&mut part_encoder, dst)?; + if size != 0 { + self.stages.push_front(MultiStage::MimePart(part_encoder)); + } else { + self.init_src(); + } + Ok(size) + } +} + +impl sync_impl::Body for MimeMultiEncoder<'_> { + type Error = std::io::Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + let mut count = 0; + while count != buf.len() { + let stage = match self.stages.pop_front() { + Some(stage) => stage, + None => break, + }; + self.temp_src(&stage); + let size = match stage { + MultiStage::Crlf => self.crlf_encode(&mut buf[count..]), + MultiStage::Dash => self.dash_encode(&mut buf[count..]), + MultiStage::Boundary(boundary) => self.boundary_encode(&mut buf[count..], boundary), + MultiStage::Headers(headers_encoder) => { + self.headers_encode(&mut buf[count..], headers_encoder) + } + MultiStage::MimePart(part_encoder) => { + self.sync_mimepart_encode(&mut buf[count..], part_encoder) + } + }?; + count += size; + } + Ok(count) + } +} + +impl async_impl::Body for MimeMultiEncoder<'_> { + type Error = std::io::Error; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut count = 0; + while count != buf.len() { + let stage = match self.stages.pop_front() { + Some(stage) => stage, + None => break, + }; + self.temp_src(&stage); + let poll_size: Poll = match stage { + MultiStage::Crlf => Poll::Ready(self.crlf_encode(&mut buf[count..])), + MultiStage::Dash => Poll::Ready(self.dash_encode(&mut buf[count..])), + MultiStage::Boundary(boundary) => { + Poll::Ready(self.boundary_encode(&mut buf[count..], boundary)) + } + MultiStage::Headers(headers_encoder) => { + Poll::Ready(self.headers_encode(&mut buf[count..], headers_encoder)) + } + MultiStage::MimePart(mut part_encoder) => { + let poll_result = Pin::new(&mut part_encoder).poll_data(cx, &mut buf[count..]); + if let Poll::Ready(Ok(0)) = poll_result { + // complete async read body + self.init_src(); + } else if let Poll::Ready(Ok(_)) = poll_result { + self.stages.push_front(MultiStage::MimePart(part_encoder)); + } + poll_result + } + }; + + match poll_size { + Poll::Ready(Ok(size)) => count += size, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Pending => return Poll::Pending, + } + } + Poll::Ready(Ok(count)) + } +} + +#[derive(Debug)] +enum MultiStage<'a> { + Crlf, // \r\n + Dash, // -- + Boundary(Vec), // boundary + Headers(EncodeHeaders), // headers + MimePart(MimePartEncoder<'a>), // part +} + +#[cfg(test)] +mod ut_mime_multi_encoder { + use crate::body::{async_impl, sync_impl, MimeMulti, MimeMultiEncoder, MimePart}; + + // TODO: Uses a commonly macro like `part_encode_compare` to replace this one. + macro_rules! encode_and_compare { + ( $buf: expr, $encode: block, $v_size: expr, $v_str: expr) => { + let mut v_size = vec![]; + let mut v_str = vec![]; + + loop { + let len = $encode; + if len == 0 { + break; + } + v_size.push(len); + v_str.extend_from_slice(&$buf[..len]); + } + assert_eq!(v_size, $v_size); + assert_eq!(v_str, $v_str); + }; + } + + /// UT test cases for `syn::Body::data` of `MimeMultiEncoder`. + /// + /// # Brief + /// 1. Creates a `MimeMulti`. + /// 2. Builds a `MimeMultiEncoder` by `from_multi` and encodes. + /// 3. Checks whether the result is correct. + #[test] + fn ut_mime_multi_encoder_from_multi() { + let multi = MimeMulti::builder().build().unwrap(); + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 10]; + encode_and_compare!( + buf, + { sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap() }, + vec![10, 4], + b"---\r\n\r\n-----\r\n" + ); + } + + /// UT test cases for `syn::Body::data` of `MimeMultiEncoder`. + /// + /// # Brief + /// 1. Creates a `MimeMulti`. + /// 2. Sets headers. + /// 3. Builds a `MimeMultiEncoder` by `from_multi` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_multi_encoder_data_one_part() { + let part = MimePart::builder() + .header("key1", "value1") + .body_from_reader("9876543210\r\n".as_bytes()) + .build() + .unwrap(); + let multi = MimeMulti::builder().add_part(part).build().unwrap(); + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 10]; + encode_and_compare!( + buf, + { sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap() }, + vec![10, 10, 10, 10, 1], + b"---\r\nkey1:value1\r\n\r\n9876543210\r\n\r\n-----\r\n" + ); + } + + /// UT test cases for `syn::Body::data` of `MimeMultiEncoder`. + /// + /// # Brief + /// 1. Creates a `MimeMulti`. + /// 2. Creates several `MimePart`, sets headers, sets body, builds. + /// 3. Adds `MimePart` to `MimeMulti` by `add_part`. + /// 4. Builds a `MimeMultiEncoder` by `from_multi` and encodes synchronously. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_encoder_data_many_parts() { + let multi = MimeMulti::builder() + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_reader("98765432\r\n".as_bytes()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_reader("22222".as_bytes()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 10]; + encode_and_compare!( + buf, + { sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap() }, + vec![10, 10, 10, 10, 10, 10, 6], + b"---\r\nkey1:value1\r\n\r\n98765432\r\n\r\n---\r\nkey2:value2\r\n\r\n22222\r\n-----\r\n" + ); + } + + /// UT test cases for `syn::Body::data` of `MimeMultiEncoder`. + /// + /// # Brief + /// 1. Creates a `MimeMulti`. + /// 2. Creates several `MimePart`, sets headers, sets body, builds. + /// 3. Creates a main `MimeMulti`, adds parts. + /// 4. Builds a `MimeMultiEncoder` by `from_multi` and encodes synchronously. + /// 5. Checks whether the result is correct. + #[test] + fn ut_mime_multi_encoder_data_many_parts_nesting() { + let multi1 = MimeMulti::builder() + .set_content_type(b"multipart/mixed", b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_reader("111111".as_bytes()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_reader("22222".as_bytes()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let multi = MimeMulti::builder() + .set_boundary(b"abcde".to_vec()) + .add_part( + MimePart::builder() + .header("key3", "value3") + .body_from_reader("33333".as_bytes()) + .build() + .unwrap(), + ) + .add_multi(multi1) + .add_part( + MimePart::builder() + .header("key4", "value4") + .body_from_async_reader("444444".as_bytes()) // nothing in sync + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 30]; + encode_and_compare!( + buf, + { sync_impl::Body::data(&mut multi_encoder, &mut buf).unwrap() }, + vec![30, 30, 30, 30, 30, 30, 13], + b"--abcde\r\nkey3:value3\r\n\r\n33333\r\n--abcde\r\n\ + content-type:multipart/mixed; boundary=abc\r\n\r\n--abc\r\n\ + key1:value1\r\n\r\n111111\r\n--abc\r\nkey2:value2\r\n\r\n22222\r\n\ + --abc--\r\n\r\n--abcde\r\nkey4:value4\r\n\r\n\r\n--abcde--\r\n" + ); + } + + /// UT test cases for `asyn::Body::data` of `MimeMultiEncoder`. + /// + /// # Brief + /// 1. Creates a `MimeMulti`. + /// 2. Creates several `MimePart`, sets headers, sets body, builds. + /// 3. Creates a main `MimeMulti`, adds parts. + /// 4. Builds a `MimeMultiEncoder` by `from_multi` and encodes asynchronously. + /// 5. Checks whether the result is correct. + #[tokio::test] + async fn ut_mime_multi_encoder_data_many_parts_nesting_then_async_data() { + let multi1 = MimeMulti::builder() + .set_content_type(b"multipart/mixed", b"abc".to_vec()) + .add_part( + MimePart::builder() + .header("key1", "value1") + .body_from_async_reader("111111".as_bytes()) + .build() + .unwrap(), + ) + .add_part( + MimePart::builder() + .header("key2", "value2") + .body_from_async_reader("22222".as_bytes()) + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let multi = MimeMulti::builder() + .set_boundary(b"abcde".to_vec()) + .add_part( + MimePart::builder() + .header("key3", "value3") + .body_from_bytes("33333".as_bytes()) + .build() + .unwrap(), + ) + .add_multi(multi1) + .add_part( + MimePart::builder() + .header("key4", "value4") + .body_from_reader("444444".as_bytes()) // nothing in async + .build() + .unwrap(), + ) + .build() + .unwrap(); + + let mut multi_encoder = MimeMultiEncoder::from_multi(multi); + let mut buf = vec![0u8; 30]; + encode_and_compare!( + buf, + { + async_impl::Body::data(&mut multi_encoder, &mut buf) + .await + .unwrap() + }, + vec![30, 30, 30, 30, 30, 30, 13], + b"--abcde\r\nkey3:value3\r\n\r\n33333\r\n--abcde\r\n\ + content-type:multipart/mixed; boundary=abc\r\n\r\n--abc\r\n\ + key1:value1\r\n\r\n111111\r\n--abc\r\nkey2:value2\r\n\r\n22222\r\n\ + --abc--\r\n\r\n--abcde\r\nkey4:value4\r\n\r\n\r\n--abcde--\r\n" + ); + } +} diff --git a/ylong_http/src/body/mime/encode/part.rs b/ylong_http/src/body/mime/encode/part.rs new file mode 100644 index 0000000..936bfde --- /dev/null +++ b/ylong_http/src/body/mime/encode/part.rs @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + body::{ + async_impl, + mime::{ + common::{data_copy, SizeResult, TokenStatus}, + EncodeHeaders, MixFrom, PartStatus, + }, + sync_impl, MimePart, + }, + error::{ErrorKind, HttpError}, +}; +use core::{ + convert::TryFrom, + pin::Pin, + task::{Context, Poll}, +}; +use std::io::Read; +use tokio::io::{AsyncRead, ReadBuf}; + +/// `MimePartEncoder` can get a [`MimePart`] to encode into data that can be transmitted. +/// +/// [`MimePart`]: MimePart +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::{sync_impl, MimePart, MimePartEncoder}; +/// +/// let part = MimePart::builder() +/// .header("accept", "text/html") +/// .body_from_reader("01234567890123456789".as_bytes()) +/// .body_from_bytes(b"9876543210\r\n") +/// .build() +/// .unwrap(); +/// let mut part_encoder = MimePartEncoder::from_part(part); +/// let mut buf = vec![0u8; 10]; +/// let mut v_size = vec![]; +/// let mut v_str = vec![]; +/// +/// loop { +/// let len = sync_impl::Body::data(&mut part_encoder, &mut buf).unwrap(); +/// if len == 0 { +/// break; +/// } +/// v_size.push(len); +/// v_str.extend_from_slice(&buf[..len]); +/// } +/// assert_eq!(v_size, vec![10, 10, 10, 2]); +/// assert_eq!(v_str, b"accept:text/html\r\n\r\n9876543210\r\n"); +/// ``` +#[derive(Debug)] +pub struct MimePartEncoder<'a> { + stage: PartStatus, // Encode stage now + headers_encode: EncodeHeaders, + body: Option>, + src: Vec, // src which is need to encode + src_idx: usize, // index of src +} + +impl<'a> MimePartEncoder<'a> { + /// Creates a `MimePartEncoder` by a [`MimePart`]. + /// + /// [`MimePart`]: MimePart + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::{MimePart, MimePartEncoder}; + /// + /// let part = MimePart::builder() + /// .build() + /// .unwrap(); + /// let part_encoder = MimePartEncoder::from_part(part); + /// ``` + pub fn from_part(part: MimePart<'a>) -> Self { + let body = (!part.body.is_empty()).then_some(part.body); + + MimePartEncoder { + stage: PartStatus::Start, + headers_encode: EncodeHeaders::new(part.headers), + src: vec![], + src_idx: 0, + body, + } + } + + fn check_next(&mut self) { + match self.stage { + PartStatus::Start => { + self.stage = PartStatus::Headers; + // init + self.src_idx = 0; + self.src = vec![]; + } + PartStatus::Headers => { + self.stage = PartStatus::Crlf; + if self.body.is_some() { + self.src = b"\r\n".to_vec(); // has body, so has Crlf + } else { + self.src = vec![]; + } + self.src_idx = 0; + } + PartStatus::Crlf => { + self.stage = PartStatus::Body; + self.src_idx = 0; // Just record index + } + PartStatus::Body => { + self.stage = PartStatus::End; + self.src_idx = 0; + } + PartStatus::End => { + // init + self.src_idx = 0; + self.src = vec![]; + } + } + } + + fn start_encode(&mut self) -> SizeResult { + self.check_next(); + Ok(0) + } + + // use EncodeHeaders.encode + fn headers_encode(&mut self, dst: &mut [u8]) -> SizeResult { + match self.headers_encode.encode(dst)? { + TokenStatus::Partial(size) => Ok(size), + TokenStatus::Complete(size) => { + self.check_next(); + Ok(size) + } + } + } + + fn crlf_encode(&mut self, dst: &mut [u8]) -> SizeResult { + match data_copy(&self.src, &mut self.src_idx, dst)? { + TokenStatus::Partial(size) => Ok(size), + TokenStatus::Complete(size) => { + self.check_next(); + Ok(size) + } + } + } + + // Uses `syn::Body::data` of `MixFrom`. + fn body_sync_encode(&mut self, dst: &mut [u8]) -> SizeResult { + if let Some(body) = &mut self.body { + let size = sync_impl::Body::data(body, dst)?; + if size == 0 { + self.check_next(); + } + return Ok(size); + } + self.check_next(); + Ok(0) + } +} + +impl sync_impl::Body for MimePartEncoder<'_> { + type Error = std::io::Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + let mut count = 0; + while count != buf.len() { + let encode_size = match self.stage { + PartStatus::Start => self.start_encode(), + PartStatus::Headers => self.headers_encode(&mut buf[count..]), + PartStatus::Crlf => self.crlf_encode(&mut buf[count..]), + PartStatus::Body => self.body_sync_encode(&mut buf[count..]), + PartStatus::End => return Ok(count), + }; + count += encode_size?; + } + Ok(count) + } +} + +impl async_impl::Body for MimePartEncoder<'_> { + type Error = std::io::Error; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut count = 0; + while count != buf.len() { + let encode_size: Poll = match self.stage { + PartStatus::Start => Poll::Ready(self.start_encode()), + PartStatus::Headers => Poll::Ready(self.headers_encode(&mut buf[count..])), + PartStatus::Crlf => Poll::Ready(self.crlf_encode(&mut buf[count..])), + PartStatus::Body => match &mut self.body { + Some(body) => { + let poll_result = Pin::new(body).poll_data(cx, &mut buf[count..]); + if let Poll::Ready(Ok(0)) = poll_result { + // complete async read body + self.check_next(); + }; + poll_result + } + _ => { + self.check_next(); + Poll::Ready(Ok(0)) + } + }, + PartStatus::End => return Poll::Ready(Ok(count)), + }; + + match encode_size { + Poll::Ready(Ok(size)) => count += size, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Pending => return Poll::Pending, + } + } + Poll::Ready(Ok(count)) + } +} + +#[cfg(test)] +mod ut_mime_part_encoder { + use crate::{ + body::{async_impl, sync_impl, MimePart, MimePartEncoder}, + headers::Headers, + }; + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets body by `body_from_owned`. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_body_from_owned() { + part_encode_compare! ( + MimePart: { + BodyOwned: b"123456".to_vec(), + }, + ResultData: b"\r\n123456", + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets body by `body_from_bytes`. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_body_from_bytes() { + part_encode_compare! ( + MimePart: { + BodySlice: "123456".as_bytes(), + }, + BufSize: 5, + ResultData: b"\r\n123456", + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets body by `body_from_reader`. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_body_from_reader() { + part_encode_compare! ( + MimePart: { + BodyReader: "123456".as_bytes(), + }, + BufSize: 5, + ResultData: b"\r\n123456", + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets headers and sets body. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_data_common() { + part_encode_compare! ( + MimePart: { + Header: "accept", "text/html", + BodyReader: "9876543210\r\n".as_bytes(), + }, + BufSize: 10, + ResultSize: vec![10, 10, 10, 2], + ResultData: b"accept:text/html\r\n\r\n9876543210\r\n", + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Doesn't set headers and only sets body. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_data_noheaders() { + part_encode_compare! ( + MimePart: { + BodySlice: b"0123456789--0123", + }, + BufSize: 10, + ResultSize: vec![10, 8], + ResultData: b"\r\n0123456789--0123", + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Doesn't set body and only sets headers. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_data_nobody() { + // no body no CRLF + part_encode_compare! ( + MimePart: { + Header: "key", "\"value\"", + }, + ResultData: b"key:\"value\"\r\n", + Sync, + ); + + part_encode_compare! ( + MimePart: { + Header: "key1", "value1", + Header: "key2", "value2", + }, + BufSize: 10, + ResultSize: vec![10, 10, 6], + Sync, + ); + } + + /// UT test cases for `syn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Doesn't set headers and doesn't set headers. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes. + /// 4. Checks whether the result is correct. + #[test] + fn ut_mime_part_encoder_data_noheaders_nobody() { + part_encode_compare! ( + MimePart: { + }, + ResultData: b"", + Sync, + ); + } + + /// UT test cases for `asyn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets body by `body_from_owned`. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes asynchronously. + /// 4. Checks whether the result is correct. + #[tokio::test] + async fn ut_mime_part_encoder_body_from_owned_then_async_data() { + part_encode_compare! ( + MimePart: { + BodyOwned: b"123456".to_vec(), + }, + BufSize: 5, + ResultSize: vec![5, 3], + ResultData: b"\r\n123456", + Async, + ); + } + + /// UT test cases for `asyn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets body by `body_from_bytes`. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes asynchronously. + /// 4. Checks whether the result is correct. + #[tokio::test] + async fn ut_mime_part_encoder_body_from_bytes_then_async_data() { + part_encode_compare! ( + MimePart: { + BodySlice: b"123456", + }, + BufSize: 5, + ResultSize: vec![5, 3], + ResultData: b"\r\n123456", + Async, + ); + } + + /// UT test cases for `asyn::Body::data` of `MimePartEncoder`. + /// + /// # Brief + /// 1. Creates a `MimePart`. + /// 2. Sets headers and sets body. + /// 3. Builds a `MimePartEncoder` by `from_part` and encodes asynchronously. + /// 4. Checks whether the result is correct. + #[tokio::test] + async fn ut_mime_part_encoder_common_then_async_data() { + part_encode_compare! ( + MimePart: { + Header: "accept", "text/html", + BodySlice: b"9876543210\r\n", + }, + BufSize: 10, + ResultSize: vec![10, 10, 10, 2], + ResultData: b"accept:text/html\r\n\r\n9876543210\r\n", + Async, + ); + } +} diff --git a/ylong_http/src/body/mime/mime_test_macro.rs b/ylong_http/src/body/mime/mime_test_macro.rs new file mode 100644 index 0000000..72afc93 --- /dev/null +++ b/ylong_http/src/body/mime/mime_test_macro.rs @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// Builds a `MimePart`. +macro_rules! part_build { + ( + MimePart: { + $(Headers: $headers: expr,)? + $(Header: $name: expr, $value: expr,)* + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + ) => { + MimePart::builder() + $(.set_headers($headers))? + $(.header($name, $value))* + $(.body_from_owned($body1))? + $(.body_from_bytes($body2))? + $(.body_from_reader($body3))? + $(.body_from_async_reader($body4))? + .build() + .expect("MimePart build failed") + } +} + +/// Builds a `MimePart`, encodes it, Compares with result. +macro_rules! part_encode_compare { + ( + MimePart: { + $(Headers: $headers: expr,)? + $(Header: $name: expr, $value: expr,)* + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + $(BufSize: $size: expr,)? + $(ResultSize: $v_size: expr,)? + $(ResultData: $v_data: expr,)? + Sync, + ) => { + let part = part_build!( + MimePart: { + $(Headers: $headers: expr,)? + $(Header: $name, $value,)* + $(BodyOwned: $body1,)? + $(BodySlice: $body2,)? + $(BodyReader: $body3,)? + $(BodyAsyncReader: $body4,)? + }, + ); + + #[allow(unused_assignments, unused_mut)] + let mut len = 1; // default 1 + + $(len = $size;)? + let mut buf = vec![0u8; len]; + let mut v_data = vec![]; + let mut v_size = vec![]; + let mut part_encoder = MimePartEncoder::from_part(part); + + loop { + let size = sync_impl::Body::data(&mut part_encoder, &mut buf).expect("MimePart encode failed"); + if size == 0 { + break; + } + v_size.push(size); + v_data.extend_from_slice(&buf[..size]); + } + $(assert_eq!(v_size, $v_size);)? + $(assert_eq!(v_data, $v_data);)? + }; + + ( + MimePart: { + $(Headers: $headers: expr,)? + $(Header: $name: expr, $value: expr,)* + $(BodyOwned: $body1: expr,)? + $(BodySlice: $body2: expr,)? + $(BodyReader: $body3: expr,)? + $(BodyAsyncReader: $body4: expr,)? + }, + $(BufSize: $size: expr,)? + $(ResultSize: $v_size: expr,)? + $(ResultData: $v_data: expr,)? + Async, + ) => { + let part = part_build!( + MimePart: { + $(Headers: $headers: expr,)? + $(Header: $name, $value,)* + $(BodyOwned: $body1,)? + $(BodySlice: $body2,)? + $(BodyReader: $body3,)? + $(BodyAsyncReader: $body4,)? + }, + ); + + #[allow(unused_assignments, unused_mut)] + let mut len = 1; // default 1 + + $(len = $size;)? + let mut buf = vec![0u8; len]; + let mut v_data = vec![]; + let mut v_size = vec![]; + let mut part_encoder = MimePartEncoder::from_part(part); + + loop { + // async + let size = async_impl::Body::data(&mut part_encoder, &mut buf).await.expect("MimePart encode failed"); + if size == 0 { + break; + } + v_size.push(size); + v_data.extend_from_slice(&buf[..size]); + } + $(assert_eq!(v_size, $v_size);)? + $(assert_eq!(v_data, $v_data);)? + }; +} diff --git a/ylong_http/src/body/mime/mimetype.rs b/ylong_http/src/body/mime/mimetype.rs new file mode 100644 index 0000000..7f947ad --- /dev/null +++ b/ylong_http/src/body/mime/mimetype.rs @@ -0,0 +1,993 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{ErrorKind, HttpError}; +use core::str; +use std::path::Path; + +/// A type that defines the general structure of the `MIME` media typing system. +/// +/// A `MIME` type most-commonly consists of just two parts: +/// +/// - `Type` +/// - `Subtype` +/// +/// `Type` and `SubType` are separated by a slash (/) — with no whitespace between: +/// +/// ```type/subtype``` +/// +/// It is case-insensitive but are traditionally written in lowercase, such as: +/// +/// ```application/octet-stream```. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::MimeType; +/// +/// let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap(); +/// assert!(mime_type.is_application()); +/// ``` +#[derive(Debug, Eq, PartialEq)] +pub struct MimeType<'a> { + tag: MimeTypeTag, + bytes: &'a [u8], + slash: usize, // Index of '/'. +} + +impl<'a> MimeType<'a> { + /// Creates a `MimeType` from a bytes slice. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap(); + /// assert_eq!(mime_type.main_type(), "application"); + /// assert_eq!(mime_type.sub_type(), "octet-stream"); + /// ``` + pub fn from_bytes(bytes: &'a [u8]) -> Result { + // From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2): + // and SHOULD be limited to 64 characters. + // Both top-level type and subtype names are case-insensitive. + + let (slash, _) = bytes + .iter() + .enumerate() + .find(|(_, &b)| b == b'/') + .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput))?; + + let tag = MimeTypeTag::from_bytes(&bytes[..slash])?; + + let sub_type = &bytes[slash + 1..]; + if sub_type.len() > 64 || !is_valid(sub_type) { + return Err(ErrorKind::InvalidInput.into()); + } + + Ok(MimeType { tag, bytes, slash }) + } + + /// Creates a new `MimeType` from a file path. The extension of the file + /// will be used to create it. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use ylong_http::body::MimeType; + /// + /// let path = Path::new("./foo/bar.pdf"); + /// let mime_type = MimeType::from_path(path).unwrap(); + /// assert_eq!(mime_type.main_type(), "application"); + /// assert_eq!(mime_type.sub_type(), "pdf"); + /// ``` + pub fn from_path(path: &'a Path) -> Result { + let str = path + .extension() + .and_then(|ext| ext.to_str()) + .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput))?; + Self::from_extension(str) + } + + /// Returns a `&str` which represents the `MimeType`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap(); + /// assert_eq!(mime_type.as_str(), "application/pdf"); + /// ``` + pub fn as_str(&self) -> &str { + // Safety: The input byte slice is checked, so it can be directly + // converted to `&str` here. + unsafe { str::from_utf8_unchecked(self.bytes) } + } + + /// Returns main type string, such as `text` of `text/plain`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap(); + /// assert_eq!(mime_type.main_type(), "application"); + /// ``` + pub fn main_type(&self) -> &str { + // Safety: The input byte slice is checked, so it can be directly + // converted to `&str` here. + unsafe { str::from_utf8_unchecked(&self.bytes[..self.slash]) } + } + + /// Returns sub type string, such as `plain` of `text/plain`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap(); + /// assert_eq!(mime_type.sub_type(), "pdf"); + /// ``` + pub fn sub_type(&self) -> &str { + // Safety: The input byte slice is checked, so it can be directly + // converted to `&str` here. + unsafe { str::from_utf8_unchecked(&self.bytes[self.slash + 1..]) } + } + + /// Checks whether the main type is `application`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"application/pdf").unwrap(); + /// assert!(mime_type.is_application()); + /// ``` + pub fn is_application(&self) -> bool { + matches!(self.tag, MimeTypeTag::Application) + } + + /// Checks whether the main type is `audio`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"audio/basic").unwrap(); + /// assert!(mime_type.is_audio()); + /// ``` + pub fn is_audio(&self) -> bool { + matches!(self.tag, MimeTypeTag::Audio) + } + + /// Checks whether the main type is `font`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"font/collection").unwrap(); + /// assert!(mime_type.is_font()); + /// ``` + pub fn is_font(&self) -> bool { + matches!(self.tag, MimeTypeTag::Font) + } + + /// Checks whether the main type is `image`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"image/gif").unwrap(); + /// assert!(mime_type.is_image()); + /// ``` + pub fn is_image(&self) -> bool { + matches!(self.tag, MimeTypeTag::Image) + } + + /// Checks whether the main type is `message`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"message/rfc822").unwrap(); + /// assert!(mime_type.is_message()); + /// ``` + pub fn is_message(&self) -> bool { + matches!(self.tag, MimeTypeTag::Message) + } + + /// Checks whether the main type is `model`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"model/e57").unwrap(); + /// assert!(mime_type.is_model()); + /// ``` + pub fn is_model(&self) -> bool { + matches!(self.tag, MimeTypeTag::Model) + } + + /// Checks whether the main type is `multipart`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"multipart/form-data").unwrap(); + /// assert!(mime_type.is_multipart()); + /// ``` + pub fn is_multipart(&self) -> bool { + matches!(self.tag, MimeTypeTag::Multipart) + } + + /// Checks whether the main type is `text`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"text/richtext").unwrap(); + /// assert!(mime_type.is_text()); + /// ``` + pub fn is_text(&self) -> bool { + matches!(self.tag, MimeTypeTag::Text) + } + + /// Checks whether the main type is `video`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"video/mpeg").unwrap(); + /// assert!(mime_type.is_video()); + /// ``` + pub fn is_video(&self) -> bool { + matches!(self.tag, MimeTypeTag::Video) + } + + /// Checks whether the main type is non-standard type `x-`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_bytes(b"x-world/x-vrml").unwrap(); + /// assert!(mime_type.is_xnew()); + /// ``` + pub fn is_xnew(&self) -> bool { + matches!(self.tag, MimeTypeTag::Xnew) + } +} + +impl<'a> Default for MimeType<'a> { + fn default() -> Self { + Self { + tag: MimeTypeTag::Application, + bytes: b"application/octet-stream", + slash: 11, + } + } +} + +macro_rules! mime { + ($($ext: expr, $str: expr, $slash: expr, $tag: expr$(;)?)*) => { + impl MimeType<'_> { + /// Creates a new `MimeType` from file extension. + /// + /// Returns `application/octet-stream` if extension is not discerned. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::MimeType; + /// + /// let mime_type = MimeType::from_extension("pdf").unwrap(); + /// assert_eq!(mime_type.main_type(), "application"); + /// assert_eq!(mime_type.sub_type(), "pdf"); + /// ``` + pub fn from_extension(s: &str) -> Result { + Ok(match s { + $( + $ext => MimeType { + tag: $tag, + bytes: $str.as_bytes(), + slash: $slash, + }, + )* + _=> MimeType { + tag: MimeTypeTag::Application, + bytes: b"application/octet-stream", + slash: 11, + } + }) + } + } + + /// UT test cases for `ut_mime_type_from_extension`. + /// + /// # Brief + /// 1. Creates a `MimeType` from file extension. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_from_extension() { + $( + let mime_type = MimeType::from_extension($ext).unwrap(); + assert_eq!(mime_type.tag, $tag); + assert_eq!(mime_type.bytes, $str.as_bytes()); + assert_eq!(mime_type.slash, $slash); + )* + } + }; +} + +mime!( + "evy", "application/envoy", 11, MimeTypeTag::Application; + "fif", "application/fractals", 11, MimeTypeTag::Application; + "spl", "application/futuresplash", 11, MimeTypeTag::Application; + "hta", "application/hta", 11, MimeTypeTag::Application; + "acx", "application/internet-property-stream", 11, MimeTypeTag::Application; + "hqx", "application/mac-binhex40", 11, MimeTypeTag::Application; + "doc", "application/msword", 11, MimeTypeTag::Application; + "dot", "application/msword", 11, MimeTypeTag::Application; + "*", "application/octet-stream", 11, MimeTypeTag::Application; + "bin", "application/octet-stream", 11, MimeTypeTag::Application; + "class", "application/octet-stream", 11, MimeTypeTag::Application; + "dms", "application/octet-stream", 11, MimeTypeTag::Application; + "exe", "application/octet-stream", 11, MimeTypeTag::Application; + "lha", "application/octet-stream", 11, MimeTypeTag::Application; + "lzh", "application/octet-stream", 11, MimeTypeTag::Application; + "oda", "application/oda", 11, MimeTypeTag::Application; + "axs", "application/olescript", 11, MimeTypeTag::Application; + "pdf", "application/pdf", 11, MimeTypeTag::Application; + "prf", "application/pics-rules", 11, MimeTypeTag::Application; + "p10", "application/pkcs10", 11, MimeTypeTag::Application; + "crl", "application/pkix-crl", 11, MimeTypeTag::Application; + "ai", "application/postscript", 11, MimeTypeTag::Application; + "eps", "application/postscript", 11, MimeTypeTag::Application; + "ps", "application/postscript", 11, MimeTypeTag::Application; + "rtf", "application/rtf", 11, MimeTypeTag::Application; + "setpay", "application/set-payment-initiation", 11, MimeTypeTag::Application; + "setreg", "application/set-registration-initiation", 11, MimeTypeTag::Application; + "xla", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "xlc", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "xlm", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "xls", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "xlt", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "xlw", "application/vnd.ms-excel", 11, MimeTypeTag::Application; + "msg", "application/vnd.ms-outlook", 11, MimeTypeTag::Application; + "sst", "application/vnd.ms-pkicertstore", 11, MimeTypeTag::Application; + "cat", "application/vnd.ms-pkiseccat", 11, MimeTypeTag::Application; + "stl", "application/vnd.ms-pkistl", 11, MimeTypeTag::Application; + "pot", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application; + "pps", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application; + "ppt", "application/vnd.ms-powerpoint", 11, MimeTypeTag::Application; + "mpp", "application/vnd.ms-project", 11, MimeTypeTag::Application; + "wcm", "application/vnd.ms-works", 11, MimeTypeTag::Application; + "wdb", "application/vnd.ms-works", 11, MimeTypeTag::Application; + "wks", "application/vnd.ms-works", 11, MimeTypeTag::Application; + "wps", "application/vnd.ms-works", 11, MimeTypeTag::Application; + "hlp", "application/winhlp", 11, MimeTypeTag::Application; + "bcpio", "application/x-bcpio", 11, MimeTypeTag::Application; + "cdf", "application/x-cdf", 11, MimeTypeTag::Application; // "cdf" also can be "application/x-netcdf" + "z", "application/x-compress", 11, MimeTypeTag::Application; + "tgz", "application/x-compressed", 11, MimeTypeTag::Application; + "cpio", "application/x-cpio", 11, MimeTypeTag::Application; + "csh", "application/x-csh", 11, MimeTypeTag::Application; + "dcr", "application/x-director", 11, MimeTypeTag::Application; + "dir", "application/x-director", 11, MimeTypeTag::Application; + "dxr", "application/x-director", 11, MimeTypeTag::Application; + "dvi", "application/x-dvi", 11, MimeTypeTag::Application; + "gtar", "application/x-gtar", 11, MimeTypeTag::Application; + "gz", "application/x-gzip", 11, MimeTypeTag::Application; + "hdf", "application/x-hdf", 11, MimeTypeTag::Application; + "ins", "application/x-internet-signup", 11, MimeTypeTag::Application; + "isp", "application/x-internet-signup", 11, MimeTypeTag::Application; + "iii", "application/x-iphone", 11, MimeTypeTag::Application; + "js", "application/x-javascript", 11, MimeTypeTag::Application; + "latex", "application/x-latex", 11, MimeTypeTag::Application; + "mdb", "application/x-msaccess", 11, MimeTypeTag::Application; + "crd", "application/x-mscardfile", 11, MimeTypeTag::Application; + "clp", "application/x-msclip", 11, MimeTypeTag::Application; + "dll", "application/x-msdownload", 11, MimeTypeTag::Application; + "m13", "application/x-msmediaview", 11, MimeTypeTag::Application; + "m14", "application/x-msmediaview", 11, MimeTypeTag::Application; + "mvb", "application/x-msmediaview", 11, MimeTypeTag::Application; + "wmf", "application/x-msmetafile", 11, MimeTypeTag::Application; + "mny", "application/x-msmoney", 11, MimeTypeTag::Application; + "pub", "application/x-mspublisher", 11, MimeTypeTag::Application; + "scd", "application/x-msschedule", 11, MimeTypeTag::Application; + "trm", "application/x-msterminal", 11, MimeTypeTag::Application; + "wri", "application/x-mswrite", 11, MimeTypeTag::Application; + "nc", "application/x-netcdf", 11, MimeTypeTag::Application; + "pma", "application/x-perfmon", 11, MimeTypeTag::Application; + "pmc", "application/x-perfmon", 11, MimeTypeTag::Application; + "pml", "application/x-perfmon", 11, MimeTypeTag::Application; + "pmr", "application/x-perfmon", 11, MimeTypeTag::Application; + "pmw", "application/x-perfmon", 11, MimeTypeTag::Application; + "p12", "application/x-pkcs12", 11, MimeTypeTag::Application; + "pfx", "application/x-pkcs12", 11, MimeTypeTag::Application; + "p7b", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application; + "spc", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application; + "p7r", "application/x-pkcs7-certificates", 11, MimeTypeTag::Application; + "p7c", "application/x-pkcs7-mime", 11, MimeTypeTag::Application; + "p7m", "application/x-pkcs7-mime", 11, MimeTypeTag::Application; + "p7s", "application/x-pkcs7-signature", 11, MimeTypeTag::Application; + "sh", "application/x-sh", 11, MimeTypeTag::Application; + "shar", "application/x-shar", 11, MimeTypeTag::Application; + "swf", "application/x-shockwave-flash", 11, MimeTypeTag::Application; + "sit", "application/x-stuffit", 11, MimeTypeTag::Application; + "sv4cpio", "application/x-sv4cpio", 11, MimeTypeTag::Application; + "sv4crc", "application/x-sv4crc", 11, MimeTypeTag::Application; + "tar", "application/x-tar", 11, MimeTypeTag::Application; + "tcl", "application/x-tcl", 11, MimeTypeTag::Application; + "tex", "application/x-tex", 11, MimeTypeTag::Application; + "texi", "application/x-texinfo", 11, MimeTypeTag::Application; + "texinfo", "application/x-texinfo", 11, MimeTypeTag::Application; + "roff", "application/x-troff", 11, MimeTypeTag::Application; + "t", "application/x-troff", 11, MimeTypeTag::Application; + "tr", "application/x-troff", 11, MimeTypeTag::Application; + "man", "application/x-troff-man", 11, MimeTypeTag::Application; + "me", "application/x-troff-me", 11, MimeTypeTag::Application; + "ms", "application/x-troff-ms", 11, MimeTypeTag::Application; + "ustar", "application/x-ustar", 11, MimeTypeTag::Application; + "src", "application/x-wais-source", 11, MimeTypeTag::Application; + "cer", "application/x-x509-ca-cert", 11, MimeTypeTag::Application; + "crt", "application/x-x509-ca-cert", 11, MimeTypeTag::Application; + "der", "application/x-x509-ca-cert", 11, MimeTypeTag::Application; + "pko", "application/ynd.ms-pkipko", 11, MimeTypeTag::Application; + "zip", "application/zip", 11, MimeTypeTag::Application; + "au", "audio/basic", 5, MimeTypeTag::Audio; + "snd", "audio/basic", 5, MimeTypeTag::Audio; + "mid", "audio/mid", 5, MimeTypeTag::Audio; + "rmi", "audio/mid", 5, MimeTypeTag::Audio; + "mp3", "audio/mpeg", 5, MimeTypeTag::Audio; + "aif", "audio/x-aiff", 5, MimeTypeTag::Audio; + "aifc", "audio/x-aiff", 5, MimeTypeTag::Audio; + "aiff", "audio/x-aiff", 5, MimeTypeTag::Audio; + "m3u", "audio/x-mpegurl", 5, MimeTypeTag::Audio; + "ra", "audio/x-pn-realaudio", 5, MimeTypeTag::Audio; + "ram", "audio/x-pn-realaudio", 5, MimeTypeTag::Audio; + "wav", "audio/x-wav", 5, MimeTypeTag::Audio; + "bmp", "image/bmp", 5, MimeTypeTag::Image; + "cod", "image/cis-cod", 5, MimeTypeTag::Image; + "gif", "image/gif", 5, MimeTypeTag::Image; + "ief", "image/ief", 5, MimeTypeTag::Image; + "jpe", "image/jpeg", 5, MimeTypeTag::Image; + "jpeg", "image/jpeg", 5, MimeTypeTag::Image; + "jpg", "image/jpeg", 5, MimeTypeTag::Image; + "jfif", "image/pipeg", 5, MimeTypeTag::Image; + "svg", "image/svg+xml", 5, MimeTypeTag::Image; + "tif", "image/tiff", 5, MimeTypeTag::Image; + "tiff", "image/tiff", 5, MimeTypeTag::Image; + "ras", "image/x-cmu-raster", 5, MimeTypeTag::Image; + "cmx", "image/x-cmx", 5, MimeTypeTag::Image; + "ico", "image/x-icon", 5, MimeTypeTag::Image; + "pnm", "image/x-portable-anymap", 5, MimeTypeTag::Image; + "pbm", "image/x-portable-bitmap", 5, MimeTypeTag::Image; + "pgm", "image/x-portable-graymap", 5, MimeTypeTag::Image; + "ppm", "image/x-portable-pixmap", 5, MimeTypeTag::Image; + "rgb", "image/x-rgb", 5, MimeTypeTag::Image; + "xbm", "image/x-xbitmap", 5, MimeTypeTag::Image; + "xpm", "image/x-xpixmap", 5, MimeTypeTag::Image; + "xwd", "image/x-xwindowdump", 5, MimeTypeTag::Image; + "mht", "message/rfc822", 7, MimeTypeTag::Message; + "mhtml", "message/rfc822", 7, MimeTypeTag::Message; + "nws", "message/rfc822", 7, MimeTypeTag::Message; + "css", "text/css", 4, MimeTypeTag::Text; + "323", "text/h323", 4, MimeTypeTag::Text; + "htm", "text/html", 4, MimeTypeTag::Text; + "html", "text/html", 4, MimeTypeTag::Text; + "stm", "text/html", 4, MimeTypeTag::Text; + "uls", "text/iuls", 4, MimeTypeTag::Text; + "bas", "text/plain", 4, MimeTypeTag::Text; + "c", "text/plain", 4, MimeTypeTag::Text; + "h", "text/plain", 4, MimeTypeTag::Text; + "txt", "text/plain", 4, MimeTypeTag::Text; + "rtx", "text/richtext", 4, MimeTypeTag::Text; + "sct", "text/scriptlet", 4, MimeTypeTag::Text; + "tsv", "text/tab-separated-values", 4, MimeTypeTag::Text; + "htt", "text/webviewhtml", 4, MimeTypeTag::Text; + "htc", "text/x-component", 4, MimeTypeTag::Text; + "etx", "text/x-setext", 4, MimeTypeTag::Text; + "vcf", "text/x-vcard", 4, MimeTypeTag::Text; + "mp2", "video/mpeg", 5, MimeTypeTag::Video; + "mpa", "video/mpeg", 5, MimeTypeTag::Video; + "mpe", "video/mpeg", 5, MimeTypeTag::Video; + "mpeg", "video/mpeg", 5, MimeTypeTag::Video; + "mpg", "video/mpeg", 5, MimeTypeTag::Video; + "mpv2", "video/mpeg", 5, MimeTypeTag::Video; + "mov", "video/quicktime", 5, MimeTypeTag::Video; + "qt", "video/quicktime", 5, MimeTypeTag::Video; + "lsf", "video/x-la-asf", 5, MimeTypeTag::Video; + "lsx", "video/x-la-asf", 5, MimeTypeTag::Video; + "asf", "video/x-ms-asf", 5, MimeTypeTag::Video; + "asr", "video/x-ms-asf", 5, MimeTypeTag::Video; + "asx", "video/x-ms-asf", 5, MimeTypeTag::Video; + "avi", "video/x-msvideo", 5, MimeTypeTag::Video; + "movie", "video/x-sgi-movie", 5, MimeTypeTag::Video; + "flr", "x-world/x-vrml", 7, MimeTypeTag::Xnew; + "vrml", "x-world/x-vrml", 7, MimeTypeTag::Xnew; + "wrl", "x-world/x-vrml", 7, MimeTypeTag::Xnew; + "wrz", "x-world/x-vrml", 7, MimeTypeTag::Xnew; + "xaf", "x-world/x-vrml", 7, MimeTypeTag::Xnew; + "xof", "x-world/x-vrml", 7, MimeTypeTag::Xnew; +); + +/// `MIME` main type. +#[derive(Debug, PartialEq, Eq)] +enum MimeTypeTag { + Application, + Audio, + Font, + Image, + Message, + Model, + Multipart, + Text, + Video, + Xnew, // A type not included in the standard, beginning with `x-` +} + +impl MimeTypeTag { + fn from_bytes(b: &[u8]) -> Result { + // From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2) + // and SHOULD be limited to 64 characters. + // Both top-level type and subtype names are case-insensitive. + + if b.len() > 64 || b.len() < 2 { + return Err(ErrorKind::InvalidInput.into()); + } + + match b[0].to_ascii_lowercase() { + // beginning with "x-" + b'x' => { + if b[1] == b'-' && is_valid(&b[2..]) { + return Ok(Self::Xnew); + } + } + b'a' => { + // application + if b[1..].eq_ignore_ascii_case(b"pplication") { + return Ok(Self::Application); + // audio + } else if b[1..].eq_ignore_ascii_case(b"udio") { + return Ok(Self::Audio); + } + } + b'f' => { + // font + if b[1..].eq_ignore_ascii_case(b"ont") { + return Ok(Self::Font); + } + } + b'i' => { + // image + if b[1..].eq_ignore_ascii_case(b"mage") { + return Ok(Self::Image); + } + } + b'm' => { + // message + if b[1..].eq_ignore_ascii_case(b"essage") { + return Ok(Self::Message); + // model + } else if b[1..].eq_ignore_ascii_case(b"odel") { + return Ok(Self::Model); + // multipart + } else if b[1..].eq_ignore_ascii_case(b"ultipart") { + return Ok(Self::Multipart); + } + } + b't' => { + // text + if b[1..].eq_ignore_ascii_case(b"ext") { + return Ok(Self::Text); + } + } + b'v' => { + // video + if b[1..].eq_ignore_ascii_case(b"ideo") { + return Ok(Self::Video); + } + } + _ => return Err(ErrorKind::InvalidInput.into()), + }; + + Err(ErrorKind::InvalidInput.into()) + } +} + +// From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2): +// +// All registered media types MUST be assigned top-level type and +// subtype names. The combination of these names serves to uniquely +// identify the media type, and the subtype name facet (or the absence +// of one) identifies the registration tree. Both top-level type and +// subtype names are case-insensitive. +// +// Type and subtype names MUST conform to the following ABNF: +// +// type-name = restricted-name +// subtype-name = restricted-name +// +// restricted-name = restricted-name-first *126restricted-name-chars +// restricted-name-first = ALPHA / DIGIT +// restricted-name-chars = ALPHA / DIGIT / "!" / "#" / +// "$" / "&" / "-" / "^" / "_" +// restricted-name-chars =/ "." ; Characters before first dot always +// ; specify a facet name +// restricted-name-chars =/ "+" ; Characters after last plus always +// ; specify a structured syntax suffix +#[rustfmt::skip] +static MEDIA_TYPE_VALID_BYTES: [bool; 256] = { + const __: bool = false; + const TT: bool = true; + [ +// \0 HT LF CR + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, +// \w ! " # $ % & ' ( ) * + , - . / + __, TT, __, TT, TT, __, TT, __, __, __, __, TT, __, TT, TT, __, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, __, __, __, +// @ A B C D E F G H I J K L M N O + __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// P Q R S T U V W X Y Z [ \ ] ^ _ + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, TT, TT, +// ` a b c d e f g h i j k l m n o + __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// p q r s t u v w x y z { | } ~ del + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, __, __, +// Expand ascii + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + ] +}; + +fn is_valid(v: &[u8]) -> bool { + v.iter().all(|b| MEDIA_TYPE_VALID_BYTES[*b as usize]) +} + +#[cfg(test)] +mod ut_mime { + use super::{MimeType, MimeTypeTag}; + use crate::error::{ErrorKind, HttpError}; + + /// UT test cases for `MimeTypeTag::from_bytes`. + /// + /// # Brief + /// 1. Creates a `MimeTypeTag` from `&[u8]`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_tag_from_bytes() { + assert_eq!( + MimeTypeTag::from_bytes(b"application"), + Ok(MimeTypeTag::Application) + ); + assert_eq!(MimeTypeTag::from_bytes(b"audio"), Ok(MimeTypeTag::Audio)); + assert_eq!(MimeTypeTag::from_bytes(b"font"), Ok(MimeTypeTag::Font)); + assert_eq!(MimeTypeTag::from_bytes(b"image"), Ok(MimeTypeTag::Image)); + assert_eq!( + MimeTypeTag::from_bytes(b"message"), + Ok(MimeTypeTag::Message) + ); + assert_eq!(MimeTypeTag::from_bytes(b"model"), Ok(MimeTypeTag::Model)); + assert_eq!( + MimeTypeTag::from_bytes(b"multipart"), + Ok(MimeTypeTag::Multipart) + ); + assert_eq!(MimeTypeTag::from_bytes(b"text"), Ok(MimeTypeTag::Text)); + assert_eq!(MimeTypeTag::from_bytes(b"video"), Ok(MimeTypeTag::Video)); + assert_eq!(MimeTypeTag::from_bytes(b"x-world"), Ok(MimeTypeTag::Xnew)); + assert_eq!( + MimeTypeTag::from_bytes(b"APPLICATION"), + Ok(MimeTypeTag::Application) + ); + assert_eq!( + MimeTypeTag::from_bytes(b"x-ab/cd"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + MimeTypeTag::from_bytes(b"notype"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } + + /// UT test cases for `MimeType::from_bytes`. + /// + /// # Brief + /// 1. Creates a `MimeType` from `&[u8]`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_from_bytes() { + assert_eq!( + MimeType::from_bytes(b"application/octet-stream"), + Ok(MimeType { + tag: MimeTypeTag::Application, + bytes: b"application/octet-stream", + slash: 11, + }) + ); + + assert_eq!( + MimeType::from_bytes(b"TEXT/PLAIN"), + Ok(MimeType { + tag: MimeTypeTag::Text, + bytes: b"TEXT/PLAIN", + slash: 4, + }) + ); + + assert_eq!( + MimeType::from_bytes(b"TEXT/~PLAIN"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + assert_eq!( + MimeType::from_bytes(b"application/octet/stream"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } + + /// UT test cases for `MimeType::from_path`. + /// + /// # Brief + /// 1. Creates a `MimeType` from path. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_from_path() { + use crate::error::HttpError; + use std::path::Path; + + assert_eq!( + MimeType::from_path(Path::new("./foo/bar.evy")), + MimeType::from_bytes(b"application/envoy") + ); + assert_eq!( + MimeType::from_path(Path::new("foo.*")), + MimeType::from_bytes(b"application/octet-stream") + ); + assert_eq!( + MimeType::from_path(Path::new("")), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + MimeType::from_path(Path::new(".txt")), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + MimeType::from_path(Path::new("./foo/bar")), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } + + /// UT test cases for `MimeType::main_type`. + /// + /// # Brief + /// 1. Gets main type string by `main_type`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_main_type() { + let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap(); + assert_eq!(mime_type.main_type(), "application"); + + let mime_type = MimeType::from_bytes(b"TeXT/PLAIN").unwrap(); + assert_eq!(mime_type.main_type(), "TeXT"); + } + + /// UT test cases for `MimeType::sub_type`. + /// + /// # Brief + /// 1. Gets subtype type string by `sub_type`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mimetype_sub_type() { + let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap(); + assert_eq!(mime_type.sub_type(), "octet-stream"); + } + + /// UT test cases for `MimeType::as_str`. + /// + /// # Brief + /// 1. Gets string from `MimeType` by `as_str`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mimetype_as_str() { + let mime_type = MimeType::from_bytes(b"application/pdf").unwrap(); + assert_eq!(mime_type.as_str(), "application/pdf"); + + let mime_type = MimeType::from_bytes(b"application/octet-stream").unwrap(); + assert_eq!(mime_type.as_str(), "application/octet-stream"); + } + + /// UT test cases for `Mimetype::eq`. + /// + /// # Brief + /// 1. Creates some `MimeType`, and check if they are equal. + /// 2. Checks if the test results are correct. + #[test] + fn ut_mime_type_eq() { + assert_eq!( + MimeType { + tag: MimeTypeTag::Application, + bytes: b"application/octet-stream", + slash: 11, + }, + MimeType { + tag: MimeTypeTag::Application, + bytes: b"application/octet-stream", + slash: 11, + } + ); + + assert_ne!( + MimeType::from_bytes(b"application/octet-stream"), + MimeType::from_bytes(b"application/pdf") + ); + + assert_eq!( + MimeType::from_extension("pdf"), + MimeType::from_bytes(b"application/pdf") + ); + } + + /// UT test cases for `MimeType::is_application`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_application() { + assert!(MimeType::from_bytes(b"application/pdf") + .unwrap() + .is_application()); + assert!(!MimeType::from_bytes(b"audio/basic") + .unwrap() + .is_application()); + } + + /// UT test cases for `MimeType::is_audio`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_audio() { + assert!(MimeType::from_bytes(b"audio/basic").unwrap().is_audio()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_audio()); + } + + /// UT test cases for `MimeType::is_font`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mime_type_is_font() { + assert!(MimeType::from_bytes(b"font/collection").unwrap().is_font()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_font()); + } + + /// UT test cases for `MimeType::is_image`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_image() { + assert!(MimeType::from_bytes(b"image/bmp").unwrap().is_image()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_image()); + } + + /// UT test cases for `MimeType::is_message`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_message() { + assert!(MimeType::from_bytes(b"message/example") + .unwrap() + .is_message()); + assert!(!MimeType::from_bytes(b"application/pdf") + .unwrap() + .is_message()); + } + + /// UT test cases for `MimeType::is_model`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_model() { + assert!(MimeType::from_bytes(b"model/e57").unwrap().is_model()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_model()); + } + + /// UT test cases for `MimeType::is_multipart`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mime_type_is_multipart() { + assert!(MimeType::from_bytes(b"multipart/form-data") + .unwrap() + .is_multipart()); + assert!(!MimeType::from_bytes(b"application/pdf") + .unwrap() + .is_multipart()); + } + + /// UT test cases for `MimeType::is_text`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mime_type_is_text() { + assert!(MimeType::from_bytes(b"text/csv").unwrap().is_text()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_text()); + } + + /// UT test cases for `MimeType::is_video`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mimetype_is_video() { + assert!(MimeType::from_bytes(b"video/mpeg").unwrap().is_video()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_video()); + } + + /// UT test cases for `MimeType::is_xnew`. + /// + /// # Brief + /// 1. Creates `MimeType` from `&[u8]` by `MimeType::from_bytes`. + /// 2. Checks whether the main types are correct. + #[test] + fn ut_mime_type_is_xnew() { + assert!(MimeType::from_bytes(b"x-world/x-vrml").unwrap().is_xnew()); + assert!(!MimeType::from_bytes(b"application/pdf").unwrap().is_xnew()); + } +} diff --git a/ylong_http/src/body/mime/mod.rs b/ylong_http/src/body/mime/mod.rs new file mode 100644 index 0000000..2c5d0dc --- /dev/null +++ b/ylong_http/src/body/mime/mod.rs @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#[cfg(test)] +#[macro_use] +#[rustfmt::skip] +pub(crate) mod mime_test_macro; + +mod common; +mod decode; +mod encode; +mod mimetype; + +pub use common::{MimeMulti, MimeMultiBuilder, MimePart, MimePartBuilder, TokenStatus, XPart}; +pub use decode::MimeMultiDecoder; +pub use encode::MimeMultiEncoder; +pub use encode::MimePartEncoder; +pub use mimetype::MimeType; + +pub(crate) use common::{ + DecodeHeaders, EncodeHeaders, HeaderStatus, MixFrom, PartStatus, CR, CRLF, HTAB, LF, SP, +}; +pub(crate) use decode::MimePartDecoder; + +// TODO: reuse mime later. + +// TODO: Adapter, remove this later. +use crate::body::async_impl::{poll_read, Body}; + +use std::io::Cursor; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::vec::IntoIter; +use tokio::io::{AsyncRead, ReadBuf}; + +/// A structure that helps you build a `multipart/form-data` message. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http::body::{MultiPart, Part}; +/// +/// let multipart = MultiPart::new() +/// .part(Part::new().name("name").body("xiaoming")) +/// .part(Part::new().name("password").body("123456789")); +/// ``` +pub struct MultiPart { + parts: Vec, + boundary: String, + status: ReadStatus, +} + +impl MultiPart { + /// Creates an empty `Multipart` with boundary created automatically. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::MultiPart; + /// + /// let multipart = MultiPart::new(); + /// ``` + pub fn new() -> Self { + Self { + parts: Vec::new(), + boundary: gen_boundary(), + status: ReadStatus::Never, + } + } + + /// Sets a part to the `Multipart`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::{MultiPart, Part}; + /// + /// let multipart = MultiPart::new() + /// .part(Part::new().name("name").body("xiaoming")); + /// ``` + pub fn part(mut self, part: Part) -> Self { + self.parts.push(part); + self + } + + /// Gets the boundary of this `Multipart`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::MultiPart; + /// + /// let multipart = MultiPart::new(); + /// let boundary = multipart.boundary(); + /// ``` + pub fn boundary(&self) -> &str { + self.boundary.as_str() + } + + /// Get the total bytes of the `multpart/form-data` message, including + /// length of every parts, such as boundaries, headers, bodies, etc. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::{MultiPart, Part}; + /// + /// let multipart = MultiPart::new() + /// .part(Part::new().name("name").body("xiaoming")); + /// + /// let bytes = multipart.total_bytes(); + /// ``` + pub fn total_bytes(&self) -> Option { + let mut size = 0u64; + for part in self.parts.iter() { + size += part.length?; + + // start boundary + \r\n + size += 2 + self.boundary.len() as u64 + 2; + + // Content-Disposition: form-data + size += 30; + + // ; name="xxx" + if let Some(name) = part.name.as_ref() { + size += 9 + name.len() as u64; + } + + // ; filename="xxx" + if let Some(name) = part.file_name.as_ref() { + size += 13 + name.len() as u64; + } + + // \r\n + size += 2; + + // Content-Type: xxx + if let Some(mime) = part.mime.as_ref() { + size += 16 + mime.len() as u64; + } + + // \r\n + size += 2 + 2; + } + // last boundary + size += 2 + self.boundary.len() as u64 + 4; + Some(size) + } + + pub(crate) fn build_status(&mut self) { + let mut states = Vec::new(); + for part in self.parts.iter_mut() { + states.push(MultiPartState::bytes( + format!("--{}\r\n", self.boundary).into_bytes(), + )); + states.push(MultiPartState::bytes( + b"Content-Disposition: form-data".to_vec(), + )); + + if let Some(ref name) = part.name { + states.push(MultiPartState::bytes( + format!("; name=\"{name}\"").into_bytes(), + )); + } + + if let Some(ref file_name) = part.file_name { + states.push(MultiPartState::bytes( + format!("; filename=\"{file_name}\"").into_bytes(), + )); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + + if let Some(ref mime) = part.mime { + states.push(MultiPartState::bytes( + format!("Content-Type: {mime}\r\n").into_bytes(), + )); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + + if let Some(body) = part.body.take() { + states.push(body); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + } + states.push(MultiPartState::bytes( + format!("--{}--\r\n", self.boundary).into_bytes(), + )); + self.status = ReadStatus::Reading(MultiPartStates { + states: states.into_iter(), + curr: None, + }) + } +} + +impl Default for MultiPart { + fn default() -> Self { + Self::new() + } +} + +impl AsyncRead for MultiPart { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match self.status { + ReadStatus::Never => self.build_status(), + ReadStatus::Reading(_) => {} + ReadStatus::Finish => return Poll::Ready(Ok(())), + } + + if let ReadStatus::Reading(ref mut status) = self.status { + if buf.initialize_unfilled().is_empty() { + return Poll::Ready(Ok(())); + } + let filled = buf.filled().len(); + return match Pin::new(status).poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + let new_filled = buf.filled().len(); + if filled == new_filled { + self.status = ReadStatus::Finish; + } + Poll::Ready(Ok(())) + } + Poll::Pending => { + let new_filled = buf.filled().len(); + return if new_filled != filled { + Poll::Ready(Ok(())) + } else { + Poll::Pending + }; + } + x => x, + }; + } + Poll::Ready(Ok(())) + } +} + +// TODO: Adapter, remove this later. +impl Body for MultiPart { + type Error = std::io::Error; + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + poll_read(self, cx, buf) + } +} + +/// A structure that represents a part of `multipart/form-data` message. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http::body::Part; +/// +/// let part = Part::new().name("name").body("xiaoming"); +/// ``` +pub struct Part { + name: Option, + file_name: Option, + mime: Option, + length: Option, + body: Option, +} + +impl Part { + /// Creates an empty `Part`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new(); + /// ``` + pub fn new() -> Self { + Self { + name: None, + file_name: None, + mime: None, + length: None, + body: None, + } + } + + /// Sets the name of this `Part`. + /// + /// The name message will be set to `Content-Disposition` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().name("name"); + /// ``` + pub fn name(mut self, name: &str) -> Self { + self.name = Some(String::from(name)); + self + } + + /// Sets the file name of this `Part`. + /// + /// The file name message will be set to `Content-Disposition` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().file_name("example.txt"); + /// ``` + pub fn file_name(mut self, file_name: &str) -> Self { + self.file_name = Some(String::from(file_name)); + self + } + + /// Sets the mime type of this `Part`. + /// + /// The mime type message will be set to `Content-Type` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().mime("application/octet-stream"); + /// ``` + pub fn mime(mut self, mime: &str) -> Self { + self.mime = Some(String::from(mime)); + self + } + + /// Sets the length of body of this `Part`. + /// + /// The length message will be set to `Content-Length` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().length(Some(8)).body("xiaoming"); + /// ``` + pub fn length(mut self, length: Option) -> Self { + self.length = length; + self + } + + /// Sets a slice body of this `Part`. + /// + /// The body message will be set to the body part. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().mime("application/octet-stream"); + /// ``` + pub fn body>(mut self, body: T) -> Self { + let body = body.as_ref().to_vec(); + self.length = Some(body.len() as u64); + self.body = Some(MultiPartState::bytes(body)); + self + } + + /// Sets a stream body of this `Part`. + /// + /// The body message will be set to the body part. + /// + /// # Examples + /// + /// ``` + /// # use tokio::io::AsyncRead; + /// # use ylong_http::body::Part; + /// + /// # fn set_stream_body(stream: R) { + /// let part = Part::new().stream(stream); + /// # } + /// ``` + pub fn stream(mut self, body: T) -> Self { + self.body = Some(MultiPartState::stream(Box::pin(body))); + self + } +} + +impl Default for Part { + fn default() -> Self { + Self::new() + } +} + +impl AsRef for MultiPart { + fn as_ref(&self) -> &MultiPart { + self + } +} + +enum ReadStatus { + Never, + Reading(MultiPartStates), + Finish, +} + +struct MultiPartStates { + states: IntoIter, + curr: Option, +} + +impl MultiPartStates { + fn poll_read_curr( + &mut self, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if let Some(mut state) = self.curr.take() { + return match state { + MultiPartState::Bytes(ref mut bytes) => { + let filled_len = buf.filled().len(); + let unfilled = buf.initialize_unfilled(); + let unfilled_len = unfilled.len(); + let new = std::io::Read::read(bytes, unfilled).unwrap(); + buf.set_filled(filled_len + new); + + if new >= unfilled_len { + self.curr = Some(state); + } + Poll::Ready(Ok(())) + } + MultiPartState::Stream(ref mut stream) => { + let old_len = buf.filled().len(); + match stream.as_mut().poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + if old_len != buf.filled().len() { + self.curr = Some(state); + } + Poll::Ready(Ok(())) + } + Poll::Pending => { + self.curr = Some(state); + Poll::Pending + } + x => x, + } + } + }; + } + Poll::Ready(Ok(())) + } +} + +enum MultiPartState { + Bytes(Cursor>), + Stream(Pin>), +} + +impl MultiPartState { + fn bytes(bytes: Vec) -> Self { + Self::Bytes(Cursor::new(bytes)) + } + + fn stream(reader: Pin>) -> Self { + Self::Stream(reader) + } +} + +impl AsyncRead for MultiPartStates { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + while !buf.initialize_unfilled().is_empty() { + if this.curr.is_none() { + this.curr = match this.states.next() { + None => break, + x => x, + } + } + + match this.poll_read_curr(cx, buf) { + Poll::Ready(Ok(())) => {} + x => return x, + } + } + Poll::Ready(Ok(())) + } +} + +fn gen_boundary() -> String { + format!( + "{:016x}-{:016x}-{:016x}-{:016x}", + xor_shift(), + xor_shift(), + xor_shift(), + xor_shift() + ) +} + +// XORShift* fast-random realization. +fn xor_shift() -> u64 { + use std::cell::Cell; + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + use std::num::Wrapping; + + thread_local! { + static RNG: Cell> = Cell::new(Wrapping(seed())); + } + + // The returned value of `seed()` must be nonzero. + fn seed() -> u64 { + let seed = RandomState::new(); + + let mut out; + let mut cnt = 1; + let mut hasher = seed.build_hasher(); + + loop { + hasher.write_usize(cnt); + out = hasher.finish(); + if out != 0 { + break; + } + cnt += 1; + hasher = seed.build_hasher(); + } + out + } + + RNG.with(|rng| { + let mut n = rng.get(); + n ^= n >> 12; + n ^= n << 25; + n ^= n >> 27; + rng.set(n); + n.0.wrapping_mul(0x2545_f491_4f6c_dd1d) + }) +} diff --git a/ylong_http/src/body/mod.rs b/ylong_http/src/body/mod.rs new file mode 100644 index 0000000..c341994 --- /dev/null +++ b/ylong_http/src/body/mod.rs @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Content`] implementation. +//! +//! # Introduction +//! +//! HTTP messages often transfer a complete or partial representation as the +//! message content: a stream of octets sent after the header section, as +//! delineated by the message framing. +//! +//! This abstract definition of content reflects the data after it has been +//! extracted from the message framing. For example, an `HTTP/1.1` message body +//! might consist of a stream of data encoded with the chunked transfer coding — +//! a sequence of data chunks, one zero-length chunk, and a trailer section — +//! whereas the content of that same message includes only the data stream after +//! the transfer coding has been decoded; it does not include the chunk lengths, +//! chunked framing syntax, nor the trailer fields. +//! +//! [`Content`]: https://httpwg.org/specs/rfc9110.html#content +//! +//! # Various Body Types +//! +//! This module provides following body types: +//! +//! - [`EmptyBody`]: `EmptyBody` represents an empty body. +//! - [`TextBody`]: `TextBody` represents a plain-text body. +//! +//! [`EmptyBody`]: EmptyBody +//! [`TextBody`]: TextBody +//! + +// TODO: Support `Trailers`. + +mod chunk; +mod empty; +mod mime; +mod text; + +pub use chunk::{Chunk, ChunkBody, ChunkBodyDecoder, ChunkExt, ChunkState, Chunks}; +pub use empty::EmptyBody; +pub use mime::{ + MimeMulti, MimeMultiBuilder, MimeMultiDecoder, MimeMultiEncoder, MimePart, MimePartBuilder, + MimePartEncoder, MimeType, MultiPart, Part, TokenStatus, XPart, +}; +pub use text::{Text, TextBody, TextBodyDecoder}; + +/// Synchronous `Body` trait definition. +pub mod sync_impl { + use crate::headers::Headers; + use std::error::Error; + use std::io::Read; + + /// The `sync_impl::Body` trait allows for reading body data synchronously. + /// + /// # Examples + /// + /// [`TextBody`] implements `sync_impl::Body`: + /// + /// ``` + /// use ylong_http::body::sync_impl::Body; + /// use ylong_http::body::TextBody; + /// + /// // `TextBody` has 5-bytes length content. + /// let mut body = TextBody::from_bytes(b"Hello"); + /// + /// // We can use any non-zero length `buf` to read it. + /// let mut buf = [0u8; 4]; + /// + /// // Read 4 bytes. `buf` is filled. + /// // The remaining 1 bytes of `TextBody` have not been read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..read], b"Hell"); + /// + /// // Read next 1 bytes. Part of `buf` is filled. + /// // All bytes of `TextBody` have been read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 1); + /// assert_eq!(&buf[..read], b"o"); + /// + /// // The `TextBody` has already been read, and no more content can be read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 0); + /// ``` + /// + /// [`TextBody`]: super::text::TextBody + pub trait Body { + /// Errors that may occur when reading body data. + type Error: Into>; + + /// Synchronously reads part of the body data, returning how many bytes + /// were read. Body data will be written into buf as much as possible. + /// + /// # Return Value + /// + /// - `Ok(0)`: + /// If the length of the `buf` is not 0, it means that this + /// body has been completely read. + /// + /// - `Ok(size)` && `size != 0`: + /// A part of this body has been read, but the body may not be fully + /// read. You can call this method again to obtain next part of data. + /// + /// - `Err(e)`: + /// An error occurred while reading body data. + /// + /// # Note + /// + /// It is better for you **not** to use a `buf` with a length of 0, + /// otherwise it may lead to misunderstanding of the return value. + /// + /// # Examples + /// + /// [`TextBody`] implements `sync_impl::Body`: + /// + /// ``` + /// use ylong_http::body::sync_impl::Body; + /// use ylong_http::body::TextBody; + /// + /// // `TextBody` has 5-bytes length content. + /// let mut body = TextBody::from_bytes(b"Hello"); + /// + /// // We can use any non-zero length `buf` to read it. + /// let mut buf = [0u8; 4]; + /// + /// // Read 4 bytes. `buf` is filled. + /// // The remaining 1 bytes of `TextBody` have not been read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..read], b"Hell"); + /// + /// // Read next 1 bytes. Part of `buf` is filled. + /// // All bytes of `TextBody` have been read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 1); + /// assert_eq!(&buf[..read], b"o"); + /// + /// // The `TextBody` has already been read, and no more content can be read. + /// let read = body.data(&mut buf).unwrap(); + /// assert_eq!(read, 0); + /// ``` + /// + /// [`TextBody`]: super::text::TextBody + fn data(&mut self, buf: &mut [u8]) -> Result; + + /// Gets trailer headers. + fn trailer(&mut self) -> Result, Self::Error> { + Ok(None) + } + } + + impl Body for T { + type Error = std::io::Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + self.read(buf) + } + } +} + +/// Asynchronous `Body` trait definition. +pub mod async_impl { + use crate::headers::Headers; + use core::future::Future; + use core::pin::Pin; + use core::task::{Context, Poll}; + use std::error::Error; + use tokio::io::{AsyncRead, ReadBuf}; + + /// The `async_impl::Body` trait allows for reading body data asynchronously. + /// + /// # Examples + /// + /// [`TextBody`] implements `async_impl::Body`: + /// + /// ``` + /// use ylong_http::body::async_impl::Body; + /// use ylong_http::body::TextBody; + /// + /// # async fn read_body_data() { + /// // `TextBody` has 5-bytes length content. + /// let mut body = TextBody::from_bytes(b"Hello"); + /// + /// // We can use any non-zero length `buf` to read it. + /// let mut buf = [0u8; 4]; + /// + /// // Read 4 bytes. `buf` is filled. + /// // The remaining 1 bytes of `TextBody` have not been read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..read], b"Hell"); + /// + /// // Read next 1 bytes. Part of `buf` is filled. + /// // All bytes of `TextBody` have been read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 1); + /// assert_eq!(&buf[..read], b"o"); + /// + /// // The `TextBody` has already been read, and no more content can be read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 0); + /// # } + /// ``` + /// + /// [`TextBody`]: super::text::TextBody + pub trait Body: Unpin + Sized { + /// Errors that may occur when reading body data. + type Error: Into>; + + /// Reads part of the body data, returning how many bytes were read. + /// + /// Body data will be written into buf as much as possible. + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll>; + + /// Returns a future that reads part of the body data, returning how + /// many bytes were read. Body data will be written into buf as much + /// as possible. + /// + /// # Return Value + /// + /// - `Ok(0)`: + /// If the length of the `buf` is not 0, it means that this body has + /// been completely read. + /// + /// - `Ok(size)` && `size != 0`: + /// A part of this body has been read, but the body may not be + /// completely read. You can call this method again to obtain next part + /// of data. + /// + /// - `Err(e)`: + /// An error occurred while reading body data. + /// + /// # Note + /// + /// It is better for you **not** to use a `buf` with a length of 0, + /// otherwise it may lead to misunderstanding of the return value. + /// + /// # Examples + /// + /// [`TextBody`] implements `async_impl::Body`: + /// + /// ``` + /// use ylong_http::body::async_impl::Body; + /// use ylong_http::body::TextBody; + /// + /// # async fn read_body_data() { + /// // `TextBody` has 5-bytes length content. + /// let mut body = TextBody::from_bytes(b"Hello"); + /// + /// // We can use any non-zero length `buf` to read it. + /// let mut buf = [0u8; 4]; + /// + /// // Read 4 bytes. `buf` is filled. + /// // The remaining 1 bytes of `TextBody` have not been read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 4); + /// assert_eq!(&buf[..read], b"Hell"); + /// + /// // Read next 1 bytes. Part of `buf` is filled. + /// // All bytes of `TextBody` have been read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 1); + /// assert_eq!(&buf[..read], b"o"); + /// + /// // The `TextBody` has already been read, and no more content can be read. + /// let read = body.data(&mut buf).await.unwrap(); + /// assert_eq!(read, 0); + /// # } + /// ``` + /// + /// [`TextBody`]: super::text::TextBody + fn data<'a, 'b>(&'a mut self, buf: &'b mut [u8]) -> DataFuture<'a, 'b, Self> + where + Self: 'a, + Self: 'b, + { + DataFuture { body: self, buf } + } + + /// Gets trailer headers. + fn trailer(&mut self) -> Result, Self::Error> { + Ok(None) + } + } + + /// A future that reads part of the body data, returning how many bytes + /// were read. + /// + /// This future is the return value of `async_impl::Body::data`. + /// + /// [`async_impl::Body::data`]: Body::data + pub struct DataFuture<'a, 'b, T> + where + T: Body + 'a + 'b, + { + body: &'a mut T, + buf: &'b mut [u8], + } + + impl<'a, 'b, T> Future for DataFuture<'a, 'b, T> + where + T: Body + 'a + 'b, + { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let fut = self.get_mut(); + Pin::new(&mut *fut.body).poll_data(cx, fut.buf) + } + } + + // TODO: Adapter, remove this later. + pub(crate) fn poll_read( + reader: Pin<&mut T>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut read_buf = ReadBuf::new(buf); + let filled = read_buf.filled().len(); + match reader.poll_read(cx, &mut read_buf) { + Poll::Ready(Ok(())) => { + let new_filled = read_buf.filled().len(); + Poll::Ready(Ok(new_filled - filled)) + } + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } + + // TODO: Adapter, remove this later. + impl Body for &'static [u8] { + type Error = std::io::Error; + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + poll_read(self, cx, buf) + } + } + + // TODO: Adapter, remove this later. + impl Body for &'static str { + type Error = std::io::Error; + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + poll_read(Pin::new(&mut self.as_bytes()), cx, buf) + } + } + + // TODO: Adapter, remove this later. + impl Body for String { + type Error = std::io::Error; + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + poll_read(Pin::new(&mut self.as_bytes()), cx, buf) + } + } + + // TODO: The following code will be useful in the future. + // impl Body for T { + // type Error = std::io::Error; + // + // fn poll_data( + // self: Pin<&mut Self>, + // cx: &mut Context<'_>, + // buf: &mut [u8], + // ) -> Poll> { + // let mut read_buf = ReadBuf::new(buf); + // let filled = read_buf.filled().len(); + // match self.poll_read(cx, &mut read_buf) { + // Poll::Ready(Ok(())) => { + // let new_filled = read_buf.filled().len(); + // Poll::Ready(Ok(new_filled - filled)) + // } + // Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + // Poll::Pending => Poll::Pending, + // } + // } + // } +} + +// Type definitions of the origin of the body data. +pub(crate) mod origin { + use core::ops::{Deref, DerefMut}; + use std::io::Read; + use tokio::io::AsyncRead; + + /// A type that represents the body data is from memory. + pub struct FromBytes<'a> { + pub(crate) bytes: &'a [u8], + } + + impl<'a> FromBytes<'a> { + pub(crate) fn new(bytes: &'a [u8]) -> Self { + Self { bytes } + } + } + + impl<'a> Deref for FromBytes<'a> { + type Target = &'a [u8]; + + fn deref(&self) -> &Self::Target { + &self.bytes + } + } + + impl<'a> DerefMut for FromBytes<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } + } + + /// A type that represents the body data is from a synchronous reader. + pub struct FromReader { + pub(crate) reader: T, + } + + impl FromReader { + pub(crate) fn new(reader: T) -> Self { + Self { reader } + } + } + + impl Deref for FromReader { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.reader + } + } + + impl DerefMut for FromReader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reader + } + } + + /// A type that represent the body data is from an asynchronous reader. + pub struct FromAsyncReader { + pub(crate) reader: T, + } + + impl FromAsyncReader { + pub(crate) fn new(reader: T) -> Self { + Self { reader } + } + } + + impl Deref for FromAsyncReader { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.reader + } + } + + impl DerefMut for FromAsyncReader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reader + } + } + + /// A type that represents the body data is from an asynchronous body. + pub struct FromAsyncBody { + pub(crate) body: T, + } + + impl FromAsyncBody { + pub(crate) fn new(body: T) -> Self { + Self { body } + } + } + + impl Deref for FromAsyncBody { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.body + } + } + + impl DerefMut for FromAsyncBody { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.body + } + } +} + +#[cfg(test)] +mod ut_mod { + use crate::body::EmptyBody; + + /// UT test cases for `sync_impl::Body::data` of `&mut sync_impl::Body`. + /// + /// # Brief + /// 1. Creates a `sync_impl::Body` object. + /// 2. Gets its mutable reference. + /// 3. Calls its `sync_impl::Body::data` method and then checks the results. + #[test] + fn ut_syn_body_mut_syn_body_data() { + use crate::body::sync_impl::Body; + + let mut body = EmptyBody::new(); + let body_mut = &mut body; + let mut buf = [0u8; 1]; + assert_eq!(body_mut.data(&mut buf), Ok(0)); + } + + /// UT test cases for `async_impl::Body::data` of `&mut async_impl::Body`. + /// + /// # Brief + /// 1. Creates a `async_impl::Body` object. + /// 2. Gets its mutable reference. + /// 3. Calls its `async_impl::Body::data` method and then checks the results. + #[tokio::test] + async fn ut_asyn_body_mut_asyn_body_data() { + use crate::body::async_impl::Body; + + let mut body = EmptyBody::new(); + let body_mut = &mut body; + let mut buf = [0u8; 1]; + assert_eq!(body_mut.data(&mut buf).await, Ok(0)); + } +} diff --git a/ylong_http/src/body/text.rs b/ylong_http/src/body/text.rs new file mode 100644 index 0000000..b10dc56 --- /dev/null +++ b/ylong_http/src/body/text.rs @@ -0,0 +1,657 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::origin::{FromAsyncReader, FromBytes, FromReader}; +use super::{async_impl, sync_impl}; +use crate::body::origin::FromAsyncBody; +use core::cmp::min; +use core::pin::Pin; +use core::task::{Context, Poll}; +use std::io::{Error, Read}; +use tokio::io::{AsyncRead, ReadBuf}; + +/// `TextBody` is used to represent the body of plain text type. +/// +/// You can create a `TextBody` in a variety of ways, such as reading from +/// memory or reading from a file. +/// +/// # Read From Memory +/// +/// You can create a `TextBody` by reading memory slice. +/// +/// For example, you can use a memory slice to create a `TextBody`: +/// +/// ``` +/// use ylong_http::body::TextBody; +/// +/// let text = "Hello World"; +/// let body = TextBody::from_bytes(text.as_bytes()); +/// ``` +/// +/// This type of `TextBody` implements both [`sync_impl::Body`] and [`async_impl::Body`]. +/// +/// # Read From Reader +/// +/// You can create a `TextBody` by reading from a synchronous reader. +/// +/// For example, you can use a `&[u8]` to create a `TextBody`: +/// +/// ```no_run +/// use ylong_http::body::TextBody; +/// +/// // In this usage `&[u8]` is treated as a synchronous reader. +/// let reader = "Hello World"; +/// let body = TextBody::from_reader(reader.as_bytes()); +/// ``` +/// +/// This type of `TextBody` **only** implements [`sync_impl::Body`]. +/// +/// # Read From Async Reader +/// +/// You can create a `TextBody` by reading from an asynchronous reader. +/// +/// For example, you can use a `&[u8]` to create a `TextBody`: +/// +/// ```no_run +/// use ylong_http::body::TextBody; +/// +/// async fn text_body_from_async_reader() { +/// // In this usage `&[u8]` is treated as an asynchronous reader. +/// let reader = "Hello World"; +/// let body = TextBody::from_async_reader(reader.as_bytes()); +/// } +/// ``` +/// +/// This type of `TextBody` **only** implements [`async_impl::Body`]. +/// +/// # Read Body Content +/// +/// After you have created a `TextBody`, you can use the methods of [`sync_impl::Body`] +/// or [`async_impl::Body`] to read data, like the examples below: +/// +/// sync: +/// +/// ```no_run +/// use ylong_http::body::sync_impl::Body; +/// use ylong_http::body::TextBody; +/// +/// let text = "Hello World"; +/// let mut body = TextBody::from_bytes(text.as_bytes()); +/// +/// let mut buf = [0u8; 1024]; +/// loop { +/// let size = body.data(&mut buf).unwrap(); +/// if size == 0 { +/// break; +/// } +/// // Operates on the data you read.. +/// } +/// ``` +/// +/// async: +/// +/// ```no_run +/// use ylong_http::body::async_impl::Body; +/// use ylong_http::body::TextBody; +/// +/// async fn read_from_body() { +/// let text = "Hello World"; +/// let mut body = TextBody::from_bytes(text.as_bytes()); +/// +/// let mut buf = [0u8; 1024]; +/// loop { +/// let size = body.data(&mut buf).await.unwrap(); +/// if size == 0 { +/// break; +/// } +/// // Operates on the data you read.. +/// } +/// } +/// ``` +/// +/// [`sync_impl::Body`]: sync_impl::Body +/// [`async_impl::Body`]: async_impl::Body +pub struct TextBody { + from: T, +} + +impl<'a> TextBody> { + /// Creates a `TextBody` by a memory slice. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBody; + /// + /// let text = "Hello World"; + /// let body = TextBody::from_bytes(text.as_bytes()); + /// ``` + pub fn from_bytes(bytes: &'a [u8]) -> Self { + TextBody { + from: FromBytes::new(bytes), + } + } +} + +impl TextBody> { + /// Creates a `TextBody` from a synchronous reader. + /// + /// ```no_run + /// use ylong_http::body::TextBody; + /// + /// // In this usage `&[u8]` is treated as a synchronous reader. + /// let reader = "Hello World"; + /// let body = TextBody::from_reader(reader.as_bytes()); + /// ``` + pub fn from_reader(reader: T) -> Self { + TextBody { + from: FromReader::new(reader), + } + } +} + +impl TextBody> { + /// Creates a `TextBody` from an asynchronous reader. + /// + /// ```no_run + /// use ylong_http::body::TextBody; + /// + /// async fn text_body_from_async_reader() { + /// let reader = "Hello World"; + /// let body = TextBody::from_async_reader(reader.as_bytes()); + /// } + /// ``` + pub fn from_async_reader(reader: T) -> Self { + Self { + from: FromAsyncReader::new(reader), + } + } +} + +impl TextBody> { + /// Creates a `TextBody` from an asynchronous body. + /// + /// ```no_run + /// use ylong_http::body::TextBody; + /// + /// async fn text_body_from_async_body() { + /// let reader = "Hello World"; + /// let body = TextBody::from_async_body(reader.as_bytes()); + /// } + /// ``` + pub fn from_async_body(body: T) -> Self { + Self { + from: FromAsyncBody::new(body), + } + } +} + +impl<'a> sync_impl::Body for TextBody> { + type Error = Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + Read::read(&mut *self.from, buf) + } +} + +impl<'c> async_impl::Body for TextBody> { + type Error = Error; + + fn poll_data( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Poll::Ready(Read::read(&mut *self.from, buf)) + } +} + +impl sync_impl::Body for TextBody> { + type Error = Error; + + fn data(&mut self, buf: &mut [u8]) -> Result { + self.from.read(buf) + } +} + +impl async_impl::Body for TextBody> { + type Error = Error; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut buf = ReadBuf::new(buf); + match Pin::new(&mut *self.from).poll_read(cx, &mut buf) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(buf.filled().len())), + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } +} + +impl async_impl::Body for TextBody> { + type Error = T::Error; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut *self.from).poll_data(cx, buf) + } +} + +/// A decoder for decoding plaintext body. +/// +/// You need to provide the decoder with a body length and some byte slices +/// containing a legal body. The decoder will divide the correct body and +/// redundant parts according to the `HTTP` syntax. +/// +/// This decoder supports decoding segmented byte slices. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::body::TextBodyDecoder; +/// +/// // Creates a decoder and set the body length to 20. +/// let mut decoder = TextBodyDecoder::new(20); +/// +/// // Provides the decoder with the first slice that may contain the body data. +/// // The length of this slice is 10, which is less than 20, so it is considered +/// // legal body data. +/// // The remaining body length is 10 after decoding. +/// let slice1 = b"This is a "; +/// let (text, left) = decoder.decode(slice1); +/// // Since the slice provided before is not enough for the decoder to +/// // complete the decoding, the status of the returned `Text` is `Partial` +/// // and no left data is returned. +/// assert!(text.is_partial()); +/// assert_eq!(text.data(), b"This is a "); +/// assert!(left.is_empty()); +/// +/// // Provides the decoder with the second slice that may contain the body data. +/// // The data length is 26, which is more than 10, so the first 10 bytes of +/// // the data will be considered legal body, and the rest will be considered +/// // redundant data. +/// let slice2 = b"text body.[REDUNDANT DATA]"; +/// let (text, left) = decoder.decode(slice2); +/// // Since the body data is fully decoded, the status of the returned `Text` +/// // is `Complete`. The left data is also returned. +/// assert!(text.is_complete()); +/// assert_eq!(text.data(), b"text body."); +/// assert_eq!(left, b"[REDUNDANT DATA]"); +/// +/// // Provides the decoder with the third slice. Since the body data has been +/// // fully decoded, the given slice is regard as redundant data. +/// let slice3 = b"[REDUNDANT DATA]"; +/// let (text, left) = decoder.decode(slice3); +/// assert!(text.is_complete()); +/// assert!(text.data().is_empty()); +/// assert_eq!(left, b"[REDUNDANT DATA]"); +/// ``` +pub struct TextBodyDecoder { + left: usize, +} + +impl TextBodyDecoder { + /// Creates a new `TextBodyDecoder` from a body length. + /// + /// This body length generally comes from the `Content-Length` field. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBodyDecoder; + /// + /// let decoder = TextBodyDecoder::new(10); + /// ``` + pub fn new(length: usize) -> TextBodyDecoder { + TextBodyDecoder { left: length } + } + + /// Decodes a byte slice that may contain a plaintext body. This method + /// supports decoding segmented byte slices. + /// + /// After each call to this method, a `Text` and a `&[u8]` are returned. + /// `Text` contains a piece of legal body data inside. The returned `&[u8]` + /// contains redundant data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBodyDecoder; + /// + /// // Creates a decoder and set the body length to 20. + /// let mut decoder = TextBodyDecoder::new(20); + /// + /// // Provides the decoder with the first slice that may contain the body data. + /// // The length of this slice is 10, which is less than 20, so it is considered + /// // legal body data. + /// // The remaining body length is 10 after decoding. + /// let slice1 = b"This is a "; + /// let (text, left) = decoder.decode(slice1); + /// // Since the slice provided before is not enough for the decoder to + /// // complete the decoding, the status of the returned `Text` is `Partial` + /// // and no left data is returned. + /// assert!(text.is_partial()); + /// assert_eq!(text.data(), b"This is a "); + /// assert!(left.is_empty()); + /// + /// // Provides the decoder with the second slice that may contain the body data. + /// // The data length is 26, which is more than 10, so the first 10 bytes of + /// // the data will be considered legal body, and the rest will be considered + /// // redundant data. + /// let slice2 = b"text body.[REDUNDANT DATA]"; + /// let (text, left) = decoder.decode(slice2); + /// // Since the body data is fully decoded, the status of the returned `Text` + /// // is `Complete`. The left data is also returned. + /// assert!(text.is_complete()); + /// assert_eq!(text.data(), b"text body."); + /// assert_eq!(left, b"[REDUNDANT DATA]"); + /// + /// // Provides the decoder with the third slice. Since the body data has been + /// // fully decoded, the given slice is regard as redundant data. + /// let slice3 = b"[REDUNDANT DATA]"; + /// let (text, left) = decoder.decode(slice3); + /// assert!(text.is_complete()); + /// assert!(text.data().is_empty()); + /// assert_eq!(left, b"[REDUNDANT DATA]"); + /// ``` + pub fn decode<'a>(&mut self, buf: &'a [u8]) -> (Text<'a>, &'a [u8]) { + if self.left == 0 { + return (Text::complete(&buf[..0]), buf); + } + + let size = min(self.left, buf.len()); + self.left -= size; + if self.left == 0 { + (Text::complete(&buf[..size]), &buf[size..]) + } else { + (Text::partial(&buf[..size]), &buf[size..]) + } + } +} + +/// Decode result of a text buffer. +/// The `state` records the decode status, and the data records the decoded data. +#[derive(Debug)] +pub struct Text<'a> { + state: TextState, + data: &'a [u8], +} + +impl<'a> Text<'a> { + /// Checks whether this `Text` contains the last valid part of the body data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBodyDecoder; + /// + /// let bytes = b"This is a "; + /// let mut decoder = TextBodyDecoder::new(20); + /// let (text, _) = decoder.decode(bytes); + /// assert!(!text.is_complete()); + /// + /// let bytes = b"text body."; + /// let (text, _) = decoder.decode(bytes); + /// assert!(text.is_complete()); + /// ``` + pub fn is_complete(&self) -> bool { + matches!(self.state, TextState::Complete) + } + + /// Checks whether this `Text` contains a non-last part of the body data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBodyDecoder; + /// + /// let bytes = b"This is a "; + /// let mut decoder = TextBodyDecoder::new(20); + /// let (text, _) = decoder.decode(bytes); + /// assert!(text.is_partial()); + /// + /// let bytes = b"text body."; + /// let (text, _) = decoder.decode(bytes); + /// assert!(!text.is_partial()); + /// ``` + pub fn is_partial(&self) -> bool { + !self.is_complete() + } + + /// Gets the underlying data of this `Text`. The returned data is a part + /// of the body data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::TextBodyDecoder; + /// + /// let bytes = b"This is a text body."; + /// let mut decoder = TextBodyDecoder::new(20); + /// let (text, _) = decoder.decode(bytes); + /// assert_eq!(text.data(), b"This is a text body."); + /// ``` + pub fn data(&self) -> &[u8] { + self.data + } + + pub(crate) fn complete(data: &'a [u8]) -> Self { + Self { + state: TextState::Complete, + data, + } + } + + pub(crate) fn partial(data: &'a [u8]) -> Self { + Self { + state: TextState::Partial, + data, + } + } +} + +#[derive(Debug)] +enum TextState { + Partial, + Complete, +} + +#[cfg(test)] +mod ut_text { + use crate::body::text::{TextBody, TextBodyDecoder}; + + /// UT test cases for `TextBody::from_bytes`. + /// + /// # Brief + /// 1. Calls `TextBody::from_bytes()` to create a `TextBody`. + #[test] + fn ut_text_body_from_bytes() { + let bytes = b"Hello World!"; + let _body = TextBody::from_bytes(bytes); + // Success if no panic. + } + + /// UT test cases for `TextBody::from_reader`. + /// + /// # Brief + /// 1. Calls `TextBody::from_reader()` to create a `TextBody`. + #[test] + fn ut_text_body_from_reader() { + let reader = "Hello World!".as_bytes(); + let _body = TextBody::from_reader(reader); + // Success if no panic. + } + + /// UT test cases for `TextBody::from_async_reader`. + /// + /// # Brief + /// 1. Calls `TextBody::from_async_reader()` to create a `TextBody`. + #[test] + fn ut_text_body_from_async_reader() { + let reader = "Hello World!".as_bytes(); + let _body = TextBody::from_async_reader(reader); + // Success if no panic. + } + + /// UT test cases for `sync_impl::Body::data` of `TextBody>`. + /// + /// # Brief + /// 1. Creates a `TextBody>`. + /// 2. Calls its `sync_impl::Body::data` method and then checks the results. + #[test] + fn ut_text_body_from_bytes_syn_data() { + use crate::body::sync_impl::Body; + + let bytes = b"Hello World!"; + let mut body = TextBody::from_bytes(bytes); + let mut buf = [0u8; 5]; + + let size = body.data(&mut buf).expect("First read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b"Hello"); + + let size = body.data(&mut buf).expect("Second read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b" Worl"); + + let size = body.data(&mut buf).expect("Third read failed."); + assert_eq!(size, 2); + assert_eq!(&buf[..size], b"d!"); + } + + /// UT test cases for `async_impl::Body::data` of `TextBody>`. + /// + /// # Brief + /// 1. Creates a `TextBody>`. + /// 2. Calls its `async_impl::Body::data` method and then checks the results. + #[tokio::test] + async fn ut_text_body_from_bytes_asyn_data() { + use crate::body::async_impl::Body; + + let bytes = b"Hello World!"; + let mut body = TextBody::from_bytes(bytes); + let mut buf = [0u8; 5]; + + let size = body.data(&mut buf).await.expect("First read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b"Hello"); + + let size = body.data(&mut buf).await.expect("Second read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b" Worl"); + + let size = body.data(&mut buf).await.expect("Third read failed."); + assert_eq!(size, 2); + assert_eq!(&buf[..size], b"d!"); + } + + /// UT test cases for `sync_impl::Body::data` of `TextBody>`. + /// + /// # Brief + /// 1. Creates a `TextBody>`. + /// 2. Calls its `sync_impl::Body::data` method and then checks the results. + #[test] + fn ut_text_body_from_reader_syn_data() { + use crate::body::sync_impl::Body; + + let reader = "Hello World!".as_bytes(); + let mut body = TextBody::from_reader(reader); + let mut buf = [0u8; 5]; + + let size = body.data(&mut buf).expect("First read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b"Hello"); + + let size = body.data(&mut buf).expect("Second read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b" Worl"); + + let size = body.data(&mut buf).expect("Third read failed."); + assert_eq!(size, 2); + assert_eq!(&buf[..size], b"d!"); + } + + /// UT test cases for `async_impl::Body::data` of `TextBody>`. + /// + /// # Brief + /// 1. Creates a `TextBody>`. + /// 2. Calls its `async_impl::Body::data` method and then checks the results. + #[tokio::test] + async fn ut_text_body_from_async_reader_asyn_data() { + use crate::body::async_impl::Body; + + let reader = "Hello World!".as_bytes(); + let mut body = TextBody::from_async_reader(reader); + let mut buf = [0u8; 5]; + + let size = body.data(&mut buf).await.expect("First read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b"Hello"); + + let size = body.data(&mut buf).await.expect("Second read failed."); + assert_eq!(size, 5); + assert_eq!(&buf[..size], b" Worl"); + + let size = body.data(&mut buf).await.expect("Third read failed."); + assert_eq!(size, 2); + assert_eq!(&buf[..size], b"d!"); + } + + /// UT test cases for `TextBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `TextBodyDecoder` by calling `TextBodyDecoder::new`. + /// 2. Decodes text body by calling `TextBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_text_body_decoder_decode() { + // Test 1: + let bytes = b"this is the text body! and this is remaining data"; + let mut decoder = TextBodyDecoder::new(22); + let (text, left) = decoder.decode(&bytes[..4]); + assert!(text.is_partial()); + assert_eq!(text.data(), b"this"); + assert!(left.is_empty()); + + let (text, left) = decoder.decode(&bytes[4..11]); + assert!(text.is_partial()); + assert_eq!(text.data(), b" is the"); + assert!(left.is_empty()); + + let (text, left) = decoder.decode(&bytes[11..26]); + assert!(text.is_complete()); + assert_eq!(text.data(), b" text body!"); + assert_eq!(left, b" and"); + + let (text, left) = decoder.decode(&bytes[26..]); + assert!(text.is_complete()); + assert!(text.data().is_empty()); + assert_eq!(left, b" this is remaining data"); + + // Test 2: + let bytes = b"this is the text body! And this is remaining data"; + let mut decoder = TextBodyDecoder::new(22); + let (text, left) = decoder.decode(bytes); + assert!(text.is_complete()); + assert_eq!(text.data(), b"this is the text body!"); + assert_eq!(left, b" And this is remaining data"); + } +} diff --git a/ylong_http/src/error.rs b/ylong_http/src/error.rs new file mode 100644 index 0000000..2d6887b --- /dev/null +++ b/ylong_http/src/error.rs @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Errors that may occur in this crate. +//! +//! This module provide unified encapsulation of HTTP errors. +//! +//! [`HttpError`] encapsulates error information related to all http protocols including `UriError`, `H1Error`, etc. +//! +//! [`HttpError`]: HttpError + +use crate::request::uri::InvalidUri; +use core::fmt::{Debug, Display, Formatter}; +use std::convert::Infallible; +use std::error::Error; + +#[cfg(feature = "http1_1")] +use crate::h1::H1Error; + +#[cfg(feature = "http2")] +use crate::h2::H2Error; + +/// Errors that may occur when using this crate. +#[derive(Debug, Eq, PartialEq)] +pub struct HttpError { + kind: ErrorKind, +} + +impl From for HttpError { + fn from(kind: ErrorKind) -> Self { + HttpError { kind } + } +} + +impl From for HttpError { + fn from(err: InvalidUri) -> Self { + ErrorKind::Uri(err).into() + } +} + +#[cfg(feature = "http2")] +impl From for HttpError { + fn from(err: H2Error) -> Self { + ErrorKind::H2(err).into() + } +} + +impl From for HttpError { + fn from(_value: Infallible) -> Self { + unreachable!() + } +} + +impl Display for HttpError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Debug::fmt(self, f) + } +} + +impl Error for HttpError {} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum ErrorKind { + /// An invalid input parameter was passed to a method of this crate. + InvalidInput, + + /// Errors related to URIs. + Uri(InvalidUri), + + /// Errors related to `HTTP/1.1`. + #[cfg(feature = "http1_1")] + H1(H1Error), + + /// Errors related to `HTTP/2`. + #[cfg(feature = "http2")] + H2(H2Error), +} diff --git a/ylong_http/src/h1/error.rs b/ylong_http/src/h1/error.rs new file mode 100644 index 0000000..969c7d5 --- /dev/null +++ b/ylong_http/src/h1/error.rs @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// Errors that may occur when using `HTTP/1.1`. +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum H1Error { + /// The incoming bytes does not conform to the `HTTP/1.1` request format. + InvalidRequest, + + /// The incoming bytes does not conform to the `HTTP/1.1` response format. + InvalidResponse, +} diff --git a/ylong_http/src/h1/mod.rs b/ylong_http/src/h1/mod.rs new file mode 100644 index 0000000..a4599db --- /dev/null +++ b/ylong_http/src/h1/mod.rs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! `HTTP/1.1` Module. + +mod error; +mod request; +pub(crate) mod response; + +pub(crate) use error::H1Error; + +pub use request::RequestEncoder; +pub use response::ResponseDecoder; diff --git a/ylong_http/src/h1/request/encoder.rs b/ylong_http/src/h1/request/encoder.rs new file mode 100644 index 0000000..71311df --- /dev/null +++ b/ylong_http/src/h1/request/encoder.rs @@ -0,0 +1,774 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP/1.1 request encoder implementation. +//! +//! The encoder is used to serialize the request into the specified buf in +//! a certain format. +//! +//! # Examples +//! +//! ``` +//! use ylong_http::h1::RequestEncoder; +//! use ylong_http::request::Request; +//! +//! let request = Request::builder() +//! .method("GET") +//! .url("www.example.com") +//! .version("HTTP/1.1") +//! .header("ACCEPT","text/html") +//! .body(()) +//! .unwrap(); +//! +//! // Gets `RequestPart`. +//! let (part, _) = request.into_parts(); +//! let mut encoder = RequestEncoder::new(part); +//! +//! // We use `message` to store all the body data. +//! let mut message = Vec::new(); +//! // We use `buf` to store save temporary data. +//! let mut buf = [0u8; 20]; +//! +//! // First encoding, buf is filled. +//! let size = encoder.encode(&mut buf).unwrap(); +//! assert_eq!(&buf[..size], "GET www.example.com ".as_bytes()); +//! message.extend_from_slice(&buf[..size]); +//! +//! // Second encoding, buf is filled. +//! let size = encoder.encode(&mut buf).unwrap(); +//! assert_eq!(&buf[..size], "HTTP/1.1\r\naccept:tex".as_bytes()); +//! message.extend_from_slice(&buf[..size]); +//! +//! // Third encoding, part of buf is filled, this indicates that encoding has ended. +//! let size = encoder.encode(&mut buf).unwrap(); +//! assert_eq!(&buf[..size], "t/html\r\n\r\n".as_bytes()); +//! message.extend_from_slice(&buf[..size]); +//! +//! // We can assemble temporary data into a complete data. +//! let result = "GET www.example.com HTTP/1.1\r\naccept:text/html\r\n\r\n"; +//! assert_eq!(message.as_slice(), result.as_bytes()); +//! ``` + +use crate::error::{ErrorKind, HttpError}; +use crate::headers::{HeaderName, Headers, HeadersIntoIter}; +use crate::request::method::Method; +use crate::request::uri::Uri; +use crate::request::RequestPart; +use crate::version::Version; +use std::io::Read; + +/// A encoder that is used to encode request message in `HTTP/1.1` format. +/// +/// This encoder supports you to use the encode method multiple times to output +/// the result in multiple bytes slices. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::h1::RequestEncoder; +/// use ylong_http::request::Request; +/// +/// let request = Request::builder() +/// .method("GET") +/// .url("www.example.com") +/// .version("HTTP/1.1") +/// .header("ACCEPT","text/html") +/// .body(()) +/// .unwrap(); +/// +/// // Gets `RequestPart`. +/// let (part, _) = request.into_parts(); +/// let mut encoder = RequestEncoder::new(part); +/// +/// // We use `message` to store all the body data. +/// let mut message = Vec::new(); +/// // We use `buf` to store save temporary data. +/// let mut buf = [0u8; 20]; +/// +/// // First encoding, buf is filled. +/// let size = encoder.encode(&mut buf).unwrap(); +/// assert_eq!(&buf[..size], "GET www.example.com ".as_bytes()); +/// message.extend_from_slice(&buf[..size]); +/// +/// // Second encoding, buf is filled. +/// let size = encoder.encode(&mut buf).unwrap(); +/// assert_eq!(&buf[..size], "HTTP/1.1\r\naccept:tex".as_bytes()); +/// message.extend_from_slice(&buf[..size]); +/// +/// // Third encoding, part of buf is filled, this indicates that encoding has ended. +/// let size = encoder.encode(&mut buf).unwrap(); +/// assert_eq!(&buf[..size], "t/html\r\n\r\n".as_bytes()); +/// message.extend_from_slice(&buf[..size]); +/// +/// // We can assemble temporary data into a complete data. +/// let result = "GET www.example.com HTTP/1.1\r\naccept:text/html\r\n\r\n"; +/// assert_eq!(message.as_slice(), result.as_bytes()); +/// ``` +pub struct RequestEncoder { + encode_status: EncodeState, + method_part: EncodeMethod, + method_sp_part: EncodeSp, + uri_part: EncodeUri, + uri_sp_part: EncodeSp, + version_part: EncodeVersion, + version_crlf_part: EncodeCrlf, + headers_part: EncodeHeader, + headers_crlf_part: EncodeCrlf, + is_proxy: bool, +} + +enum EncodeState { + Method, // "Method" phase of encoding request-message. + MethodSp, // "MethodSp" phase of encoding whitespace after method. + Uri, // "Uri" phase of encoding request-message. + UriSp, // "UriSp" phase of encoding whitespace after uri. + Version, // "Version" phase of encoding request-message. + VersionCrlf, // "VersionCrlf" phase of encoding whitespace after version. + Header, // "Header" phase of encoding request-message. + HeaderCrlf, // "HeaderCrlf" phase of encoding /r/n after header. + EncodeFinished, // "EncodeFinished" phase of finishing the encoding. +} + +// Component encoding status. +enum TokenStatus { + Complete(T), // The current component is completely encoded. + Partial(E), // The current component is partially encoded. +} + +type TokenResult = Result, HttpError>; + +impl RequestEncoder { + /// Creates a new `RequestEncoder` from a `RequestPart`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h1::RequestEncoder; + /// use ylong_http::request::Request; + /// + /// let request = Request::builder() + /// .method("GET") + /// .url("www.example.com") + /// .version("HTTP/1.1") + /// .header("ACCEPT","text/html") + /// .body(()) + /// .unwrap(); + /// + /// let (part, _) = request.into_parts(); + /// let encoder = RequestEncoder::new(part); + /// ``` + pub fn new(part: RequestPart) -> Self { + Self { + encode_status: EncodeState::Method, + method_part: EncodeMethod::new(part.method), + method_sp_part: EncodeSp::new(), + uri_part: EncodeUri::new(part.uri, false), + uri_sp_part: EncodeSp::new(), + version_part: EncodeVersion::new(part.version), + version_crlf_part: EncodeCrlf::new(), + headers_part: EncodeHeader::new(part.headers), + headers_crlf_part: EncodeCrlf::new(), + is_proxy: false, + } + } + + /// Encodes `RequestPart` into target buf and returns the number of + /// bytes written. + /// + /// If the length of buf is not enough to write all the output results, + /// the state will be saved until the next call to this method. + /// + /// # Return Value + /// + /// This method may return the following results: + /// + /// - `Ok(size) && size == buf.len()`: it means that buf has been completely + /// filled, but the result may not be fully output. You **must** call this + /// method again to obtain the rest part of the result. Otherwise you may + /// lose some parts of the result. + /// + /// - `Ok(size) && size < buf.len()`: it indicates that the result has been + /// fully output. + /// + /// - `Err(e)`: it indicates that an error has occurred during encoding. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h1::RequestEncoder; + /// use ylong_http::request::Request; + /// + /// let request = Request::builder() + /// .method("GET") + /// .url("www.example.com") + /// .version("HTTP/1.1") + /// .header("ACCEPT", "text/html") + /// .body(()) + /// .unwrap(); + /// + /// let (part, _) = request.into_parts(); + /// let mut encoder = RequestEncoder::new(part); + /// + /// let mut buf = [0_u8; 10]; + /// let mut message = Vec::new(); + /// let mut idx = 0; + /// loop { + /// let size = encoder.encode(&mut buf).unwrap(); + /// message.extend_from_slice(&buf[..size]); + /// if size < buf.len() { + /// break; + /// } + /// } + /// + /// let result = "GET www.example.com HTTP/1.1\r\naccept:text/html\r\n\r\n"; + /// assert_eq!(message.as_slice(), result.as_bytes()); + /// ``` + pub fn encode(&mut self, dst: &mut [u8]) -> Result { + if dst.is_empty() { + return Err(ErrorKind::InvalidInput.into()); + } + let mut count = 0; + while count != dst.len() { + count += match self.encode_status { + EncodeState::Method => self.method_encode(&mut dst[count..]), + EncodeState::MethodSp => self.method_sp_encode(&mut dst[count..]), + EncodeState::Uri => self.uri_encode(&mut dst[count..]), + EncodeState::UriSp => self.uri_sp_encode(&mut dst[count..]), + EncodeState::Version => self.version_encode(&mut dst[count..]), + EncodeState::VersionCrlf => self.version_crlf_encode(&mut dst[count..]), + EncodeState::Header => self.header_encode(&mut dst[count..]), + EncodeState::HeaderCrlf => self.header_crlf_encode(&mut dst[count..]), + EncodeState::EncodeFinished => return Ok(count), + }?; + } + Ok(dst.len()) + } + + /// Sets the `is_proxy` flag. + /// + /// If you enable the flag, the uri part will be encoded as a relative path + /// in the headline. Otherwise the uri part will be fully encoded in the + /// headline. + /// + /// You should use this method before the uri part being encoded. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h1::RequestEncoder; + /// use ylong_http::request::Request; + /// + /// let request = Request::builder() + /// .method("GET") + /// .url("www.example.com") + /// .version("HTTP/1.1") + /// .header("ACCEPT","text/html") + /// .body(()) + /// .unwrap(); + /// + /// let (part, _) = request.into_parts(); + /// let mut encoder = RequestEncoder::new(part); + /// // After you create the request encoder, users can choose to set the proxy. + /// encoder.set_proxy(true); + /// + /// let mut buf = [0u8; 1024]; + /// let size = encoder.encode(&mut buf).unwrap(); + /// // If you set the `is_proxy` flag, the uri will be encoded as a relative path. + /// assert_eq!(&buf[..size], b"GET / HTTP/1.1\r\naccept:text/html\r\n\r\n".as_slice()); + /// ``` + pub fn set_proxy(&mut self, is_proxy: bool) { + self.is_proxy = is_proxy; + } + + fn method_encode(&mut self, dst: &mut [u8]) -> Result { + match self.method_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::MethodSp; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::Method; + Ok(output_size) + } + } + } + + fn method_sp_encode(&mut self, dst: &mut [u8]) -> Result { + match self.method_sp_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.uri_part.is_proxy = self.is_proxy; + self.encode_status = EncodeState::Uri; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::MethodSp; + Ok(output_size) + } + } + } + + fn uri_encode(&mut self, dst: &mut [u8]) -> Result { + match self.uri_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::UriSp; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::Uri; + Ok(output_size) + } + } + } + + fn uri_sp_encode(&mut self, dst: &mut [u8]) -> Result { + match self.uri_sp_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::Version; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::UriSp; + Ok(output_size) + } + } + } + + fn version_encode(&mut self, dst: &mut [u8]) -> Result { + match self.version_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::VersionCrlf; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::Version; + Ok(output_size) + } + } + } + + fn version_crlf_encode(&mut self, dst: &mut [u8]) -> Result { + match self.version_crlf_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::Header; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::VersionCrlf; + Ok(output_size) + } + } + } + + fn header_encode(&mut self, dst: &mut [u8]) -> Result { + match self.headers_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::HeaderCrlf; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::Header; + Ok(output_size) + } + } + } + + fn header_crlf_encode(&mut self, dst: &mut [u8]) -> Result { + match self.headers_crlf_part.encode(dst)? { + TokenStatus::Complete(output_size) => { + self.encode_status = EncodeState::EncodeFinished; + Ok(output_size) + } + TokenStatus::Partial(output_size) => { + self.encode_status = EncodeState::HeaderCrlf; + Ok(output_size) + } + } + } +} + +struct EncodeMethod { + inner: Method, + src_idx: usize, +} + +impl EncodeMethod { + fn new(method: Method) -> Self { + Self { + inner: method, + src_idx: 0, + } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + let method = self.inner.as_str().as_bytes(); + WriteData::new(method, &mut self.src_idx, buf).write() + } +} + +struct EncodeUri { + inner: Vec, + inner_proxy: Vec, + src_idx: usize, + is_proxy: bool, +} + +impl EncodeUri { + fn new(uri: Uri, is_proxy: bool) -> Self { + let mut init_proxy_uri = vec![]; + let path = uri.path_and_query(); + if let Some(p) = path { + init_proxy_uri = p.as_bytes().to_vec(); + } else { + init_proxy_uri.extend_from_slice(b"/"); + } + let init_uri = uri.to_string().into_bytes(); + Self { + inner: init_uri, + inner_proxy: init_proxy_uri, + src_idx: 0, + is_proxy, + } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + let mut uri = self.inner.as_slice(); + if self.is_proxy { + uri = self.inner_proxy.as_slice(); + } + WriteData::new(uri, &mut self.src_idx, buf).write() + } +} + +struct EncodeVersion { + inner: Version, + src_idx: usize, +} + +impl EncodeVersion { + fn new(version: Version) -> Self { + Self { + inner: version, + src_idx: 0, + } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + let version = self.inner.as_str().as_bytes(); + let mut task = WriteData::new(version, &mut self.src_idx, buf); + task.write() + } +} + +struct EncodeHeader { + inner: HeadersIntoIter, + status: Option, + name: HeaderName, + value: Vec, + name_idx: usize, + colon_idx: usize, + value_idx: usize, +} + +enum HeaderStatus { + Name, + Colon, + Value, + Crlf(EncodeCrlf), + EmptyHeader, +} + +impl EncodeHeader { + fn new(header: Headers) -> Self { + let mut header_iter = header.into_iter(); + if let Some((header_name, header_value)) = header_iter.next() { + Self { + inner: header_iter, + status: Some(HeaderStatus::Name), + name: header_name, + value: header_value.to_str().unwrap().into_bytes(), + name_idx: 0, + colon_idx: 0, + value_idx: 0, + } + } else { + Self { + inner: header_iter, + status: Some(HeaderStatus::EmptyHeader), + name: HeaderName::from_bytes(" ".as_bytes()).unwrap(), + value: vec![], + name_idx: 0, + colon_idx: 0, + value_idx: 0, + } + } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + match self.status.take().unwrap() { + HeaderStatus::Name => { + let name = self.name.as_bytes(); + let mut task = WriteData::new(name, &mut self.name_idx, buf); + match task.write()? { + TokenStatus::Complete(size) => { + self.status = Some(HeaderStatus::Colon); + Ok(TokenStatus::Partial(size)) + } + TokenStatus::Partial(size) => { + self.status = Some(HeaderStatus::Name); + Ok(TokenStatus::Partial(size)) + } + } + } + HeaderStatus::Colon => { + let colon = ":".as_bytes(); + let mut task = WriteData::new(colon, &mut self.colon_idx, buf); + match task.write()? { + TokenStatus::Complete(size) => { + self.status = Some(HeaderStatus::Value); + Ok(TokenStatus::Partial(size)) + } + TokenStatus::Partial(size) => { + self.status = Some(HeaderStatus::Colon); + Ok(TokenStatus::Partial(size)) + } + } + } + HeaderStatus::Value => { + let value = self.value.as_slice(); + let mut task = WriteData::new(value, &mut self.value_idx, buf); + match task.write()? { + TokenStatus::Complete(size) => { + let crlf = EncodeCrlf::new(); + self.status = Some(HeaderStatus::Crlf(crlf)); + Ok(TokenStatus::Partial(size)) + } + TokenStatus::Partial(size) => { + self.status = Some(HeaderStatus::Value); + Ok(TokenStatus::Partial(size)) + } + } + } + HeaderStatus::Crlf(mut crlf) => match crlf.encode(buf)? { + TokenStatus::Complete(size) => { + if let Some(iter) = self.inner.next() { + let (header_name, header_value) = iter; + self.status = Some(HeaderStatus::Name); + self.name = header_name; + self.value = header_value.to_str().unwrap().into_bytes(); + self.name_idx = 0; + self.colon_idx = 0; + self.value_idx = 0; + Ok(TokenStatus::Partial(size)) + } else { + Ok(TokenStatus::Complete(size)) + } + } + TokenStatus::Partial(size) => { + self.status = Some(HeaderStatus::Crlf(crlf)); + Ok(TokenStatus::Partial(size)) + } + }, + HeaderStatus::EmptyHeader => Ok(TokenStatus::Complete(0)), + } + } +} + +struct EncodeSp { + src_idx: usize, +} + +impl EncodeSp { + fn new() -> Self { + Self { src_idx: 0 } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + let sp = " ".as_bytes(); + let mut task = WriteData::new(sp, &mut self.src_idx, buf); + task.write() + } +} + +struct EncodeCrlf { + src_idx: usize, +} + +impl EncodeCrlf { + fn new() -> Self { + Self { src_idx: 0 } + } + + fn encode(&mut self, buf: &mut [u8]) -> TokenResult { + let crlf = "\r\n".as_bytes(); + let mut task = WriteData::new(crlf, &mut self.src_idx, buf); + task.write() + } +} + +struct WriteData<'a> { + src: &'a [u8], + src_idx: &'a mut usize, + dst: &'a mut [u8], +} + +impl<'a> WriteData<'a> { + fn new(src: &'a [u8], src_idx: &'a mut usize, dst: &'a mut [u8]) -> Self { + WriteData { src, src_idx, dst } + } + + fn write(&mut self) -> TokenResult { + let src_idx = *self.src_idx; + let input_len = self.src.len() - src_idx; + let output_len = self.dst.len(); + let num = (&self.src[src_idx..]).read(self.dst).unwrap(); + if output_len >= input_len { + return Ok(TokenStatus::Complete(num)); + } + *self.src_idx += num; + Ok(TokenStatus::Partial(num)) + } +} +impl Default for RequestEncoder { + fn default() -> Self { + RequestEncoder::new(RequestPart::default()) + } +} + +#[cfg(test)] +mod ut_request_encoder { + use super::RequestEncoder; + use crate::request::{Request, RequestBuilder}; + + /// UT test cases for `RequestEncoder::new`. + /// + /// # Brief + /// 1. Calls `RequestEncoder::new` to create a `RequestEncoder`. + #[test] + fn ut_request_encoder_new() { + let request = Request::new(()); + let (part, _) = request.into_parts(); + let _encoder = RequestEncoder::new(part); + // Success if no panic. + } + + /// UT test cases for `RequestEncoder::encode`. + /// + /// # Brief + /// 1. Creates a `Request` by calling methods of `Request::builder`. + /// 2. Gets a request part by calling `Request::into_parts`. + /// 3. Creates a `RequestEncoder` by calling `RequestBuilder::new`. + /// 4. Calls `RequestEncoder::encode` method in a loop and collects the results. + /// 5. Checks if the test result is correct. + #[test] + fn ut_request_encoder_encode_1() { + macro_rules! encoder_test_case { + ( + Method: $method:expr, + Uri: $uri:expr, + Version: $version:expr, + $(Header: $name:expr, $value:expr,)* + RequestLine: $request_line:expr, + ) => {{ + let request = Request::builder() + .method($method) + .url($uri) + .version($version) + $(.header($name, $value))* + .body(()) + .unwrap(); + + let (part, _) = request.into_parts(); + let mut encoder = RequestEncoder::new(part); + let mut buf = [0u8; 5]; + let mut res = Vec::new(); + loop { + let size = encoder.encode(&mut buf).unwrap(); + res.extend_from_slice(&buf[..size]); + if size < buf.len() { + break; + } + } + + let str = std::str::from_utf8(res.as_slice()) + .expect("Cannot convert res to &str"); + + assert!(str.find($request_line).is_some()); + + $( + let target_header = format!( + "{}:{}\r\n", + ($name).to_lowercase(), + ($value).to_lowercase(), + ); + assert!(str.find(target_header.as_str()).is_some()); + )* + }}; + } + + // No header-lines. + encoder_test_case! { + Method: "GET", + Uri: "www.example.com", + Version: "HTTP/1.1", + RequestLine: "GET www.example.com HTTP/1.1\r\n", + } + + // 1 header-line. + encoder_test_case! { + Method: "GET", + Uri: "www.example.com", + Version: "HTTP/1.1", + Header: "ACCEPT", "text/html", + RequestLine: "GET www.example.com HTTP/1.1\r\n", + } + + // More than 1 header-lines. + encoder_test_case! { + Method: "GET", + Uri: "www.example.com", + Version: "HTTP/1.1", + Header: "ACCEPT", "text/html", + Header: "HOST", "127.0.0.1", + RequestLine: "GET www.example.com HTTP/1.1\r\n", + } + } + + /// UT test cases for `RequestEncoder::set_proxy`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `RequestBuilder::build`. + /// 2. Calls set_proxy. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_encoder_set_proxy() { + let request = RequestBuilder::new() + .method("GET") + .url("www.example.com") + .version("HTTP/1.1") + .body(()) + .unwrap(); + let (part, _) = request.into_parts(); + let mut encoder = RequestEncoder::new(part); + assert!(!encoder.is_proxy); + + encoder.set_proxy(true); + assert!(encoder.is_proxy); + + let mut buf = [0u8; 100]; + let size = encoder.encode(&mut buf).unwrap(); + let res = std::str::from_utf8(&buf[..size]).unwrap(); + assert_eq!(res, "GET / HTTP/1.1\r\n\r\n"); + } +} diff --git a/ylong_http/src/h1/request/mod.rs b/ylong_http/src/h1/request/mod.rs new file mode 100644 index 0000000..65bc04f --- /dev/null +++ b/ylong_http/src/h1/request/mod.rs @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! `HTTP/1.1` request serialization and deserialization. + +// TODO: RequestDecoder implementation. + +mod encoder; + +pub use encoder::RequestEncoder; diff --git a/ylong_http/src/h1/response/decoder.rs b/ylong_http/src/h1/response/decoder.rs new file mode 100644 index 0000000..b37520b --- /dev/null +++ b/ylong_http/src/h1/response/decoder.rs @@ -0,0 +1,952 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{ErrorKind, HttpError}; +use crate::h1::H1Error; +use crate::headers::Headers; +use crate::response::status::StatusCode; +use crate::response::ResponsePart; +use crate::version::Version; +use core::mem::take; + +/// `HTTP/1.1` response decoder, which support decoding multi-segment byte +/// streams into `Response`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::h1::ResponseDecoder; +/// use ylong_http::version::Version; +/// +/// // The complete message is: +/// // "HTTP/1.1 304 OK\r\nContent-Length:4\r\n\r\nbody" +/// let strings = [ +/// "HTTP/1.1 304 OK\r\nCon", +/// "tent-Length:", +/// "4\r\n\r\nbody", +/// ]; +/// +/// // We need to create a decoder first. +/// let mut decoder = ResponseDecoder::new(); +/// +/// // Then we use it to decode some bytes. +/// // The first part of bytes is correct, but we need more bytes to get a `ResponsePart`. +/// assert_eq!(decoder.decode(strings[0].as_bytes()), Ok(None)); +/// // The second part is also correct, but we still need more bytes. +/// assert_eq!(decoder.decode(strings[1].as_bytes()), Ok(None)); +/// // After decoding the third part, a complete `ResponsePart` is decoded. +/// let (part, body) = decoder.decode(strings[2].as_bytes()).unwrap().unwrap(); +/// +/// // Then we can use the decode result. +/// assert_eq!(part.version.as_str(), "HTTP/1.1"); +/// assert_eq!(part.status.as_u16(), 304); +/// assert_eq!(part.headers.get("content-length").unwrap().to_str().unwrap(), "4"); +/// assert_eq!(body, b"body"); +/// ``` +pub struct ResponseDecoder { + stage: ParseStage, // Parsing phase, corresponding to each component of response-message. + version: Option, + status_code: Option, + headers: Option, + head_key: Vec, // Cache the parsed header key. + rest: Vec, // Cache the response-message component whose current bytes segment is incomplete, + new_line: bool, // The value is true when the last byte of the current byte segment is CR. +} + +// Component parsing status +enum TokenStatus { + Complete(T), // The current component is completely parsed. + Partial(E), // The current component is not completely parsed. +} + +// ResponseDecoder parsing phase, All components of response-message are as follows: +// --------------------------------------------------------- +// | HTTP-version SP status-code SP [ reason-phrase ]CRLF | // status-line +// | *( field-name ":" OWS field-value OWS CRLF ) | // field-line +// | CRLF | +// | [message-body ] | +// --------------------------------------------------------- +#[derive(Clone)] +enum ParseStage { + Initial, // Decoder initialization phase, The decoder parses the bytes for the first time. + Version, // "HTTP-version" phase of parsing response-message + StatusCode, // "status-code" phase of parsing response-message + Reason, // "reason-phrase" phase of parsing response-message + StatusCrlf, // CRLF after "reason-phrase" of parsing response-message + Header(HeaderStage), // "field-line" phase of parsing response-message + BlankLine, // CRLF after "field-line" of parsing response-message +} + +// Stage of parsing field-line, the filed line component is as follows: +// ------------------------------------------------ +// | *( field-name ":" OWS field-value OWS CRLF ) | +// ------------------------------------------------ +#[derive(Clone)] +enum HeaderStage { + // Check whether the response-message contains field-line. + Start, + Key, + // OWS phase before "field-value" + OwsBefore, + Value, + Crlf, + // After a filed-line line is parsed, the parsing phase is EndCrlf. + // In this case, you need to check whether all field lines are ended. + EndCrlf, +} + +impl Default for ResponseDecoder { + fn default() -> Self { + Self::new() + } +} + +type TokenResult<'a> = Result, HttpError>; + +macro_rules! get_unparsed_or_return { + ($case:expr, $buffer:expr) => {{ + match $case { + Some(unparsed) => $buffer = unparsed, + None => return Ok(None), + } + }}; +} + +macro_rules! detect_blank_and_return { + ($case:expr, $buffer:expr) => {{ + if $buffer.is_empty() { + $case = ParseStage::Header(HeaderStage::EndCrlf); + return Ok(None); + } else if $buffer[0] == b'\r' || $buffer[0] == b'\n' { + return Ok(Some($buffer)); + } + }}; +} + +impl ResponseDecoder { + /// Creates a new `ResponseDecoder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h1::ResponseDecoder; + /// + /// let decoder = ResponseDecoder::new(); + /// ``` + pub fn new() -> Self { + ResponseDecoder { + stage: ParseStage::Initial, + version: None, + status_code: None, + headers: None, + head_key: vec![], + rest: vec![], + new_line: false, + } + } + + /// Decodes some bytes to get a complete `ResponsePart`. This method can be + /// invoked multiple times util a complete `ResponsePart` is returned. + /// + /// Only status line and field line is decoded. The body of `Response` is + /// not be decoded. + /// + /// Returns `Ok(None)` if decoder needs more bytes to decode. + /// + /// Returns `ResponsePart` and the remaining bytes if decoder has complete + /// decoding. The remaining bytes will be returned as a slice. + /// + /// Returns `Err` if the input is not syntactically valid. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::version::Version; + /// use ylong_http::h1::ResponseDecoder; + /// + /// let valid = [ + /// "HTTP/1.1", + /// " 304 OK\r\n\r\n", + /// ]; + /// let mut decoder = ResponseDecoder::new(); + /// // Returns `Ok(None)` if decoder needs more bytes to decode. + /// assert_eq!(decoder.decode(valid[0].as_bytes()), Ok(None)); + /// //Returns `ResponsePart` and a slice of bytes if decoder has complete decoding. + /// let (part, body) = decoder.decode(valid[1].as_bytes()).unwrap().unwrap(); + /// assert_eq!(part.version, Version::HTTP1_1); + /// assert_eq!(part.status.as_u16(), 304); + /// assert!(part.headers.is_empty()); + /// assert!(body.is_empty()); + /// + /// // Returns `Err` if the input is not syntactically valid. + /// let mut decoder = ResponseDecoder::new(); + /// let invalid_str = "invalid str".as_bytes(); + /// assert!(decoder.decode(invalid_str).is_err()); + /// ``` + pub fn decode<'a>( + &mut self, + buf: &'a [u8], + ) -> Result, HttpError> { + match self.stage { + ParseStage::Initial => self.version_phase(buf), + ParseStage::Version => self.version_phase(buf), + ParseStage::StatusCode => self.status_code_phase(buf), + ParseStage::Reason => self.reason_phase(buf), + ParseStage::StatusCrlf => self.status_crlf_phase(buf), + ParseStage::Header(_) => self.header_phase(buf), + ParseStage::BlankLine => self.blank_line_phase(buf), + } + } + + fn version_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::Version; + match status_token(buffer)? { + TokenStatus::Complete((version, unparsed)) => { + let version = self.take_value(version); + match version.as_slice() { + b"HTTP/1.1" => { + self.version = Some(Version::HTTP1_1); + self.status_code_phase(unparsed) + } + // TODO: Support for other `HTTP` versions. + _ => Err(ErrorKind::H1(H1Error::InvalidResponse).into()), + } + } + TokenStatus::Partial(rest) => { + self.rest.extend_from_slice(rest); + Ok(None) + } + } + } + + fn status_code_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::StatusCode; + match status_token(buffer)? { + TokenStatus::Complete((code, unparsed)) => { + let code = self.take_value(code); + self.status_code = Some( + StatusCode::from_bytes(code.as_slice()) + .map_err(|_| HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))?, + ); + self.reason_phase(unparsed) + } + TokenStatus::Partial(rest) => { + self.rest.extend_from_slice(rest); + Ok(None) + } + } + } + + fn reason_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::Reason; + match decode_reason(buffer)? { + Some(unparsed) => self.status_crlf_phase(unparsed), + None => Ok(None), + } + } + + fn status_crlf_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::StatusCrlf; + match self.decode_status_crlf(buffer)? { + Some(unparsed) => self.header_phase_with_init(unparsed), + None => Ok(None), + } + } + + fn header_phase_with_init<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let headers = Headers::new(); + self.headers = Some(headers); + if buffer.is_empty() { + self.stage = ParseStage::Header(HeaderStage::Start); + return Ok(None); + } else if buffer[0] == b'\r' || buffer[0] == b'\n' { + return self.blank_line_phase(buffer); + } else { + self.stage = ParseStage::Header(HeaderStage::Key); + } + match self.decode_header(buffer)? { + Some(unparsed) => self.blank_line_phase(unparsed), + None => Ok(None), + } + } + + fn header_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + match self.decode_header(buffer)? { + Some(unparsed) => self.blank_line_phase(unparsed), + None => Ok(None), + } + } + + fn blank_line_phase<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::BlankLine; + match self.decode_status_crlf(buffer)? { + Some(unparsed) => { + let response_part = ResponsePart { + version: self.version.take().unwrap(), + status: self.status_code.take().unwrap(), + headers: self.headers.take().unwrap(), + }; + Ok(Some((response_part, unparsed))) + } + None => Ok(None), + } + } + + fn decode_status_crlf<'a>(&mut self, buffer: &'a [u8]) -> Result, HttpError> { + match consume_crlf(buffer, take(&mut self.new_line))? { + TokenStatus::Complete(unparsed) => { + self.new_line = false; + Ok(Some(unparsed)) + } + TokenStatus::Partial(0) => Ok(None), + TokenStatus::Partial(1) => { + self.new_line = true; + Ok(None) + } + _ => Err(ErrorKind::H1(H1Error::InvalidResponse).into()), + } + } + + fn decode_header<'a>(&mut self, buffer: &'a [u8]) -> Result, HttpError> { + return match &self.stage { + ParseStage::Header(header_stage) => match header_stage { + HeaderStage::Start => { + if buffer.is_empty() { + return Ok(None); + } else if buffer[0] == b'\r' || buffer[0] == b'\n' { + return Ok(Some(buffer)); + } else { + self.decode_header_from_key(buffer) + } + } + HeaderStage::Key => self.decode_header_from_key(buffer), + HeaderStage::OwsBefore => self.decode_header_from_ows_before(buffer), + HeaderStage::Value => self.decode_header_from_value(buffer), + HeaderStage::Crlf => self.decode_header_from_crlf(buffer), + HeaderStage::EndCrlf => self.decode_header_from_crlf_end(buffer), + }, + _ => Err(ErrorKind::H1(H1Error::InvalidResponse).into()), + }; + } + + fn decode_header_from_value<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let mut buffer = buffer; + loop { + get_unparsed_or_return!(self.decode_value(buffer)?, buffer); + get_unparsed_or_return!(self.decode_crlf(false, buffer)?, buffer); + detect_blank_and_return!(self.stage, buffer); + get_unparsed_or_return!(self.decode_key(buffer)?, buffer); + get_unparsed_or_return!(self.decode_ows(buffer)?, buffer); + } + } + + fn decode_header_from_key<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let mut buffer = buffer; + loop { + get_unparsed_or_return!(self.decode_key(buffer)?, buffer); + get_unparsed_or_return!(self.decode_ows(buffer)?, buffer); + get_unparsed_or_return!(self.decode_value(buffer)?, buffer); + get_unparsed_or_return!(self.decode_crlf(false, buffer)?, buffer); + detect_blank_and_return!(self.stage, buffer); + } + } + + fn decode_header_from_ows_before<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let mut buffer = buffer; + loop { + get_unparsed_or_return!(self.decode_ows(buffer)?, buffer); + get_unparsed_or_return!(self.decode_value(buffer)?, buffer); + get_unparsed_or_return!(self.decode_crlf(false, buffer)?, buffer); + detect_blank_and_return!(self.stage, buffer); + get_unparsed_or_return!(self.decode_key(buffer)?, buffer); + } + } + + fn decode_header_from_crlf<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let mut buffer = buffer; + loop { + get_unparsed_or_return!(self.decode_crlf(false, buffer)?, buffer); + detect_blank_and_return!(self.stage, buffer); + get_unparsed_or_return!(self.decode_key(buffer)?, buffer); + get_unparsed_or_return!(self.decode_ows(buffer)?, buffer); + get_unparsed_or_return!(self.decode_value(buffer)?, buffer); + } + } + + fn decode_header_from_crlf_end<'a>( + &mut self, + buffer: &'a [u8], + ) -> Result, HttpError> { + let mut buffer = buffer; + loop { + detect_blank_and_return!(self.stage, buffer); + get_unparsed_or_return!(self.decode_key(buffer)?, buffer); + get_unparsed_or_return!(self.decode_ows(buffer)?, buffer); + get_unparsed_or_return!(self.decode_value(buffer)?, buffer); + get_unparsed_or_return!(self.decode_crlf(false, buffer)?, buffer); + } + } + + fn decode_ows<'a>(&mut self, buffer: &'a [u8]) -> Result, HttpError> { + self.stage = ParseStage::Header(HeaderStage::OwsBefore); + trim_ows(buffer) + } + + fn decode_key<'a>(&mut self, buffer: &'a [u8]) -> Result, HttpError> { + self.stage = ParseStage::Header(HeaderStage::Key); + match get_header_name(buffer)? { + TokenStatus::Complete((key, unparsed)) => { + if !self.rest.is_empty() { + self.rest.extend_from_slice(key); + let key = take(&mut self.rest); + self.head_key = key; + } else { + self.head_key = key.to_vec(); + } + Ok(Some(unparsed)) + } + TokenStatus::Partial(rest) => { + self.rest.extend_from_slice(rest); + Ok(None) + } + } + } + + // TODO: Try use `&[u8]` instead of `Vec`. + fn take_value(&mut self, value: &[u8]) -> Vec { + if !self.rest.is_empty() { + self.rest.extend_from_slice(value); + take(&mut self.rest) + } else { + value.to_vec() + } + } + + fn decode_value<'a>(&mut self, buffer: &'a [u8]) -> Result, HttpError> { + self.stage = ParseStage::Header(HeaderStage::Value); + match get_header_value(buffer)? { + TokenStatus::Complete((value, unparsed)) => { + let complete_value = self.take_value(value); + let header_value = if let Some(last_visible) = complete_value + .iter() + .rposition(|b| *b != b' ' && *b != b'\t') + { + complete_value[..last_visible + 1].to_vec() + } else { + return Err(ErrorKind::H1(H1Error::InvalidResponse).into()); + }; + self.headers = header_insert( + take(&mut self.head_key), + header_value, + self.headers.take().unwrap(), + )?; + Ok(Some(unparsed)) + } + TokenStatus::Partial(rest) => { + self.rest.extend_from_slice(rest); + Ok(None) + } + } + } + + fn decode_crlf<'a>( + &mut self, + cr_meet: bool, + buffer: &'a [u8], + ) -> Result, HttpError> { + self.stage = ParseStage::Header(HeaderStage::Crlf); + match consume_crlf(buffer, cr_meet)? { + TokenStatus::Complete(unparsed) => { + self.new_line = false; + Ok(Some(unparsed)) + } + TokenStatus::Partial(step) => { + if step == 1 { + self.new_line = true; + } + Ok(None) + } + } + } +} + +fn status_token(buffer: &[u8]) -> TokenResult { + for (i, &b) in buffer.iter().enumerate() { + if b == b' ' { + return Ok(TokenStatus::Complete((&buffer[..i], &buffer[i + 1..]))); + } else if !is_valid_byte(b) { + return Err(ErrorKind::H1(H1Error::InvalidResponse).into()); + } + } + Ok(TokenStatus::Partial(buffer)) +} + +fn decode_reason(buffer: &[u8]) -> Result, HttpError> { + for (i, b) in buffer.iter().enumerate() { + if *b == b'\r' || *b == b'\n' { + return Ok(Some(&buffer[i..])); + } else if !is_valid_byte(*b) { + return Err(ErrorKind::H1(H1Error::InvalidResponse).into()); + } + } + Ok(None) +} + +fn consume_crlf(buffer: &[u8], cr_meet: bool) -> Result, HttpError> { + if buffer.is_empty() { + return Ok(TokenStatus::Partial(0)); + } + match buffer[0] { + b'\r' => { + if cr_meet { + Err(ErrorKind::H1(H1Error::InvalidResponse).into()) + } else if buffer.len() == 1 { + Ok(TokenStatus::Partial(1)) + } else if buffer[1] == b'\n' { + Ok(TokenStatus::Complete(&buffer[2..])) + } else { + Err(ErrorKind::H1(H1Error::InvalidResponse).into()) + } + } + b'\n' => Ok(TokenStatus::Complete(&buffer[1..])), + _ => Err(ErrorKind::H1(H1Error::InvalidResponse).into()), + } +} + +fn get_header_name(buffer: &[u8]) -> TokenResult { + for (i, b) in buffer.iter().enumerate() { + if *b == b':' { + return Ok(TokenStatus::Complete((&buffer[..i], &buffer[i + 1..]))); + } else if !HEADER_NAME_BYTES[*b as usize] { + return Err(ErrorKind::H1(H1Error::InvalidResponse).into()); + } + } + Ok(TokenStatus::Partial(buffer)) +} + +fn get_header_value(buffer: &[u8]) -> TokenResult { + for (i, b) in buffer.iter().enumerate() { + if *b == b'\r' || *b == b'\n' { + return Ok(TokenStatus::Complete((&buffer[..i], &buffer[i..]))); + } else if !HEADER_VALUE_BYTES[*b as usize] { + return Err(ErrorKind::H1(H1Error::InvalidResponse).into()); + } + } + Ok(TokenStatus::Partial(buffer)) +} + +fn trim_ows(buffer: &[u8]) -> Result, HttpError> { + for (i, &b) in buffer.iter().enumerate() { + match b { + b' ' | b'\t' => {} + _ => return Ok(Some(&buffer[i..])), + } + } + Ok(None) +} + +fn header_insert( + header_name: Vec, + header_value: Vec, + mut headers: Headers, +) -> Result, HttpError> { + let name = unsafe { String::from_utf8_unchecked(header_name) }; + let value = unsafe { String::from_utf8_unchecked(header_value) }; + // TODO: Convert `HeaderName` to lowercase when decoding it. + let key = name.to_lowercase(); + let header_name = key.as_str(); + let header_value = value.as_str(); + // If the response contains headers with the same name, add them to one `Headers`. + headers.append(header_name, header_value)?; + Ok(Some(headers)) +} + +// token = 1*tchar +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / +// "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +fn is_valid_byte(byte: u8) -> bool { + byte > 0x1F && byte < 0x7F +} + +// token = 1*tchar +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +// / DIGIT / ALPHA +// ; any VCHAR, except delimiters +// delimitersd = DQUOTE and "(),/:;<=>?@[\]{}" +#[rustfmt::skip] +pub(crate) static HEADER_NAME_BYTES: [bool; 256] = { + const __: bool = false; + const TT: bool = true; + [ +// \0 HT LF CR + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, +// \w ! " # $ % & ' ( ) * + , - . / + __, TT, __, TT, TT, TT, TT, TT, __, __, TT, TT, __, TT, TT, __, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, __, __, __, +// @ A B C D E F G H I J K L M N O + __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// P Q R S T U V W X Y Z [ \ ] ^ _ + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, TT, TT, +// ` a b c d e f g h i j k l m n o + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// p q r s t u v w x y z { | } ~ del + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, TT, __, TT, __, +// Expand ascii + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, + ] +}; + +// field-value = *( field-content / obs-fold ) +// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] +// field-vchar = VCHAR / obs-text +// +// obs-fold = CRLF 1*( SP / HTAB ) +// ; obsolete line folding +// ; see Section 3.2.4 +#[rustfmt::skip] +pub(crate) static HEADER_VALUE_BYTES: [bool; 256] = { + const __: bool = false; + const TT: bool = true; + [ +// \0 HT LF CR + __, __, __, __, __, __, __, __, __, TT, __, __, __, __, __, __, + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, +// \w ! " # $ % & ' ( ) * + , - . / + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// @ A B C D E F G H I J K L M N O + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// P Q R S T U V W X Y Z [ \ ] ^ _ + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// ` a b c d e f g h i j k l m n o + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, +// p q r s t u v w x y z { | } ~ del + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, +// Expand ascii + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, + ] +}; + +// TODO: Add more test cases. +#[cfg(test)] +mod ut_decoder { + use super::{H1Error, ResponseDecoder}; + use crate::error::{ErrorKind, HttpError}; + + macro_rules! test_unit_complete { + ($res1:expr, $res2:expr, $res3:expr, $res4:expr, $res5:expr) => {{ + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode($res1).unwrap().unwrap(); + assert_eq!($res2, result.0.version.as_str()); + assert_eq!($res3, result.0.status.as_u16()); + assert_eq!($res4.len(), result.0.headers.len()); + for (key, value) in $res4 { + assert_eq!(value, result.0.headers.get(key).unwrap().to_str().unwrap()) + } + assert_eq!($res5, result.1); + }}; + } + + macro_rules! test_unit_segment { + ($res1:expr, $res2:expr, $res3:expr, $res4:expr, $res5:expr) => {{ + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode($res1.0).unwrap(); + assert_eq!(true, result.is_none()); + let result = decoder.decode($res1.1).unwrap().unwrap(); + assert_eq!($res2, result.0.version.as_str()); + assert_eq!($res3, result.0.status.as_u16()); + assert_eq!($res4.len(), result.0.headers.len()); + for (key, value) in $res4 { + assert_eq!(value, result.0.headers.get(key).unwrap().to_str().unwrap()) + } + assert_eq!($res5, result.1); + }}; + } + + macro_rules! test_unit_invalid { + ($res1:expr, $res2:expr) => {{ + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode($res1); + assert_eq!($res2, result.err()); + }}; + } + + /// UT test cases for `ResponseDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ResponseDecoder` by calling `ResponseDecoder::new`. + /// 2. Decodes response bytes by calling `ResponseDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_decoder_decode_0() { + // Decode a complete response separated by CRLF. + test_unit_complete!( + "HTTP/1.1 304 OK\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + // Decode a complete response separated by LF. + test_unit_complete!( + "HTTP/1.1 304 OK\nAge:270646\nDate:Mon, 19 Dec 2022 01:46:59 GMT\nEtag:\"3147526947+gzip\"\n\nbody part".as_bytes(), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + // Decode a response without reason-phrase. + test_unit_complete!( + "HTTP/1.1 304 \r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + + // Decode a response that contains the OWS. + test_unit_complete!( + "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes(), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + // Decode a response without a message-body + test_unit_complete!( + "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#""#.as_bytes() + ); + // Decode has multiple set-cookie responses. + test_unit_complete!( + "HTTP/1.1 304 \r\nSet-Cookie: \t template=; Path=/; Domain=example.com; Expires=Mon, 19 Dec 2022 12:58:54 UTC \t \t\r\n\ + Set-Cookie: \t ezov=06331; Path=/; Domain=example.com; Expires=Mon, 19 Dec 2022 12:58:54 UTC \t \t\r\n\r\n".as_bytes(), + "HTTP/1.1", + 304_u16, + [("set-cookie", "template=; Path=/; Domain=example.com; Expires=Mon, 19 Dec 2022 12:58:54 UTC, ezov=06331; Path=/; Domain=example.com; Expires=Mon, 19 Dec 2022 12:58:54 UTC")], + r#""#.as_bytes() + ); + // Decode a response without a header. + test_unit_complete!( + "HTTP/1.1 304 \r\n\r\n".as_bytes(), + "HTTP/1.1", + 304_u16, + [] as [(&str, &str); 0], + r#""#.as_bytes() + ); + // Decode a response without a header and separated by LF. + test_unit_complete!( + "HTTP/1.1 304 \n\n".as_bytes(), + "HTTP/1.1", + 304_u16, + [] as [(&str, &str); 0], + r#""#.as_bytes() + ); + } + + /// UT test cases for `ResponseDecoder::decode`. + /// + /// # Brief + /// Decode a segmented transmission response and test `ParseStage` parsing rules. + /// 1. Creates a `ResponseDecoder` by calling `ResponseDecoder::new`. + /// 2. Decodes response bytes by calling `ResponseDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_decoder_decode_1() { + test_unit_segment!( + ("HT".as_bytes(), "TP/1.1 304 OK\r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 3".as_bytes(), "04 OK\r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 O".as_bytes(), "K\r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r".as_bytes(), "\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nA".as_bytes(), "ge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: ".as_bytes(), "\t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270".as_bytes(), "646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t".as_bytes(), " \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t \t\r".as_bytes(), "\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t \t\r\n".as_bytes(), "Date: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t \t\r\nDa".as_bytes(), "te: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n".as_bytes(), "\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r".as_bytes(), "\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [("age", "270646"), ("date", "Mon, 19 Dec 2022 01:46:59 GMT"), ("etag", r#""3147526947+gzip""#)], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\n".as_bytes(), "\r\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [] as [(&str, &str); 0], + r#"body part"#.as_bytes() + ); + test_unit_segment!( + ("HTTP/1.1 304 OK\r\n".as_bytes(), "\nbody part".as_bytes()), + "HTTP/1.1", + 304_u16, + [] as [(&str, &str); 0], + r#"body part"#.as_bytes() + ); + } + + /// UT test cases for `ResponseDecoder::decode`. + /// + /// # Brief + /// Decode an incorrect response bytes. + /// 1. Creates a `ResponseDecoder` by calling `ResponseDecoder::new`. + /// 2. Decodes response bytes by calling `ResponseDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_decoder_decode_2() { + test_unit_invalid!("HTTP/1.2 304 OK\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 3040 OK\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 3 4 OK\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 \tK\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\r\nAge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nA;ge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nA;ge:270646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge:270\r646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge:270\r646\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge: \r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge:270646\r\r\nDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge:270646\r\n\rDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + test_unit_invalid!("HTTP/1.1 304 OK\r\nAge:270646\r\n\rDate:Mon, 19 Dec 2022 01:46:59 GMT\r\nEtag:\"3147526947+gzip\"\r\n\r\r\nbody part".as_bytes(), Some(HttpError::from(ErrorKind::H1(H1Error::InvalidResponse)))); + } +} diff --git a/ylong_http/src/h1/response/mod.rs b/ylong_http/src/h1/response/mod.rs new file mode 100644 index 0000000..f6d31e9 --- /dev/null +++ b/ylong_http/src/h1/response/mod.rs @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! `HTTP/1.1` response serialization and deserialization. + +// TODO: ResponseEncoder implementation. + +pub(crate) mod decoder; + +pub use decoder::ResponseDecoder; diff --git a/ylong_http/src/h2/decoder.rs b/ylong_http/src/h2/decoder.rs new file mode 100644 index 0000000..ea721b3 --- /dev/null +++ b/ylong_http/src/h2/decoder.rs @@ -0,0 +1,1797 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{Frame, H2Error}; +use crate::error::ErrorKind::H2; +use crate::h2; +use crate::h2::decoder::Stage::{Header, Payload}; +use crate::h2::error::ErrorCode; +use crate::h2::frame::{ + Data, FrameFlags, Goaway, Ping, Priority, RstStream, WindowUpdate, ACK_MASK, END_HEADERS_MASK, + END_STREAM_MASK, HEADERS_PRIORITY_MASK, PADDED_MASK, +}; +use crate::h2::{frame, HpackDecoder, Parts, Setting, Settings}; +use crate::headers::Headers; +use std::ops::{Deref, DerefMut}; + +const FRAME_HEADER_LENGTH: usize = 9; +const DEFAULT_MAX_FRAME_SIZE: u32 = 2 << 13; +const MAX_ALLOWED_MAX_FRAME_SIZE: u32 = (2 << 23) - 1; +const DEFAULT_HEADER_TABLE_SIZE: usize = 4096; +const DEFAULT_MAX_HEADER_LIST_SIZE: usize = 16 << 20; +const MAX_INITIAL_WINDOW_SIZE: usize = (1 << 31) - 1; + +/// A set of consecutive Frames. +/// When Headers Frames or Continuation Frames are not End Headers, they are represented as `FrameKind::Partial`. +/// +/// - use `Frames` iterator. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::h2::Frames; +/// +/// # fn get_frames_iter(frames: Frames) { +/// let mut iter = frames.iter(); +/// let next_frame = iter.next(); +/// # } +/// ``` +/// +/// - use `Frames` consuming iterator. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::h2::Frames; +/// +/// # fn get_frames_into_iter(frames: Frames) { +/// let mut iter = frames.into_iter(); +/// let next_frame = iter.next(); +/// # } +/// ``` +pub struct Frames { + list: Vec, +} + +/// An iterator of `Frames`. +pub struct FramesIter<'a> { + iter: core::slice::Iter<'a, FrameKind>, +} + +/// A consuming iterator of `Frames`. +pub struct FramesIntoIter { + into_iter: std::vec::IntoIter, +} + +impl Frames { + /// Returns an iterator over `Frames`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h2::Frames; + /// + /// # fn get_frames_iter(frames: Frames) { + /// let mut iter = frames.iter(); + /// let next_frame = iter.next(); + /// # } + /// ``` + pub fn iter(&self) -> FramesIter { + FramesIter { + iter: self.list.iter(), + } + } + + /// Returns the size of `Frames`. + pub fn len(&self) -> usize { + self.list.len() + } + + /// Checks if the `Frames` is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a> Deref for FramesIter<'a> { + type Target = core::slice::Iter<'a, FrameKind>; + + fn deref(&self) -> &Self::Target { + &self.iter + } +} + +impl<'a> DerefMut for FramesIter<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.iter + } +} + +// TODO Added the Iterator trait implementation of ChunksIter. +impl<'a> Iterator for FramesIter<'a> { + type Item = &'a FrameKind; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +impl Iterator for FramesIntoIter { + type Item = FrameKind; + + fn next(&mut self) -> Option { + self.into_iter.next() + } +} + +impl core::iter::IntoIterator for Frames { + type Item = FrameKind; + type IntoIter = FramesIntoIter; + + fn into_iter(self) -> Self::IntoIter { + FramesIntoIter { + into_iter: self.list.into_iter(), + } + } +} + +/// When Headers Frames or Continuation Frames are not End Headers, they are represented as `FrameKind::Partial`. +pub enum FrameKind { + /// PUSH_PROMISE or HEADRS frame parsing completed. + Complete(Frame), + /// Partial decoded of PUSH_PROMISE or HEADRS frame. + Partial, +} + +/// Frame bytes sequence decoder, supporting fragment deserialization of Frames. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::h2::FrameDecoder; +/// +/// let mut decoder = FrameDecoder::new(); +/// decoder.set_max_header_list_size(30); +/// let data_frame_bytes = &[0, 0, 5, 0, 0, 0, 0, 0, 1, b'h', b'e', b'l', b'l', b'o']; +/// let decoded_frames = decoder.decode(data_frame_bytes).unwrap(); +/// let frame_kind =decoded_frames.iter().next().unwrap(); +/// ``` +pub struct FrameDecoder { + buffer: Vec, + // buffer's length + offset: usize, + max_frame_size: u32, + // Current decode Stage of decoder + stage: Stage, + // 9-byte header information of the current frame + header: FrameHeader, + hpack: HpackDecoderLayer, + // The Headers Frame flags information is saved to ensure the continuity between Headers Frames and Continuation Frames. + continuations: Continuations, +} + +enum Stage { + Header, + Payload, +} + +struct HpackDecoderLayer { + hpack: HpackDecoder, +} + +#[derive(Default)] +struct FrameHeader { + stream_id: usize, + flags: u8, + frame_type: u8, + payload_length: usize, +} + +struct Continuations { + flags: u8, + is_end_stream: bool, + stream_id: usize, + is_end_headers: bool, + promised_stream_id: u32, +} + +impl HpackDecoderLayer { + fn new() -> Self { + Self { + hpack: HpackDecoder::with_max_size( + DEFAULT_HEADER_TABLE_SIZE, + DEFAULT_MAX_HEADER_LIST_SIZE, + ), + } + } + + fn hpack_decode(&mut self, buf: &[u8]) -> Result<(), H2Error> { + self.hpack.decode(buf) + } + + fn hpack_finish(&mut self) -> Result { + self.hpack.finish() + } + + pub fn set_max_header_list_size(&mut self, size: usize) { + self.hpack.update_header_list_size(size) + } +} + +impl FrameHeader { + fn new() -> Self { + FrameHeader::default() + } + + fn reset(&mut self) { + self.stream_id = 0; + self.flags = 0; + self.frame_type = 0; + self.payload_length = 0 + } + + fn is_end_stream(&self) -> bool { + END_STREAM_MASK & self.flags == END_STREAM_MASK + } + + fn is_padded(&self) -> bool { + PADDED_MASK & self.flags == PADDED_MASK + } + + fn is_end_headers(&self) -> bool { + END_HEADERS_MASK & self.flags == END_HEADERS_MASK + } + + fn is_headers_priority(&self) -> bool { + HEADERS_PRIORITY_MASK & self.flags == HEADERS_PRIORITY_MASK + } + + fn is_ack(&self) -> bool { + ACK_MASK & self.flags == ACK_MASK + } +} + +impl Continuations { + fn new() -> Self { + Continuations { + flags: 0, + is_end_stream: false, + stream_id: 0, + // The initial value is true. + is_end_headers: true, + promised_stream_id: 0, + } + } + + fn reset(&mut self) { + self.flags = 0; + self.is_end_stream = false; + self.is_end_headers = true; + self.stream_id = 0; + self.promised_stream_id = 0; + } +} + +impl Default for FrameDecoder { + fn default() -> Self { + FrameDecoder { + buffer: vec![], + offset: 0, + max_frame_size: DEFAULT_MAX_FRAME_SIZE, + stage: Stage::Header, + header: FrameHeader::new(), + hpack: HpackDecoderLayer::new(), + continuations: Continuations::new(), + } + } +} + +impl Frames { + fn new() -> Self { + Frames { list: vec![] } + } + fn push(&mut self, frame: FrameKind) { + self.list.push(frame) + } +} + +impl FrameDecoder { + /// `FrameDecoder` constructor. Three parameters are defined in SETTINGS Frame. + pub fn new() -> Self { + FrameDecoder::default() + } + + /// Updates the SETTINGS_MAX_FRAME_SIZE. + pub fn set_max_frame_size(&mut self, size: u32) -> Result<(), H2Error> { + if size < DEFAULT_MAX_FRAME_SIZE && size > MAX_ALLOWED_MAX_FRAME_SIZE { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + self.max_frame_size = size; + Ok(()) + } + + /// Updates the SETTINGS_MAX_HEADER_LIST_SIZE. + pub fn set_max_header_list_size(&mut self, size: usize) { + self.hpack.set_max_header_list_size(size) + } + + /// Frames deserialization interface, supporting segment decode. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h2::FrameDecoder; + /// + /// let mut decoder = FrameDecoder::new(); + /// decoder.set_max_header_list_size(30); + /// let data_frame_bytes = &[0, 0, 5, 0, 0, 0, 0, 0, 1, b'h', b'e', b'l', b'l', b'o']; + /// let decoded_frames = decoder.decode(&data_frame_bytes[..9]).unwrap(); + /// assert_eq!(decoded_frames.len(), 0); + /// let decoded_frames = decoder.decode(&data_frame_bytes[9..]).unwrap(); + /// assert_eq!(decoded_frames.len(), 1); + /// ``` + pub fn decode(&mut self, buf: &[u8]) -> Result { + let mut frames = Frames::new(); + let mut buffer = buf; + loop { + match self.stage { + Header => match self.decode_frame_header(buffer)? { + Some(remain) => { + buffer = remain; + self.stage = Payload; + } + None => { + break; + } + }, + Payload => match self.decode_frame_payload(buffer)? { + Some((remain, frame)) => { + frames.push(frame); + buffer = remain; + self.stage = Header; + } + None => { + break; + } + }, + } + } + Ok(frames) + } + + fn decode_frame_payload<'a>( + &mut self, + buf: &'a [u8], + ) -> Result, H2Error> { + // Frames of other types or streams are not allowed between Headers Frame and Continuation Frame. + if !self.continuations.is_end_headers + && (self.header.stream_id != self.continuations.stream_id + || self.header.frame_type != 9) + { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + + let frame_end_index = self.header.payload_length - self.offset; + if buf.len() < frame_end_index { + self.offset += buf.len(); + self.buffer.extend_from_slice(buf); + Ok(None) + } else { + let frame = match self.header.frame_type { + 0 => self.decode_data_payload(&buf[..frame_end_index])?, + 1 => self.decode_headers_payload(&buf[..frame_end_index])?, + 2 => self.decode_priority_payload(&buf[..frame_end_index])?, + 3 => self.decode_reset_payload(&buf[..frame_end_index])?, + 4 => self.decode_settings_payload(&buf[..frame_end_index])?, + 5 => self.decode_push_promise_payload(&buf[..frame_end_index])?, + 6 => self.decode_ping_payload(&buf[..frame_end_index])?, + 7 => self.decode_goaway_payload(&buf[..frame_end_index])?, + 8 => self.decode_window_update_payload(&buf[..frame_end_index])?, + 9 => self.decode_continuation_payload(&buf[..frame_end_index])?, + _ => { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + }; + self.header.reset(); + if self.offset != 0 { + self.offset = 0; + self.buffer.clear() + } + Ok(Some((&buf[frame_end_index..], frame))) + } + } + + fn decode_ping_payload(&mut self, buf: &[u8]) -> Result { + if !is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length != 8 || buf.len() != 8 { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let mut opaque_data = [0; 8]; + opaque_data.copy_from_slice(buf); + let ping = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Ping(Ping::new(opaque_data)), + ); + Ok(FrameKind::Complete(ping)) + } + + fn decode_priority_payload(&mut self, buf: &[u8]) -> Result { + const EXCLUSIVE_MASK: u8 = 0x80; + + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + if self.header.payload_length != 5 || buf.len() != 5 { + return Err(H2Error::StreamError( + self.header.stream_id as u32, + ErrorCode::FrameSizeError, + )); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + let exclusive = buf[0] & EXCLUSIVE_MASK == EXCLUSIVE_MASK; + let stream_dependency = get_stream_id(&buf[..4]); + let weight = buf[4]; + let priority = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Priority(Priority::new(exclusive, stream_dependency, weight)), + ); + Ok(FrameKind::Complete(priority)) + } + + fn decode_goaway_payload(&mut self, buf: &[u8]) -> Result { + if !is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length < 8 || buf.len() < 8 { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let last_stream_id = get_stream_id(&buf[..4]) as usize; + let error_code = get_code_value(&buf[4..8]); + let mut debug_data = vec![]; + debug_data.extend_from_slice(&buf[8..]); + let goaway = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Goaway(Goaway::new(error_code, last_stream_id, debug_data)), + ); + Ok(FrameKind::Complete(goaway)) + } + + // window_update frame contains stream frame and connection frame + fn decode_window_update_payload(&mut self, buf: &[u8]) -> Result { + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length != 4 || buf.len() != 4 { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let increment_size = (((0x7f | buf[0]) as u32) << 24) + | ((buf[1] as u32) << 16) + | ((buf[2] as u32) << 8) + | (buf[3] as u32); + if increment_size == 0 { + return Err(H2Error::StreamError( + self.header.stream_id as u32, + ErrorCode::ProtocolError, + )); + } + let window_update = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::WindowUpdate(WindowUpdate::new(increment_size)), + ); + Ok(FrameKind::Complete(window_update)) + } + + fn decode_reset_payload(&mut self, buf: &[u8]) -> Result { + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length != 4 || buf.len() != 4 { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let code = get_code_value(&buf[..4]); + let reset = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::RstStream(RstStream::new(code)), + ); + Ok(FrameKind::Complete(reset)) + } + + fn decode_settings_payload(&mut self, buf: &[u8]) -> Result { + if !is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length % 6 != 0 { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + if self.header.is_ack() { + if self.header.payload_length != 0 || !buf.is_empty() { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let settings = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Settings(Settings::new(vec![])), + ); + + return Ok(FrameKind::Complete(settings)); + } + let mut settings = vec![]; + for chunk in buf.chunks(6) { + if let Some(setting) = split_token_to_setting(chunk)? { + settings.push(setting); + } + } + let frame = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Settings(Settings::new(settings)), + ); + Ok(FrameKind::Complete(frame)) + } + + fn decode_data_payload(&mut self, buf: &[u8]) -> Result { + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + if self.header.payload_length == 0 { + let frame = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Data(Data::new(vec![])), + ); + return Ok(FrameKind::Complete(frame)); + } + let is_padded = self.header.is_padded(); + let data = if is_padded { + let padded_length = buf[0] as usize; + if self.header.payload_length <= padded_length { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let data_end_index = self.header.payload_length - padded_length; + let mut data: Vec = vec![]; + data.extend_from_slice(&buf[1..data_end_index]); + data + } else { + let mut data: Vec = vec![]; + data.extend_from_slice(&buf[..self.header.payload_length]); + data + }; + let frame = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Data(Data::new(data)), + ); + Ok(FrameKind::Complete(frame)) + } + + fn decode_continuation_payload(&mut self, buf: &[u8]) -> Result { + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + let end_headers = self.header.is_end_headers(); + if end_headers { + self.hpack.hpack_decode(buf)?; + let headers = self.hpack.hpack_finish()?; + let frame = if self.continuations.promised_stream_id != 0 { + Frame::new( + self.continuations.stream_id, + FrameFlags::new(self.continuations.flags), + frame::Payload::PushPromise(frame::PushPromise::new( + self.continuations.promised_stream_id, + headers, + )), + ) + } else { + Frame::new( + self.continuations.stream_id, + FrameFlags::new(self.continuations.flags), + frame::Payload::Headers(frame::Headers::new(headers)), + ) + }; + self.continuations.reset(); + Ok(FrameKind::Complete(frame)) + } else { + self.hpack.hpack_decode(buf)?; + Ok(FrameKind::Partial) + } + } + + fn decode_headers_payload(&mut self, buf: &[u8]) -> Result { + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + let priority = self.header.is_headers_priority(); + let is_padded = self.header.is_padded(); + let end_headers = self.header.is_end_headers(); + let end_stream = self.header.is_end_stream(); + + let mut fragment_start_index = 0; + let mut fragment_end_index = self.header.payload_length; + if is_padded { + let padded_length = buf[0] as usize; + if self.header.payload_length <= padded_length { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + fragment_start_index += 1; + fragment_end_index -= padded_length; + } + if priority { + fragment_start_index += 5; + } + + if fragment_start_index > fragment_end_index { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + if end_headers { + self.hpack + .hpack_decode(&buf[fragment_start_index..fragment_end_index])?; + let headers = self.hpack.hpack_finish()?; + let frame = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::Headers(h2::frame::Headers::new(headers)), + ); + Ok(FrameKind::Complete(frame)) + } else { + self.continuations.flags = self.header.flags; + self.continuations.is_end_stream = end_stream; + self.continuations.is_end_headers = false; + self.continuations.stream_id = self.header.stream_id; + + // TODO Performance optimization, The storage structure Vec is optimized. When a complete field block exists in the buffer, fragments do not need to be copied to the Vec. + self.hpack + .hpack_decode(&buf[fragment_start_index..fragment_end_index])?; + Ok(FrameKind::Partial) + } + } + + fn decode_push_promise_payload(&mut self, buf: &[u8]) -> Result { + if is_connection_frame(self.header.stream_id) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let buf = if self.offset != 0 { + self.buffer.extend_from_slice(buf); + self.offset += buf.len(); + self.buffer.as_slice() + } else { + buf + }; + let is_padded = self.header.is_padded(); + let end_headers = self.header.is_end_headers(); + let mut fragment_start_index = 4; + let mut fragment_end_index = self.header.payload_length; + if is_padded { + let padded_length = buf[0] as usize; + if self.header.payload_length <= padded_length { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + fragment_start_index += 1; + fragment_end_index -= padded_length; + } + if fragment_start_index > fragment_end_index { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + let promised_stream_id = if is_padded { + get_stream_id(&buf[1..5]) + } else { + get_stream_id(&buf[..4]) + }; + if is_connection_frame(promised_stream_id as usize) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + if end_headers { + self.hpack + .hpack_decode(&buf[fragment_start_index..fragment_end_index])?; + let headers = self.hpack.hpack_finish()?; + let frame = Frame::new( + self.header.stream_id, + FrameFlags::new(self.header.flags), + frame::Payload::PushPromise(h2::frame::PushPromise::new( + promised_stream_id, + headers, + )), + ); + Ok(FrameKind::Complete(frame)) + } else { + self.continuations.flags = self.header.flags; + self.continuations.is_end_headers = false; + self.continuations.stream_id = self.header.stream_id; + self.continuations.promised_stream_id = promised_stream_id; + self.hpack + .hpack_decode(&buf[fragment_start_index..fragment_end_index])?; + Ok(FrameKind::Partial) + } + } + + fn decode_frame_header<'a>(&mut self, buf: &'a [u8]) -> Result, H2Error> { + let payload_pos = FRAME_HEADER_LENGTH - self.offset; + return if buf.len() < payload_pos { + self.offset += buf.len(); + self.buffer.extend_from_slice(buf); + Ok(None) + } else { + let header_buffer = if self.offset == 0 { + buf + } else { + self.buffer.extend_from_slice(&buf[..payload_pos]); + self.buffer.as_slice() + }; + let payload_length = ((header_buffer[0] as usize) << 16) + + ((header_buffer[1] as usize) << 8) + + (header_buffer[2] as usize); + if payload_length > self.max_frame_size as usize { + return Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)); + } + let frame_type = header_buffer[3]; + let flags = header_buffer[4]; + let stream_id = get_stream_id(&header_buffer[5..9]) as usize; + let frame_header = FrameHeader { + stream_id, + flags, + frame_type, + payload_length, + }; + if self.offset != 0 { + self.offset = 0; + self.buffer.clear(); + } + self.header = frame_header; + Ok(Some(&buf[payload_pos..])) + }; + } +} + +fn is_connection_frame(id: usize) -> bool { + id == 0 +} + +fn get_stream_id(token: &[u8]) -> u32 { + (((token[0] & 0x7f) as u32) << 24) + | ((token[1] as u32) << 16) + | ((token[2] as u32) << 8) + | (token[3] as u32) +} + +fn get_code_value(token: &[u8]) -> u32 { + ((token[0] as u32) << 24) + | ((token[1] as u32) << 16) + | ((token[2] as u32) << 8) + | (token[3] as u32) +} + +fn split_token_to_setting(token: &[u8]) -> Result, H2Error> { + let id = u16::from(token[0]) << 8 | u16::from(token[1]); + let value = get_code_value(&token[2..6]); + get_setting(id, value) +} + +pub fn get_setting(id: u16, value: u32) -> Result, H2Error> { + match id { + 1 => Ok(Some(Setting::HeaderTableSize(value))), + 2 => { + let enable_push = match value { + 0 => false, + 1 => true, + _ => return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)), + }; + Ok(Some(Setting::EnablePush(enable_push))) + } + 3 => Ok(Some(Setting::MaxConcurrentStreams(value))), + 4 => { + if value as usize > MAX_INITIAL_WINDOW_SIZE { + return Err(H2Error::ConnectionError(ErrorCode::FlowControlError)); + } + Ok(Some(Setting::InitialWindowSize(value))) + } + 5 => { + if !(DEFAULT_MAX_FRAME_SIZE..=MAX_ALLOWED_MAX_FRAME_SIZE).contains(&value) { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + Ok(Some(Setting::MaxFrameSize(value))) + } + 6 => Ok(Some(Setting::MaxHeaderListSize(value))), + _ => Ok(None), + } +} + +#[cfg(test)] +mod ut_frame_decoder { + use crate::h2::decoder::{get_setting, FrameDecoder, FrameHeader, FrameKind}; + use crate::h2::frame::{Payload, Ping, Setting}; + use crate::h2::{ErrorCode, H2Error, PseudoHeaders}; + use crate::test_util::decode; + + macro_rules! check_complete_frame { + ( + @header; + FrameKind: $frame_kind:expr, + Frame: { + StreamId: $stream_id:expr, + Flags: $flags:expr, + Payload: $payload:expr, + $(Padding: $frame_padding:expr,)? + }, + ) => { + match $frame_kind { + FrameKind::Complete(frame) => { + assert_eq!(frame.stream_id(), $stream_id); + assert_eq!(frame.flags().bits(), $flags); + match frame.payload() { + Payload::Headers(headers_frame) => { + let (pseudo, header) = headers_frame.parts(); + assert_eq!( + header.len(), + $payload.1.len(), + "assert header length failed" + ); + for (key, value) in $payload.1.iter() { + assert_eq!(header.get(*key).unwrap().to_str().unwrap(), *value); + } + for (key, value) in $payload.0.iter() { + match *key { + ":method" => { + assert_eq!( + pseudo.method().expect("pseudo.method get failed !"), + *value + ); + } + ":scheme" => { + assert_eq!( + pseudo.scheme().expect("pseudo.scheme get failed !"), + *value + ); + } + ":authority" => { + assert_eq!( + pseudo + .authority() + .expect("pseudo.authority get failed !"), + *value + ); + } + ":path" => { + assert_eq!( + pseudo.path().expect("pseudo.path get failed !"), + *value + ); + } + ":status" => { + assert_eq!( + pseudo.status().expect("pseudo.status get failed !"), + *value + ); + } + _ => { + panic!("Unexpected pseudo header input !"); + } + } + } + } + _ => { + panic!("Unrecognized frame type !"); + } + } + } + FrameKind::Partial => { + panic!("Incorrect decode result !"); + } + }; + }; + ( + @data; + FrameKind: $frame_kind:expr, + Frame: { + StreamId: $stream_id:expr, + Flags: $flags:expr, + Payload: $payload:expr, + $(Padding: $frame_padding:expr,)? + }, + ) => { + match $frame_kind { + FrameKind::Complete(frame) => { + assert_eq!(frame.stream_id(), $stream_id); + assert_eq!(frame.flags().bits(), $flags); + match frame.payload() { + Payload::Data(data) => { + assert_eq!(data.data().as_slice(), $payload.as_bytes()) + } + _ => { + panic!("Unrecognized frame type !"); + } + } + } + FrameKind::Partial => { + panic!("Incorrect decode result !"); + } + }; + }; + ( + @partial; + FrameKind: $frame_kind:expr, + ) => { + match $frame_kind { + FrameKind::Complete(_) => { + panic!("Incorrect decode result !"); + } + FrameKind::Partial => {} + } + }; + } + + macro_rules! decode_frames { + ( + @data; + Bytes: $frame_hex:expr, + Count: $frame_count:expr, + $( + Frame: { + StreamId: $stream_id:expr, + Flags: $flags:expr, + Payload: $payload:expr, + $(Padding: $frame_padding:expr,)? + }, + )* + + ) => { + let mut decoder = FrameDecoder::default(); + let frame_bytes = decode($frame_hex).expect("convert frame hex to bytes failed !"); + let decoded_frames = decoder.decode(frame_bytes.as_slice()).expect("decode frame bytes failed !"); + assert_eq!(decoded_frames.len(), $frame_count); + let mut frames_iter = decoded_frames.iter(); + $( + check_complete_frame!( + @data; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: $stream_id, + Flags: $flags, + Payload: $payload, + }, + ); + )* + + }; + ( + @header; + Bytes: $frame_hex:expr, + Count: $frame_count:expr, + $( + Frame: { + StreamId: $stream_id:expr, + Flags: $flags:expr, + Payload: $payload:expr, + $(Padding: $frame_padding:expr,)? + }, + )* + ) => { + let mut decoder = FrameDecoder::default(); + let frame_bytes = decode($frame_hex).expect("convert frame hex to bytes failed !"); + let decoded_frames = decoder.decode(frame_bytes.as_slice()).expect("decode frame bytes failed !"); + assert_eq!(decoded_frames.len(), $frame_count); + let mut frames_iter = decoded_frames.iter(); + $( + check_complete_frame!( + @header; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: $stream_id, + Flags: $flags, + Payload: $payload, + }, + ); + )* + }; + ( + @error; + Bytes: $frame_hex:expr, + Decoder: { + MAX_HEADER_LIST_SIZE: $header_list_size: expr, + MAX_FRAME_SIZE: $max_frame_size: expr, + }, + Error: $error_type:expr, + ) => { + let mut decoder = FrameDecoder::new(); + decoder.set_max_header_list_size($header_list_size); + decoder.set_max_frame_size($max_frame_size).expect("Illegal size of SETTINGS_MAX_FRAME_SIZE !"); + let frame_bytes = decode($frame_hex).expect("convert frame hex to bytes failed !"); + let decoded_frames = decoder.decode(frame_bytes.as_slice()); + assert!(decoded_frames.is_err()); + assert_eq!(decoded_frames.err().expect("Get Error type failed !"), $error_type); + }; + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a simple complete DATA frame. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_complete_data_frame() { + decode_frames!( + @data; + Bytes: "00000b00010000000168656c6c6f20776f726c64", + Count: 1, + Frame: { + StreamId: 1, + Flags: 1, + Payload: "hello world", + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a complete padded DATA frame with padding. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_complete_padded_data_frame() { + decode_frames!( + @data; + Bytes: "0000140008000000020648656C6C6F2C20776F726C6421486F77647921", + Count: 1, + Frame: { + StreamId: 2, + Flags: 8, + Payload: "Hello, world!", + Padding: "Howdy!", + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test Data Frames in Segments. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_segmented_data_frame() { + // (stream_id, flags, is_end_stream, content) + let mut decoder = FrameDecoder::default(); + let frame_bytes = decode("00000b00010000000168656c6c6f20776f726c640000140008000000020648656C6C6F2C20776F726C6421486F77647921").unwrap(); + let frame_bytes = frame_bytes.as_slice(); + let decoded_frames = decoder.decode(&frame_bytes[..8]).unwrap(); + assert_eq!(decoded_frames.len(), 0); + let decoded_frames = decoder.decode(&frame_bytes[8..12]).unwrap(); + assert_eq!(decoded_frames.len(), 0); + let decoded_frames = decoder.decode(&frame_bytes[12..24]).unwrap(); + assert_eq!(decoded_frames.len(), 1); + let mut frames_iter = decoded_frames.iter(); + check_complete_frame!( + @data; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: 1, + Flags: 1, + Payload: "hello world", + }, + ); + let decoded_frames = decoder.decode(&frame_bytes[24..]).unwrap(); + let mut frames_iter = decoded_frames.iter(); + check_complete_frame!( + @data; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: 2, + Flags: 8, + Payload: "Hello, world!", + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a complete Request HEADERS Frames with padding and priority. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_complete_padded_priority_headers_frame() { + decode_frames!( + @header; + Bytes: "000040012D000000011080000014098286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f546869732069732070616464696E672E", + Count: 1, + Frame: { + StreamId: 1, + Flags: 45, + Payload:( + [(":method", "GET"), (":scheme", "http"), (":authority", "127.0.0.1:3000"), (":path", "/resource")], + [("host", "127.0.0.1"), ("accept", "image/jpeg")]), + Padding: "This is padding.", + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a complete Response HEADERS Frames. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_complete_response_headers_frame() { + decode_frames!( + @header; + Bytes: "0000390105000000018b0f1385f3ebdfbf5f6496dc34fd2826d4d03b141004ca8015c0b9702053168dff6196dc34fd2826d4d03b141004ca806ee361b82654c5a37f", + Count: 1, + Frame: { + StreamId: 1, + Flags: 5, + Payload:( + [(":status", "304")], + [("etag", "xyzzy"), ("expires", "Sat, 25 Mar 2023 02:16:10 GMT"), ("date", "Sat, 25 Mar 2023 05:51:23 GMT")]), + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test HEADERS Frames exceeded by SETTINGS_MAX_HEADER_LIST_SIZE. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_size_exceeded_headers_frame() { + decode_frames!( + @error; + Bytes: "00002a0105000000018286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f", + Decoder: { + MAX_HEADER_LIST_SIZE: 60, + MAX_FRAME_SIZE: 2 << 13, + }, + Error: H2Error::ConnectionError(ErrorCode::ConnectError), + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a series of complete request Frames. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + /// Test a complete `Request` frames. + #[test] + fn ut_frame_decoder_with_series_request_frames() { + let mut decoder = FrameDecoder::default(); + let frame_bytes = decode("00002e0100000000018286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f0f0d817f0000040904000000010f0d817f0000090001000000017468697320626f6479").unwrap(); + let decoded_frames = decoder.decode(frame_bytes.as_slice()).unwrap(); + assert_eq!(decoded_frames.len(), 3); + let mut frames_iter = decoded_frames.iter(); + + // HEADERS frame END_HEADERS Flag is false, so it returns Partial + check_complete_frame!( + @partial; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + ); + + // continuation is ("content-length", "9"), so it will append to headers' content-length because of repeat. + check_complete_frame!( + @header; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: 1, + Flags: 0, + Payload: ( + [(":method", "GET"), (":scheme", "http"), (":authority", "127.0.0.1:3000"), (":path", "/resource"),], + [("host", "127.0.0.1"), ("accept", "image/jpeg"), ("content-length", "9, 9")]), + }, + ); + check_complete_frame!( + @data; + FrameKind: frames_iter.next().expect("take next frame from iterator failed !"), + Frame: { + StreamId: 1, + Flags: 1, + Payload: "this body", + }, + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test the function of inserting HEADERS of other streams between HEADERS and CONTINUATION of the same stream. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_headers_frame_in_another_stream() { + decode_frames!( + @error; + Bytes: "00002e0100000000018286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f0f0d817f00002e0104000000028286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f0f0d817f0000040904000000010f0d817f0000090001000000017468697320626f6479", + Decoder: { + MAX_HEADER_LIST_SIZE: 16 << 20, + MAX_FRAME_SIZE: 2 << 13, + }, + Error: H2Error::ConnectionError(ErrorCode::ProtocolError), + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test the function of inserting CONTINUATION of other streams between HEADERS and CONTINUATION of the same stream. + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_continuation_frame_in_another_stream() { + decode_frames!( + @error; + Bytes: "00002e0100000000018286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f0f0d817f0000040904000000020f0d817f0000090001000000017468697320626f6479", + Decoder: { + MAX_HEADER_LIST_SIZE: 16 << 20, + MAX_FRAME_SIZE: 2 << 13, + }, + Error: H2Error::ConnectionError(ErrorCode::ProtocolError), + ); + } + + /// UT test cases for `FrameDecoder::decode`. + /// + /// # Brief + /// + /// Test a complete Request HEADERS Frames with padding and priority, the purpose is to test the method of `FrameFlags`. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode` method. + /// 3. Checks the results. + #[test] + fn ut_frame_decoder_with_padded_end_stream_headers_frame() { + let mut decoder = FrameDecoder::default(); + let frame_bytes = decode("000040012D000000011080000014098286418a089d5c0b8170dc640007048762c2a0f6d842ff6687089d5c0b8170ff5388352398ac74acb37f546869732069732070616464696E672E").unwrap(); + let decoded_frames = decoder.decode(frame_bytes.as_slice()).unwrap(); + let frames_kind = decoded_frames.iter().next().unwrap(); + match frames_kind { + FrameKind::Complete(frame) => { + assert!(frame.flags().is_padded()); + assert!(frame.flags().is_end_stream()); + assert_eq!(frame.flags().bits(), 0x2D); + } + FrameKind::Partial => { + panic!("Unexpected FrameKind !") + } + } + } + + /// UT test cases for `FrameDecoder::decode_ping_payload`. + /// + /// # Brief + /// + /// Tests the case of a ping payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode_ping_payload` method. + /// 3. Checks the results. + #[test] + fn ut_decode_ping_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 8, + frame_type: 0x6, + flags: 0x0, + stream_id: 0x0, + }; + let ping_payload = &[b'p', b'i', b'n', b'g', b't', b'e', b's', b't']; + let frame_kind = decoder.decode_ping_payload(ping_payload).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::Ping(ping) => { + let data = ping.data(); + assert_eq!(data.len(), 8); + assert_eq!(data[0], 112); + assert_eq!(data[7], 116); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the payload length is not 8, which should return an error. + decoder.header.payload_length = 7; + let result = decoder.decode_ping_payload(ping_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)) + )); + + // Tests the case where the stream id is not 0, which should return an error. + decoder.header.stream_id = 1; + let result = decoder.decode_ping_payload(ping_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_priority_payload`. + /// + /// # Brief + /// + /// This test case checks the behavior of the `decode_priority_payload` method in two scenarios. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `decode_priority_payload` method with a valid `priority_payload`. + /// 3. Verifies the method correctly decodes the payload and returns the expected values. + /// 4. Sets the `stream_id` in the header to 0 and checks if the method returns an error as expected. + #[test] + fn ut_decode_priority_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 5, + frame_type: 0x2, + flags: 0x0, + stream_id: 0x1, + }; + let priority_payload = &[0x80, 0x0, 0x0, 0x1, 0x20]; + let frame_kind = decoder.decode_priority_payload(priority_payload).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::Priority(priority) => { + assert!(priority.get_exclusive()); + assert_eq!(priority.get_stream_dependency(), 1); + assert_eq!(priority.get_weight(), 32); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the stream id is 0, which should return an error. + decoder.header.stream_id = 0; + let result = decoder.decode_priority_payload(priority_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_goaway_payload`. + /// + /// # Brief + /// + /// This test case checks the behavior of the `decode_goaway_payload` method in two scenarios. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `decode_goaway_payload` method with a valid `goaway_payload`. + /// 3. Verifies the method correctly decodes the payload and returns the expected values. + /// 4. Sets the `stream_id` in the header to a non-zero value and checks if the method returns an error as expected. + #[test] + fn ut_decode_goaway_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 13, + frame_type: 0x7, + flags: 0x0, + stream_id: 0x0, + }; + let goaway_payload = &[ + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, b'd', b'e', b'b', b'u', b'g', + ]; + let frame_kind = decoder.decode_goaway_payload(goaway_payload).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::Goaway(goaway) => { + assert_eq!(goaway.get_last_stream_id(), 1); + assert_eq!(goaway.get_error_code(), 2); + assert_eq!(goaway.get_debug_data(), b"debug"); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the stream id is not 0, which should return an error. + decoder.header.stream_id = 1; + let result = decoder.decode_goaway_payload(goaway_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_window_update_payload`. + /// + /// # Brief + /// + /// Tests the case of a window update payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode_window_update_payload` method. + /// 3. Checks the results. + #[test] + fn ut_decode_window_update_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 4, + frame_type: 0x8, + flags: 0x0, + stream_id: 0x1, + }; + let window_update_payload = &[0x0, 0x0, 0x0, 0x1]; + let frame_kind = decoder + .decode_window_update_payload(window_update_payload) + .unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::WindowUpdate(_) => { + // println!("{:?}", window_update.get_increment()); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the payload length is not 4, which should return an error. + decoder.header.payload_length = 5; + let result = decoder.decode_window_update_payload(window_update_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_reset_payload`. + /// + /// # Brief + /// + /// Tests the case of a reset payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode_reset_payload` method. + /// 3. Checks the results. + #[test] + fn ut_decode_reset_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 4, + frame_type: 0x3, + flags: 0x0, + stream_id: 0x1, + }; + let reset_payload = &[0x0, 0x0, 0x0, 0x1]; + let frame_kind = decoder.decode_reset_payload(reset_payload).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::RstStream(reset_stream) => { + assert_eq!(reset_stream.error_code(), 1); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the payload length is not 4, which should return an error. + decoder.header.payload_length = 5; + let result = decoder.decode_reset_payload(reset_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)) + )); + + // Tests the case where the stream id is 0, which should return an error. + decoder.header.stream_id = 0; + let result = decoder.decode_reset_payload(reset_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_settings_payload`. + /// + /// # Brief + /// + /// Tests the case of a settings payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode_settings_payload` method. + /// 3. Checks the results. + #[test] + fn ut_decode_settings_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 12, + frame_type: 0x4, + flags: 0x0, + stream_id: 0x0, + }; + + // Mock a settings payload: [0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01] + // Setting 1: Header Table Size, Value: 128 + // Setting 2: Enable Push, Value: 1 + let settings_payload = &[ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + ]; + let frame_kind = decoder.decode_settings_payload(settings_payload).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::Settings(settings) => { + let settings_vec = settings.get_settings(); + assert_eq!(settings_vec.len(), 2); + assert_eq!(settings_vec[0], Setting::HeaderTableSize(128)); + assert_eq!(settings_vec[1], Setting::EnablePush(true)); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Test the case where the settings frame is an acknowledgment. + // For this, we should set the ACK flag (0x1) and the payload length should be 0. + decoder.header = FrameHeader { + payload_length: 0, + frame_type: 0x4, + flags: 0x1, + stream_id: 0x0, + }; + let frame_kind = decoder.decode_settings_payload(&[]).unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::Settings(settings) => { + let settings_vec = settings.get_settings(); + assert_eq!(settings_vec.len(), 0); + } + _ => panic!("Unexpected payload type!"), + }, + FrameKind::Partial => { + panic!("Unexpected FrameKind!") + } + } + + // Tests the case where the payload length is not a multiple of 6, which should return an error. + decoder.header.payload_length = 5; + let result = decoder.decode_settings_payload(settings_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::FrameSizeError)) + )); + + // Tests the case where the stream id is not 0, which should return an error. + decoder.header.stream_id = 1; + let result = decoder.decode_settings_payload(settings_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::decode_push_promise_payload`. + /// + /// # Brief + /// + /// Tests the case of a push promise payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::decode_push_promise_payload` method. + /// 3. Checks the results. + #[test] + fn ut_decode_push_promise_payload() { + let mut decoder = FrameDecoder::new(); + decoder.header = FrameHeader { + payload_length: 10, + frame_type: 0x5, + flags: 0x88, + stream_id: 0x1, + }; + let push_promise_payload = &[0x0, 0x0, 0x0, 0x2, b'h', b'e', b'l', b'l', b'o', b'w']; + + // Tests the case where the payload is a valid push promise. + let frame_kind = decoder + .decode_push_promise_payload(push_promise_payload) + .unwrap(); + match frame_kind { + FrameKind::Complete(frame) => match frame.payload() { + Payload::PushPromise(_) => {} + _ => panic!("Unexpected payload type!"), + }, + + FrameKind::Partial => {} + } + + // Tests the case where the payload length is less than the promised_stream_id size, which should return an error. + decoder.header.payload_length = 3; + let result = decoder.decode_push_promise_payload(push_promise_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + + // Tests the case where the stream id is 0, which should return an error. + decoder.header.stream_id = 0; + let result = decoder.decode_push_promise_payload(push_promise_payload); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + } + + /// UT test cases for `FrameDecoder::get_setting`. + /// + /// # Brief + /// + /// Tests the case of a settings payload. + /// + /// 1. Creates a `FrameDecoder`. + /// 2. Calls its `FrameDecoder::get_setting` method. + /// 3. Checks the results. + #[test] + fn ut_get_setting() { + // Test the case where the id is for a HeaderTableSize + match get_setting(1, 4096).unwrap() { + Some(Setting::HeaderTableSize(size)) => { + assert_eq!(size, 4096); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for an EnablePush + match get_setting(2, 0).unwrap() { + Some(Setting::EnablePush(push)) => { + assert!(!push); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for a MaxConcurrentStreams + match get_setting(3, 100).unwrap() { + Some(Setting::MaxConcurrentStreams(streams)) => { + assert_eq!(streams, 100); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for an InitialWindowSize + match get_setting(4, 20000).unwrap() { + Some(Setting::InitialWindowSize(size)) => { + assert_eq!(size, 20000); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for a MaxFrameSize + match get_setting(5, 16384).unwrap() { + Some(Setting::MaxFrameSize(size)) => { + assert_eq!(size, 16384); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for a MaxHeaderListSize + match get_setting(6, 8192).unwrap() { + Some(Setting::MaxHeaderListSize(size)) => { + assert_eq!(size, 8192); + } + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is not recognized + match get_setting(7, 1000).unwrap() { + None => {} + _ => panic!("Unexpected Setting!"), + }; + + // Test the case where the id is for an EnablePush, but the value is invalid + let result = get_setting(2, 2); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + )); + + // Test the case where the id is for an InitialWindowSize, but the value is too large + let result = get_setting(4, 2usize.pow(31) as u32); + assert!(matches!( + result, + Err(H2Error::ConnectionError(ErrorCode::FlowControlError)) + )); + } +} diff --git a/ylong_http/src/h2/encoder.rs b/ylong_http/src/h2/encoder.rs new file mode 100644 index 0000000..ead2113 --- /dev/null +++ b/ylong_http/src/h2/encoder.rs @@ -0,0 +1,1722 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::frame::{FrameFlags, FrameType, Payload, Setting}; +use crate::h2::{Frame, HpackEncoder}; + +// TODO: Classify encoder errors per RFC specifications into categories like stream or connection errors. +// Identify specific error types such as Frame_size_error/Protocol Error. +#[derive(Debug)] +pub enum FrameEncoderErr { + EncodingData, + UnexpectedPayloadType, + NoCurrentFrame, + InternalError, +} + +#[derive(PartialEq, Debug)] +enum FrameEncoderState { + // The initial state for the frame encoder. + Idle, + // The initial state for encoding the HEADERS frame, including the frame header and the Field Block Fragment. + EncodingHeadersFrame, + // The state for encoding the payload of the HEADERS frame, including the header block fragment. + EncodingHeadersPayload, + // The state for encoding the padding octets for the HEADERS frame, if the PADDED flag is set. + EncodingHeadersPadding, + // The state for encoding CONTINUATION frames if the header block exceeds the max_frame_size. + EncodingContinuationFrames, + // The final state, indicating that the HEADERS frame and any necessary CONTINUATION frames have been fully encoded. + HeadersComplete, + // The initial state for encoding the DATA frame, including the frame header and the Pad Length field (if PADDED flag is set). + EncodingDataHeader, + // The state for encoding the actual data payload of the DATA frame. + EncodingDataPayload, + // The state for encoding the padding octets for the DATA frame, if the PADDED flag is set. + EncodingDataPadding, + // The initial state for encoding the SETTINGS frame, including the frame header. + EncodingSettingsFrame, + // The state for encoding the SETTINGS frame payload. + EncodingSettingsPayload, + // The initial state for encoding the GOAWAY frame, including the frame header. + EncodingGoawayFrame, + // The state for encoding the GOAWAY frame payload. + EncodingGoawayPayload, + // The initial state for encoding the WINDOW_UPDATE frame, including the frame header. + EncodingWindowUpdateFrame, + // The state for encoding the WINDOW_UPDATE frame payload. + EncodingWindowUpdatePayload, + // The initial state for encoding the PRIORITY frame, including the frame header. + EncodingPriorityFrame, + // The state for encoding Priority frame payload. + EncodingPriorityPayload, + // The initial state for encoding the RST_STREAM frame, including the frame header. + EncodingRstStreamFrame, + // The state for encoding the RST_STREAM frame payload. + EncodingRstStreamPayload, + // The initial state for encoding the PING frame, including the frame header. + EncodingPingFrame, + // The state for encoding the PING frame payload. + EncodingPingPayload, + // The final state, indicating that the DATA frame has been fully encoded. + DataComplete, +} + +/// A structure for encoding frames into bytes, supporting the serialization of HTTP/2 Frames. +/// It maintains the state of the current frame being encoded and also handles the fragmentation of frames. +pub struct FrameEncoder { + current_frame: Option, + max_frame_size: usize, + max_header_list_size: usize, + hpack_encoder: HpackEncoder, + state: FrameEncoderState, + encoded_bytes: usize, + remaining_header_payload: usize, + remaining_payload_bytes: usize, + is_end_stream: bool, + is_end_headers: bool, + header_payload_buffer: Vec, + header_payload_index: usize, +} + +impl FrameEncoder { + /// Constructs a new `FrameEncoder` with specified maximum frame size and maximum header list size. + pub fn new(max_frame_size: usize, max_header_list_size: usize) -> Self { + FrameEncoder { + current_frame: None, + max_frame_size, + max_header_list_size, + hpack_encoder: HpackEncoder::with_max_size(max_header_list_size), + state: FrameEncoderState::Idle, + encoded_bytes: 0, + remaining_header_payload: 0, + remaining_payload_bytes: 0, + is_end_stream: false, + is_end_headers: false, + header_payload_buffer: vec![0; 16383], // Initialized to default max frame size. + header_payload_index: 0, + } + } + + /// Sets the current frame to be encoded by the `FrameEncoder`. The state of the encoder is updated based on the payload type of the frame. + pub fn set_frame(&mut self, frame: Frame) { + self.is_end_stream = frame.flags().is_end_stream(); + self.is_end_headers = frame.flags().is_end_headers(); + self.current_frame = Some(frame); + self.encoded_bytes = 0; // Reset the encoded bytes counter + + // Set the initial state based on the frame payload type + match &self.current_frame { + Some(frame) => match frame.payload() { + Payload::Headers(headers) => { + self.hpack_encoder.set_parts(headers.get_parts()); + self.header_payload_index = 0; + // TODO: Handle potential scenario where HPACK encoding may not be able to complete output in one go. + let payload_size = self.hpack_encoder.encode(&mut self.header_payload_buffer); + self.remaining_header_payload = payload_size; + self.state = FrameEncoderState::EncodingHeadersFrame; + } + Payload::Priority(_) => self.state = FrameEncoderState::EncodingPriorityFrame, + Payload::RstStream(_) => self.state = FrameEncoderState::EncodingRstStreamFrame, + Payload::Ping(_) => self.state = FrameEncoderState::EncodingPingFrame, + Payload::Data(_) => self.state = FrameEncoderState::EncodingDataHeader, + Payload::Settings(_) => self.state = FrameEncoderState::EncodingSettingsFrame, + Payload::Goaway(_) => self.state = FrameEncoderState::EncodingGoawayFrame, + Payload::WindowUpdate(_) => { + self.state = FrameEncoderState::EncodingWindowUpdateFrame + } + _ => {} + }, + None => self.state = FrameEncoderState::Idle, + } + } + + /// Encodes the current frame into the given buffer. The state of the encoder determines which part of the frame is currently being encoded. + /// This function returns the number of bytes written to the buffer or an error if the encoding process fails. + pub fn encode(&mut self, buf: &mut [u8]) -> Result { + let mut written_bytes = 0; + + while written_bytes < buf.len() { + match self.state { + FrameEncoderState::Idle + | FrameEncoderState::HeadersComplete + | FrameEncoderState::DataComplete => { + break; + } + FrameEncoderState::EncodingHeadersFrame => { + let bytes = self.encode_headers_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingHeadersFrame { + break; + } + } + FrameEncoderState::EncodingHeadersPayload => { + let bytes = self.encode_headers_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingHeadersPayload { + break; + } + } + FrameEncoderState::EncodingHeadersPadding => { + let bytes = self.encode_padding(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingHeadersPadding { + break; + } + } + FrameEncoderState::EncodingContinuationFrames => { + let bytes = self.encode_continuation_frames(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingContinuationFrames { + break; + } + } + FrameEncoderState::EncodingDataHeader => { + let bytes = self.encode_data_header(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingDataHeader { + break; + } + } + FrameEncoderState::EncodingDataPayload => { + let bytes = self.encode_data_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingDataPayload { + break; + } + } + FrameEncoderState::EncodingDataPadding => { + let bytes = self.encode_padding(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingDataPadding { + break; + } + } + FrameEncoderState::EncodingSettingsFrame => { + let bytes = self.encode_settings_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingSettingsFrame { + break; + } + } + FrameEncoderState::EncodingGoawayFrame => { + let bytes = self.encode_goaway_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingGoawayFrame { + break; + } + } + FrameEncoderState::EncodingWindowUpdateFrame => { + let bytes = self.encode_window_update_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingWindowUpdateFrame { + break; + } + } + FrameEncoderState::EncodingSettingsPayload => { + let bytes = self.encode_settings_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingSettingsPayload { + break; + } + } + FrameEncoderState::EncodingGoawayPayload => { + let bytes = self.encode_goaway_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingGoawayPayload { + break; + } + } + FrameEncoderState::EncodingWindowUpdatePayload => { + let bytes = self.encode_window_update_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingWindowUpdatePayload { + break; + } + } + FrameEncoderState::EncodingPriorityFrame => { + let bytes = self.encode_priority_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingPriorityFrame { + break; + } + } + FrameEncoderState::EncodingPriorityPayload => { + let bytes = self.encode_priority_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingPriorityPayload { + break; + } + } + FrameEncoderState::EncodingRstStreamFrame => { + let bytes = self.encode_rst_stream_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingRstStreamFrame { + break; + } + } + FrameEncoderState::EncodingRstStreamPayload => { + let bytes = self.encode_rst_stream_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingRstStreamPayload { + break; + } + } + FrameEncoderState::EncodingPingFrame => { + let bytes = self.encode_ping_frame(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingPingFrame { + break; + } + } + FrameEncoderState::EncodingPingPayload => { + let bytes = self.encode_ping_payload(&mut buf[written_bytes..])?; + written_bytes += bytes; + if self.state == FrameEncoderState::EncodingPingPayload { + break; + } + } + } + } + + Ok(written_bytes) + } + + /// Updates the provided setting for the current frame if it is a `Settings` frame. + pub fn update_setting(&mut self, setting: Setting) { + if let Some(frame) = &mut self.current_frame { + if let Payload::Settings(settings) = frame.payload_mut() { + settings.update_setting(setting); + } + } + } + + /// Sets the maximum frame size for the current encoder instance. + pub fn update_max_frame_size(&mut self, size: usize) { + self.max_frame_size = size; + } + + /// Sets the maximum header table size for the current encoder instance. + pub fn update_header_table_size(&mut self, size: usize) { + self.max_header_list_size = size; + self.hpack_encoder = HpackEncoder::with_max_size(self.max_header_list_size) + } + + fn encode_headers_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Headers(_) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. + 0..=2 => { + let payload_len = self.remaining_header_payload; + *item = ((payload_len >> (16 - (8 * header_byte_index))) & 0xFF) as u8; + } + // The 4th byte represents the frame type in the frame header. + 3 => { + *item = FrameType::Headers as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + *item = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + 5..=8 => { + let stream_id_byte_index = header_byte_index - 5; + *item = (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + + self.encoded_bytes += bytes_to_write; + let bytes_written = bytes_to_write; + let mut payload_bytes_written = 0; + + if self.encoded_bytes >= frame_header_size { + payload_bytes_written = self + .write_payload(&mut buf[bytes_written..], self.remaining_header_payload); + + if self.remaining_header_payload <= self.max_frame_size { + self.state = if self.is_end_stream { + FrameEncoderState::HeadersComplete + } else { + FrameEncoderState::EncodingHeadersPayload + }; + } else { + self.state = FrameEncoderState::EncodingContinuationFrames; + } + } + + Ok(bytes_written + payload_bytes_written) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_headers_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Headers(_) = frame.payload() { + let available_space = buf.len(); + if available_space == 0 { + return Ok(0); + } + + let payload_bytes_written = self.write_payload(buf, self.remaining_header_payload); + self.encoded_bytes += payload_bytes_written; + self.remaining_header_payload -= payload_bytes_written; + + // Updates the state based on the encoding progress + if self.hpack_encoder.is_finished() { + if self.remaining_header_payload <= self.max_frame_size { + self.state = if self.is_end_stream || self.is_end_headers { + FrameEncoderState::HeadersComplete + } else { + FrameEncoderState::EncodingContinuationFrames + }; + } else { + self.state = FrameEncoderState::EncodingContinuationFrames; + } + } else { + self.state = FrameEncoderState::EncodingHeadersPayload; + } + + Ok(payload_bytes_written) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_continuation_frames(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Headers(_) = frame.payload() { + if self.remaining_header_payload == 0 { + self.state = FrameEncoderState::HeadersComplete; + return Ok(0); + } + + let available_space = buf.len(); + let frame_header_size = 9; + if available_space < frame_header_size { + return Ok(0); + } + + // Encodes CONTINUATION frame header. + let continuation_frame_len = self.remaining_header_payload.min(self.max_frame_size); + for (buf_index, item) in buf.iter_mut().enumerate().take(3) { + *item = ((continuation_frame_len >> (16 - (8 * buf_index))) & 0xFF) as u8; + } + buf[3] = FrameType::Continuation as u8; + let mut new_flags = FrameFlags::empty(); + if self.remaining_header_payload <= self.max_frame_size { + if self.is_end_headers { + new_flags.set_end_headers(true); // Sets the END_HEADERS flag on the last CONTINUATION frame. + } + if self.is_end_stream { + new_flags.set_end_stream(true); // Sets the END_STREAM flag. + } + } + buf[4] = new_flags.bits(); + + for buf_index in 0..4 { + let stream_id_byte_index = buf_index; + buf[5 + buf_index] = + (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + + // Encodes CONTINUATION frame payload. + let payload_bytes_written = + self.write_payload(&mut buf[frame_header_size..], continuation_frame_len); + self.encoded_bytes += payload_bytes_written; + self.remaining_header_payload -= payload_bytes_written; + + // Updates the state based on the encoding progress. + if self.hpack_encoder.is_finished() + && self.remaining_header_payload <= self.max_frame_size + { + self.state = if self.is_end_stream || self.is_end_headers { + FrameEncoderState::HeadersComplete + } else { + FrameEncoderState::EncodingContinuationFrames + }; + } else { + self.state = FrameEncoderState::EncodingContinuationFrames; + } + + Ok(frame_header_size + payload_bytes_written) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_data_header(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Data(data_frame) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. + 0..=2 => { + let payload_len = data_frame.data().len(); + *item = ((payload_len >> (16 - (8 * header_byte_index))) & 0xFF) as u8; + } + // The 4th byte represents the frame type in the frame header. + 3 => { + *item = frame.payload().frame_type() as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + *item = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + 5..=8 => { + let stream_id_byte_index = header_byte_index - 5; + *item = (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingDataPayload; + self.remaining_payload_bytes = data_frame.data().len(); + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_data_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = self.current_frame.as_ref() { + if let Payload::Data(data_frame) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let encoded_payload_bytes = self.encoded_bytes - frame_header_size; + let payload = data_frame.data(); + let bytes_to_write = self.encode_slice(buf, payload, encoded_payload_bytes); + self.encoded_bytes += bytes_to_write; + + if self.remaining_payload_bytes == 0 { + self.state = if self.is_end_stream { + FrameEncoderState::DataComplete + } else { + FrameEncoderState::EncodingDataPayload + }; + } else if self.remaining_payload_bytes > self.max_frame_size { + self.state = FrameEncoderState::EncodingDataPayload; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_padding(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if frame.flags().is_padded() { + let padding_len = if let Payload::Data(data_frame) = frame.payload() { + data_frame.data().len() + } else { + return Err(FrameEncoderErr::UnexpectedPayloadType); + }; + + let remaining_padding_bytes = padding_len - self.encoded_bytes; + let bytes_to_write = remaining_padding_bytes.min(buf.len()); + + for item in buf.iter_mut().take(bytes_to_write) { + *item = 0; // Padding bytes are filled with 0. + } + + self.encoded_bytes += bytes_to_write; + + if self.encoded_bytes == padding_len { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Ok(0) // No padding to encode, so return 0 bytes written. + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_goaway_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Goaway(_) = frame.payload() { + let frame_header_size = 9; + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + 0..=2 => { + if let Payload::Goaway(goaway_payload) = frame.payload() { + let payload_size = goaway_payload.encoded_len(); + *item = + ((payload_size >> (8 * (2 - header_byte_index))) & 0xFF) as u8; + } else { + return Err(FrameEncoderErr::UnexpectedPayloadType); + } + } + 3 => { + *item = FrameType::Goaway as u8; + } + 4 => { + *item = frame.flags().bits(); + } + 5..=8 => { + let stream_id_byte_index = header_byte_index - 5; + *item = (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingGoawayPayload; + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_goaway_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Goaway(goaway) = frame.payload() { + let payload_size = goaway.encoded_len(); + let remaining_payload_bytes = + payload_size.saturating_sub(self.encoded_bytes.saturating_sub(9)); + let bytes_to_write = remaining_payload_bytes.min(buf.len()); + for (buf_index, buf_item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = self.encoded_bytes - 9 + buf_index; + match payload_byte_index { + 0..=3 => { + let last_stream_id_byte_index = payload_byte_index; + *buf_item = (goaway.get_last_stream_id() + >> (24 - (8 * last_stream_id_byte_index))) + as u8; + } + 4..=7 => { + let error_code_byte_index = payload_byte_index - 4; + *buf_item = (goaway.get_error_code() + >> (24 - (8 * error_code_byte_index))) + as u8; + } + _ => { + let debug_data_index = payload_byte_index - 8; + if debug_data_index < goaway.get_debug_data().len() { + *buf_item = goaway.get_debug_data()[debug_data_index]; + } else { + return Err(FrameEncoderErr::InternalError); + } + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == 9 + payload_size { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_window_update_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::WindowUpdate(_) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. For WindowUpdate frame, this is always 4 bytes. + 0..=1 => { + *item = 0; + } + 2 => { + *item = 4; // Window Update frame payload size is always 4 bytes. + } + // The 4th byte represents the frame type in the frame header. + 3 => { + *item = FrameType::WindowUpdate as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + *item = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + 5..=8 => { + let stream_id_byte_index = header_byte_index - 5; + *item = (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingWindowUpdatePayload; + self.encoded_bytes = 0; // Resets the encoded_bytes counter here. + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_window_update_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::WindowUpdate(window_update) = frame.payload() { + let payload_size = 4usize; + let remaining_payload_bytes = + payload_size.saturating_sub(self.encoded_bytes.saturating_sub(9usize)); + let bytes_to_write = remaining_payload_bytes.min(buf.len()); + for (buf_index, buf_item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = self + .encoded_bytes + .saturating_sub(9) + .saturating_add(buf_index); + let increment_byte_index = payload_byte_index; + *buf_item = + (window_update.get_increment() >> (24 - (8 * increment_byte_index))) as u8; + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == payload_size { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_settings_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Settings(settings) = frame.payload() { + let frame_header_size = 9; + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + for buf_index in 0..bytes_to_write { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. + 0..=2 => { + let payload_len = settings.get_settings().len() * 6; + buf[buf_index] = ((payload_len >> (16 - (8 * buf_index))) & 0xFF) as u8; + } + // The 4th byte represents the frame type in the frame header. + 3 => { + buf[3] = FrameType::Settings as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + buf[4] = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + // For SETTINGS frames, this should always be 0. + 5..=8 => { + buf[buf_index] = 0; // Stream ID should be 0 for SETTINGS frames. + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingSettingsPayload; + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_settings_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Settings(settings) = frame.payload() { + let settings_len = settings.get_settings().len() * 6; + let remaining_payload_bytes = + settings_len.saturating_sub(self.encoded_bytes.saturating_sub(9)); + let bytes_to_write = remaining_payload_bytes.min(buf.len()); + for (buf_index, buf_item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = self.encoded_bytes - 9 + buf_index; + let setting_index = payload_byte_index / 6; + let setting_byte_index = payload_byte_index % 6; + + if let Some(setting) = settings.get_settings().get(setting_index) { + let (id, value) = match setting { + Setting::HeaderTableSize(v) => (0x1, *v), + Setting::EnablePush(v) => (0x2, *v as u32), + Setting::MaxConcurrentStreams(v) => (0x3, *v), + Setting::InitialWindowSize(v) => (0x4, *v), + Setting::MaxFrameSize(v) => (0x5, *v), + Setting::MaxHeaderListSize(v) => (0x6, *v), + }; + match setting_byte_index { + 0..=1 => { + *buf_item = ((id >> (8 * (1 - setting_byte_index))) & 0xFF) as u8; + } + 2..=5 => { + let shift_amount = 8 * (3 - (setting_byte_index - 2)); + *buf_item = ((value >> shift_amount) & 0xFF) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } else { + return Err(FrameEncoderErr::InternalError); + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == 9 + settings_len { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_priority_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Priority(_) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. + 0..=2 => { + let payload_len = 5; + *item = ((payload_len >> (16 - (8 * header_byte_index))) & 0xFF) as u8; + } + // The 4th byte represents the frame type in the frame header. + 3 => { + *item = frame.payload().frame_type() as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + *item = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + 5..=8 => { + let stream_id_byte_index = header_byte_index - 5; + *item = (frame.stream_id() >> (24 - (8 * stream_id_byte_index))) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingPriorityPayload; + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_priority_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Priority(priority) = frame.payload() { + let frame_header_size = 9; // HTTP/2 frame header size is 9 bytes. + let remaining_payload_bytes = 5 - (self.encoded_bytes - frame_header_size); + let bytes_to_write = remaining_payload_bytes.min(buf.len()); + + for (buf_index, buf_item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = self + .encoded_bytes + .saturating_sub(frame_header_size) + .saturating_add(buf_index); + match payload_byte_index { + 0 => { + *buf_item = (priority.get_exclusive() as u8) << 7 + | ((priority.get_stream_dependency() >> 24) & 0x7F) as u8; + } + 1..=3 => { + let stream_dependency_byte_index = payload_byte_index - 1; + *buf_item = (priority.get_stream_dependency() + >> (16 - (8 * stream_dependency_byte_index))) + as u8; + } + 4 => { + // The last byte is the weight. + *buf_item = priority.get_weight(); + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size + 5 { + self.state = FrameEncoderState::DataComplete + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_rst_stream_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + let frame_header_size = 9; + if self.encoded_bytes >= frame_header_size { + return Ok(0); + } + + let bytes_to_write = (frame_header_size - self.encoded_bytes).min(buf.len()); + + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + 0..=2 => { + let payload_len = 4; + *item = ((payload_len >> (16 - (8 * buf_index))) & 0xFF) as u8; + } + 3 => { + *item = FrameType::RstStream as u8; + } + 4 => { + *item = frame.flags().bits(); + } + 5..=8 => { + let stream_id = frame.stream_id(); + *item = ((stream_id >> (24 - (8 * (buf_index - 5)))) & 0xFF) as u8; + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingRstStreamPayload; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_rst_stream_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::RstStream(rst_stream) = frame.payload() { + let frame_header_size = 9; + if self.encoded_bytes < frame_header_size { + return Ok(0); + } + + let payload_size = 4; + let encoded_payload_bytes = self.encoded_bytes - frame_header_size; + + if encoded_payload_bytes >= payload_size { + return Ok(0); + } + + let bytes_to_write = (payload_size - encoded_payload_bytes).min(buf.len()); + + for (buf_index, item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = encoded_payload_bytes + buf_index; + *item = + ((rst_stream.error_code() >> (24 - (8 * payload_byte_index))) & 0xFF) as u8; + } + + self.encoded_bytes += bytes_to_write; + + if self.encoded_bytes == frame_header_size + payload_size { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_ping_frame(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Ping(_) = frame.payload() { + let frame_header_size = 9; + let remaining_header_bytes = if self.encoded_bytes >= frame_header_size { + 0 + } else { + frame_header_size - self.encoded_bytes + }; + let bytes_to_write = remaining_header_bytes.min(buf.len()); + for buf_index in 0..bytes_to_write { + let header_byte_index = self.encoded_bytes + buf_index; + match header_byte_index { + // The first 3 bytes represent the payload length in the frame header. + 0..=2 => { + let payload_len = 8; // PING payload is always 8 bytes. + buf[buf_index] = ((payload_len >> (16 - (8 * buf_index))) & 0xFF) as u8; + } + // The 4th byte represents the frame type in the frame header. + 3 => { + buf[3] = FrameType::Ping as u8; + } + // The 5th byte represents the frame flags in the frame header. + 4 => { + buf[4] = frame.flags().bits(); + } + // The last 4 bytes (6th to 9th) represent the stream identifier in the frame header. + // For PING frames, this should always be 0. + 5..=8 => { + buf[buf_index] = 0; // Stream ID should be 0 for PING frames. + } + _ => { + return Err(FrameEncoderErr::InternalError); + } + } + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == frame_header_size { + self.state = FrameEncoderState::EncodingPingPayload; + } + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_ping_payload(&mut self, buf: &mut [u8]) -> Result { + if let Some(frame) = &self.current_frame { + if let Payload::Ping(ping) = frame.payload() { + let payload_size = 8usize; // PING payload is always 8 bytes. + let remaining_payload_bytes = + payload_size.saturating_sub(self.encoded_bytes.saturating_sub(9usize)); + let bytes_to_write = remaining_payload_bytes.min(buf.len()); + for (buf_index, buf_item) in buf.iter_mut().enumerate().take(bytes_to_write) { + let payload_byte_index = self + .encoded_bytes + .saturating_sub(9) + .saturating_add(buf_index); + *buf_item = ping.data[payload_byte_index]; + } + self.encoded_bytes += bytes_to_write; + if self.encoded_bytes == 9 + 8 { + self.state = FrameEncoderState::DataComplete; + } + + Ok(bytes_to_write) + } else { + Err(FrameEncoderErr::UnexpectedPayloadType) + } + } else { + Err(FrameEncoderErr::NoCurrentFrame) + } + } + + fn encode_slice(&self, buf: &mut [u8], data: &[u8], start: usize) -> usize { + let data_len = data.len(); + let remaining_data_bytes = data_len.saturating_sub(start); + let bytes_to_write = remaining_data_bytes.min(buf.len()); + + buf[..bytes_to_write].copy_from_slice(&data[start..start + bytes_to_write]); + bytes_to_write + } + + // Helper method for writing the payload from the buffer to the output buffer. + fn write_payload(&mut self, buf: &mut [u8], payload_len: usize) -> usize { + let bytes_to_write = buf.len().min(payload_len - self.header_payload_index); + buf[..bytes_to_write].copy_from_slice( + &self.header_payload_buffer + [self.header_payload_index..self.header_payload_index + bytes_to_write], + ); + self.header_payload_index += bytes_to_write; + bytes_to_write + } +} + +#[cfg(test)] +mod ut_frame_encoder { + use super::*; + use crate::error::HttpError; + use crate::h2::frame::{ + Data, FrameFlags, Goaway, Headers, Ping, Priority, RstStream, Settings, WindowUpdate, + }; + use crate::h2::{Parts, PseudoHeaders}; + + /// UT test cases for `FrameEncoder` encoding `DATA` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Data`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_data_frame_encoding() { + let mut encoder = FrameEncoder::new(4096, 4096); + let data_payload = b"hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh".to_vec(); + + let data_frame = Frame::new( + 131, + FrameFlags::new(0), + Payload::Data(Data::new(data_payload.clone())), + ); + + encoder.set_frame(data_frame); + + let mut first_buf = [0u8; 2]; + let mut second_buf = [0u8; 38]; + + let first_encoded = encoder.encode(&mut first_buf).unwrap(); + let second_encoded = encoder.encode(&mut second_buf).unwrap(); + + assert_eq!(first_encoded, 2); + assert_eq!(second_encoded, 38); + assert_eq!(first_buf[0], 0); + assert_eq!(first_buf[1], 0); + assert_eq!(second_buf[0], data_payload.len() as u8); + assert_eq!(second_buf[6], 131); + + for &item in second_buf.iter().skip(7).take(30) { + assert_eq!(item, 104); + } + } + + /// UT test cases for `FrameEncoder` encoding `HEADERS` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Headers`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_headers_frame_encoding() { + let mut frame_encoder = FrameEncoder::new(4096, 8190); + + let mut new_parts = Parts::new(); + new_parts.pseudo.set_method(Some("GET".to_string())); + new_parts.pseudo.set_scheme(Some("https".to_string())); + new_parts.pseudo.set_path(Some("/code".to_string())); + new_parts + .pseudo + .set_authority(Some("example.com".to_string())); + let mut frame_flag = FrameFlags::empty(); + frame_flag.set_end_headers(true); + frame_flag.set_end_stream(true); + let frame = Frame::new(1, frame_flag, Payload::Headers(Headers::new(new_parts))); + + // Set the current frame for the encoder + frame_encoder.set_frame(frame); + + let mut buf = vec![0; 50]; + let first_encoded = frame_encoder.encode(&mut buf).unwrap(); + assert_eq!(first_encoded, 22 + 9); + assert_eq!(buf[0], 0); + assert_eq!(buf[2], 22); + assert_eq!(buf[3], 0x1); + assert_eq!(buf[4], 5); + assert_eq!(buf[8], 1); + + assert_eq!(frame_encoder.state, FrameEncoderState::HeadersComplete); + } + + /// UT test cases for `FrameEncoder` encoding `SETTINGS` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Settings`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_settings_frame_encoding() { + let mut encoder = FrameEncoder::new(4096, 4096); + let settings_payload = vec![ + Setting::HeaderTableSize(4096), + Setting::EnablePush(true), + Setting::MaxConcurrentStreams(100), + Setting::InitialWindowSize(65535), + Setting::MaxFrameSize(16384), + Setting::MaxHeaderListSize(8192), + ]; + + let settings = Settings::new(settings_payload.clone()); + + let settings_frame = Frame::new(0, FrameFlags::new(0), Payload::Settings(settings)); + + let mut first_buf = [0u8; 9]; + let mut second_buf = [0u8; 30]; + let mut third_buf = [0u8; 6]; + encoder.set_frame(settings_frame); + + let first_encoded = encoder.encode(&mut first_buf).unwrap(); + assert_eq!(encoder.state, FrameEncoderState::EncodingSettingsPayload); + let second_encoded = encoder.encode(&mut second_buf).unwrap(); + let third_encoded = encoder.encode(&mut third_buf).unwrap(); + + assert_eq!(encoder.state, FrameEncoderState::DataComplete); + assert_eq!(first_encoded, 9); // Updated expected value for first_encoded + assert_eq!(second_encoded, 30); + assert_eq!(third_encoded, 6); // Updated expected value for third_encoded + + // Validate the encoded settings + let mut expected_encoded_settings = vec![0u8; 60]; + for (i, setting) in settings_payload.iter().enumerate() { + let offset = i * 6; + let (id, value) = match setting { + Setting::HeaderTableSize(v) => (0x1, *v), + Setting::EnablePush(v) => (0x2, *v as u32), + Setting::MaxConcurrentStreams(v) => (0x3, *v), + Setting::InitialWindowSize(v) => (0x4, *v), + Setting::MaxFrameSize(v) => (0x5, *v), + Setting::MaxHeaderListSize(v) => (0x6, *v), + }; + expected_encoded_settings[offset] = (id >> 8) as u8; + expected_encoded_settings[offset + 1] = (id & 0xFF) as u8; + expected_encoded_settings[offset + 2] = (value >> 24) as u8; + expected_encoded_settings[offset + 3] = ((value >> 16) & 0xFF) as u8; + expected_encoded_settings[offset + 4] = ((value >> 8) & 0xFF) as u8; + expected_encoded_settings[offset + 5] = (value & 0xFF) as u8; + } + + let actual_encoded_settings = [&second_buf[..], &third_buf[..]].concat(); + for i in 0..35 { + assert_eq!(expected_encoded_settings[i], actual_encoded_settings[i]); + } + } + + /// UT test cases for `FrameEncoder` encoding `PING` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Ping`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_ping_frame_encoding() { + let mut encoder = FrameEncoder::new(4096, 4096); + let ping_payload = [1, 2, 3, 4, 5, 6, 7, 8]; + + let ping_frame = Frame::new( + 0, + FrameFlags::new(0), + Payload::Ping(Ping { data: ping_payload }), + ); + + encoder.set_frame(ping_frame); + + let mut first_buf = [0u8; 9]; + let mut second_buf = [0u8; 8]; + + let first_encoded = encoder.encode(&mut first_buf).unwrap(); + let second_encoded = encoder.encode(&mut second_buf).unwrap(); + + assert_eq!(first_encoded, 9); + assert_eq!(second_encoded, 8); + + assert_eq!(first_buf[0], 0); + assert_eq!(first_buf[1], 0); + assert_eq!(first_buf[2], 8); // payload length + assert_eq!(first_buf[3], FrameType::Ping as u8); + assert_eq!(first_buf[4], 0); // flags + assert_eq!(first_buf[5], 0); // stream id + assert_eq!(first_buf[6], 0); // stream id + assert_eq!(first_buf[7], 0); // stream id + assert_eq!(first_buf[8], 0); // stream id + + for i in 0..8 { + assert_eq!(second_buf[i], ping_payload[i]); + } + } + + /// UT test case for FrameEncoder encoding a sequence of frames: Headers, Data, Headers. + /// + /// # Brief + /// 1. Creates a FrameEncoder. + /// 2. Creates multiple frames including Headers and Data frames. + /// 3. Sets each frame for the encoder and encodes them using buffer segments. + /// 4. Checks whether the encoding results are correct. + #[test] + fn ut_continue_frame_encoding() { + let mut encoder = FrameEncoder::new(4096, 8190); + + let mut new_parts = Parts::new(); + new_parts.pseudo.set_method(Some("GET".to_string())); + new_parts.pseudo.set_scheme(Some("https".to_string())); + new_parts.pseudo.set_path(Some("/code".to_string())); + new_parts + .pseudo + .set_authority(Some("example.com".to_string())); + let mut frame_flag = FrameFlags::empty(); + frame_flag.set_end_headers(true); + frame_flag.set_end_stream(false); + let frame_1 = Frame::new( + 1, + frame_flag.clone(), + Payload::Headers(Headers::new(new_parts.clone())), + ); + + let data_payload = b"hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh".to_vec(); + let data_frame = Frame::new( + 1, + FrameFlags::new(1), + Payload::Data(Data::new(data_payload)), + ); + + let frame_2 = Frame::new( + 1, + frame_flag.clone(), + Payload::Headers(Headers::new(new_parts.clone())), + ); + + encoder.set_frame(frame_1); + let mut first_buf = [0u8; 50]; + let first_encoding = encoder.encode(&mut first_buf).unwrap(); + + encoder.set_frame(data_frame); + let mut second_buf = [0u8; 50]; + let second_encoding = encoder.encode(&mut second_buf).unwrap(); + + encoder.set_frame(frame_2); + let mut third_buf = [0u8; 50]; + let third_encoding = encoder.encode(&mut third_buf).unwrap(); + + assert_eq!(first_encoding, 31); + assert_eq!(second_encoding, 40); + assert_eq!(third_encoding, 13); + + assert_eq!(first_buf[2], 22); + assert_eq!(second_buf[2], 31); + assert_eq!(third_buf[2], 4); + } + + /// UT test cases for `FrameEncoder` encoding `RST_STREAM` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::RstStream`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_rst_stream_frame_encoding() { + let mut frame_encoder = FrameEncoder::new(4096, 8190); + + let error_code = 12345678; + let rst_stream_payload = Payload::RstStream(RstStream::new(error_code)); + + let frame_flags = FrameFlags::empty(); + let frame = Frame::new( + 1, // Stream ID can be non-zero for RST_STREAM frames + frame_flags, + rst_stream_payload, + ); + + // Set the current frame for the encoder + frame_encoder.set_frame(frame); + + let mut buf = vec![0; 50]; + let first_encoded = frame_encoder.encode(&mut buf).unwrap(); + assert_eq!(first_encoded, 9 + 4); // 9 bytes for header, 4 bytes for payload + assert_eq!(buf[0], 0); + assert_eq!(buf[2], 4); // payload length should be 4 for RST_STREAM frames + assert_eq!(buf[3], FrameType::RstStream as u8); + assert_eq!(buf[4], 0); // frame flags should be 0 for RST_STREAM frames + assert_eq!(buf[8], 1); // stream ID should be 1 for this test case + + // Check if encoded error code is correct + assert_eq!(&buf[9..13], &error_code.to_be_bytes()); + + assert_eq!(frame_encoder.state, FrameEncoderState::DataComplete); + } + + /// UT test cases for `FrameEncoder` encoding `WINDOW_UPDATE` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::WindowUpdate`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_window_update_frame_encoding() { + let mut frame_encoder = FrameEncoder::new(4096, 8190); + + let window_size_increment = 12345678; + let window_update_payload = Payload::WindowUpdate(WindowUpdate::new(window_size_increment)); + + let frame_flags = FrameFlags::empty(); + let frame = Frame::new( + 0, // Stream ID can be zero for WINDOW_UPDATE frames. + frame_flags, + window_update_payload, + ); + + // Sets the current frame for the encoder. + frame_encoder.set_frame(frame); + + let mut buf = vec![0; 50]; + let first_encoded = frame_encoder.encode(&mut buf).unwrap(); + assert_eq!(first_encoded, 9 + 4); // 9 bytes for header, 4 bytes for payload. + assert_eq!(buf[0], 0); + assert_eq!(buf[2], 4); // Payload length should be 4 for WINDOW_UPDATE frames. + assert_eq!(buf[3], FrameType::WindowUpdate as u8); + assert_eq!(buf[4], 0); // Frame flags should be 0 for WINDOW_UPDATE frames. + assert_eq!(buf[8], 0); // Stream ID should be 0 for this test case. + + // Checks if encoded window size increment is correct. + assert_eq!(&buf[9..13], &window_size_increment.to_be_bytes()); + + assert_eq!(frame_encoder.state, FrameEncoderState::DataComplete); + } + + /// UT test case for FrameEncoder encoding `PRIORITY` frame. + /// + /// # Brief + /// 1. Creates a FrameEncoder. + /// 2. Creates a Frame with Payload::Priority. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_priority_frame_encoding() { + let mut encoder = FrameEncoder::new(4096, 4096); + let stream_dependency = 0x7FFFFFFF; // Maximum value for a 31-bit integer + let priority_payload = Priority::new(true, stream_dependency, 15); + + let priority_frame = + Frame::new(131, FrameFlags::new(0), Payload::Priority(priority_payload)); + + encoder.set_frame(priority_frame); + + let mut buf = [0u8; 14]; + + let encoded = encoder.encode(&mut buf).unwrap(); + + assert_eq!(encoded, 14); + assert_eq!(buf[0], 0); // Payload length (most significant byte) + assert_eq!(buf[1], 0); // Payload length (middle byte) + assert_eq!(buf[2], 5); // Payload length (least significant byte) + assert_eq!(buf[3], FrameType::Priority as u8); + assert_eq!(buf[4], 0); // Frame flags + assert_eq!(buf[5], 0); // Stream ID (most significant byte) + assert_eq!(buf[6], 0); // Stream ID (middle bytes) + assert_eq!(buf[7], 0); // Stream ID (middle bytes) + assert_eq!(buf[8], 131); // Stream ID (least significant byte) + assert_eq!(buf[9], (0x80 | ((stream_dependency >> 24) & 0x7F)) as u8); // Exclusive flag and most significant byte of stream dependency + assert_eq!(buf[10], ((stream_dependency >> 16) & 0xFF) as u8); // Stream dependency (middle bytes) + assert_eq!(buf[11], ((stream_dependency >> 8) & 0xFF) as u8); // Stream dependency (middle bytes) + assert_eq!(buf[12], (stream_dependency & 0xFF) as u8); // Stream dependency (least significant byte) + assert_eq!(buf[13], 15); // Weight + } + + /// UT test cases for `FrameEncoder` encoding `GOAWAY` frame. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Goaway`. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the frame and its payload using buffer segments. + /// 5. Checks whether the result is correct. + #[test] + fn ut_goaway_frame_encoding() { + // 1. Creates a `FrameEncoder`. + let mut encoder = FrameEncoder::new(4096, 4096); + + // 2. Creates a `Frame` with `Payload::Goaway`. + let last_stream_id = 1; + let error_code = 1; + let debug_data = vec![1, 2, 3, 4, 5]; + let goaway_frame = Frame::new( + 131, + FrameFlags::new(0), + Payload::Goaway(Goaway::new(error_code, last_stream_id, debug_data.clone())), + ); + + // 3. Sets the frame for the encoder. + encoder.set_frame(goaway_frame); + + // 4. Encodes the frame and its payload using buffer segments. + let mut first_buf = [0u8; 9]; + let mut second_buf = [0u8; 13]; + let first_encoded = encoder.encode(&mut first_buf).unwrap(); + let second_encoded = encoder.encode(&mut second_buf).unwrap(); + + // 5. Checks whether the result is correct. + assert_eq!(first_encoded, 9); + assert_eq!(second_encoded, 13); + + // Validate the encoded GOAWAY frame. + let mut expected_encoded_goaway = vec![0u8; 13]; + expected_encoded_goaway[0..4].copy_from_slice(&(last_stream_id as u32).to_be_bytes()); + expected_encoded_goaway[4..8].copy_from_slice(&(error_code).to_be_bytes()); + + expected_encoded_goaway[8..13].copy_from_slice(&debug_data[..]); + + assert_eq!(first_buf[0..3], [0u8, 0, 13]); // payload length should be 13 bytes + assert_eq!(first_buf[3], FrameType::Goaway as u8); + assert_eq!(first_buf[4], 0); // flags + + // Validate the encoded Last-Stream-ID, Error Code, and debug data + assert_eq!(second_buf[..], expected_encoded_goaway[..]); + } + + /// UT test cases for `FrameEncoder::update_max_frame_size`. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Updates the maximum frame size. + /// 3. Checks whether the maximum frame size was updated correctly. + #[test] + fn ut_update_max_frame_size() { + let mut encoder = FrameEncoder::new(4096, 4096); + encoder.update_max_frame_size(8192); + assert_eq!(encoder.max_frame_size, 8192); + } + + /// UT test cases for `FrameEncoder::update_header_table_size`. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Updates the maximum header table size. + /// 3. Checks whether the maximum header table size was updated correctly. + #[test] + fn ut_update_header_table_size() { + let mut encoder = FrameEncoder::new(4096, 4096); + encoder.update_header_table_size(8192); + assert_eq!(encoder.max_header_list_size, 8192); + } + + /// UT test cases for `FrameEncoder::update_setting`. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Setting` variant. + /// 3. Creates a `Frame` with `Payload::Settings`. + /// 4. Sets the frame for the encoder. + /// 5. Updates the setting. + /// 6. Checks whether the setting was updated correctly. + #[test] + fn ut_update_setting() { + let mut encoder = FrameEncoder::new(4096, 4096); + let settings_payload = vec![Setting::MaxFrameSize(4096)]; + let settings = Settings::new(settings_payload); + let settings_frame = Frame::new(0, FrameFlags::new(0), Payload::Settings(settings)); + + encoder.set_frame(settings_frame); + let new_setting = Setting::MaxFrameSize(8192); + encoder.update_setting(new_setting.clone()); + + if let Some(frame) = &mut encoder.current_frame { + if let Payload::Settings(settings) = frame.payload_mut() { + let updated_settings = settings.get_settings(); + assert!(updated_settings.iter().any(|s| *s == new_setting)); + } + } + } + + /// UT test cases for `FrameEncoder` encoding continuation frames. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Headers` and sets the flags. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the continuation frames using a buffer. + /// 5. Checks whether the result is correct. + #[test] + fn ut_encode_continuation_frames() { + let mut frame_encoder = FrameEncoder::new(4096, 8190); + let mut new_parts = Parts::new(); + assert!(new_parts.is_empty()); + new_parts.pseudo.set_method(Some("GET".to_string())); + new_parts.pseudo.set_scheme(Some("https".to_string())); + new_parts.pseudo.set_path(Some("/code".to_string())); + new_parts + .pseudo + .set_authority(Some("example.com".to_string())); + + let mut frame_flag = FrameFlags::empty(); + frame_flag.set_end_headers(false); + frame_flag.set_end_stream(false); + let frame = Frame::new( + 1, + frame_flag.clone(), + Payload::Headers(Headers::new(new_parts.clone())), + ); + + frame_encoder.set_frame(frame); + frame_encoder.state = FrameEncoderState::EncodingContinuationFrames; + let mut buf = [0u8; 5000]; + + assert!(frame_encoder.encode_continuation_frames(&mut buf).is_ok()); + + let frame_flag = FrameFlags::empty(); + let frame = Frame::new( + 1, + frame_flag, + Payload::Headers(Headers::new(new_parts.clone())), + ); + + frame_encoder.set_frame(frame); + frame_encoder.state = FrameEncoderState::EncodingContinuationFrames; + assert!(frame_encoder.encode_continuation_frames(&mut buf).is_ok()); + + let frame_flag = FrameFlags::empty(); + let frame = Frame::new(1, frame_flag, Payload::Ping(Ping::new([0; 8]))); + + frame_encoder.set_frame(frame); + frame_encoder.state = FrameEncoderState::EncodingContinuationFrames; + assert!(frame_encoder.encode_continuation_frames(&mut buf).is_err()); + } + + /// UT test cases for `FrameEncoder` encoding padded data. + /// + /// # Brief + /// 1. Creates a `FrameEncoder`. + /// 2. Creates a `Frame` with `Payload::Data` and sets the flags. + /// 3. Sets the frame for the encoder. + /// 4. Encodes the padding using a buffer. + /// 5. Checks whether the result is correct. + #[test] + fn ut_encode_padding() { + let mut frame_encoder = FrameEncoder::new(4096, 8190); + + // Creates a padded data frame. + let mut frame_flags = FrameFlags::empty(); + frame_flags.set_padded(true); + let data_payload = vec![0u8; 500]; + let data_frame = Frame::new( + 1, + frame_flags.clone(), + Payload::Data(Data::new(data_payload)), + ); + + // Sets the frame to the frame_encoder and test padding encoding. + frame_encoder.set_frame(data_frame); + frame_encoder.state = FrameEncoderState::EncodingDataPadding; + let mut buf = [0u8; 600]; + assert!(frame_encoder.encode_padding(&mut buf).is_ok()); + + let headers_payload = Payload::Headers(Headers::new(Parts::new())); + let headers_frame = Frame::new(1, frame_flags.clone(), headers_payload); + frame_encoder.set_frame(headers_frame); + frame_encoder.state = FrameEncoderState::EncodingDataPadding; + assert!(frame_encoder.encode_padding(&mut buf).is_err()); + + frame_encoder.current_frame = None; + assert!(frame_encoder.encode_padding(&mut buf).is_err()); + } +} diff --git a/ylong_http/src/h2/error.rs b/ylong_http/src/h2/error.rs new file mode 100644 index 0000000..10c4d9e --- /dev/null +++ b/ylong_http/src/h2/error.rs @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! [`Error Codes`] in [`HTTP/2`]. +//! +//! [`Error Codes`]: https://httpwg.org/specs/rfc9113.html#ErrorCodes +//! [`HTTP/2`]: https://httpwg.org/specs/rfc9113.html +//! +//! # introduction +//! Error codes are 32-bit fields that are used in `RST_STREAM` and `GOAWAY` +//! frames to convey the reasons for the stream or connection error. +//! +//! Error codes share a common code space. Some error codes apply only to either +//! streams or the entire connection and have no defined semantics in the other +//! context. + +use crate::error::{ErrorKind, HttpError}; +use std::convert::{Infallible, TryFrom}; + +/// The http2 error handle implementation +#[derive(Debug, Eq, PartialEq)] +pub enum H2Error { + /// [`Stream Error`] Handling. + /// + /// [`Stream Error`]: https://www.rfc-editor.org/rfc/rfc9113.html#name-stream-error-handling + StreamError(u32, ErrorCode), + + /// [`Connection Error`] Handling. + /// + /// [`Connection Error`]: https://www.rfc-editor.org/rfc/rfc9113.html#name-connection-error-handling + ConnectionError(ErrorCode), +} + +/// [`Error Codes`] implementation. +/// +/// [`Error Codes`]: https://httpwg.org/specs/rfc9113.html#ErrorCodes +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum ErrorCode { + /// The associated condition is not a result of an error. For example, + /// a `GOAWAY` might include this code to indicate graceful shutdown of a + /// connection. + NoError = 0x00, + + /// The endpoint detected an unspecific protocol error. This error is for + /// use when a more specific error code is not available. + ProtocolError = 0x01, + + /// The endpoint encountered an unexpected internal error. + IntervalError = 0x02, + + /// The endpoint detected that its peer violated the flow-control protocol. + FlowControlError = 0x03, + + /// The endpoint sent a `SETTINGS` frame but did not receive a response in + /// a timely manner. + SettingsTimeout = 0x04, + + /// The endpoint received a frame after a stream was half-closed. + StreamClosed = 0x05, + + /// The endpoint received a frame with an invalid size. + FrameSizeError = 0x06, + + /// The endpoint refused the stream prior to performing any application + /// processing. + RefusedStream = 0x07, + + /// The endpoint uses this error code to indicate that the stream is no + /// longer needed. + Cancel = 0x08, + + /// The endpoint is unable to maintain the field section compression context + /// for the connection. + CompressionError = 0x09, + + /// The connection established in response to a `CONNECT` request was reset + /// or abnormally closed. + ConnectError = 0x0a, + + /// The endpoint detected that its peer is exhibiting a behavior that might + /// be generating excessive load. + EnhanceYourCalm = 0x0b, + + /// The underlying transport has properties that do not meet minimum + /// security requirements. + InadequateSecurity = 0x0c, + + /// The endpoint requires that HTTP/1.1 be used instead of HTTP/2. + Http1_1Required = 0x0d, +} + +impl ErrorCode { + /// Gets the error code of the `ErrorCode` enum. + pub fn into_code(self) -> u32 { + self as u32 + } +} + +impl TryFrom for ErrorCode { + type Error = HttpError; + fn try_from(value: u32) -> Result { + let err = match value { + 0x00 => ErrorCode::NoError, + 0x01 => ErrorCode::ProtocolError, + 0x02 => ErrorCode::IntervalError, + 0x03 => ErrorCode::FlowControlError, + 0x04 => ErrorCode::SettingsTimeout, + 0x05 => ErrorCode::StreamClosed, + 0x06 => ErrorCode::FrameSizeError, + 0x07 => ErrorCode::RefusedStream, + 0x08 => ErrorCode::Cancel, + 0x09 => ErrorCode::CompressionError, + 0x0a => ErrorCode::ConnectError, + 0x0b => ErrorCode::EnhanceYourCalm, + 0x0c => ErrorCode::InadequateSecurity, + 0x0d => ErrorCode::Http1_1Required, + _ => return Err(HttpError::from(ErrorKind::InvalidInput)), + }; + Ok(err) + } +} + +#[cfg(test)] +mod ut_h2_error { + use super::*; + use std::convert::TryInto; + + /// Unit test cases for `ErrorCode::try_from`. + /// + /// # Brief + /// 1. Iterates over a range of valid u32 values that represent `ErrorCode`s. + /// 2. Attempts to convert each u32 value into an `ErrorCode` using `try_into`. + /// 3. Checks that the conversion is successful for each valid `ErrorCode`. + /// 4. Also attempts to convert an invalid u32 value into an `ErrorCode`. + /// 5. Checks that the conversion fails for the invalid value. + #[test] + fn ut_test_error_code_try_from() { + // Test conversion from u32 to ErrorCode for valid error codes + for i in 0x00..=0x0d { + let error_code: Result = i.try_into(); + assert!(error_code.is_ok()); + } + + // Test conversion from u32 to ErrorCode for invalid error codes + let invalid_error_code: Result = 0x0e.try_into(); + assert!(invalid_error_code.is_err()); + } +} diff --git a/ylong_http/src/h2/frame.rs b/ylong_http/src/h2/frame.rs new file mode 100644 index 0000000..7465c68 --- /dev/null +++ b/ylong_http/src/h2/frame.rs @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::HttpError; +use crate::h2::{ErrorCode, H2Error, Parts, PseudoHeaders}; +use crate::headers; +use std::convert::TryFrom; + +/// Mask for the END_STREAM flag. +/// When set, indicates that the sender will not send further frames for this stream. +pub(crate) const END_STREAM_MASK: u8 = 0x01; + +/// Mask for the RST_STREAM flag. +/// When set, indicates that the stream is being terminated. +pub(crate) const RST_STREAM_MASK: u8 = 0x03; + +/// Mask for the END_HEADERS flag. +/// When set, indicates that this frame contains an entire header block and not a fragment. +pub(crate) const END_HEADERS_MASK: u8 = 0x04; + +/// Mask for the PADDED flag. +/// When set, indicates that the frame payload is followed by a padding field. +pub(crate) const PADDED_MASK: u8 = 0x08; + +/// Mask for the HEADERS_PRIORITY flag. +/// When set, indicates that the headers frame also contains the priority information. +pub(crate) const HEADERS_PRIORITY_MASK: u8 = 0x20; + +/// Mask for the ACK flag +pub(crate) const ACK_MASK: u8 = 0x1; + +/// HTTP/2 frame structure, including the stream ID, flags, and payload information. +/// The frame type information is represented by the `Payload` type. +/// This structure represents the fundamental unit of communication in HTTP/2. +#[derive(Clone)] +pub struct Frame { + id: StreamId, + flags: FrameFlags, + payload: Payload, +} + +/// Enum representing the type of HTTP/2 frame. +/// Each HTTP/2 frame type serves a unique role in the communication process. +#[derive(PartialEq, Eq, Debug)] +pub enum FrameType { + Data = 0x0, + Headers = 0x1, + Priority = 0x2, + RstStream = 0x03, + Settings = 0x4, + PushPromise = 0x5, + Ping = 0x6, + Goaway = 0x7, + WindowUpdate = 0x8, + Continuation = 0x9, + // add more frame types as needed +} + +/// Enum representing the payload of an HTTP/2 frame. +/// The payload differs based on the type of frame. +#[derive(Clone)] +pub enum Payload { + /// HEADERS frame payload. + Headers(Headers), + /// DATA frame payload. + Data(Data), + /// PRIORITY frame payload. + Priority(Priority), + /// RST_STREAM frame payload. + RstStream(RstStream), + /// PING frame payload. + Ping(Ping), + /// SETTINGS frame payload. + Settings(Settings), + /// GOAWAY frame payload. + Goaway(Goaway), + /// WINDOW_UPDATE frame payload. + WindowUpdate(WindowUpdate), + /// PUSH_PROMISE + PushPromise(PushPromise), +} + +/// Enum representing the different settings that can be included in a SETTINGS frame. +/// Each setting has a different role in the HTTP/2 communication process. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Setting { + /// SETTINGS_HEADER_TABLE_SIZE + HeaderTableSize(u32), + /// SETTINGS_ENABLE_PUSH + EnablePush(bool), + /// SETTINGS_MAX_CONCURRENT_STREAMS + MaxConcurrentStreams(u32), + /// SETTINGS_INITIAL_WINDOW_SIZE + InitialWindowSize(u32), + /// SETTINGS_MAX_FRAME_SIZE + MaxFrameSize(u32), + /// SETTINGS_MAX_HEADER_LIST_SIZE + MaxHeaderListSize(u32), +} + +type StreamId = usize; + +/// HTTP/2 frame flags. +#[derive(Clone)] +pub struct FrameFlags(u8); + +/// HTTP/2 HEADERS frame's payload, contains pseudo headers and other headers. +#[derive(Clone)] +pub struct Headers { + parts: Parts, +} + +/// HTTP/2 DATA frame's payload, contains all content after padding is removed. +/// The DATA frame defines the payload data of an HTTP/2 request or response. +#[derive(Clone)] +pub struct Data { + data: Vec, +} + +/// Represents the PRIORITY frame payload. +/// The PRIORITY frame specifies the sender-advised priority of a stream. +#[derive(Clone)] +pub struct Priority { + exclusive: bool, + stream_dependency: u32, + weight: u8, +} + +/// The RST_STREAM frame allows for immediate termination of a stream. +/// RST_STREAM is sent to request cancellation of a stream or to indicate an error situation. +#[derive(Clone)] +pub struct RstStream { + error_code: u32, +} + +/// Represents the PING frame payload. +/// The PING frame is a mechanism for measuring a minimal round-trip time from the sender. +#[derive(Clone)] +pub struct Ping { + /// The opaque data of PING + pub data: [u8; 8], +} + +/// Represents the SETTINGS frame payload. +/// The SETTINGS frame conveys configuration parameters that affect how endpoints communicate. +#[derive(Clone)] +pub struct Settings { + settings: Vec, +} + +/// Represents the GOAWAY frame payload. +/// The GOAWAY frame is used to initiate shutdown of a connection or to signal serious error conditions. +#[derive(Clone)] +pub struct Goaway { + error_code: u32, + last_stream_id: StreamId, + debug_data: Vec, +} + +/// Represents the WINDOW_UPDATE frame payload. +/// The WINDOW_UPDATE frame is used to implement flow control in HTTP/2. +#[derive(Clone)] +pub struct WindowUpdate { + window_size_increment: u32, +} + +/// Represents the PUSH_PROMISE frame payload. +/// The PUSH_PROMISE frame is used to notify the peer endpoint in advance of streams the sender intends to initiate. +#[derive(Clone)] +pub struct PushPromise { + promised_stream_id: u32, + parts: Parts, +} + +/// A Builder of SETTINGS payload. +pub struct SettingsBuilder { + settings: Vec, +} + +impl Frame { + /// Returns the stream identifier (`StreamId`) of the frame. + pub fn stream_id(&self) -> StreamId { + self.id + } + + /// Constructs a new `Frame` with the given `StreamId`, `FrameFlags`, `Payload`. + pub fn new(id: StreamId, flags: FrameFlags, payload: Payload) -> Self { + Frame { id, flags, payload } + } + + /// Returns a reference to the frame's flags (`FrameFlags`). + pub fn flags(&self) -> &FrameFlags { + &self.flags + } + + /// Returns a reference to the frame's payload (`Payload`). + pub fn payload(&self) -> &Payload { + &self.payload + } + + /// Returns a mutable reference to the frame's payload (`Payload`). + /// This can be used to modify the payload of the frame. + pub(crate) fn payload_mut(&mut self) -> &mut Payload { + &mut self.payload + } +} + +impl FrameFlags { + /// Creates a new `FrameFlags` instance with the given `flags` byte. + pub fn new(flags: u8) -> Self { + FrameFlags(flags) + } + + /// Creates a new `FrameFlags` instance with no flags set. + pub fn empty() -> Self { + FrameFlags(0) + } + + /// Judges the END_FLOW Flag is true. + pub fn is_end_stream(&self) -> bool { + self.0 & END_STREAM_MASK == END_STREAM_MASK + } + + /// Judges the END_HEADERS Flag is true. + pub fn is_end_headers(&self) -> bool { + self.0 & END_HEADERS_MASK == END_HEADERS_MASK + } + + /// Judges the PADDED Flag is true. + pub fn is_padded(&self) -> bool { + self.0 & PADDED_MASK == PADDED_MASK + } + + /// Judge the ACK flag is true. + pub fn is_ack(&self) -> bool { + self.0 & ACK_MASK == ACK_MASK + } + + /// Get Flags octet. + pub fn bits(&self) -> u8 { + self.0 + } + + /// Sets the END_STREAM flag. + pub fn set_end_stream(&mut self, end_stream: bool) { + if end_stream { + self.0 |= END_STREAM_MASK; + } else { + self.0 &= !END_STREAM_MASK; + } + } + + /// Sets the END_HEADERS flag. + pub fn set_end_headers(&mut self, end_headers: bool) { + if end_headers { + self.0 |= END_HEADERS_MASK; + } else { + self.0 &= !END_HEADERS_MASK; + } + } + + /// Sets the PADDED flag. + pub fn set_padded(&mut self, padded: bool) { + if padded { + self.0 |= PADDED_MASK; + } else { + self.0 &= !PADDED_MASK; + } + } +} + +impl Payload { + /// Returns a reference to the Headers if the Payload is of the Headers variant. + /// If the Payload is not of the Headers variant, returns None. + pub(crate) fn as_headers(&self) -> Option<&Headers> { + if let Payload::Headers(headers) = self { + Some(headers) + } else { + None + } + } + + /// Returns the type of the frame (`FrameType`) that this payload would be associated with. + /// The returned `FrameType` is determined based on the variant of the Payload. + pub fn frame_type(&self) -> FrameType { + match self { + Payload::Headers(_) => FrameType::Headers, + Payload::Data(_) => FrameType::Data, + Payload::Priority(_) => FrameType::Priority, + Payload::Ping(_) => FrameType::Ping, + Payload::RstStream(_) => FrameType::RstStream, + Payload::Settings(_) => FrameType::Settings, + Payload::Goaway(_) => FrameType::Goaway, + Payload::WindowUpdate(_) => FrameType::WindowUpdate, + Payload::PushPromise(_) => FrameType::PushPromise, + } + } +} + +impl Headers { + /// Creates a new Headers instance from the provided Parts. + pub fn new(parts: Parts) -> Self { + Headers { parts } + } + + /// Returns pseudo headers and other headers as tuples. + pub fn parts(&self) -> (&PseudoHeaders, &headers::Headers) { + self.parts.parts() + } + + /// Returns a copy of the internal parts of the Headers. + pub(crate) fn get_parts(&self) -> Parts { + self.parts.clone() + } +} + +impl Data { + /// Creates a new Data instance containing the provided data. + pub fn new(data: Vec) -> Self { + Data { data } + } + + /// Return the `Vec` that contains the data payload. + pub fn data(&self) -> &Vec { + &self.data + } +} + +impl Settings { + /// Creates a new Settings instance containing the provided settings. + pub fn new(settings: Vec) -> Self { + Settings { settings } + } + + /// Returns a slice of the settings. + pub fn get_settings(&self) -> &[Setting] { + &self.settings + } + + /// Adds or updates a setting. + pub(crate) fn update_setting(&mut self, setting: Setting) { + let setting_id = setting.setting_identifier(); + if let Some(existing_setting) = self + .settings + .iter_mut() + .find(|s| s.setting_identifier() == setting_id) + { + *existing_setting = setting; + } else { + self.settings.push(setting); + } + } + + /// Returns the total length of the settings when encoded. + pub fn encoded_len(&self) -> usize { + let settings_count = self.settings.len(); + let setting_size = 6; // Each setting has a 2-byte ID and a 4-byte value + settings_count * setting_size + } +} + +impl Setting { + /// Returns the identifier associated with the setting. + pub fn setting_identifier(&self) -> u16 { + match self { + Setting::HeaderTableSize(_) => 0x01, + Setting::EnablePush(_) => 0x02, + Setting::MaxConcurrentStreams(_) => 0x03, + Setting::InitialWindowSize(_) => 0x04, + Setting::MaxFrameSize(_) => 0x05, + Setting::MaxHeaderListSize(_) => 0x06, + } + } +} + +impl SettingsBuilder { + /// `SettingsBuilder` constructor. + pub fn new() -> Self { + SettingsBuilder { settings: vec![] } + } + + /// SETTINGS_HEADER_TABLE_SIZE (0x01) setting. + pub fn header_table_size(mut self, size: u32) -> Self { + self.settings.push(Setting::HeaderTableSize(size)); + self + } + + /// SETTINGS_ENABLE_PUSH (0x02) setting. + pub fn enable_push(mut self, is_enable: bool) -> Self { + self.settings.push(Setting::EnablePush(is_enable)); + self + } + + /// SETTINGS_MAX_FRAME_SIZE (0x05) setting. + pub fn max_frame_size(mut self, size: u32) -> Self { + self.settings.push(Setting::MaxFrameSize(size)); + self + } + + /// SETTINGS_MAX_HEADER_LIST_SIZE (0x06) setting. + pub fn max_header_list_size(mut self, size: u32) -> Self { + self.settings.push(Setting::MaxHeaderListSize(size)); + self + } + + /// Consumes the Builder and constructs a SETTINGS payload. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::h2::SettingsBuilder; + /// + /// let settings = SettingsBuilder::new() + /// .enable_push(true) + /// .header_table_size(4096) + /// .max_frame_size(2 << 13) + /// .build(); + /// ``` + pub fn build(self) -> Settings { + Settings::new(self.settings) + } +} + +impl Default for SettingsBuilder { + fn default() -> Self { + Self::new() + } +} + +impl Goaway { + /// Creates a new Goaway instance with the provided error code, last stream ID, and debug data. + pub fn new(error_code: u32, last_stream_id: StreamId, debug_data: Vec) -> Self { + Goaway { + error_code, + last_stream_id, + debug_data, + } + } + + /// Returns a slice of the debug data. + pub fn get_debug_data(&self) -> &[u8] { + &self.debug_data + } + + /// Returns the identifier of the last stream processed by the sender. + pub fn get_last_stream_id(&self) -> StreamId { + self.last_stream_id + } + + /// Returns the error code. + pub fn get_error_code(&self) -> u32 { + self.error_code + } + + /// Returns the total length of the Goaway frame when encoded. + pub fn encoded_len(&self) -> usize { + 8 + self.debug_data.len() // 4-byte Last-Stream-ID + 4-byte Error Code + Debug Data length + } +} + +impl WindowUpdate { + /// Creates a new WindowUpdate instance with the provided window size increment. + pub fn new(window_size_increment: u32) -> Self { + WindowUpdate { + window_size_increment, + } + } + + /// Returns the window size increment. + pub(crate) fn get_increment(&self) -> u32 { + self.window_size_increment + } + + /// Returns the length of the WindowUpdate frame when encoded. + pub fn encoded_len(&self) -> usize { + 4 // 4-byte window size increment + } +} + +impl Priority { + /// Creates a new Priority instance with the provided exclusive flag, stream dependency, and weight. + pub fn new(exclusive: bool, stream_dependency: u32, weight: u8) -> Self { + Priority { + exclusive, + stream_dependency, + weight, + } + } + + /// Returns whether the stream is exclusive. + pub fn get_exclusive(&self) -> bool { + self.exclusive + } + + /// Returns the stream dependency. + pub fn get_stream_dependency(&self) -> u32 { + self.stream_dependency + } + + /// Returns the weight of the stream. + pub fn get_weight(&self) -> u8 { + self.weight + } +} + +impl RstStream { + /// Creates a new RstStream instance with the provided error code. + pub fn new(error_code: u32) -> Self { + Self { error_code } + } + + /// Returns the error code associated with the RstStream. + pub fn error_code(&self) -> u32 { + self.error_code + } + + /// GET the `ErrorCode` of `RstStream` + pub fn error(&self, id: u32) -> Result { + Ok(H2Error::StreamError( + id, + ErrorCode::try_from(self.error_code)?, + )) + } +} + +impl Ping { + /// Creates a new Ping instance with the provided data. + pub fn new(data: [u8; 8]) -> Self { + Ping { data } + } + + /// Returns the data associated with the Ping. + pub fn data(&self) -> [u8; 8] { + self.data + } +} + +impl PushPromise { + /// `PushPromise` constructor. + pub fn new(promised_stream_id: u32, parts: Parts) -> Self { + Self { + promised_stream_id, + parts, + } + } +} + +#[cfg(test)] +mod ut_frame { + use super::*; + use crate::h2::Parts; + + /// UT test cases for `SettingsBuilder`. + /// + /// # Brief + /// 1. Creates a `SettingsBuilder`. + /// 2. Sets various setting parameters using builder methods. + /// 3. Builds a `Settings` object. + /// 4. Gets a reference to the underlying `Vec` from the `Settings` object. + /// 5. Iterates over each setting in the `Vec` and checks whether it matches the expected value. + #[test] + fn ut_settings_builder() { + let settings = SettingsBuilder::new() + .header_table_size(4096) + .enable_push(true) + .max_frame_size(16384) + .max_header_list_size(8192) + .build(); + + let mut setting_iter = settings.get_settings().iter(); + assert_eq!(setting_iter.next(), Some(&Setting::HeaderTableSize(4096))); // Check that the first setting is as expected + assert_eq!(setting_iter.next(), Some(&Setting::EnablePush(true))); // Check that the second setting is as expected + assert_eq!(setting_iter.next(), Some(&Setting::MaxFrameSize(16384))); // Check that the third setting is as expected + assert_eq!(setting_iter.next(), Some(&Setting::MaxHeaderListSize(8192))); // Check that the fourth setting is as expected + assert_eq!(setting_iter.next(), None); // Check that there are no more settings + } + + /// UT test cases for `Ping`. + /// + /// # Brief + /// 1. Creates a `Ping` instance with specific data. + /// 2. Checks if the data of the `Ping` instance is correct. + #[test] + fn ut_ping() { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let ping = Ping::new(data); + assert_eq!(ping.data(), data); + } + + /// UT test cases for `Setting`. + /// + /// # Brief + /// 1. Creates a `Setting` instance for each possible variant with a specific value. + /// 2. Checks if the identifier of the `Setting` instance is correct. + #[test] + fn ut_setting() { + let setting_header_table_size = Setting::HeaderTableSize(4096); + assert_eq!(setting_header_table_size.setting_identifier(), 0x01); + + let setting_enable_push = Setting::EnablePush(true); + assert_eq!(setting_enable_push.setting_identifier(), 0x02); + + let setting_max_concurrent_streams = Setting::MaxConcurrentStreams(100); + assert_eq!(setting_max_concurrent_streams.setting_identifier(), 0x03); + + let setting_initial_window_size = Setting::InitialWindowSize(5000); + assert_eq!(setting_initial_window_size.setting_identifier(), 0x04); + + let setting_max_frame_size = Setting::MaxFrameSize(16384); + assert_eq!(setting_max_frame_size.setting_identifier(), 0x05); + + let setting_max_header_list_size = Setting::MaxHeaderListSize(8192); + assert_eq!(setting_max_header_list_size.setting_identifier(), 0x06); + } + + /// UT test cases for `Settings`. + /// + /// # Brief + /// 1. Creates a `Settings` instance with a list of settings. + /// 2. Checks if the list of settings in the `Settings` instance is correct. + /// 3. Checks if the encoded length of the settings is correct. + #[test] + fn ut_settings() { + let settings_list = vec![ + Setting::HeaderTableSize(4096), + Setting::EnablePush(true), + Setting::MaxFrameSize(16384), + Setting::MaxHeaderListSize(8192), + ]; + let settings = Settings::new(settings_list.clone()); + assert_eq!(settings.get_settings(), settings_list.as_slice()); + + let encoded_len = settings.encoded_len(); + assert_eq!(encoded_len, settings_list.len() * 6); + } + + /// UT test cases for `Payload`. + /// + /// # Brief + /// 1. Creates an instance of `Payload` for each possible variant. + /// 2. Checks if the `frame_type` of the `Payload` instance is correct. + /// 3. Checks if `as_headers` method returns Some or None correctly. + #[test] + fn ut_payload() { + let payload_headers = Payload::Headers(Headers::new(Parts::new())); + assert_eq!(payload_headers.frame_type(), FrameType::Headers); + assert!(payload_headers.as_headers().is_some()); + + let payload_data = Payload::Data(Data::new(b"hh".to_vec())); + assert_eq!(payload_data.frame_type(), FrameType::Data); + assert!(payload_data.as_headers().is_none()); + + let payload_priority = Payload::Priority(Priority::new(true, 1, 10)); + assert_eq!(payload_priority.frame_type(), FrameType::Priority); + assert!(payload_priority.as_headers().is_none()); + + let payload_rst_stream = Payload::RstStream(RstStream::new(20)); + assert_eq!(payload_rst_stream.frame_type(), FrameType::RstStream); + assert!(payload_rst_stream.as_headers().is_none()); + + let payload_ping = Payload::Ping(Ping::new([0; 8])); + assert_eq!(payload_ping.frame_type(), FrameType::Ping); + assert!(payload_ping.as_headers().is_none()); + + let payload_goaway = Payload::Goaway(Goaway::new(30, 20, vec![0; 0])); + assert_eq!(payload_goaway.frame_type(), FrameType::Goaway); + assert!(payload_goaway.as_headers().is_none()); + + let payload_window_update = Payload::WindowUpdate(WindowUpdate::new(1024)); + assert_eq!(payload_window_update.frame_type(), FrameType::WindowUpdate); + assert!(payload_window_update.as_headers().is_none()); + + let payload_push_promise = Payload::PushPromise(PushPromise::new(3, Parts::new())); + assert_eq!(payload_push_promise.frame_type(), FrameType::PushPromise); + assert!(payload_push_promise.as_headers().is_none()); + } +} diff --git a/ylong_http/src/h2/hpack/decoder.rs b/ylong_http/src/h2/hpack/decoder.rs new file mode 100644 index 0000000..5a9b586 --- /dev/null +++ b/ylong_http/src/h2/hpack/decoder.rs @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::error::ErrorCode; +use crate::h2::hpack::representation::{ + Name, ReprDecStateHolder, ReprDecodeState, ReprDecoder, Representation, +}; +use crate::h2::hpack::table::{DynamicTable, Header, TableSearcher}; +use crate::h2::{H2Error, Parts}; +use core::mem::take; + +// A structure used to store header lines and octets lengths of header lines. +struct HeaderLines { + parts: Parts, + header_size: usize, +} + +/// Decoder implementation of [`HPACK`]. +/// +/// [`HPACK`]: https://httpwg.org/specs/rfc7541.html +pub(crate) struct HpackDecoder { + header_list_size: usize, + table: DynamicTable, + lines: HeaderLines, + holder: ReprDecStateHolder, +} + +impl HpackDecoder { + /// Creates a `HpackDecoder` with the given max size. + pub(crate) fn with_max_size(header_table_size: usize, max_header_list_size: usize) -> Self { + Self { + header_list_size: max_header_list_size, + table: DynamicTable::with_max_size(header_table_size), + lines: HeaderLines { + parts: Parts::new(), + header_size: 0, + }, + holder: ReprDecStateHolder::new(), + } + } + + /// Users can call `decode` multiple times to decode the byte stream in segments. + pub(crate) fn decode(&mut self, buf: &[u8]) -> Result<(), H2Error> { + // Initialize ReprDecoder. + let mut decoder = ReprDecoder::new(buf); + decoder.load(&mut self.holder); + + let mut updater = Updater::new(&mut self.table, &mut self.lines, self.header_list_size); + loop { + match decoder.decode()? { + // If a `Repr` is decoded, the `Updater` updates it immediately. + Some(repr) => updater.update(repr)?, + // If no `Repr` is decoded at this time, the intermediate result + // needs to be saved. + None => { + decoder.save(&mut self.holder); + return Ok(()); + } + } + } + } + + /// Users call `finish` to stop decoding and get the result. + pub(crate) fn finish(&mut self) -> Result { + if !self.holder.is_empty() { + return Err(H2Error::ConnectionError(ErrorCode::CompressionError)); + } + self.lines.header_size = 0; + Ok(take(&mut self.lines.parts)) + } + + /// Update the SETTING_HEADER_LIST_SIZE + pub(crate) fn update_header_list_size(&mut self, size: usize) { + self.header_list_size = size + } +} + +/// `Updater` is used to update `DynamicTable` `PseudoHeaders` and `HttpHeaderMap`. +struct Updater<'a> { + header_list_size: usize, + table: &'a mut DynamicTable, + lines: &'a mut HeaderLines, +} + +impl<'a> Updater<'a> { + /// Creates a new `Updater`. + fn new( + table: &'a mut DynamicTable, + lines: &'a mut HeaderLines, + header_list_size: usize, + ) -> Self { + Self { + table, + lines, + header_list_size, + } + } + + /// Updates the `DynamicTable` and `Parts`. + fn update(&mut self, repr: Representation) -> Result<(), H2Error> { + match repr { + Representation::Indexed { index } => self.update_indexed(index), + Representation::LiteralWithIndexing { name: n, value: v } => { + self.update_literal_with_indexing(n, v) + } + Representation::LiteralWithoutIndexing { name: n, value: v } => { + self.update_literal_without_indexing(n, v) + } + Representation::LiteralNeverIndexed { name: n, value: v } => { + self.update_literal_never_indexing(n, v) + } + Representation::SizeUpdate { max_size } => { + self.table.update_size(max_size); + Ok(()) + } + } + } + + fn update_indexed(&mut self, index: usize) -> Result<(), H2Error> { + let searcher = TableSearcher::new(self.table); + let (h, v) = searcher + .search_header(index) + .ok_or(H2Error::ConnectionError(ErrorCode::CompressionError))?; + self.check_header_list_size(&h, &v)?; + self.lines.parts.update(h, v); + Ok(()) + } + + fn update_literal_with_indexing(&mut self, name: Name, value: Vec) -> Result<(), H2Error> { + let (h, v) = self.get_header_by_name_and_value(name, value)?; + self.check_header_list_size(&h, &v)?; + self.table.update(h.clone(), v.clone()); + self.lines.parts.update(h, v); + Ok(()) + } + + fn update_literal_without_indexing( + &mut self, + name: Name, + value: Vec, + ) -> Result<(), H2Error> { + let (h, v) = self.get_header_by_name_and_value(name, value)?; + self.check_header_list_size(&h, &v)?; + self.lines.parts.update(h, v); + Ok(()) + } + + // TODO: 支持 `LiteralNeverIndexed`. + fn update_literal_never_indexing(&mut self, name: Name, value: Vec) -> Result<(), H2Error> { + self.update_literal_without_indexing(name, value) + } + + fn check_header_list_size(&mut self, key: &Header, value: &String) -> Result<(), H2Error> { + let line_size = header_line_length(key.len(), value.len()); + self.update_size(line_size); + if self.lines.header_size > self.header_list_size { + Err(H2Error::ConnectionError(ErrorCode::ConnectError)) + } else { + Ok(()) + } + } + + pub(crate) fn update_size(&mut self, addition: usize) { + self.lines.header_size += addition; + } + + fn get_header_by_name_and_value( + &self, + name: Name, + value: Vec, + ) -> Result<(Header, String), H2Error> { + let h = match name { + Name::Index(index) => { + let searcher = TableSearcher::new(self.table); + searcher + .search_header_name(index) + .ok_or(H2Error::ConnectionError(ErrorCode::CompressionError))? + } + Name::Literal(octets) => Header::Other( + String::from_utf8(octets) + .map_err(|_| H2Error::ConnectionError(ErrorCode::CompressionError))?, + ), + }; + let v = String::from_utf8(value) + .map_err(|_| H2Error::ConnectionError(ErrorCode::CompressionError))?; + Ok((h, v)) + } +} + +fn header_line_length(key_size: usize, value_size: usize) -> usize { + key_size + value_size + 32 +} + +#[cfg(test)] +mod ut_hpack_decoder { + use crate::h2::hpack::table::Header; + use crate::h2::hpack::HpackDecoder; + use crate::test_util::decode; + + const MAX_HEADER_LIST_SIZE: usize = 16 << 20; + + /// UT test cases for `HpackDecoder`. + /// + /// # Brief + /// 1. Creates a `HpackDecoder`. + /// 2. Calls `HpackDecoder::decode()` function, passing in the specified + /// parameters. + /// 3. Checks if the test results are correct. + #[test] + fn ut_hpack_decoder() { + rfc7541_test_cases(); + slices_test_cases(); + + macro_rules! check_pseudo { + ( + $pseudo: expr, + { $a: expr, $m: expr, $p: expr, $sc: expr, $st: expr } $(,)? + ) => { + assert_eq!($pseudo.authority(), $a); + assert_eq!($pseudo.method(), $m); + assert_eq!($pseudo.path(), $p); + assert_eq!($pseudo.scheme(), $sc); + assert_eq!($pseudo.status(), $st); + }; + } + + macro_rules! check_map { + ($map: expr, { $($(,)? $k: literal => $v: literal)* } $(,)?) => { + $( + assert_eq!($map.get($k).unwrap().to_str().unwrap(), $v); + )* + } + } + + macro_rules! check_table { + ( + $hpack: expr, $size: expr, + { $($(,)? $($k: literal)? $($k2: ident)? => $v: literal)* } $(,)? + ) => { + assert_eq!($hpack.table.curr_size(), $size); + let mut _cnt = 0; + $( + + $( + match $hpack.table.header(_cnt) { + Some((Header::Other(k), v)) if k == $k && v == $v => {}, + _ => panic!("DynamicTable::header() failed! (branch 1)"), + } + )? + $( + match $hpack.table.header(_cnt) { + Some((Header::$k2, v)) if v == $v => {}, + _ => panic!("DynamicTable::header() failed! (branch 2)"), + } + )? + _cnt += 1; + )* + } + } + + macro_rules! get_parts { + ($hpack: expr $(, $input: literal)*) => {{ + $( + let text = decode($input).unwrap(); + assert!($hpack.decode(text.as_slice()).is_ok()); + )* + match $hpack.finish() { + Ok(parts) => parts, + Err(_) => panic!("HpackDecoder::finish() failed!"), + } + }}; + } + + macro_rules! hpack_test_case { + ( + $hpack: expr $(, $input: literal)*, + { $a: expr, $m: expr, $p: expr, $sc: expr, $st: expr }, + { $size: expr $(, $($k2: literal)? $($k3: ident)? => $v2: literal)* } $(,)? + ) => { + let mut _hpack = $hpack; + let (pseudo, _) = get_parts!(_hpack $(, $input)*).into_parts(); + check_pseudo!(pseudo, { $a, $m, $p, $sc, $st }); + check_table!(_hpack, $size, { $($($k2)? $($k3)? => $v2)* }); + }; + + ( + $hpack: expr $(, $input: literal)*, + { $($(,)? $k1: literal => $v1: literal)* }, + { $size: expr $(, $($k2: literal)? $($k3: ident)? => $v2: literal)* } $(,)? + ) => { + let mut _hpack = $hpack; + let (_, map) = get_parts!(_hpack $(, $input)*).into_parts(); + check_map!(map, { $($k1 => $v1)* }); + check_table!(_hpack, $size, { $($($k2)? $($k3)? => $v2)* }); + }; + + ( + $hpack: expr $(, $input: literal)*, + { $a: expr, $m: expr, $p: expr, $sc: expr, $st: expr }, + { $($(,)? $k1: literal => $v1: literal)* }, + { $size: expr $(, $($k2: literal)? $($k3: ident)? => $v2: literal)* } $(,)? + ) => { + let mut _hpack = $hpack; + let (pseudo, map) = get_parts!(_hpack $(, $input)*).into_parts(); + check_pseudo!(pseudo, { $a, $m, $p, $sc, $st }); + check_map!(map, { $($k1 => $v1)* }); + check_table!(_hpack, $size, { $($($k2)? $($k3)? => $v2)* }); + }; + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.2.1. Literal Header Field with Indexing + hpack_test_case!( + HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE), + "400a637573746f6d2d6b65790d637573746f6d2d686561646572", + { "custom-key" => "custom-header" }, + { 55, "custom-key" => "custom-header" }, + ); + + // C.2.2. Literal Header Field without Indexing + hpack_test_case!( + HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE), + "040c2f73616d706c652f70617468", + { None, None, Some("/sample/path"), None, None }, + { 0 } + ); + + // C.2.3. Literal Header Field Never Indexed + hpack_test_case!( + HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE), + "100870617373776f726406736563726574", + { "password" => "secret" }, + { 0 }, + ); + + // C.2.4. Indexed Header Field + hpack_test_case!( + HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE), + "82", + { None, Some("GET"), None, None, None }, + { 0 } + ); + + // Request Examples without Huffman Coding. + { + let mut hpack_decoder = HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE); + // C.3.1. First Request + hpack_test_case!( + &mut hpack_decoder, + "828684410f7777772e6578616d706c652e636f6d", + { Some("www.example.com"), Some("GET"), Some("/"), Some("http"), None }, + { 57, Authority => "www.example.com" } + ); + + // C.3.2. Second Request + hpack_test_case!( + &mut hpack_decoder, + "828684be58086e6f2d6361636865", + { Some("www.example.com"), Some("GET"), Some("/"), Some("http"), None }, + { "cache-control" => "no-cache" }, + { 110, "cache-control" => "no-cache", Authority => "www.example.com" } + ); + + // C.3.3. Third Request + hpack_test_case!( + &mut hpack_decoder, + "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565", + { Some("www.example.com"), Some("GET"), Some("/index.html"), Some("https"), None }, + { "custom-key" => "custom-value" }, + { 164, "custom-key" => "custom-value", "cache-control" => "no-cache", Authority => "www.example.com" } + ); + } + + // C.4. Request Examples with Huffman Coding + { + let mut hpack_decoder = HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE); + // C.4.1. First Request + hpack_test_case!( + &mut hpack_decoder, + "828684418cf1e3c2e5f23a6ba0ab90f4ff", + { Some("www.example.com"), Some("GET"), Some("/"), Some("http"), None }, + { 57, Authority => "www.example.com" } + ); + + // C.4.2. Second Request + hpack_test_case!( + &mut hpack_decoder, + "828684be5886a8eb10649cbf", + { Some("www.example.com"), Some("GET"), Some("/"), Some("http"), None }, + { "cache-control" => "no-cache" }, + { 110, "cache-control" => "no-cache", Authority => "www.example.com" } + ); + + // C.4.3. Third Request + hpack_test_case!( + &mut hpack_decoder, + "828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf", + { Some("www.example.com"), Some("GET"), Some("/index.html"), Some("https"), None }, + { "custom-key" => "custom-value" }, + { 164, "custom-key" => "custom-value", "cache-control" => "no-cache", Authority => "www.example.com" } + ); + } + + // C.5. Response Examples without Huffman Coding + { + let mut hpack_decoder = HpackDecoder::with_max_size(256, MAX_HEADER_LIST_SIZE); + // C.5.1. First Response + hpack_test_case!( + &mut hpack_decoder, + "4803333032580770726976617465611d\ + 4d6f6e2c203231204f63742032303133\ + 2032303a31333a323120474d546e1768\ + 747470733a2f2f7777772e6578616d70\ + 6c652e636f6d", + { None, None, None, None, Some("302") }, + { + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private" + }, + { + 222, + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private", + Status => "302" + } + ); + + // C.5.2. Second Response + hpack_test_case!( + &mut hpack_decoder, + "4803333037c1c0bf", + { None, None, None, None, Some("307") }, + { + "cache-control" => "private", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "location" => "https://www.example.com" + }, + { + 222, + Status => "307", + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private" + } + ); + + // C.5.3. Third Response + hpack_test_case!( + &mut hpack_decoder, + "88c1611d4d6f6e2c203231204f637420\ + 323031332032303a31333a323220474d\ + 54c05a04677a69707738666f6f3d4153\ + 444a4b48514b425a584f5157454f5049\ + 5541585157454f49553b206d61782d61\ + 67653d333630303b2076657273696f6e\ + 3d31", + { None, None, None, None, Some("200") }, + { + "cache-control" => "private", + "date" => "Mon, 21 Oct 2013 20:13:22 GMT", + "location" => "https://www.example.com", + "content-encoding" => "gzip", + "set-cookie" => "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1" + }, + { + 215, + "set-cookie" => "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + "content-encoding" => "gzip", + "date" => "Mon, 21 Oct 2013 20:13:22 GMT" + } + ); + } + + // C.6. Response Examples with Huffman Coding + { + let mut hpack_decoder = HpackDecoder::with_max_size(256, MAX_HEADER_LIST_SIZE); + // C.6.1. First Response + hpack_test_case!( + &mut hpack_decoder, + "488264025885aec3771a4b6196d07abe\ + 941054d444a8200595040b8166e082a6\ + 2d1bff6e919d29ad171863c78f0b97c8\ + e9ae82ae43d3", + { None, None, None, None, Some("302") }, + { + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private" + }, + { + 222, + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private", + Status => "302" + } + ); + + // C.6.2. Second Response + hpack_test_case!( + &mut hpack_decoder, + "4883640effc1c0bf", + { None, None, None, None, Some("307") }, + { + "cache-control" => "private", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "location" => "https://www.example.com" + }, + { + 222, + Status => "307", + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private" + } + ); + + // C.6.3. Third Response + hpack_test_case!( + &mut hpack_decoder, + "88c16196d07abe941054d444a8200595\ + 040b8166e084a62d1bffc05a839bd9ab\ + 77ad94e7821dd7f2e6c7b335dfdfcd5b\ + 3960d5af27087f3672c1ab270fb5291f\ + 9587316065c003ed4ee5b1063d5007", + { None, None, None, None, Some("200") }, + { + "cache-control" => "private", + "date" => "Mon, 21 Oct 2013 20:13:22 GMT", + "location" => "https://www.example.com", + "content-encoding" => "gzip", + "set-cookie" => "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1" + }, + { + 215, + "set-cookie" => "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + "content-encoding" => "gzip", + "date" => "Mon, 21 Oct 2013 20:13:22 GMT" + } + ); + } + } + + fn slices_test_cases() { + // C.2.1. Literal Header Field with Indexing + hpack_test_case!( + HpackDecoder::with_max_size(4096, MAX_HEADER_LIST_SIZE), + "04", "0c", "2f", "73", "61", "6d", "70", "6c", "65", "2f", "70", "61", "74", "68", + { None, None, Some("/sample/path"), None, None }, + { 0 } + ); + + // C.6.1. First Response + hpack_test_case!( + HpackDecoder::with_max_size(256, MAX_HEADER_LIST_SIZE), + "488264025885aec3771a4b6196d07abe", + "941054d444a8200595040b8166e082a6", + "2d1bff6e919d29ad171863c78f0b97c8", + "e9ae82ae43d3", + { None, None, None, None, Some("302") }, + { + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private" + }, + { + 222, + "location" => "https://www.example.com", + "date" => "Mon, 21 Oct 2013 20:13:21 GMT", + "cache-control" => "private", + Status => "302" + } + ); + } + } +} diff --git a/ylong_http/src/h2/hpack/encoder.rs b/ylong_http/src/h2/hpack/encoder.rs new file mode 100644 index 0000000..a649686 --- /dev/null +++ b/ylong_http/src/h2/hpack/encoder.rs @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::hpack::representation::{ReprEncStateHolder, ReprEncodeState, ReprEncoder}; +use crate::h2::hpack::table::{DynamicTable, Header}; +use crate::h2::{Parts, PseudoHeaders}; + +/// Decoder implementation of [`HPACK`]. +/// +/// [`HPACK`]: https://httpwg.org/specs/rfc7541.html +// TODO: 增加 SizeUpdate 字段以支持用户动态变化 DynamicTable 大小。 +pub(crate) struct HpackEncoder { + table: DynamicTable, + holder: ReprEncStateHolder, +} + +impl HpackEncoder { + /// Create a `HpackEncoder` with the given max size. + pub(crate) fn with_max_size(max_size: usize) -> Self { + Self { + table: DynamicTable::with_max_size(max_size), + holder: ReprEncStateHolder::new(), + } + } + + /// Set the `Parts` to be encoded. + pub(crate) fn set_parts(&mut self, parts: Parts) { + self.holder.set_parts(parts) + } + + /// Users can call `encode` multiple times to encode the previously set + /// `Parts` in segments. + pub(crate) fn encode(&mut self, dst: &mut [u8]) -> usize { + let mut encoder = ReprEncoder::new(&mut self.table); + encoder.load(&mut self.holder); + let size = encoder.encode(dst); + if size == dst.len() { + encoder.save(&mut self.holder); + } + size + } + + /// Check the previously set `Parts` if encoding is complete. + pub(crate) fn is_finished(&self) -> bool { + self.holder.is_empty() + } +} + +#[cfg(test)] +mod ut_hpack_encoder { + use crate::h2::hpack::table::Header; + use crate::h2::hpack::HpackEncoder; + use crate::h2::Parts; + use crate::test_util::decode; + + #[test] + fn ut_hpack_encoder() { + rfc7541_test_cases(); + + // In order to ensure that Header and Value are added in the order of + // `RFC`, each time a Parts is generated separately and passed in + macro_rules! hpack_test_cases { + ($enc: expr, $len: expr, $res: literal, $size: expr , { $($h: expr, $v: expr $(,)?)*} $(,)?) => { + let mut _encoder = $enc; + let mut vec = [0u8; $len]; + let mut cur = 0; + $( + let mut parts = Parts::new(); + parts.update($h, $v); + _encoder.set_parts(parts); + cur += _encoder.encode(&mut vec[cur..]); + )* + assert_eq!(cur, $len); + let result = decode($res).unwrap(); + assert_eq!(vec.as_slice(), result.as_slice()); + assert_eq!(_encoder.table.curr_size(), $size); + } + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.2.1. Literal Header Field with Indexing + hpack_test_cases!( + HpackEncoder::with_max_size(4096), + 26, "400a637573746f6d2d6b65790d637573746f6d2d686561646572", 55, + { + Header::Other(String::from("custom-key")), + String::from("custom-header"), + }, + ); + + // TODO: C.2.2. Literal Header Field without Indexing + // TODO: C.2.3. Literal Header Field Never Indexed + + // C.2.4. Indexed Header Field + hpack_test_cases!( + HpackEncoder::with_max_size(4096), + 1, "82", 0, + { + Header::Method, + String::from("GET"), + }, + ); + + // C.3. Request Examples without Huffman Coding + { + let mut encoder = HpackEncoder::with_max_size(4096); + // C.3.1. First Request + hpack_test_cases!( + &mut encoder, + 20, "828684410f7777772e6578616d706c652e636f6d", 57, + { + Header::Method, + String::from("GET"), + Header::Scheme, + String::from("http"), + Header::Path, + String::from("/"), + Header::Authority, + String::from("www.example.com"), + }, + ); + + // C.3.2. Second Request + hpack_test_cases!( + &mut encoder, + 14, "828684be58086e6f2d6361636865", 110, + { + Header::Method, + String::from("GET"), + Header::Scheme, + String::from("http"), + Header::Path, + String::from("/"), + Header::Authority, + String::from("www.example.com"), + Header::Other(String::from("cache-control")), + String::from("no-cache"), + }, + ); + + // C.3.3. Third Request + hpack_test_cases!( + &mut encoder, + 29, "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565", 164, + { + Header::Method, + String::from("GET"), + Header::Scheme, + String::from("https"), + Header::Path, + String::from("/index.html"), + Header::Authority, + String::from("www.example.com"), + Header::Other(String::from("custom-key")), + String::from("custom-value"), + }, + ); + } + + // TODO: C.4. Request Examples with Huffman Coding + + // C.5. Response Examples without Huffman Coding + { + let mut encoder = HpackEncoder::with_max_size(256); + // C.5.1. First Response + hpack_test_cases!( + &mut encoder, + 70, + "4803333032580770726976617465611d\ + 4d6f6e2c203231204f63742032303133\ + 2032303a31333a323120474d546e1768\ + 747470733a2f2f7777772e6578616d70\ + 6c652e636f6d", + 222, + { + Header::Status, + String::from("302"), + Header::Other(String::from("cache-control")), + String::from("private"), + Header::Other(String::from("date")), + String::from("Mon, 21 Oct 2013 20:13:21 GMT"), + Header::Other(String::from("location")), + String::from("https://www.example.com"), + }, + ); + + // C.5.2. Second Response + hpack_test_cases!( + &mut encoder, + 8, "4803333037c1c0bf", 222, + { + Header::Status, + String::from("307"), + Header::Other(String::from("cache-control")), + String::from("private"), + Header::Other(String::from("date")), + String::from("Mon, 21 Oct 2013 20:13:21 GMT"), + Header::Other(String::from("location")), + String::from("https://www.example.com"), + }, + ); + + // C.5.3. Third Response + hpack_test_cases!( + &mut encoder, + 98, + "88c1611d4d6f6e2c203231204f637420\ + 323031332032303a31333a323220474d\ + 54c05a04677a69707738666f6f3d4153\ + 444a4b48514b425a584f5157454f5049\ + 5541585157454f49553b206d61782d61\ + 67653d333630303b2076657273696f6e\ + 3d31", + 215, + { + Header::Status, + String::from("200"), + Header::Other(String::from("cache-control")), + String::from("private"), + Header::Other(String::from("date")), + String::from("Mon, 21 Oct 2013 20:13:22 GMT"), + Header::Other(String::from("location")), + String::from("https://www.example.com"), + Header::Other(String::from("content-encoding")), + String::from("gzip"), + Header::Other(String::from("set-cookie")), + String::from("foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), + }, + ); + } + + // TODO: C.6. Response Examples with Huffman Coding + } + } +} diff --git a/ylong_http/src/h2/hpack/integer.rs b/ylong_http/src/h2/hpack/integer.rs new file mode 100644 index 0000000..e74fc16 --- /dev/null +++ b/ylong_http/src/h2/hpack/integer.rs @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! [Integer Representation] implementation of [HPACK]. +//! +//! [Integer Representation]: https://httpwg.org/specs/rfc7541.html#integer.representation +//! [HPACK]: https://httpwg.org/specs/rfc7541.html +//! +//! # Introduction +//! Integers are used to represent name indexes, header field indexes, or +//! string lengths. An integer representation can start anywhere within an octet. +//! To allow for optimized processing, an integer representation always finishes +//! at the end of an octet. + +use crate::h2::error::ErrorCode; +use crate::h2::H2Error; +use core::cmp::Ordering; + +/// `IntegerDecoder` implementation according to `Pseudocode to decode an +/// integer I` in `RFC7541 section-5.1`. +/// +/// # Pseudocode +/// ```text +/// decode I from the next N bits +/// if I < 2^N - 1, return I +/// else +/// M = 0 +/// repeat +/// B = next octet +/// I = I + (B & 127) * 2^M +/// M = M + 7 +/// while B & 128 == 128 +/// return I +/// ``` +pub(crate) struct IntegerDecoder { + index: usize, + shift: u32, +} + +impl IntegerDecoder { + /// Calculates an integer based on the incoming first byte and mask. + /// If no subsequent bytes exist, return the result directly, otherwise + /// return the decoder itself. + pub(crate) fn first_byte(byte: u8, mask: u8) -> Result { + let index = byte & mask; + match index.cmp(&mask) { + Ordering::Less => Ok(index as usize), + _ => Err(Self { + index: index as usize, + shift: 1, + }), + } + } + + /// Continues computing the integer based on the next byte of the input. + /// Returns `Ok(Some(index))` if the result is obtained, otherwise returns + /// `Ok(None)`, and returns Err in case of overflow. + pub(crate) fn next_byte(&mut self, byte: u8) -> Result, H2Error> { + self.index = 1usize + .checked_shl(self.shift - 1) + .and_then(|res| res.checked_mul((byte & 0x7f) as usize)) + .and_then(|res| res.checked_add(self.index)) + .ok_or(H2Error::ConnectionError(ErrorCode::CompressionError))?; + self.shift += 7; + match (byte & 0x80) == 0x00 { + true => Ok(Some(self.index)), + false => Ok(None), + } + } +} + +/// `IntegerEncoder` implementation according to `Pseudocode to represent an +/// integer I` in `RFC7541 section-5.1`. +/// +/// # Pseudocode +/// ```text +/// if I < 2^N - 1, encode I on N bits +/// else +/// encode (2^N - 1) on N bits +/// I = I - (2^N - 1) +/// while I >= 128 +/// encode (I % 128 + 128) on 8 bits +/// I = I / 128 +/// encode I on 8 bits +/// ``` +pub(crate) struct IntegerEncoder { + i: usize, + mask: u8, + pre: u8, + state: IntegerEncodeState, +} + +/// Enumeration of states that the `IntegerEncoder` needs to use. +enum IntegerEncodeState { + First, + Other, + Finish, +} + +impl IntegerEncoder { + /// Creates a new `IntegerEncoder`. + pub(crate) fn new(i: usize, mask: u8, pre: u8) -> Self { + Self { + i, + mask, + pre, + state: IntegerEncodeState::First, + } + } + + /// Gets the next byte of the integer. If no remaining bytes are calculated, + /// return `None`. + pub(crate) fn next_byte(&mut self) -> Option { + match self.state { + IntegerEncodeState::First => { + if self.i < self.mask as usize { + self.state = IntegerEncodeState::Finish; + return Some(self.pre | (self.i as u8)); + } + self.i -= self.mask as usize; + self.state = IntegerEncodeState::Other; + Some(self.pre | self.mask) + } + IntegerEncodeState::Other => Some(if self.i >= 128 { + let res = (self.i & 0x7f) as u8; + self.i >>= 7; + res | 0x80 + } else { + self.state = IntegerEncodeState::Finish; + (self.i & 0x7f) as u8 + }), + IntegerEncodeState::Finish => None, + } + } + + /// Checks if calculation is over. + pub(crate) fn is_finish(&self) -> bool { + matches!(self.state, IntegerEncodeState::Finish) + } +} + +#[cfg(test)] +mod ut_integer { + use crate::h2::hpack::integer::{IntegerDecoder, IntegerEncoder}; + + /// UT test cases for `IntegerDecoder`. + /// + /// # Brief + /// 1. Creates an `IntegerDecoder`. + /// 2. Calls `IntegerDecoder::first_byte()` an `IntegerDecoder::next_byte()`, + /// passing in the specified parameters. + /// 3. Checks if the test results are correct. + #[test] + fn ut_integer_decode() { + rfc7541_test_cases(); + + macro_rules! integer_test_case { + ($fb: literal, $mask: literal => $fb_res: expr) => { + match IntegerDecoder::first_byte($fb, $mask) { + Ok(idx) => assert_eq!(idx, $fb_res), + _ => panic!("IntegerDecoder::first_byte() failed!"), + } + }; + ($fb: literal, $mask: literal $(, $nb: literal => $nb_res: expr)* $(,)?) => { + match IntegerDecoder::first_byte($fb, $mask) { + Err(mut int) => { + $(match int.next_byte($nb) { + Ok(v) => assert_eq!(v, $nb_res), + _ => panic!("IntegerDecoder::next_byte() failed!"), + })* + } + _ => panic!("IntegerDecoder::first_byte() failed!"), + } + }; + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.1.1. Example 1: Encoding 10 Using a 5-Bit Prefix + integer_test_case!(0x0a, 0x1f => 10); + + // C.1.2. Example 2: Encoding 1337 Using a 5-Bit Prefix + integer_test_case!( + 0x1f, 0x1f, + 0x9a => None, + 0x0a => Some(1337), + ); + + // C.1.3. Example 3: Encoding 42 Starting at an Octet Boundary + integer_test_case!(0x2a, 0xff => 42); + } + } + + /// UT test cases for `IntegerEncoder`. + /// + /// # Brief + /// 1. Creates an `IntegerEncoder`. + /// 2. Calls `IntegerEncoder::first_byte()` and `IntegerEncoder::next_byte()`, + /// passing in the specified parameters. + /// 3. Checks if the test results are correct. + #[test] + fn ut_integer_encode() { + rfc7541_test_cases(); + + macro_rules! integer_test_case { + ($int: expr, $mask: expr, $pre: expr $(, $byte: expr)* $(,)? ) => { + let mut integer = IntegerEncoder::new($int, $mask, $pre); + $( + assert_eq!(integer.next_byte(), Some($byte)); + )* + assert_eq!(integer.next_byte(), None); + } + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.1.1. Example 1: Encoding 10 Using a 5-Bit Prefix + integer_test_case!(10, 0x1f, 0x00, 0x0a); + + // C.1.2. Example 2: Encoding 1337 Using a 5-Bit Prefix + integer_test_case!(1337, 0x1f, 0x00, 0x1f, 0x9a, 0x0a); + + // C.1.3. Example 3: Encoding 42 Starting at an Octet Boundary + integer_test_case!(42, 0xff, 0x00, 0x2a); + } + } +} diff --git a/ylong_http/src/h2/hpack/mod.rs b/ylong_http/src/h2/hpack/mod.rs new file mode 100644 index 0000000..26804fe --- /dev/null +++ b/ylong_http/src/h2/hpack/mod.rs @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! [HPACK] implementation of the [HTTP/2 protocol]. +//! +//! [HPACK]: https://httpwg.org/specs/rfc7541.html +//! [HTTP/2 protocol]: https://httpwg.org/specs/rfc9113.html +//! +//! # Introduction +//! In [HTTP/1.1], header fields are not compressed. As web pages have grown +//! to require dozens to hundreds of requests, the redundant header fields in +//! these requests unnecessarily consume bandwidth, measurably increasing latency. +//! +//! [SPDY] initially addressed this redundancy by compressing header fields +//! using the [DEFLATE] format, which proved very effective at efficiently +//! representing the redundant header fields. However, that approach exposed a +//! security risk as demonstrated by the +//! [CRIME (Compression Ratio Info-leak Made Easy)] attack. +//! +//! This specification defines HPACK, a new compressor that eliminates redundant +//! header fields, limits vulnerability to known security attacks, and has a +//! bounded memory requirement for use in constrained environments. Potential +//! security concerns for HPACK are described in Section 7. +//! +//! The HPACK format is intentionally simple and inflexible. Both +//! characteristics reduce the risk of interoperability or security issues due +//! to implementation error. No extensibility mechanisms are defined; changes +//! to the format are only possible by defining a complete replacement. +//! +//! [HTTP/1.1]: https://www.rfc-editor.org/rfc/rfc9112.html +//! [SPDY]: https://datatracker.ietf.org/doc/html/draft-mbelshe-httpbis-spdy-00 +//! [DEFLATE]: https://www.rfc-editor.org/rfc/rfc1951.html +//! [CRIME (Compression Ratio Info-leak Made Easy)]: https://en.wikipedia.org/w/index.php?title=CRIME&oldid=660948120 +//! + +mod decoder; +mod encoder; +mod integer; +mod representation; +pub(crate) mod table; + +pub(crate) use decoder::HpackDecoder; +pub(crate) use encoder::HpackEncoder; diff --git a/ylong_http/src/h2/hpack/representation/decoder.rs b/ylong_http/src/h2/hpack/representation/decoder.rs new file mode 100644 index 0000000..6f7eb8f --- /dev/null +++ b/ylong_http/src/h2/hpack/representation/decoder.rs @@ -0,0 +1,845 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::error::ErrorCode; +use crate::h2::hpack::integer::IntegerDecoder; +use crate::h2::hpack::representation::{Name, PrefixBit, Representation}; +use crate::h2::H2Error; +use crate::huffman::HuffmanDecoder; +use core::cmp::Ordering; + +/// Decoder implementation for decoding representation. Every time users call +/// `decode`, the `ReprDecoder` will try to decode a `Repr`. If `buf` has been +/// fully decoded and users continue to call `decode`, it will return `None`. +/// Users need to save intermediate results in time. +pub(crate) struct ReprDecoder<'a> { + /// `buf` represents the byte stream to be decoded. + buf: &'a [u8], + /// `state` represents the remaining state after the last call to `decode`. + state: Option, +} + +impl<'a> ReprDecoder<'a> { + /// Creates a new `ReprDecoder` whose `state` is `None`. + pub(crate) fn new(buf: &'a [u8]) -> Self { + Self { buf, state: None } + } + + /// Loads state from a holder. + pub(crate) fn load(&mut self, holder: &mut ReprDecStateHolder) { + self.state = holder.state.take(); + } + + /// Decodes `self.buf`. Every time users call `decode`, it will try to + /// decode a `Representation`. + pub(crate) fn decode(&mut self) -> Result, H2Error> { + // If buf is empty, leave the state unchanged. + if self.buf.is_empty() { + return Ok(None); + } + + // If `self.state` is `None`, start decoding from the `Index` state. + match self + .state + .take() + .unwrap_or_else(|| ReprDecodeState::Index(Index::new())) + .decode(&mut self.buf) + { + // If `buf` is not enough to continue decoding a complete + // `Representation`, `Ok(None)` will be returned. Users need to call + // `save` to save the current state to a `ReprDecStateHolder`. + DecResult::NeedMore(state) => { + self.state = Some(state); + Ok(None) + } + // If a `Representation` is decoded, return it. + DecResult::Decoded(repr) => Ok(Some(repr)), + DecResult::Error(error) => Err(error), + } + } + + /// Saves current state to a `ReprDecStateHolder`. + pub(crate) fn save(self, holder: &mut ReprDecStateHolder) { + holder.state = self.state + } +} + +/// `ReprDecStateHolder` is used to hold the intermediate `DecodeState`. +pub(crate) struct ReprDecStateHolder { + state: Option, +} + +impl ReprDecStateHolder { + pub(crate) fn new() -> Self { + Self { state: None } + } + + pub(crate) fn is_empty(&self) -> bool { + self.state.is_none() + } +} + +macro_rules! state_def { + ($name: ident, $decoded: ty, $($state: ident),* $(,)?) => { + pub(crate) enum $name { + $( + $state($state), + )* + } + + impl $name { + fn decode(self, buf: &mut &[u8]) -> DecResult<$decoded, $name> { + match self { + $( + Self::$state(state) => state.decode(buf), + )* + } + } + } + + $( + impl From<$state> for $name { + fn from(s: $state) -> Self { + Self::$state(s) + } + } + )* + } +} + +// `Repr` decoding state diagram: +// +// ┌ `Index` ─ `IndexInner` ┬ `FirstByte` +// │ └ `TrailingBytes` +// │ +// `ReprDecodeState` ┼ `NameString` ┐ ┌ `LengthFirstByte` +// │ ├ `LiteralString` ┼ `LengthTrailingBytes` +// └ `ValueString` ┘ ├ `AsciiStringBytes` +// └ `HuffmanStringBytes` + +// `ReprDecodeState` contains 3 states: `Index`, `NameString` and `ValueString`. +state_def!( + ReprDecodeState, + Representation, + Index, + NameString, + ValueString +); + +// `IndexInner` contains 2 states: `FirstByte` and `TrailingBytes`. +state_def!(IndexInner, (PrefixBit, usize), FirstByte, TrailingBytes); + +// `LiteralString` contains 4 states: `LengthFirstByte`, `LengthTrailingBytes`, +// `AsciiStringBytes` and `HuffmanStringBytes` +state_def!( + LiteralString, + Vec, + LengthFirstByte, + LengthTrailingBytes, + AsciiStringBytes, + HuffmanStringBytes, +); + +/// `Index` is responsible for decoding the starting index part. +pub(crate) struct Index { + inner: IndexInner, +} + +impl Index { + fn new() -> Self { + Self::from_inner(FirstByte.into()) + } + + fn from_inner(inner: IndexInner) -> Self { + Self { inner } + } + + fn decode(self, buf: &mut &[u8]) -> DecResult { + match self.inner.decode(buf) { + // RFC7541-6.1: The index value of 0 is not used. It MUST be treated + // as a decoding error if found in an indexed header field representation. + DecResult::Decoded((PrefixBit::INDEXED, 0)) => { + H2Error::ConnectionError(ErrorCode::CompressionError).into() + } + DecResult::Decoded((PrefixBit::INDEXED, index)) => { + DecResult::Decoded(Representation::Indexed { index }) + } + DecResult::Decoded((PrefixBit::SIZE_UPDATE, max_size)) => { + DecResult::Decoded(Representation::SizeUpdate { max_size }) + } + DecResult::Decoded((repr, 0)) => NameString::new(repr).decode(buf), + DecResult::Decoded((repr, index)) => { + ValueString::new(repr, Name::Index(index)).decode(buf) + } + DecResult::NeedMore(inner) => DecResult::NeedMore(Index::from_inner(inner).into()), + DecResult::Error(e) => e.into(), + } + } +} + +/// `NameString` is responsible for decoding the name string part. +pub(crate) struct NameString { + repr: PrefixBit, + inner: LiteralString, +} + +impl NameString { + fn new(repr: PrefixBit) -> Self { + Self::from_inner(repr, LengthFirstByte.into()) + } + + fn from_inner(repr: PrefixBit, inner: LiteralString) -> Self { + Self { repr, inner } + } + + fn decode(self, buf: &mut &[u8]) -> DecResult { + match self.inner.decode(buf) { + DecResult::Decoded(octets) => { + ValueString::new(self.repr, Name::Literal(octets)).decode(buf) + } + DecResult::NeedMore(inner) => { + DecResult::NeedMore(Self::from_inner(self.repr, inner).into()) + } + DecResult::Error(e) => e.into(), + } + } +} + +/// `ValueString` is responsible for decoding the value string part. +pub(crate) struct ValueString { + repr: PrefixBit, + name: Name, + inner: LiteralString, +} + +impl ValueString { + fn new(repr: PrefixBit, name: Name) -> Self { + Self::from_inner(repr, name, LengthFirstByte.into()) + } + + fn from_inner(repr: PrefixBit, name: Name, inner: LiteralString) -> Self { + Self { repr, name, inner } + } + + fn decode(self, buf: &mut &[u8]) -> DecResult { + match (self.repr, self.inner.decode(buf)) { + (PrefixBit::LITERAL_WITH_INDEXING, DecResult::Decoded(value)) => { + DecResult::Decoded(Representation::LiteralWithIndexing { + name: self.name, + value, + }) + } + (PrefixBit::LITERAL_WITHOUT_INDEXING, DecResult::Decoded(value)) => { + DecResult::Decoded(Representation::LiteralWithoutIndexing { + name: self.name, + value, + }) + } + (_, DecResult::Decoded(value)) => { + DecResult::Decoded(Representation::LiteralNeverIndexed { + name: self.name, + value, + }) + } + (_, DecResult::NeedMore(inner)) => { + DecResult::NeedMore(Self::from_inner(self.repr, self.name, inner).into()) + } + (_, DecResult::Error(e)) => e.into(), + } + } +} + +/// `FirstByte` is responsible for decoding the first byte of the index of +/// `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +--------+----------------------+ +/// | Prefix | Index | +/// +--------+----------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct FirstByte; + +impl FirstByte { + fn decode(self, buf: &mut &[u8]) -> DecResult<(PrefixBit, usize), IndexInner> { + // If `buf` has been completely decoded here, return the current state. + if buf.is_empty() { + return DecResult::NeedMore(self.into()); + } + + // Decodes `Prefix`. + let byte = buf[0]; + let repr = PrefixBit::from_u8(byte); + let mask = repr.prefix_index_mask(); + + // Moves the pointer of `buf` backward. + *buf = &buf[1..]; + match IntegerDecoder::first_byte(byte, mask.0) { + Ok(idx) => DecResult::Decoded((repr, idx)), + Err(int) => TrailingBytes::new(repr, int).decode(buf), + } + } +} + +/// `TrailingBytes` is responsible for decoding the trailing bytes of the index +/// of `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +--------+----------------------+ +/// | Prefix | Index | +/// +---+----+----------------------+ +/// | 1 | Index | +/// +---+----+----------------------+ +/// | ... | +/// +---+----+----------------------+ +/// | 0 | Index | +/// +---+----+----------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct TrailingBytes { + repr: PrefixBit, + index: IntegerDecoder, +} + +impl TrailingBytes { + fn new(repr: PrefixBit, index: IntegerDecoder) -> Self { + Self { repr, index } + } + + fn decode(mut self, buf: &mut &[u8]) -> DecResult<(PrefixBit, usize), IndexInner> { + loop { + // If `buf` has been completely decoded here, return the current state. + if buf.is_empty() { + return DecResult::NeedMore(self.into()); + } + + let byte = buf[0]; + *buf = &buf[1..]; + // Updates trailing bytes until we get the index. + match self.index.next_byte(byte) { + Ok(None) => {} + Ok(Some(index)) => return DecResult::Decoded((self.repr, index)), + Err(e) => return e.into(), + } + } + } +} + +/// `LengthFirstByte` is responsible for decoding the first byte of the +/// length in `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +---+---------------------------+ +/// | H | Length | +/// +---+---------------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct LengthFirstByte; + +impl LengthFirstByte { + fn decode(self, buf: &mut &[u8]) -> DecResult, LiteralString> { + if buf.is_empty() { + return DecResult::NeedMore(self.into()); + } + + let byte = buf[0]; + *buf = &buf[1..]; + match ( + IntegerDecoder::first_byte(byte, 0x7f), + (byte & 0x80) == 0x80, + ) { + (Ok(len), true) => HuffmanStringBytes::new(len).decode(buf), + (Ok(len), false) => AsciiStringBytes::new(len).decode(buf), + (Err(int), huffman) => LengthTrailingBytes::new(huffman, int).decode(buf), + } + } +} + +/// `LengthTrailingBytes` is responsible for decoding the trailing bytes of the +/// length in `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +---+---------------------------+ +/// | H | Length | +/// +---+---------------------------+ +/// | 1 | Length | +/// +---+----+----------------------+ +/// | ... | +/// +---+----+----------------------+ +/// | 0 | Length | +/// +---+----+----------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct LengthTrailingBytes { + is_huffman: bool, + length: IntegerDecoder, +} + +impl LengthTrailingBytes { + fn new(is_huffman: bool, length: IntegerDecoder) -> Self { + Self { is_huffman, length } + } + + fn decode(mut self, buf: &mut &[u8]) -> DecResult, LiteralString> { + loop { + if buf.is_empty() { + return DecResult::NeedMore(self.into()); + } + + let byte = buf[0]; + *buf = &buf[1..]; + match (self.length.next_byte(byte), self.is_huffman) { + (Ok(None), _) => {} + (Err(e), _) => return e.into(), + (Ok(Some(length)), true) => return HuffmanStringBytes::new(length).decode(buf), + (Ok(Some(length)), false) => return AsciiStringBytes::new(length).decode(buf), + } + } + } +} + +/// `AsciiStringBytes` is responsible for decoding the ascii string of the +/// literal octets of `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +-------------------------------+ +/// | String | +/// +-------------------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct AsciiStringBytes { + octets: Vec, + length: usize, +} + +impl AsciiStringBytes { + fn new(length: usize) -> Self { + Self { + octets: Vec::new(), + length, + } + } + + fn decode(mut self, buf: &mut &[u8]) -> DecResult, LiteralString> { + match (buf.len() + self.octets.len()).cmp(&self.length) { + Ordering::Greater | Ordering::Equal => { + let pos = self.length - self.octets.len(); + self.octets.extend_from_slice(&buf[..pos]); + *buf = &buf[pos..]; + DecResult::Decoded(self.octets) + } + Ordering::Less => { + self.octets.extend_from_slice(buf); + *buf = &buf[buf.len()..]; + DecResult::NeedMore(self.into()) + } + } + } +} + +/// `HuffmanStringBytes` is responsible for decoding the huffman string of the +/// literal octets of `Representation`. +/// +/// # Binary Format +/// ```text +/// Bytes list: +/// +-------------------------------+ +/// | String | +/// +-------------------------------+ +/// | (trailing bytes)... | +/// +--------+----------------------+ +/// ``` +pub(crate) struct HuffmanStringBytes { + huffman: HuffmanDecoder, + read: usize, + length: usize, +} + +impl HuffmanStringBytes { + fn new(length: usize) -> Self { + Self { + huffman: HuffmanDecoder::new(), + read: 0, + length, + } + } + + fn decode(mut self, buf: &mut &[u8]) -> DecResult, LiteralString> { + match (buf.len() + self.read).cmp(&self.length) { + Ordering::Greater | Ordering::Equal => { + let pos = self.length - self.read; + if self.huffman.decode(&buf[..pos]).is_err() { + return H2Error::ConnectionError(ErrorCode::CompressionError).into(); + } + *buf = &buf[pos..]; + match self.huffman.finish() { + Ok(vec) => DecResult::Decoded(vec), + Err(_) => H2Error::ConnectionError(ErrorCode::CompressionError).into(), + } + } + Ordering::Less => { + if self.huffman.decode(buf).is_err() { + return H2Error::ConnectionError(ErrorCode::CompressionError).into(); + } + self.read += buf.len(); + *buf = &buf[buf.len()..]; + DecResult::NeedMore(self.into()) + } + } + } +} + +/// Finish state, generate `Representation` by name and value. +pub(crate) struct Finish { + repr: PrefixBit, + name: Name, + value: Vec, +} + +impl Finish { + fn new(repr: PrefixBit, name: Name, value: Vec) -> Self { + Self { repr, name, value } + } + + fn decode(self) -> DecResult { + DecResult::Decoded(match self.repr { + PrefixBit::LITERAL_WITH_INDEXING => Representation::LiteralWithIndexing { + name: self.name, + value: self.value, + }, + PrefixBit::LITERAL_WITHOUT_INDEXING => Representation::LiteralWithoutIndexing { + name: self.name, + value: self.value, + }, + _ => Representation::LiteralNeverIndexed { + name: self.name, + value: self.value, + }, + }) + } +} + +/// Decoder's possible returns during the decoding process. +enum DecResult { + /// Decoder has got a `D`. Users can continue to call `encode` to try to + /// get the next `D`. + Decoded(D), + + /// Decoder needs more bytes to decode to get a `D`. Returns the current + /// decoding state `S`. + NeedMore(S), + + /// Errors that may occur when decoding. + Error(H2Error), +} + +impl From for DecResult { + fn from(e: H2Error) -> Self { + DecResult::Error(e) + } +} + +#[cfg(test)] +mod ut_repr_decoder { + use super::*; + use crate::test_util::decode; + + /// UT test cases for `ReprDecoder`. + /// + /// # Brief + /// 1. Creates a `ReprDecoder`. + /// 2. Calls `ReprDecoder::decode()` function, passing in the specified + /// parameters. + /// 3. Checks if the test results are correct. + #[test] + fn ut_repr_decoder() { + rfc7541_test_cases(); + + macro_rules! inner_test_case { + ($decoder: expr, $pat: ident => $name: expr) => { + match $decoder.decode() { + Ok(Some(Representation::$pat { index })) => assert_eq!($name, index), + _ => panic!("ReprDecoder::decode() failed!"), + } + }; + ($decoder: expr, $pat: ident, $kind: ident => $name: expr, $value: expr) => { + match $decoder.decode() { + Ok(Some(Representation::$pat { + name: Name::$kind(n), + value: v, + })) => { + assert_eq!($name, n); + assert_eq!($value, v); + } + _ => panic!("ReprDecoder::decode() failed!"), + } + }; + } + + macro_rules! repr_test_case { + ($octets: literal, $({ $pat: ident $(, $kind: ident)? => $first: expr $(, $second: expr)?} $(,)?)*) => { + let slice = decode($octets).unwrap(); + let mut decoder = ReprDecoder::new(&slice); + $( + inner_test_case!(decoder, $pat $(, $kind)? => $first $(, $second)?); + )* + } + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.2.1. Literal Header Field with Indexing + repr_test_case!( + "400a637573746f6d2d6b65790d637573746f6d2d686561646572", + { LiteralWithIndexing, Literal => b"custom-key".to_vec(), b"custom-header".to_vec() } + ); + + // C.2.2. Literal Header Field without Indexing + repr_test_case!( + "040c2f73616d706c652f70617468", + { LiteralWithoutIndexing, Index => 4, b"/sample/path".to_vec() } + ); + + // C.2.3. Literal Header Field Never Indexed + repr_test_case!( + "100870617373776f726406736563726574", + { LiteralNeverIndexed, Literal => b"password".to_vec(), b"secret".to_vec() } + ); + + // C.2.4. Indexed Header Field + repr_test_case!( + "82", + { Indexed => 2 } + ); + + // C.3.1. First Request + repr_test_case!( + "828684410f7777772e6578616d706c652e636f6d", + { Indexed => 2 }, + { Indexed => 6 }, + { Indexed => 4 }, + { LiteralWithIndexing, Index => 1, b"www.example.com".to_vec() } + ); + + // C.3.2. Second Request + repr_test_case!( + "828684be58086e6f2d6361636865", + { Indexed => 2 }, + { Indexed => 6 }, + { Indexed => 4 }, + { Indexed => 62 }, + { LiteralWithIndexing, Index => 24, b"no-cache".to_vec() } + ); + + // C.3.3. Third Request + repr_test_case!( + "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565", + { Indexed => 2 }, + { Indexed => 7 }, + { Indexed => 5 }, + { Indexed => 63 }, + { LiteralWithIndexing, Literal => b"custom-key".to_vec(), b"custom-value".to_vec() } + ); + + // C.4.1. First Request + repr_test_case!( + "828684418cf1e3c2e5f23a6ba0ab90f4ff", + { Indexed => 2 }, + { Indexed => 6 }, + { Indexed => 4 }, + { LiteralWithIndexing, Index => 1, b"www.example.com".to_vec() } + ); + + // C.4.2. Second Request + repr_test_case!( + "828684be5886a8eb10649cbf", + { Indexed => 2 }, + { Indexed => 6 }, + { Indexed => 4 }, + { Indexed => 62 }, + { LiteralWithIndexing, Index => 24, b"no-cache".to_vec() } + ); + + // C.4.3. Third Request + repr_test_case!( + "828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf", + { Indexed => 2 }, + { Indexed => 7 }, + { Indexed => 5 }, + { Indexed => 63 }, + { LiteralWithIndexing, Literal => b"custom-key".to_vec(), b"custom-value".to_vec() } + ); + + // C.5.1. First Response + repr_test_case!( + "4803333032580770726976617465611d\ + 4d6f6e2c203231204f63742032303133\ + 2032303a31333a323120474d546e1768\ + 747470733a2f2f7777772e6578616d70\ + 6c652e636f6d", + { LiteralWithIndexing, Index => 8, b"302".to_vec() }, + { LiteralWithIndexing, Index => 24, b"private".to_vec() }, + { LiteralWithIndexing, Index => 33, b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec() }, + { LiteralWithIndexing, Index => 46, b"https://www.example.com".to_vec() } + ); + + // C.5.2. Second Response + repr_test_case!( + "4803333037c1c0bf", + { LiteralWithIndexing, Index => 8, b"307".to_vec() }, + { Indexed => 65 }, + { Indexed => 64 }, + { Indexed => 63 } + ); + + // C.5.3. Third Response + repr_test_case!( + "88c1611d4d6f6e2c203231204f637420\ + 323031332032303a31333a323220474d\ + 54c05a04677a69707738666f6f3d4153\ + 444a4b48514b425a584f5157454f5049\ + 5541585157454f49553b206d61782d61\ + 67653d333630303b2076657273696f6e\ + 3d31", + { Indexed => 8 }, + { Indexed => 65 }, + { LiteralWithIndexing, Index => 33, b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec() }, + { Indexed => 64 }, + { LiteralWithIndexing, Index => 26, b"gzip".to_vec() }, + { LiteralWithIndexing, Index => 55, b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() } + ); + + // C.6.1. First Response + repr_test_case!( + "488264025885aec3771a4b6196d07abe\ + 941054d444a8200595040b8166e082a6\ + 2d1bff6e919d29ad171863c78f0b97c8\ + e9ae82ae43d3", + { LiteralWithIndexing, Index => 8, b"302".to_vec() }, + { LiteralWithIndexing, Index => 24, b"private".to_vec() }, + { LiteralWithIndexing, Index => 33, b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec() }, + { LiteralWithIndexing, Index => 46, b"https://www.example.com".to_vec() } + ); + + // C.6.2. Second Response + repr_test_case!( + "4883640effc1c0bf", + { LiteralWithIndexing, Index => 8, b"307".to_vec() }, + { Indexed => 65 }, + { Indexed => 64 }, + { Indexed => 63 } + ); + + // C.6.3. Third Response + repr_test_case!( + "88c16196d07abe941054d444a8200595\ + 040b8166e084a62d1bffc05a839bd9ab\ + 77ad94e7821dd7f2e6c7b335dfdfcd5b\ + 3960d5af27087f3672c1ab270fb5291f\ + 9587316065c003ed4ee5b1063d5007", + { Indexed => 8 }, + { Indexed => 65 }, + { LiteralWithIndexing, Index => 33, b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec() }, + { Indexed => 64 }, + { LiteralWithIndexing, Index => 26, b"gzip".to_vec() }, + { LiteralWithIndexing, Index => 55, b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec() } + ); + } + } + + /// UT test cases for `Finish::decode`. + /// + /// # Brief + /// 1. Creates a `Finish` instance. + /// 2. Calls `Finish::decode` to decode. + /// 3. Checks the results. + #[test] + fn ut_finish_decode() { + // Case 1: Testing LiteralWithIndexing + let name = Name::Literal("test_name".as_bytes().to_vec()); + let value = vec![0u8, 1u8, 2u8]; + let finish = Finish::new(PrefixBit::LITERAL_WITH_INDEXING, name, value); + match finish.decode() { + DecResult::Decoded(repr) => match repr { + Representation::LiteralWithIndexing { name: _, value: _ } => {} + _ => panic!("Expected LiteralWithIndexing"), + }, + _ => panic!("Decode failed"), + } + + // Case 2: Testing LiteralWithoutIndexing + let name = Name::Literal("test_name".as_bytes().to_vec()); + let value = vec![0u8, 1u8, 2u8]; + let finish = Finish::new(PrefixBit::LITERAL_WITHOUT_INDEXING, name, value); + match finish.decode() { + DecResult::Decoded(repr) => match repr { + Representation::LiteralWithoutIndexing { name: _, value: _ } => {} + _ => panic!("Expected LiteralWithoutIndexing"), + }, + _ => panic!("Decode failed"), + } + + // Case 3: Testing LiteralNeverIndexed + let name = Name::Literal("test_name".as_bytes().to_vec()); + let value = vec![0u8, 1u8, 2u8]; + let finish = Finish::new(PrefixBit::LITERAL_NEVER_INDEXED, name, value); + match finish.decode() { + DecResult::Decoded(repr) => match repr { + Representation::LiteralNeverIndexed { name: _, value: _ } => {} + _ => panic!("Expected LiteralNeverIndexed"), + }, + _ => panic!("Decode failed"), + } + + // Case 4: Testing Indexed + let name = Name::Literal("test_name".as_bytes().to_vec()); + let value = vec![0u8, 1u8, 2u8]; + let finish = Finish::new(PrefixBit::INDEXED, name, value); + match finish.decode() { + DecResult::Decoded(repr) => match repr { + Representation::LiteralNeverIndexed { name: _, value: _ } => {} + _ => panic!("Expected LiteralNeverIndexed for PrefixBit::INDEXED"), + }, + _ => panic!("Decode failed"), + } + + // Case 5: Testing SizeUpdate + let name = Name::Literal("test_name".as_bytes().to_vec()); + let value = vec![0u8, 1u8, 2u8]; + let finish = Finish::new(PrefixBit::SIZE_UPDATE, name, value); + match finish.decode() { + DecResult::Decoded(repr) => match repr { + Representation::LiteralNeverIndexed { name: _, value: _ } => {} + _ => panic!("Expected LiteralNeverIndexed for PrefixBit::SIZE_UPDATE"), + }, + _ => panic!("Decode failed"), + } + } +} diff --git a/ylong_http/src/h2/hpack/representation/encoder.rs b/ylong_http/src/h2/hpack/representation/encoder.rs new file mode 100644 index 0000000..9a64645 --- /dev/null +++ b/ylong_http/src/h2/hpack/representation/encoder.rs @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::hpack::integer::IntegerEncoder; +use crate::h2::hpack::representation::PrefixIndexMask; +use crate::h2::hpack::table::{DynamicTable, Header, TableIndex, TableSearcher}; +use crate::h2::{Parts, PseudoHeaders}; +use crate::headers::HeadersIntoIter; +use core::cmp::Ordering; +use std::collections::hash_map::IntoIter; + +/// Encoder implementation for decoding representation. The encode interface +/// supports segmented writing. +pub(crate) struct ReprEncoder<'a> { + table: &'a mut DynamicTable, + iter: Option, + state: Option, +} + +impl<'a> ReprEncoder<'a> { + /// Creates a new, empty `ReprEncoder`. + pub(crate) fn new(table: &'a mut DynamicTable) -> Self { + Self { + table, + iter: None, + state: None, + } + } + + /// Loads states from a holder. + pub(crate) fn load(&mut self, holder: &mut ReprEncStateHolder) { + self.iter = holder.iter.take(); + self.state = holder.state.take(); + } + + /// Saves current state to a holder. + pub(crate) fn save(self, holder: &mut ReprEncStateHolder) { + holder.iter = self.iter; + holder.state = self.state; + } + + /// Decodes the contents of `self.iter` and `self.state`. The result will be + /// written to `dst` and the length of the decoded content will be returned. + /// + /// If the `dst` is full, users can call this method again after preparing a + /// new piece of memory, and the subsequent decoding part will be written + /// into the new memory. + /// + /// # Note + /// Decoding is complete only when `self.iter` and `self.state` are both + /// `None`. It is recommended that users save the result to a + /// `RecEncStateHolder` immediately after using the method. + pub(crate) fn encode(&mut self, dst: &mut [u8]) -> usize { + // If `dst` is empty, leave the state unchanged. + if dst.is_empty() { + return 0; + } + + // `cur` is used to record the current write position of `dst`. + let mut cur = 0; + + // If `self.state` is not `None`, we need to prioritize it. + if let Some(state) = self.state.take() { + match state.encode(&mut dst[cur..]) { + Ok(size) => cur += size, + Err(state) => { + // If `dst` is not long enough, save the current state. + self.state = Some(state); + return dst.len(); + } + } + } + + // If `self.iter` is not `None`, select the next one from the iterator + // to decode. + if let Some(mut iter) = self.iter.take() { + while let Some((h, v)) = iter.next() { + let searcher = TableSearcher::new(self.table); + let result = match searcher.index(&h, &v) { + Some(TableIndex::Header(index)) => Indexed::new(index).encode(&mut dst[cur..]), + Some(TableIndex::HeaderName(index)) => { + // Update it to the dynamic table first, then decode it. + self.table.update(h.clone(), v.clone()); + Indexing::new(index, v.into_bytes(), false).encode(&mut dst[cur..]) + } + None => { + // Update it to the dynamic table first, then decode it. + self.table.update(h.clone(), v.clone()); + IndexingWithName::new(h.into_string().into_bytes(), v.into_bytes(), false) + .encode(&mut dst[cur..]) + } + }; + match result { + Ok(size) => cur += size, + Err(state) => { + // If `dst` is not long enough, save the current state. + self.state = Some(state); + self.iter = Some(iter); + return dst.len(); + } + } + } + } + cur + } +} + +/// `ReprEncStateHolder` is used to save the intermediate results of the `ReprEncoder`. +pub(crate) struct ReprEncStateHolder { + iter: Option, + state: Option, +} + +impl ReprEncStateHolder { + /// Creates a new, empty `ReprEncStateHolder`. + pub(crate) fn new() -> Self { + Self { + iter: None, + state: None, + } + } + + /// Creates a state based on the `Parts` to be encoded. + pub(crate) fn set_parts(&mut self, parts: Parts) { + self.iter = Some(PartsIter::new(parts)) + } + + /// Determines whether `self.iter` and `self.state` are empty. if they are + /// empty, it means encoding is finished. + pub(crate) fn is_empty(&self) -> bool { + self.iter.is_none() && self.state.is_none() + } +} + +/// `PartsIter` is an iterator for traversing the contents of `Parts`. +struct PartsIter { + pseudo: PseudoHeaders, + map: HeadersIntoIter, + next_type: PartsIterDirection, +} + +/// `PartsIterDirection` is the `PartsIter`'s direction to get the next header. +enum PartsIterDirection { + Authority, + Method, + Path, + Scheme, + Status, + Other, +} + +impl PartsIter { + /// Creates a new `PartsIter` from the given `Parts`. + fn new(parts: Parts) -> Self { + Self { + pseudo: parts.pseudo, + map: parts.map.into_iter(), + next_type: PartsIterDirection::Method, + } + } + + /// Gets headers in the order of `Method`, `Status`, `Scheme`, `Path`, + /// `Authority` and `Other`. + fn next(&mut self) -> Option<(Header, String)> { + loop { + match self.next_type { + PartsIterDirection::Method => match self.pseudo.take_method() { + Some(value) => return Some((Header::Method, value)), + None => self.next_type = PartsIterDirection::Status, + }, + PartsIterDirection::Status => match self.pseudo.take_status() { + Some(value) => return Some((Header::Status, value)), + None => self.next_type = PartsIterDirection::Scheme, + }, + PartsIterDirection::Scheme => match self.pseudo.take_scheme() { + Some(value) => return Some((Header::Scheme, value)), + None => self.next_type = PartsIterDirection::Path, + }, + PartsIterDirection::Path => match self.pseudo.take_path() { + Some(value) => return Some((Header::Path, value)), + None => self.next_type = PartsIterDirection::Authority, + }, + PartsIterDirection::Authority => match self.pseudo.take_authority() { + Some(value) => return Some((Header::Authority, value)), + None => self.next_type = PartsIterDirection::Other, + }, + PartsIterDirection::Other => { + return self + .map + .next() + .map(|(h, v)| (Header::Other(h.to_string()), v.to_str().unwrap())) + } + } + } + } +} + +/// Possible states in `Representation` process. +pub(crate) enum ReprEncodeState { + Indexed(Indexed), + Indexing(Indexing), + IndexingWithName(IndexingWithName), + WithoutIndexing(WithoutIndexing), + WithoutIndexingWithName(WithoutIndexingWithName), + NeverIndexed(NeverIndexed), + NeverIndexedWithName(NeverIndexedWithName), +} + +impl ReprEncodeState { + pub(crate) fn encode(self, dst: &mut [u8]) -> Result { + match self { + Self::Indexed(s) => s.encode(dst), + Self::Indexing(s) => s.encode(dst), + Self::IndexingWithName(s) => s.encode(dst), + Self::WithoutIndexing(s) => s.encode(dst), + Self::WithoutIndexingWithName(s) => s.encode(dst), + Self::NeverIndexed(s) => s.encode(dst), + Self::NeverIndexedWithName(s) => s.encode(dst), + } + } +} + +pub(crate) struct Indexed { + index: Integer, +} +impl Indexed { + fn from(index: Integer) -> Self { + Self { index } + } + + fn new(index: usize) -> Self { + Self { + index: Integer::index(index, PrefixIndexMask::INDEXED.0, 0x80), + } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.index + .encode(dst) + .map_err(|e| ReprEncodeState::Indexed(Indexed::from(e))) + } +} + +pub(crate) struct Indexing { + inner: IndexAndValue, +} +impl Indexing { + fn new(index: usize, value: Vec, is_huffman: bool) -> Self { + Self { + inner: IndexAndValue::new() + .set_index(index, PrefixIndexMask::LITERAL_WITH_INDEXING.0, 0x40) + .set_value(value, is_huffman), + } + } + + fn from(inner: IndexAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::Indexing(Indexing::from(e))) + } +} + +pub(crate) struct IndexingWithName { + inner: NameAndValue, +} +impl IndexingWithName { + fn new(name: Vec, value: Vec, is_huffman: bool) -> Self { + Self { + inner: NameAndValue::new() + .set_index(PrefixIndexMask::LITERAL_WITH_INDEXING.0, 0x40) + .set_name_and_value(name, value, is_huffman), + } + } + + fn from(inner: NameAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::IndexingWithName(IndexingWithName::from(e))) + } +} + +pub(crate) struct WithoutIndexing { + inner: IndexAndValue, +} +impl WithoutIndexing { + fn new(index: usize, value: Vec, is_huffman: bool) -> Self { + Self { + inner: IndexAndValue::new() + .set_index(index, PrefixIndexMask::LITERAL_WITHOUT_INDEXING.0, 0x00) + .set_value(value, is_huffman), + } + } + + fn from(inner: IndexAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::WithoutIndexing(WithoutIndexing::from(e))) + } +} + +pub(crate) struct WithoutIndexingWithName { + inner: NameAndValue, +} +impl WithoutIndexingWithName { + fn new(name: Vec, value: Vec, is_huffman: bool) -> Self { + Self { + inner: NameAndValue::new() + .set_index(PrefixIndexMask::LITERAL_WITHOUT_INDEXING.0, 0x00) + .set_name_and_value(name, value, is_huffman), + } + } + + fn from(inner: NameAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::WithoutIndexingWithName(WithoutIndexingWithName::from(e))) + } +} + +pub(crate) struct NeverIndexed { + inner: IndexAndValue, +} +impl NeverIndexed { + fn new(index: usize, value: Vec, is_huffman: bool) -> Self { + Self { + inner: IndexAndValue::new() + .set_index(index, PrefixIndexMask::LITERAL_NEVER_INDEXED.0, 0x10) + .set_value(value, is_huffman), + } + } + + fn from(inner: IndexAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::NeverIndexed(NeverIndexed::from(e))) + } +} + +pub(crate) struct NeverIndexedWithName { + inner: NameAndValue, +} +impl NeverIndexedWithName { + fn new(name: Vec, value: Vec, is_huffman: bool) -> Self { + Self { + inner: NameAndValue::new() + .set_index(PrefixIndexMask::LITERAL_NEVER_INDEXED.0, 0x10) + .set_name_and_value(name, value, is_huffman), + } + } + + fn from(inner: NameAndValue) -> Self { + Self { inner } + } + + fn encode(self, dst: &mut [u8]) -> Result { + self.inner + .encode(dst) + .map_err(|e| ReprEncodeState::NeverIndexedWithName(NeverIndexedWithName::from(e))) + } +} + +macro_rules! check_and_encode { + ($item: expr, $dst: expr, $cur: expr, $self: expr) => {{ + if let Some(i) = $item.take() { + match i.encode($dst) { + Ok(len) => $cur += len, + Err(e) => { + $item = Some(e); + return Err($self); + } + }; + } + }}; +} + +pub(crate) struct IndexAndValue { + index: Option, + value_length: Option, + value_octets: Option, +} + +impl IndexAndValue { + fn new() -> Self { + Self { + index: None, + value_length: None, + value_octets: None, + } + } + + fn set_index(mut self, index: usize, mask: u8, pre: u8) -> Self { + self.index = Some(Integer::index(index, mask, pre)); + self + } + + fn set_value(mut self, value: Vec, is_huffman: bool) -> Self { + self.value_length = Some(Integer::length(value.len(), is_huffman)); + self.value_octets = Some(Octets::new(value)); + self + } + + fn encode(mut self, dst: &mut [u8]) -> Result { + let mut cur = 0; + check_and_encode!(self.index, &mut dst[cur..], cur, self); + check_and_encode!(self.value_length, &mut dst[cur..], cur, self); + check_and_encode!(self.value_octets, &mut dst[cur..], cur, self); + Ok(cur) + } +} + +pub(crate) struct NameAndValue { + index: Option, + name_length: Option, + name_octets: Option, + value_length: Option, + value_octets: Option, +} + +impl NameAndValue { + fn new() -> Self { + Self { + index: None, + name_length: None, + name_octets: None, + value_length: None, + value_octets: None, + } + } + + fn set_index(mut self, mask: u8, pre: u8) -> Self { + self.index = Some(Integer::index(0, mask, pre)); + self + } + + fn set_name_and_value(mut self, name: Vec, value: Vec, is_huffman: bool) -> Self { + self.name_length = Some(Integer::length(name.len(), is_huffman)); + self.name_octets = Some(Octets::new(name)); + self.value_length = Some(Integer::length(value.len(), is_huffman)); + self.value_octets = Some(Octets::new(value)); + self + } + + fn encode(mut self, dst: &mut [u8]) -> Result { + let mut cur = 0; + check_and_encode!(self.index, &mut dst[cur..], cur, self); + check_and_encode!(self.name_length, &mut dst[cur..], cur, self); + check_and_encode!(self.name_octets, &mut dst[cur..], cur, self); + check_and_encode!(self.value_length, &mut dst[cur..], cur, self); + check_and_encode!(self.value_octets, &mut dst[cur..], cur, self); + Ok(cur) + } +} + +pub(crate) struct Integer { + int: IntegerEncoder, +} + +impl Integer { + fn index(index: usize, mask: u8, pre: u8) -> Self { + Self { + int: IntegerEncoder::new(index, mask, pre), + } + } + + fn length(length: usize, is_huffman: bool) -> Self { + Self { + int: IntegerEncoder::new(length, 0x7f, u8::from(is_huffman)), + } + } + + fn encode(mut self, dst: &mut [u8]) -> Result { + let mut cur = 0; + while !self.int.is_finish() { + let dst = &mut dst[cur..]; + if dst.is_empty() { + return Err(self); + } + dst[0] = self.int.next_byte().unwrap(); + cur += 1; + } + Ok(cur) + } +} + +pub(crate) struct Octets { + src: Vec, + idx: usize, +} + +impl Octets { + fn new(src: Vec) -> Self { + Self { src, idx: 0 } + } + + fn encode(mut self, dst: &mut [u8]) -> Result { + let mut cur = 0; + + let input_len = self.src.len() - self.idx; + let output_len = dst.len(); + + if input_len == 0 { + return Ok(cur); + } + + match output_len.cmp(&input_len) { + Ordering::Greater | Ordering::Equal => { + dst[..input_len].copy_from_slice(&self.src[self.idx..]); + cur += input_len; + Ok(cur) + } + Ordering::Less => { + dst[..].copy_from_slice(&self.src[self.idx..self.idx + output_len]); + self.idx += output_len; + Err(self) + } + } + } +} + +#[cfg(test)] +mod ut_repre_encoder { + use super::*; + + /// UT test cases for `ReprEncodeState::encode`. + /// + /// # Brief + /// 1. Creates a `ReprEncodeState` for each possible state. + /// 2. Calls `ReprEncodeState::encode` for each state. + /// 3. Checks whether the result is correct. + #[test] + fn ut_repr_encode_state_encode() { + let mut buffer = [0u8; 256]; + + let repr = ReprEncodeState::Indexed(Indexed::new(42)); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = ReprEncodeState::Indexing(Indexing::new(42, vec![0x0a, 0x0b, 0x0c], false)); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = ReprEncodeState::IndexingWithName(IndexingWithName::new( + vec![0x0a, 0x0b, 0x0c], + vec![0x1a, 0x1b, 0x1c], + false, + )); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = ReprEncodeState::WithoutIndexing(WithoutIndexing::new( + 42, + vec![0x2a, 0x2b, 0x2c], + false, + )); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = ReprEncodeState::WithoutIndexingWithName(WithoutIndexingWithName::new( + vec![0x3a, 0x3b, 0x3c], + vec![0x4a, 0x4b, 0x4c], + false, + )); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = + ReprEncodeState::NeverIndexed(NeverIndexed::new(42, vec![0x5a, 0x5b, 0x5c], false)); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + + let repr = ReprEncodeState::NeverIndexedWithName(NeverIndexedWithName::new( + vec![0x6a, 0x6b, 0x6c], + vec![0x7a, 0x7b, 0x7c], + false, + )); + let result = repr.encode(&mut buffer); + assert!(result.is_ok()); + } +} diff --git a/ylong_http/src/h2/hpack/representation/mod.rs b/ylong_http/src/h2/hpack/representation/mod.rs new file mode 100644 index 0000000..381e511 --- /dev/null +++ b/ylong_http/src/h2/hpack/representation/mod.rs @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! [Header Field Representation] implementation of [HPACK]. +//! +//! [Header Field Representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-2.4 +//! [HPACK]: https://httpwg.org/specs/rfc7541.html +//! +//! # Description from RFC7541 +//! An encoded header field can be represented either as an index or as a literal. +//! +//! An [indexed representation] defines a header field as a reference to an +//! entry in either the static table or the dynamic table. +//! +//! A [literal representation] defines a header field by specifying its +//! name and value. The header field name can be represented literally or as a +//! reference to an entry in either the static table or the dynamic table. +//! The header field value is represented literally. +//! +//! Three different literal representations are defined: +//! +//! - A literal representation that adds the header field as a new entry at the +//! beginning of the dynamic table (see +//! [Literal Header Field with Incremental Indexing]). +//! +//! - A literal representation that does not add the header field to the dynamic +//! table (see [Literal Header Field without Indexing]). +//! +//! - A literal representation that does not add the header field to the dynamic +//! table, with the additional stipulation that this header field always use a +//! literal representation, in particular when re-encoded by an intermediary +//! (see [Literal Header Field Never Indexed]. This representation is intended +//! for protecting header field values that are not to be put at risk by +//! compressing them (see [Never-Indexed Literals] for more details). +//! +//! The selection of one of these literal representations can be guided by +//! security considerations, in order to protect sensitive header field values +//! (see [Probing Dynamic Table State]). +//! +//! The literal representation of a header field name or of a header field value +//! can encode the sequence of octets either directly or using a static +//! Huffman code (see [String Literal Representation]). +//! +//! [Literal Header Field Never Indexed]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.3 +//! [Literal Header Field with Incremental Indexing]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.1 +//! [Literal Header Field without Indexing]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.2 +//! [Never-Indexed Literals]: https://www.rfc-editor.org/rfc/rfc7541.html#section-7.1.3 +//! [Probing Dynamic Table State]: https://www.rfc-editor.org/rfc/rfc7541.html#section-7.1 +//! [String Literal Representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-5.2 +//! [indexed representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.1 +//! [literal representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2 + +mod decoder; +mod encoder; + +pub(crate) use decoder::{ReprDecStateHolder, ReprDecodeState, ReprDecoder}; +pub(crate) use encoder::{ReprEncStateHolder, ReprEncodeState, ReprEncoder}; + +/// Definition and [binary format] of each of the different +/// [header field representations] and the [dynamic table size update] instruction. +/// +/// [binary format]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6 +/// [header field representations]: https://www.rfc-editor.org/rfc/rfc7541.html#section-3.2 +/// [dynamic table size update]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.3 +pub(crate) enum Representation { + /// An [indexed header field representation] identifies an entry in either + /// the static table or the dynamic table. It causes a header field to be + /// added to the decoded header list. + /// + /// An indexed header field starts with the '1' 1-bit pattern, followed by + /// the index of the matching header field, represented as an integer with + /// a 7-bit prefix. + /// + /// [indexed header field representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.1 + /// + /// # Binary Format + /// `Indexed Header Field`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 1 | Index (7+) | + /// +---+---------------------------+ + /// ``` + Indexed { index: usize }, + + /// A [literal header field with incremental indexing representation] results + /// in appending a header field to the decoded header list and inserting it + /// as a new entry into the dynamic table. + /// + /// A literal header field with incremental indexing representation starts + /// with the '01' 2-bit pattern. + /// + /// [literal header field with incremental indexing representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.1 + /// + /// # Binary Format + /// `Literal Header Field with Incremental Indexing -- Indexed Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 1 | Index (6+) | + /// +---+---+-----------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + /// + /// `Literal Header Field with Incremental Indexing -- New Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 1 | 0 | + /// +---+---+-----------------------+ + /// | H | Name Length (7+) | + /// +---+---------------------------+ + /// | Name String (Length octets) | + /// +---+---------------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithIndexing { name: Name, value: Vec }, + + /// A [literal header field without indexing representation] results in + /// appending a header field to the decoded header list without altering + /// the dynamic table. + /// + /// A literal header field without indexing representation starts with the + /// '0000' 4-bit pattern. + /// + /// [literal header field without indexing representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.2 + /// + /// # Binary Format + /// `Literal Header Field without Indexing -- Indexed Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 0 | Index (4+) | + /// +---+---+---+---+---------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + /// `Literal Header Field without Indexing -- New Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 0 | 0 | + /// +---+---+---+---+---------------+ + /// | H | Name Length (7+) | + /// +---+---------------------------+ + /// | Name String (Length octets) | + /// +---+---------------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralWithoutIndexing { name: Name, value: Vec }, + + /// A [literal header field never-indexed representation] results in appending + /// a header field to the decoded header list without altering the dynamic + /// table. Intermediaries **MUST** use the same representation for encoding + /// this header field. + /// + /// A literal header field never-indexed representation starts with the + /// '0001' 4-bit pattern. + /// + /// [literal header field never-indexed representation]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.2.3 + /// + /// # Binary Format + /// `Literal Header Field Never Indexed -- Indexed Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 1 | Index (4+) | + /// +---+---+---+---+---------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + /// + /// `Literal Header Field Never Indexed -- New Name`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 0 | 1 | 0 | + /// +---+---+---+---+---------------+ + /// | H | Name Length (7+) | + /// +---+---------------------------+ + /// | Name String (Length octets) | + /// +---+---------------------------+ + /// | H | Value Length (7+) | + /// +---+---------------------------+ + /// | Value String (Length octets) | + /// +-------------------------------+ + /// ``` + LiteralNeverIndexed { name: Name, value: Vec }, + + /// A [dynamic table size update] signals a change to the size of the + /// dynamic table. + /// + /// [dynamic table size update]: https://www.rfc-editor.org/rfc/rfc7541.html#section-6.3 + /// + /// # Binary Format + /// `Maximum Dynamic Table Size Change`: + /// ```text + /// 0 1 2 3 4 5 6 7 + /// +---+---+---+---+---+---+---+---+ + /// | 0 | 0 | 1 | Max size (5+) | + /// +---+---+---+-------------------+ + /// ``` + SizeUpdate { max_size: usize }, +} + +impl Representation { + /// Gets the prefix bit of the `Representation`. + pub(crate) const fn prefix_bit(&self) -> PrefixBit { + match self { + Self::Indexed { .. } => PrefixBit::INDEXED, + Self::LiteralWithIndexing { .. } => PrefixBit::LITERAL_WITH_INDEXING, + Self::SizeUpdate { .. } => PrefixBit::SIZE_UPDATE, + Self::LiteralNeverIndexed { .. } => PrefixBit::LITERAL_NEVER_INDEXED, + Self::LiteralWithoutIndexing { .. } => PrefixBit::LITERAL_WITHOUT_INDEXING, + } + } + + /// Gets the index mask of the `Representation`. + pub(crate) const fn prefix_index_mask(&self) -> PrefixIndexMask { + match self { + Self::Indexed { .. } => PrefixIndexMask::INDEXED, + Self::LiteralWithIndexing { .. } => PrefixIndexMask::LITERAL_WITH_INDEXING, + Self::SizeUpdate { .. } => PrefixIndexMask::SIZE_UPDATE, + Self::LiteralNeverIndexed { .. } => PrefixIndexMask::LITERAL_NEVER_INDEXED, + Self::LiteralWithoutIndexing { .. } => PrefixIndexMask::LITERAL_WITHOUT_INDEXING, + } + } +} + +/// Prefix bit of `Representation`. An integer is represented in two +/// parts: a prefix that fills the current octet and an optional list of octets +/// that are used if the integer value does not fit within the prefix. +/// +/// # Binary Format +/// ```text +/// 0 1 2 3 4 5 6 7 +/// +---+---+---+---+---+---+---+---+ +/// | PrefixBit | Value | +/// +---+---+---+-------------------+ +/// ``` +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) struct PrefixBit(u8); + +impl PrefixBit { + pub(crate) const INDEXED: Self = Self(0x80); + pub(crate) const LITERAL_WITH_INDEXING: Self = Self(0x40); + pub(crate) const SIZE_UPDATE: Self = Self(0x20); + pub(crate) const LITERAL_NEVER_INDEXED: Self = Self(0x10); + pub(crate) const LITERAL_WITHOUT_INDEXING: Self = Self(0x00); + + /// Creates a `PrefixBit` from a byte. The interface will convert the + /// incoming byte to the most suitable prefix bit. + pub(crate) fn from_u8(byte: u8) -> Self { + match byte { + x if x >= 0x80 => Self::INDEXED, + x if x >= 0x40 => Self::LITERAL_WITH_INDEXING, + x if x >= 0x20 => Self::SIZE_UPDATE, + x if x >= 0x10 => Self::LITERAL_NEVER_INDEXED, + _ => Self::LITERAL_WITHOUT_INDEXING, + } + } + + /// Returns the corresponding `PrefixIndexMask` according to the current + /// prefix bit. + pub(crate) fn prefix_index_mask(&self) -> PrefixIndexMask { + match self.0 { + 0x80 => PrefixIndexMask::INDEXED, + 0x40 => PrefixIndexMask::LITERAL_WITH_INDEXING, + 0x20 => PrefixIndexMask::SIZE_UPDATE, + 0x10 => PrefixIndexMask::LITERAL_NEVER_INDEXED, + _ => PrefixIndexMask::LITERAL_WITHOUT_INDEXING, + } + } +} + +/// Prefix index mask of `Representation`. +/// +/// # Binary Format +/// ```text +/// 0 1 2 3 4 5 6 7 +/// +---+---+---+---+---+---+---+---+ +/// | PrefixBit | Value | +/// +---+---+---+-------------------+ +/// +/// +---+---+---+---+---+---+---+---+ +/// | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | +/// +---+---+---+---+---+---+---+---+ +/// |<- PrefixIndexMask ->| +/// ``` +pub(crate) struct PrefixIndexMask(u8); + +impl PrefixIndexMask { + pub(crate) const INDEXED: Self = Self(0x7f); + pub(crate) const LITERAL_WITH_INDEXING: Self = Self(0x3f); + pub(crate) const SIZE_UPDATE: Self = Self(0x1f); + pub(crate) const LITERAL_NEVER_INDEXED: Self = Self(0x0f); + pub(crate) const LITERAL_WITHOUT_INDEXING: Self = Self(0x0f); +} + +/// Name of `Representation`. It can be represented as string literals or an index. +pub(crate) enum Name { + Index(usize), + Literal(Vec), +} diff --git a/ylong_http/src/h2/hpack/table.rs b/ylong_http/src/h2/hpack/table.rs new file mode 100644 index 0000000..5fa6ba2 --- /dev/null +++ b/ylong_http/src/h2/hpack/table.rs @@ -0,0 +1,560 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::VecDeque; +use std::ops::Add; + +/// `TableSearcher` is used to find specified content in static and dynamic tables. +pub(crate) struct TableSearcher<'a> { + dynamic: &'a DynamicTable, +} + +impl<'a> TableSearcher<'a> { + pub(crate) fn new(dynamic: &'a DynamicTable) -> Self { + Self { dynamic } + } + + /// Searches `HeaderName` in static and dynamic tables. + pub(crate) fn search_header_name(&self, index: usize) -> Option
{ + if index <= 61 { + StaticTable::header_name(index) + } else { + self.dynamic.header_name(index - 62) + } + } + + /// Searches `Header` in static and dynamic tables. + pub(crate) fn search_header(&self, index: usize) -> Option<(Header, String)> { + if index <= 61 { + StaticTable::header(index) + } else { + self.dynamic.header(index - 62) + } + } + + /// Searches index in static and dynamic tables. + pub(crate) fn index(&self, header: &Header, value: &str) -> Option { + match ( + StaticTable::index(header, value), + self.dynamic.index(header, value), + ) { + (x @ Some(TableIndex::Header(_)), _) => x, + (_, Some(TableIndex::Header(i))) => Some(TableIndex::Header(i + 62)), + (x @ Some(TableIndex::HeaderName(_)), _) => x, + (_, Some(TableIndex::HeaderName(i))) => Some(TableIndex::Header(i + 62)), + _ => None, + } + } +} + +pub(crate) enum TableIndex { + Header(usize), + HeaderName(usize), +} + +/// The [`Dynamic Table`][dynamic_table] implementation of [HPACK]. +/// +/// [dynamic_table]: https://httpwg.org/specs/rfc7541.html#dynamic.table +/// [HPACK]: https://httpwg.org/specs/rfc7541.html +/// +/// # Introduction +/// The dynamic table consists of a list of header fields maintained in first-in, +/// first-out order. The first and newest entry in a dynamic table is at the +/// lowest index, and the oldest entry of a dynamic table is at the highest index. +/// +/// The dynamic table is initially empty. Entries are added as each header block +/// is decompressed. +/// +/// The dynamic table can contain duplicate entries (i.e., entries with the same +/// name and same value). Therefore, duplicate entries MUST NOT be treated as an +/// error by a decoder. +/// +/// The encoder decides how to update the dynamic table and as such can control +/// how much memory is used by the dynamic table. To limit the memory +/// requirements of the decoder, the dynamic table size is strictly bounded. +/// +/// The decoder updates the dynamic table during the processing of a list of +/// header field representations. +pub(crate) struct DynamicTable { + queue: VecDeque<(Header, String)>, + curr_size: usize, + max_size: usize, +} + +impl DynamicTable { + /// Creates a `Dynamic Table` based on the size limit. + pub(crate) fn with_max_size(max_size: usize) -> Self { + Self { + queue: VecDeque::new(), + curr_size: 0, + max_size, + } + } + + pub(crate) fn curr_size(&self) -> usize { + self.curr_size + } + + pub(crate) fn max_size(&self) -> usize { + self.max_size + } + + /// Gets a `Header` by the given index. + pub(crate) fn header_name(&self, index: usize) -> Option
{ + self.queue.get(index).map(|(h, _)| h.clone()) + } + + /// Gets a `Header` and a value by the given index. + pub(crate) fn header(&self, index: usize) -> Option<(Header, String)> { + self.queue.get(index).cloned() + } + + /// Updates `DynamicTable` by a given `Header` and value pair. + pub(crate) fn update(&mut self, header: Header, value: String) { + // RFC7541-4.1: The additional 32 octets account for an estimated + // overhead associated with an entry. For example, an entry + // structure using two 64-bit pointers to reference the name and the + // value of the entry and two 64-bit integers for counting the + // number of references to the name and value would have 32 octets + // of overhead. + self.curr_size += header.len() + value.len() + 32; + self.queue.push_front((header, value)); + self.fit_size(); + } + + /// Updates `DynamicTable`'s size. + pub(crate) fn update_size(&mut self, max_size: usize) { + self.max_size = max_size; + self.fit_size(); + } + + /// Adjusts dynamic table content to fit its size. + fn fit_size(&mut self) { + while self.curr_size > self.max_size && !self.queue.is_empty() { + let (key, string) = self.queue.pop_back().unwrap(); + self.curr_size -= key.len() + string.len() + 32; + } + } + + /// Tries get the index of a `Header`. + fn index(&self, header: &Header, value: &str) -> Option { + let mut index = None; + for (n, (h, v)) in self.queue.iter().enumerate() { + match (header == h, value == v, &index) { + (true, true, _) => return Some(TableIndex::Header(n)), + (true, false, None) => index = Some(TableIndex::HeaderName(n)), + _ => {} + } + } + index + } +} + +/// The [`Static Table`][static_table] implementation of [HPACK]. +/// +/// [static_table]: https://httpwg.org/specs/rfc7541.html#static.table +/// [HPACK]: https://httpwg.org/specs/rfc7541.html +/// +/// # Introduction +/// The static table consists of a predefined static list of header fields. +/// +/// # List +/// | Index | Header Name | Header Value | +/// | :---: | :---: | :---: | +/// | 1 | :authority | | +/// | 2 | :method | GET | +/// | 3 | :method | POST | +/// | 4 | :path | / | +/// | 5 | :path | /index.html | +/// | 6 | :scheme | http | +/// | 7 | :scheme | https | +/// | 8 | :status | 200 | +/// | 9 | :status | 204 | +/// | 10 | :status | 206 | +/// | 11 | :status | 304 | +/// | 12 | :status | 400 | +/// | 13 | :status | 404 | +/// | 14 | :status | 500 | +/// | 15 | accept-charset | | +/// | 16 | accept-encoding | gzip, deflate | +/// | 17 | accept-language | | +/// | 18 | accept-ranges | | +/// | 19 | accept | | +/// | 20 | access-control-allow-origin | | +/// | 21 | age | | +/// | 22 | allow | | +/// | 23 | authorization | | +/// | 24 | cache-control | | +/// | 25 | content-disposition | | +/// | 26 | content-encoding | | +/// | 27 | content-language | | +/// | 28 | content-length | | +/// | 29 | content-location | | +/// | 30 | content-range | | +/// | 31 | content-type | | +/// | 32 | cookie | | +/// | 33 | date | | +/// | 34 | etag | | +/// | 35 | expect | | +/// | 36 | expires | | +/// | 37 | from | | +/// | 38 | host | | +/// | 39 | if-match | | +/// | 40 | if-modified-since | | +/// | 41 | if-none-match | | +/// | 42 | if-range | | +/// | 43 | if-unmodified-since | | +/// | 44 | last-modified | | +/// | 45 | link | | +/// | 46 | location | | +/// | 47 | max-forwards | | +/// | 48 | proxy-authenticate | | +/// | 49 | proxy-authorization | | +/// | 50 | range | | +/// | 51 | referer | | +/// | 52 | refresh | | +/// | 53 | retry-after | | +/// | 54 | server | | +/// | 55 | set-cookie | | +/// | 56 | strict-transport-security | | +/// | 57 | transfer-encoding | | +/// | 58 | user-agent | | +/// | 59 | vary | | +/// | 60 | via | | +/// | 61 | www-authenticate | | +struct StaticTable; + +impl StaticTable { + /// Gets a `Header` by the given index. + fn header_name(index: usize) -> Option
{ + match index { + 1 => Some(Header::Authority), + 2..=3 => Some(Header::Method), + 4..=5 => Some(Header::Path), + 6..=7 => Some(Header::Scheme), + 8..=14 => Some(Header::Status), + 15 => Some(Header::Other(String::from("accept-charset"))), + 16 => Some(Header::Other(String::from("accept-encoding"))), + 17 => Some(Header::Other(String::from("accept-language"))), + 18 => Some(Header::Other(String::from("accept-ranges"))), + 19 => Some(Header::Other(String::from("accept"))), + 20 => Some(Header::Other(String::from("access-control-allow-origin"))), + 21 => Some(Header::Other(String::from("age"))), + 22 => Some(Header::Other(String::from("allow"))), + 23 => Some(Header::Other(String::from("authorization"))), + 24 => Some(Header::Other(String::from("cache-control"))), + 25 => Some(Header::Other(String::from("content-disposition"))), + 26 => Some(Header::Other(String::from("content-encoding"))), + 27 => Some(Header::Other(String::from("content-language"))), + 28 => Some(Header::Other(String::from("content-length"))), + 29 => Some(Header::Other(String::from("content-location"))), + 30 => Some(Header::Other(String::from("content-range"))), + 31 => Some(Header::Other(String::from("content-type"))), + 32 => Some(Header::Other(String::from("cookie"))), + 33 => Some(Header::Other(String::from("date"))), + 34 => Some(Header::Other(String::from("etag"))), + 35 => Some(Header::Other(String::from("expect"))), + 36 => Some(Header::Other(String::from("expires"))), + 37 => Some(Header::Other(String::from("from"))), + 38 => Some(Header::Other(String::from("host"))), + 39 => Some(Header::Other(String::from("if-match"))), + 40 => Some(Header::Other(String::from("if-modified-since"))), + 41 => Some(Header::Other(String::from("if-none-match"))), + 42 => Some(Header::Other(String::from("if-range"))), + 43 => Some(Header::Other(String::from("if-unmodified-since"))), + 44 => Some(Header::Other(String::from("last-modified"))), + 45 => Some(Header::Other(String::from("link"))), + 46 => Some(Header::Other(String::from("location"))), + 47 => Some(Header::Other(String::from("max-forwards"))), + 48 => Some(Header::Other(String::from("proxy-authenticate"))), + 49 => Some(Header::Other(String::from("proxy-authorization"))), + 50 => Some(Header::Other(String::from("range"))), + 51 => Some(Header::Other(String::from("referer"))), + 52 => Some(Header::Other(String::from("refresh"))), + 53 => Some(Header::Other(String::from("retry-after"))), + 54 => Some(Header::Other(String::from("server"))), + 55 => Some(Header::Other(String::from("set-cookie"))), + 56 => Some(Header::Other(String::from("strict-transport-security"))), + 57 => Some(Header::Other(String::from("transfer-encoding"))), + 58 => Some(Header::Other(String::from("user-agent"))), + 59 => Some(Header::Other(String::from("vary"))), + 60 => Some(Header::Other(String::from("via"))), + 61 => Some(Header::Other(String::from("www-authenticate"))), + _ => None, + } + } + + /// Tries to get a `Header` and a value by the given index. + fn header(index: usize) -> Option<(Header, String)> { + match index { + 2 => Some((Header::Method, String::from("GET"))), + 3 => Some((Header::Method, String::from("POST"))), + 4 => Some((Header::Path, String::from("/"))), + 5 => Some((Header::Path, String::from("/index.html"))), + 6 => Some((Header::Scheme, String::from("http"))), + 7 => Some((Header::Scheme, String::from("https"))), + 8 => Some((Header::Status, String::from("200"))), + 9 => Some((Header::Status, String::from("204"))), + 10 => Some((Header::Status, String::from("206"))), + 11 => Some((Header::Status, String::from("304"))), + 12 => Some((Header::Status, String::from("400"))), + 13 => Some((Header::Status, String::from("404"))), + 14 => Some((Header::Status, String::from("500"))), + 16 => Some(( + Header::Other(String::from("accept-encoding")), + String::from("gzip, deflate"), + )), + _ => None, + } + } + + /// Tries to get a `Index` by the given header and value. + fn index(header: &Header, value: &str) -> Option { + // TODO: 优化此处的比较逻辑,考虑使用单例哈希表。 + match (header, value) { + (Header::Authority, _) => Some(TableIndex::HeaderName(1)), + (Header::Method, "GET") => Some(TableIndex::Header(2)), + (Header::Method, "POST") => Some(TableIndex::Header(3)), + (Header::Method, _) => Some(TableIndex::HeaderName(2)), + (Header::Path, "/") => Some(TableIndex::Header(4)), + (Header::Path, "/index.html") => Some(TableIndex::Header(5)), + (Header::Path, _) => Some(TableIndex::HeaderName(4)), + (Header::Scheme, "http") => Some(TableIndex::Header(6)), + (Header::Scheme, "https") => Some(TableIndex::Header(7)), + (Header::Scheme, _) => Some(TableIndex::HeaderName(6)), + (Header::Status, "200") => Some(TableIndex::Header(8)), + (Header::Status, "204") => Some(TableIndex::Header(9)), + (Header::Status, "206") => Some(TableIndex::Header(10)), + (Header::Status, "304") => Some(TableIndex::Header(11)), + (Header::Status, "400") => Some(TableIndex::Header(12)), + (Header::Status, "404") => Some(TableIndex::Header(13)), + (Header::Status, "500") => Some(TableIndex::Header(14)), + (Header::Status, _) => Some(TableIndex::HeaderName(8)), + (Header::Other(s), v) => match (s.as_str(), v) { + ("accept-charset", _) => Some(TableIndex::HeaderName(15)), + ("accept-encoding", "gzip, deflate") => Some(TableIndex::Header(16)), + ("accept-encoding", _) => Some(TableIndex::HeaderName(16)), + ("accept-language", _) => Some(TableIndex::HeaderName(17)), + ("accept-ranges", _) => Some(TableIndex::HeaderName(18)), + ("accept", _) => Some(TableIndex::HeaderName(19)), + ("access-control-allow-origin", _) => Some(TableIndex::HeaderName(20)), + ("age", _) => Some(TableIndex::HeaderName(21)), + ("allow", _) => Some(TableIndex::HeaderName(22)), + ("authorization", _) => Some(TableIndex::HeaderName(23)), + ("cache-control", _) => Some(TableIndex::HeaderName(24)), + ("content-disposition", _) => Some(TableIndex::HeaderName(25)), + ("content-encoding", _) => Some(TableIndex::HeaderName(26)), + ("content-language", _) => Some(TableIndex::HeaderName(27)), + ("content-length", _) => Some(TableIndex::HeaderName(28)), + ("content-location", _) => Some(TableIndex::HeaderName(29)), + ("content-range", _) => Some(TableIndex::HeaderName(30)), + ("content-type", _) => Some(TableIndex::HeaderName(31)), + ("cookie", _) => Some(TableIndex::HeaderName(32)), + ("date", _) => Some(TableIndex::HeaderName(33)), + ("etag", _) => Some(TableIndex::HeaderName(34)), + ("expect", _) => Some(TableIndex::HeaderName(35)), + ("expires", _) => Some(TableIndex::HeaderName(36)), + ("from", _) => Some(TableIndex::HeaderName(37)), + ("host", _) => Some(TableIndex::HeaderName(38)), + ("if-match", _) => Some(TableIndex::HeaderName(39)), + ("if-modified-since", _) => Some(TableIndex::HeaderName(40)), + ("if-none-match", _) => Some(TableIndex::HeaderName(41)), + ("if-range", _) => Some(TableIndex::HeaderName(42)), + ("if-unmodified-since", _) => Some(TableIndex::HeaderName(43)), + ("last-modified", _) => Some(TableIndex::HeaderName(44)), + ("link", _) => Some(TableIndex::HeaderName(45)), + ("location", _) => Some(TableIndex::HeaderName(46)), + ("max-forwards", _) => Some(TableIndex::HeaderName(47)), + ("proxy-authenticate", _) => Some(TableIndex::HeaderName(48)), + ("proxy-authorization", _) => Some(TableIndex::HeaderName(49)), + ("range", _) => Some(TableIndex::HeaderName(50)), + ("referer", _) => Some(TableIndex::HeaderName(51)), + ("refresh", _) => Some(TableIndex::HeaderName(52)), + ("retry-after", _) => Some(TableIndex::HeaderName(53)), + ("server", _) => Some(TableIndex::HeaderName(54)), + ("set-cookie", _) => Some(TableIndex::HeaderName(55)), + ("strict-transport-security", _) => Some(TableIndex::HeaderName(56)), + ("transfer-encoding", _) => Some(TableIndex::HeaderName(57)), + ("user-agent", _) => Some(TableIndex::HeaderName(58)), + ("vary", _) => Some(TableIndex::HeaderName(59)), + ("via", _) => Some(TableIndex::HeaderName(60)), + ("www-authenticate", _) => Some(TableIndex::HeaderName(61)), + _ => None, + }, + } + } +} + +/// Possible header types in `Dynamic Table` and `Static Table`. +#[derive(Clone, PartialEq, Eq)] +pub(crate) enum Header { + Authority, + Method, + Path, + Scheme, + Status, + Other(String), +} + +impl Header { + pub(crate) fn len(&self) -> usize { + match self { + Header::Authority => 10, // 10 is the length of ":authority". + Header::Method => 7, // 7 is the length of ":method". + Header::Path => 5, // 5 is the length of ":path". + Header::Scheme => 7, // 7 is the length of "scheme". + Header::Status => 7, // 7 is the length of "status". + Header::Other(s) => s.len(), + } + } + + pub(crate) fn into_string(self) -> String { + match self { + Header::Authority => String::from(":authority"), + Header::Method => String::from(":method"), + Header::Path => String::from(":path"), + Header::Scheme => String::from(":scheme"), + Header::Status => String::from(":status"), + Header::Other(s) => s, + } + } +} + +#[cfg(test)] +mod ut_dynamic_table { + use crate::h2::hpack::table::{DynamicTable, Header, StaticTable}; + + /// UT test cases for `DynamicTable::with_max_size`. + /// + /// # Brief + /// 1. Calls `DynamicTable::with_max_size` to create a `DynamicTable`. + /// 2. Checks the results. + #[test] + fn ut_dynamic_table_with_max_size() { + let table = DynamicTable::with_max_size(4096); + assert_eq!(table.queue.len(), 0); + assert_eq!(table.curr_size, 0); + assert_eq!(table.max_size, 4096); + } + + /// UT test cases for `DynamicTable::header_name`. + /// + /// # Brief + /// 1. Creates a `DynamicTable`. + /// 2. Calls `DynamicTable::header_name` to get a header name. + /// 3. Checks the results. + #[test] + fn ut_dynamic_table_header_name() { + let mut table = DynamicTable::with_max_size(52); + assert!(table.header_name(0).is_none()); + + assert!(table.header_name(0).is_none()); + table.update(Header::Authority, String::from("Authority")); + match table.header_name(0) { + Some(Header::Authority) => {} + _ => panic!("DynamicTable::header_name() failed!"), + } + } + + /// UT test cases for `DynamicTable::header`. + /// + /// # Brief + /// 1. Creates a `DynamicTable`. + /// 2. Calls `DynamicTable::header` to get a header and a value. + /// 3. Checks the results. + #[test] + fn ut_dynamic_table_header() { + let mut table = DynamicTable::with_max_size(52); + assert!(table.header(0).is_none()); + + assert!(table.header(0).is_none()); + table.update(Header::Authority, String::from("Authority")); + match table.header(0) { + Some((Header::Authority, x)) if x == *"Authority" => {} + _ => panic!("DynamicTable::header() failed!"), + } + } + + /// UT test cases for `DynamicTable::update`. + /// + /// # Brief + /// 1. Creates a `DynamicTable`. + /// 2. Calls `DynamicTable::update` to insert a header and a value. + /// 3. Checks the results. + #[test] + fn ut_dynamic_table_update() { + let mut table = DynamicTable::with_max_size(52); + table.update(Header::Authority, String::from("Authority")); + assert_eq!(table.queue.len(), 1); + match table.header(0) { + Some((Header::Authority, x)) if x == *"Authority" => {} + _ => panic!("DynamicTable::header() failed!"), + } + + table.update(Header::Method, String::from("Method")); + assert_eq!(table.queue.len(), 1); + match table.header(0) { + Some((Header::Method, x)) if x == *"Method" => {} + _ => panic!("DynamicTable::header() failed!"), + } + } + + /// UT test cases for `DynamicTable::update_size`. + /// + /// # Brief + /// 1. Creates a `DynamicTable`. + /// 2. Calls `DynamicTable::update_size` to update its max size. + /// 3. Checks the results. + #[test] + fn ut_dynamic_table_update_size() { + let mut table = DynamicTable::with_max_size(52); + table.update(Header::Authority, String::from("Authority")); + assert_eq!(table.queue.len(), 1); + match table.header(0) { + Some((Header::Authority, x)) if x == *"Authority" => {} + _ => panic!("DynamicTable::header() failed!"), + } + + table.update_size(0); + assert_eq!(table.queue.len(), 0); + assert!(table.header(0).is_none()); + } + + /// UT test cases for `StaticTable::header_name` and `StaticTable::header`. + /// + /// # Brief + /// 1. Iterates over a range of indices, testing both `StaticTable::header_name` and `StaticTable::header`. + /// 2. Verifies the presence or absence of header names and headers based on the given index. + #[test] + fn ut_static_table() { + // Checking header names for indices 1 to 64 + for index in 1..65 { + if index < 62 { + assert!(StaticTable::header_name(index).is_some()) + } else { + assert!(StaticTable::header_name(index).is_none()) + } + } + + // Checking headers for indices 2 to 19 + for index in 2..20 { + if index < 17 && index != 15 { + assert!(StaticTable::header(index).is_some()) + } else { + assert!(StaticTable::header(index).is_none()) + } + } + } +} diff --git a/ylong_http/src/h2/mod.rs b/ylong_http/src/h2/mod.rs new file mode 100644 index 0000000..002445b --- /dev/null +++ b/ylong_http/src/h2/mod.rs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! [Http/2 Protocol] Implementation. +//! +//! [Http/2 Protocol]: https://httpwg.org/specs/rfc9113.html +//! # Introduction +//! The performance of applications using the Hypertext Transfer Protocol +//! ([HTTP]) is linked to how each version of HTTP uses the underlying +//! transport, and the conditions under which the transport operates. +//! +//! Making multiple concurrent requests can reduce latency and improve +//! application performance. HTTP/1.0 allowed only one request to be outstanding +//! at a time on a given [TCP] connection. [HTTP/1.1] added request pipelining, +//! but this only partially addressed request concurrency and still suffers from +//! application-layer head-of-line blocking. Therefore, HTTP/1.0 and HTTP/1.1 +//! clients use multiple connections to a server to make concurrent requests. +//! +//! Furthermore, HTTP fields are often repetitive and verbose, causing +//! unnecessary network traffic as well as causing the initial TCP congestion +//! window to quickly fill. This can result in excessive latency when multiple +//! requests are made on a new TCP connection. +//! +//! HTTP/2 addresses these issues by defining an optimized mapping of HTTP's +//! semantics to an underlying connection. Specifically, it allows interleaving +//! of messages on the same connection and uses an efficient coding for HTTP +//! fields. It also allows prioritization of requests, letting more important +//! requests complete more quickly, further improving performance. +//! +//! The resulting protocol is more friendly to the network because fewer TCP +//! connections can be used in comparison to HTTP/1.x. This means less +//! competition with other flows and longer-lived connections, which in turn +//! lead to better utilization of available network capacity. Note, however, +//! that TCP head-of-line blocking is not addressed by this protocol. +//! +//! Finally, HTTP/2 also enables more efficient processing of messages through +//! use of binary message framing. +//! +//! [HTTP]: https://www.rfc-editor.org/rfc/rfc9110.html +//! [HTTP/1.1]: https://www.rfc-editor.org/rfc/rfc9112.html +//! [TCP]: https://www.rfc-editor.org/rfc/rfc793.html +//! + +mod decoder; +mod encoder; +mod error; +mod frame; +mod hpack; +mod parts; +mod pseudo; + +pub use decoder::{FrameDecoder, FrameKind, Frames, FramesIntoIter}; +pub use encoder::FrameEncoder; +pub use error::{ErrorCode, H2Error}; +pub use frame::{ + Data, Frame, FrameFlags, Goaway, Headers, Payload, Ping, RstStream, Setting, Settings, + SettingsBuilder, +}; +pub(crate) use hpack::{HpackDecoder, HpackEncoder}; +pub use parts::Parts; +pub use pseudo::PseudoHeaders; diff --git a/ylong_http/src/h2/parts.rs b/ylong_http/src/h2/parts.rs new file mode 100644 index 0000000..428182c --- /dev/null +++ b/ylong_http/src/h2/parts.rs @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::h2::hpack::table::Header; +use crate::h2::pseudo::PseudoHeaders; +use crate::headers::Headers; + +/// HTTP2 HEADERS frame payload implementation. +#[derive(PartialEq, Eq, Clone)] +pub struct Parts { + pub(crate) pseudo: PseudoHeaders, + pub(crate) map: Headers, +} + +impl Parts { + /// The constructor of `Parts` + pub fn new() -> Self { + Self { + pseudo: PseudoHeaders::new(), + map: Headers::new(), + } + } + + /// Sets pseudo headers for `Parts`. + pub fn set_pseudo(&mut self, pseudo: PseudoHeaders) { + self.pseudo = pseudo; + } + + /// Sets regular field lines for `Parts`. + pub fn set_header_lines(&mut self, headers: Headers) { + self.map = headers; + } + + pub(crate) fn is_empty(&self) -> bool { + self.pseudo.is_empty() && self.map.is_empty() + } + + pub(crate) fn update(&mut self, headers: Header, value: String) { + match headers { + Header::Authority => self.pseudo.set_authority(Some(value)), + Header::Method => self.pseudo.set_method(Some(value)), + Header::Path => self.pseudo.set_path(Some(value)), + Header::Scheme => self.pseudo.set_scheme(Some(value)), + Header::Status => self.pseudo.set_status(Some(value)), + Header::Other(header) => self.map.append(header.as_str(), value.as_str()).unwrap(), + } + } + + pub(crate) fn parts(&self) -> (&PseudoHeaders, &Headers) { + (&self.pseudo, &self.map) + } + + pub(crate) fn into_parts(self) -> (PseudoHeaders, Headers) { + (self.pseudo, self.map) + } +} + +impl Default for Parts { + fn default() -> Self { + Self::new() + } +} diff --git a/ylong_http/src/h2/pseudo.rs b/ylong_http/src/h2/pseudo.rs new file mode 100644 index 0000000..36bfb27 --- /dev/null +++ b/ylong_http/src/h2/pseudo.rs @@ -0,0 +1,508 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// [Pseudo-Header fields] that may appear in http2 header fields. +/// +/// [Pseudo-Header fields]: https://httpwg.org/specs/rfc9113.html#PseudoHeaderFields +/// +/// # Note +/// The current structure is not responsible for checking every value. +// TODO: 考虑将 PseudoHeaders 拆分成 `RequestPseudo` 和 `ResponsePseudo`. +#[derive(Clone, PartialEq, Eq)] +pub struct PseudoHeaders { + authority: Option, // Request. + method: Option, // Request. + path: Option, // Request. + scheme: Option, // Request. + status: Option, // Response. +} + +// TODO: 去掉冗余的方法。 +impl PseudoHeaders { + /// Create a new `PseudoHeaders`. + pub(crate) fn new() -> Self { + Self { + authority: None, + method: None, + path: None, + scheme: None, + status: None, + } + } + + pub(crate) fn is_empty(&self) -> bool { + self.authority.is_none() + && self.method.is_none() + && self.path.is_none() + && self.scheme.is_none() + && self.status.is_none() + } + + /// Check if it contains `Authority`. + pub(crate) fn contains_authority(&self) -> bool { + self.authority.is_some() + } + + /// Get the `&str` value of `Authority`. + pub fn authority(&self) -> Option<&str> { + self.authority.as_deref() + } + + /// Set the value of `Authority`. + pub fn set_authority(&mut self, authority: Option) { + self.authority = authority; + } + + /// Take the `String` value of `Authority`. + pub(crate) fn take_authority(&mut self) -> Option { + self.authority.take() + } + + /// Check if it contains `Method`. + pub(crate) fn contains_method(&self) -> bool { + self.method.is_some() + } + + /// Get the `&str` value of `Method`. + pub fn method(&self) -> Option<&str> { + self.method.as_deref() + } + + /// Set the value of `Method`. + pub fn set_method(&mut self, method: Option) { + self.method = method; + } + + /// Take the `String` value of `Method`. + pub(crate) fn take_method(&mut self) -> Option { + self.method.take() + } + + /// Check if it contains `Path`. + pub(crate) fn contains_path(&self) -> bool { + self.path.is_some() + } + + /// Get the `&str` value of `Path`. + pub fn path(&self) -> Option<&str> { + self.path.as_deref() + } + + /// Set the value of `Path`. + pub fn set_path(&mut self, path: Option) { + self.path = path; + } + + /// Take the `String` value of `Path`. + pub(crate) fn take_path(&mut self) -> Option { + self.path.take() + } + + /// Check if it contains `Scheme`. + pub(crate) fn contains_scheme(&self) -> bool { + self.scheme.is_some() + } + + /// Get the `&str` value of `Scheme`. + pub fn scheme(&self) -> Option<&str> { + self.scheme.as_deref() + } + + /// Set the value of `Scheme`. + pub fn set_scheme(&mut self, scheme: Option) { + self.scheme = scheme; + } + + /// Take the `String` value of `Scheme`. + pub(crate) fn take_scheme(&mut self) -> Option { + self.scheme.take() + } + + /// Check if it contains `Status`. + pub(crate) fn contains_status(&self) -> bool { + self.status.is_some() + } + + /// Get the `&str` value of `Status`. + pub fn status(&self) -> Option<&str> { + self.status.as_deref() + } + + /// Set the value of `Status`. + pub fn set_status(&mut self, status: Option) { + self.status = status; + } + + /// Take the `String` value of `Status`. + pub(crate) fn take_status(&mut self) -> Option { + self.status.take() + } +} + +impl Default for PseudoHeaders { + fn default() -> Self { + PseudoHeaders::new() + } +} + +#[cfg(test)] +mod ut_pseudo_headers { + use crate::h2::pseudo::PseudoHeaders; + + /// UT test cases for `PseudoHeaders::new`. + /// + /// # Brief + /// 1. Calls `PseudoHeaders::new` to create a `PseudoHeaders`. + /// 2. Checks if the result has a default value. + #[test] + fn ut_pseudo_headers_new() { + let pseudo = PseudoHeaders::new(); + assert!(pseudo.authority.is_none()); + assert!(pseudo.method.is_none()); + assert!(pseudo.path.is_none()); + assert!(pseudo.scheme.is_none()); + assert!(pseudo.status.is_none()); + } + + /// UT test cases for `PseudoHeaders::contains_authority`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::contains_authority` of it. + /// 3. Calls `PseudoHeaders::contains_authority` of it after its `authority` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_contains_authority() { + let mut pseudo = PseudoHeaders::new(); + assert!(!pseudo.contains_authority()); + + pseudo.authority = Some(String::from("authority")); + assert!(pseudo.contains_authority()); + } + + /// UT test cases for `PseudoHeaders::authority`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::authority` of it. + /// 3. Calls `PseudoHeaders::authority` of it after its `authority` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_authority() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.authority().is_none()); + + pseudo.authority = Some(String::from("authority")); + assert_eq!(pseudo.authority(), Some("authority")); + } + + /// UT test cases for `PseudoHeaders::set_authority`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::set_authority` of it to set `authority` a value. + /// 3. Checks the results. + #[test] + fn ut_pseudo_headers_set_authority() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.authority().is_none()); + + pseudo.set_authority(Some(String::from("authority"))); + assert_eq!(pseudo.authority(), Some("authority")); + + pseudo.set_authority(None); + assert!(pseudo.authority().is_none()); + } + + /// UT test cases for `PseudoHeaders::take_authority`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::take_authority` of it. + /// 3. Calls `PseudoHeaders::take_authority` of it after its `authority` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_take_authority() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.take_authority().is_none()); + + pseudo.authority = Some(String::from("authority")); + assert_eq!(pseudo.take_authority(), Some(String::from("authority"))); + } + + /// UT test cases for `PseudoHeaders::contains_method`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::contains_method` of it. + /// 3. Calls `PseudoHeaders::contains_method` of it after its `method` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_contains_method() { + let mut pseudo = PseudoHeaders::new(); + assert!(!pseudo.contains_method()); + + pseudo.method = Some(String::from("method")); + assert!(pseudo.contains_method()); + } + + /// UT test cases for `PseudoHeaders::method`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::method` of it. + /// 3. Calls `PseudoHeaders::method` of it after its `method` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_method() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.method().is_none()); + + pseudo.method = Some(String::from("method")); + assert_eq!(pseudo.method(), Some("method")); + } + + /// UT test cases for `PseudoHeaders::set_method`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::set_method` of it to set `method` a value. + /// 3. Checks the results. + #[test] + fn ut_pseudo_headers_set_method() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.method().is_none()); + + pseudo.set_method(Some(String::from("method"))); + assert_eq!(pseudo.method(), Some("method")); + + pseudo.set_method(None); + assert!(pseudo.method().is_none()); + } + + /// UT test cases for `PseudoHeaders::take_method`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::take_method` of it. + /// 3. Calls `PseudoHeaders::take_method` of it after its `method` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_take_method() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.take_method().is_none()); + + pseudo.method = Some(String::from("method")); + assert_eq!(pseudo.take_method(), Some(String::from("method"))); + } + + /// UT test cases for `PseudoHeaders::contains_path`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::contains_path` of it. + /// 3. Calls `PseudoHeaders::contains_path` of it after its `path` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_contains_path() { + let mut pseudo = PseudoHeaders::new(); + assert!(!pseudo.contains_path()); + + pseudo.path = Some(String::from("path")); + assert!(pseudo.contains_path()); + } + + /// UT test cases for `PseudoHeaders::path`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::path` of it. + /// 3. Calls `PseudoHeaders::path` of it after its `path` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_path() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.path().is_none()); + + pseudo.path = Some(String::from("path")); + assert_eq!(pseudo.path(), Some("path")); + } + + /// UT test cases for `PseudoHeaders::set_path`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::set_path` of it to set `path` a value. + /// 3. Checks the results. + #[test] + fn ut_pseudo_headers_set_path() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.path().is_none()); + + pseudo.set_path(Some(String::from("path"))); + assert_eq!(pseudo.path(), Some("path")); + + pseudo.set_path(None); + assert!(pseudo.path().is_none()); + } + + /// UT test cases for `PseudoHeaders::take_path`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::take_path` of it. + /// 3. Calls `PseudoHeaders::take_path` of it after its `path` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_take_path() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.take_path().is_none()); + + pseudo.path = Some(String::from("path")); + assert_eq!(pseudo.take_path(), Some(String::from("path"))); + } + + /// UT test cases for `PseudoHeaders::contains_scheme`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::contains_scheme` of it. + /// 3. Calls `PseudoHeaders::contains_scheme` of it after its `scheme` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_contains_scheme() { + let mut pseudo = PseudoHeaders::new(); + assert!(!pseudo.contains_scheme()); + + pseudo.scheme = Some(String::from("scheme")); + assert!(pseudo.contains_scheme()); + } + + /// UT test cases for `PseudoHeaders::scheme`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::scheme` of it. + /// 3. Calls `PseudoHeaders::scheme` of it after its `scheme` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_scheme() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.scheme().is_none()); + + pseudo.scheme = Some(String::from("scheme")); + assert_eq!(pseudo.scheme(), Some("scheme")); + } + + /// UT test cases for `PseudoHeaders::set_scheme`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::set_scheme` of it to set `scheme` a value. + /// 3. Checks the results. + #[test] + fn ut_pseudo_headers_set_scheme() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.scheme().is_none()); + + pseudo.set_scheme(Some(String::from("scheme"))); + assert_eq!(pseudo.scheme(), Some("scheme")); + + pseudo.set_scheme(None); + assert!(pseudo.scheme().is_none()); + } + + /// UT test cases for `PseudoHeaders::take_scheme`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::take_scheme` of it. + /// 3. Calls `PseudoHeaders::take_scheme` of it after its `scheme` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_take_scheme() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.take_scheme().is_none()); + + pseudo.scheme = Some(String::from("scheme")); + assert_eq!(pseudo.take_scheme(), Some(String::from("scheme"))); + } + + /// UT test cases for `PseudoHeaders::contains_status`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::contains_status` of it. + /// 3. Calls `PseudoHeaders::contains_status` of it after its `status` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_contains_status() { + let mut pseudo = PseudoHeaders::new(); + assert!(!pseudo.contains_status()); + + pseudo.status = Some(String::from("status")); + assert!(pseudo.contains_status()); + } + + /// UT test cases for `PseudoHeaders::status`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::status` of it. + /// 3. Calls `PseudoHeaders::status` of it after its `status` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_status() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.status().is_none()); + + pseudo.status = Some(String::from("status")); + assert_eq!(pseudo.status(), Some("status")); + } + + /// UT test cases for `PseudoHeaders::set_status`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::set_status` of it to set `status` a value. + /// 3. Checks the results. + #[test] + fn ut_pseudo_headers_set_status() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.status().is_none()); + + pseudo.set_status(Some(String::from("status"))); + assert_eq!(pseudo.status(), Some("status")); + + pseudo.set_status(None); + assert!(pseudo.status().is_none()); + } + + /// UT test cases for `PseudoHeaders::take_status`. + /// + /// # Brief + /// 1. Creates a `PseudoHeaders`. + /// 2. Calls `PseudoHeaders::take_status` of it. + /// 3. Calls `PseudoHeaders::take_status` of it after its `status` is set. + /// 4. Checks the results. + #[test] + fn ut_pseudo_headers_take_status() { + let mut pseudo = PseudoHeaders::new(); + assert!(pseudo.take_status().is_none()); + + pseudo.status = Some(String::from("status")); + assert_eq!(pseudo.take_status(), Some(String::from("status"))); + } +} diff --git a/ylong_http/src/h3/mod.rs b/ylong_http/src/h3/mod.rs new file mode 100644 index 0000000..f0e384d --- /dev/null +++ b/ylong_http/src/h3/mod.rs @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: `HTTP/3` Module. diff --git a/ylong_http/src/headers.rs b/ylong_http/src/headers.rs new file mode 100644 index 0000000..2acac76 --- /dev/null +++ b/ylong_http/src/headers.rs @@ -0,0 +1,1247 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Header`][header], which is called `Field` in [`RFC9110`]. +//! +//! The module provides [`Header`], [`HeaderName`], [`HeaderValue`], [`Headers`] +//! and a number of types used for interacting with `Headers`. +//! +//! These types allow representing both `HTTP/1.1` and `HTTP/2` headers. +//! +//! [header]: https://httpwg.org/specs/rfc9110.html#fields +//! [`RFC9110`]: https://httpwg.org/specs/rfc9110.html +//! [`Header`]: Header +//! [`HeaderName`]: HeaderName +//! [`HeaderValue`]: HeaderValue +//! [`Headers`]: Headers +//! +//! # Examples +//! +//! ``` +//! use ylong_http::headers::Headers; +//! +//! let mut headers = Headers::new(); +//! headers.insert("Accept", "text/html").unwrap(); +//! headers.insert("Content-Length", "3495").unwrap(); +//! +//! assert_eq!(headers.get("accept").unwrap().to_str().unwrap(), "text/html"); +//! assert_eq!(headers.get("content-length").unwrap().to_str().unwrap(), "3495"); +//! ``` + +use crate::error::{ErrorKind, HttpError}; +use core::convert::TryFrom; +use core::{slice, str}; +use std::collections::hash_map::Entry; +use std::collections::{hash_map, HashMap}; + +/// HTTP `Header`, which consists of [`HeaderName`] and [`HeaderValue`]. +/// +/// `Header` is called `Field` in RFC9110. HTTP uses fields to provide data in +/// the form of extensible name/value pairs with a registered key namespace. +/// +/// [`HeaderName`]: HeaderName +/// [`HeaderValue`]: HeaderValue +/// +/// # Examples +/// +/// ``` +/// use core::convert::TryFrom; +/// use ylong_http::headers::Header; +/// +/// // This header name string will be normalized to lowercase. +/// let header = Header::try_from(("Example-Field", "Foo")).unwrap(); +/// assert_eq!(header.name().as_bytes(), b"example-field"); +/// +/// // All characters of this header string can be displayed, so the `to_string` +/// // interface can be used to output. +/// assert_eq!(header.value().to_str().unwrap(), "Foo"); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Header { + name: HeaderName, + value: HeaderValue, +} + +impl Header { + /// Combines a `HeaderName` and a `HeaderValue` into a `Header`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::{Header, HeaderName, HeaderValue}; + /// + /// let name = HeaderName::from_bytes(b"Example-Field").unwrap(); + /// let value = HeaderValue::from_bytes(b"Foo").unwrap(); + /// + /// let header = Header::from_raw_parts(name, value); + /// assert_eq!(header.name().as_bytes(), b"example-field"); + /// assert_eq!(header.value().to_str().unwrap(), "Foo"); + /// ``` + pub fn from_raw_parts(name: HeaderName, value: HeaderValue) -> Self { + Self { name, value } + } + + /// Gets a reference to the underlying `HeaderName`. + /// + /// # Examples + /// + /// ``` + /// use core::convert::TryFrom; + /// use ylong_http::headers::Header; + /// + /// let header = Header::try_from(("Example-Field", "Foo")).unwrap(); + /// + /// let name = header.name(); + /// assert_eq!(name.as_bytes(), b"example-field"); + /// ``` + pub fn name(&self) -> &HeaderName { + &self.name + } + + /// Gets a reference to the underlying `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use core::convert::TryFrom; + /// use ylong_http::headers::Header; + /// + /// let header = Header::try_from(("Example-Field", "Foo")).unwrap(); + /// + /// let value = header.value(); + /// assert_eq!(value.to_str().unwrap(), "Foo"); + /// ``` + pub fn value(&self) -> &HeaderValue { + &self.value + } + + /// Consumes this `Header`, get the underlying `HeaderName` and `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use core::convert::TryFrom; + /// use ylong_http::headers::Header; + /// + /// let header = Header::try_from(("Example-Field", "Foo")).unwrap(); + /// let (name, value) = header.into_parts(); + /// + /// assert_eq!(name.as_bytes(), b"example-field"); + /// assert_eq!(value.to_str().unwrap(), "Foo"); + /// ``` + pub fn into_parts(self) -> (HeaderName, HeaderValue) { + (self.name, self.value) + } +} + +impl TryFrom<(N, V)> for Header +where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, +{ + type Error = HttpError; + + fn try_from(pair: (N, V)) -> Result { + Ok(Self::from_raw_parts( + HeaderName::try_from(pair.0).map_err(Into::into)?, + HeaderValue::try_from(pair.1).map_err(Into::into)?, + )) + } +} + +/// HTTP `Header Name`, which is called [`Field Name`] in RFC9110. +/// +/// A field name labels the corresponding field value as having the semantics +/// defined by that name. +/// +/// [`Field Name`]: https://httpwg.org/specs/rfc9110.html#fields.names +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::HeaderName; +/// +/// let name = HeaderName::from_bytes(b"Example-Field").unwrap(); +/// assert_eq!(name.as_bytes(), b"example-field"); +/// ``` +// TODO: `StandardHeader` implementation. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct HeaderName { + name: String, +} + +impl HeaderName { + /// Converts a slice of bytes to a `HeaderName`. + /// + /// Since `HeaderName` is case-insensitive, characters of the input will be + /// checked and then converted to lowercase. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderName; + /// + /// let name = HeaderName::from_bytes(b"Example-Field").unwrap(); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self { + name: Self::normalize(bytes)?, + }) + } + + /// Returns a bytes representation of the `HeaderName`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderName; + /// + /// let name = HeaderName::from_bytes(b"Example-Field").unwrap(); + /// let bytes = name.as_bytes(); + /// assert_eq!(bytes, b"example-field"); + /// ``` + pub fn as_bytes(&self) -> &[u8] { + self.name.as_bytes() + } + + // Returns a Vec of the `HeaderName`. + pub(crate) fn into_bytes(self) -> Vec { + self.name.into_bytes() + } + + /// Normalizes the input bytes. + fn normalize(input: &[u8]) -> Result { + let mut dst = Vec::new(); + for b in input.iter() { + // HEADER_CHARS maps all bytes to valid single-byte UTF-8. + let b = HEADER_CHARS[*b as usize]; + if b == 0 { + return Err(ErrorKind::InvalidInput.into()); + } + dst.push(b); + } + Ok(unsafe { String::from_utf8_unchecked(dst) }) + } +} + +impl ToString for HeaderName { + fn to_string(&self) -> String { + self.name.clone() + } +} + +impl TryFrom<&str> for HeaderName { + type Error = HttpError; + + fn try_from(name: &str) -> Result { + Self::from_bytes(name.as_bytes()) + } +} + +impl TryFrom<&[u8]> for HeaderName { + type Error = HttpError; + + fn try_from(bytes: &[u8]) -> Result { + Self::from_bytes(bytes) + } +} + +/// HTTP `Header Value`, which is called [`Field Value`] in RFC9110. +/// +/// HTTP field values consist of a sequence of characters in a format defined by +/// the field's grammar. +/// +/// [`Field Value`]: https://httpwg.org/specs/rfc9110.html#fields.values +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::HeaderValue; +/// +/// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); +/// value.append_bytes(b"application/xml").unwrap(); +/// +/// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); +/// assert!(!value.is_sensitive()); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HeaderValue { + inner: Vec>, + // sensitive data: password etc. + is_sensitive: bool, +} + +impl HeaderValue { + /// Attempts to convert a byte slice to a non-sensitive `HeaderValue`. + /// + /// `HeaderValue` is case-sensitive. Legal characters will remain unchanged. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// assert_eq!(value.to_str().unwrap(), "text/html"); + /// assert!(!value.is_sensitive()); + /// + /// // `HeaderValue` is case-sensitive. Legal characters will remain unchanged. + /// let value = HeaderValue::from_bytes(b"TEXT/HTML").unwrap(); + /// assert_eq!(value.to_str().unwrap(), "TEXT/HTML"); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + if !bytes.iter().all(|b| Self::is_valid(*b)) { + return Err(ErrorKind::InvalidInput.into()); + } + + Ok(HeaderValue { + inner: vec![bytes.to_vec()], + is_sensitive: false, + }) + } + + /// Consume another `HeaderValue`, and then appends it to this `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// let other = HeaderValue::from_bytes(b"text/plain").unwrap(); + /// + /// value.append(other); + /// assert_eq!(value.to_str().unwrap(), "text/html, text/plain"); + /// ``` + pub fn append(&mut self, mut other: Self) { + self.inner.append(&mut other.inner) + } + + /// Appends a new bytes to `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// value.append_bytes(b"application/xml").unwrap(); + /// + /// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); + /// ``` + pub fn append_bytes(&mut self, bytes: &[u8]) -> Result<(), HttpError> { + if !bytes.iter().all(|b| Self::is_valid(*b)) { + return Err(ErrorKind::InvalidInput.into()); + } + self.inner.push(bytes.to_vec()); + Ok(()) + } + + /// Outputs the content of value as a string in a certain way. + /// + /// If there are characters that cannot be displayed in value, return `Err`. + /// Extra comma and whitespace(", ") will be added between each element of + /// value. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// value.append_bytes(b"application/xml").unwrap(); + /// + /// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); + /// ``` + // TODO: change this name to `to_string`? + pub fn to_str(&self) -> Result { + let mut content = Vec::new(); + for (n, i) in self.inner.iter().enumerate() { + if n != 0 { + content.extend_from_slice(b", "); + } + content.extend_from_slice(i.as_slice()); + } + Ok(unsafe { String::from_utf8_unchecked(content) }) + } + + /// Outputs the content of value as a Vec in a certain way. + pub(crate) fn to_vec(&self) -> Vec { + let mut content = Vec::new(); + for (n, i) in self.inner.iter().enumerate() { + if n != 0 { + content.extend_from_slice(b", "); + } + content.extend_from_slice(i.as_slice()); + } + content + } + + /// Returns an iterator over the `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// value.append_bytes(b"application/xml").unwrap(); + /// + /// for sub_value in value.iter() { + /// // Operate on each sub-value. + /// } + /// ``` + pub fn iter(&self) -> HeaderValueIter<'_> { + self.inner.iter() + } + + /// Returns an iterator that allows modifying each sub-value. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// value.append_bytes(b"application/xml").unwrap(); + /// + /// for sub_value in value.iter_mut() { + /// // Operate on each sub-value. + /// } + /// ``` + pub fn iter_mut(&mut self) -> HeaderValueIterMut<'_> { + self.inner.iter_mut() + } + + /// Sets the sensitivity of value. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// assert!(!value.is_sensitive()); + /// + /// value.set_sensitive(true); + /// assert!(value.is_sensitive()); + /// ``` + pub fn set_sensitive(&mut self, is_sensitive: bool) { + self.is_sensitive = is_sensitive; + } + + /// Returns `true` if the value represents sensitive data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::HeaderValue; + /// + /// let value = HeaderValue::from_bytes(b"text/html").unwrap(); + /// assert!(!value.is_sensitive()); + /// ``` + pub fn is_sensitive(&self) -> bool { + self.is_sensitive + } + + /// Returns `true` if the character matches the rules of `HeaderValue`. + fn is_valid(b: u8) -> bool { + b >= 32 && b != 127 || b == b'\t' + } +} + +impl TryFrom<&str> for HeaderValue { + type Error = HttpError; + + fn try_from(value: &str) -> Result { + Self::from_bytes(value.as_bytes()) + } +} + +// `HeaderValue` can use `%x80-FF` u8 in [`RFC9110`]. +// [`RFC9110`]: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-values +// +// |======================================================================== +// | field-value = *field-content | +// | field-content = field-vchar | +// | [ 1*( SP / HTAB / field-vchar ) field-vchar ] | +// | field-vchar = VCHAR / obs-text | +// | obs-text = %x80-FF | +// |======================================================================== +impl TryFrom<&[u8]> for HeaderValue { + type Error = HttpError; + + fn try_from(value: &[u8]) -> Result { + Self::from_bytes(value) + } +} + +/// Immutable `HeaderValue` iterator. +/// +/// This struct is created by [`HeaderValue::iter`]. +/// +/// [`HeaderValue::iter`]: HeaderValue::iter +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::HeaderValue; +/// +/// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); +/// value.append_bytes(b"application/xml").unwrap(); +/// +/// for sub_value in value.iter() { +/// // Operate on each sub-value. +/// } +/// ``` +pub type HeaderValueIter<'a> = slice::Iter<'a, Vec>; + +/// Mutable `HeaderValue` iterator. +/// +/// This struct is created by [`HeaderValue::iter_mut`]. +/// +/// [`HeaderValue::iter_mut`]: HeaderValue::iter_mut +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::HeaderValue; +/// +/// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); +/// value.append_bytes(b"application/xml").unwrap(); +/// +/// for sub_value in value.iter_mut() { +/// // Operate on each sub-value. +/// } +/// ``` +pub type HeaderValueIterMut<'a> = slice::IterMut<'a, Vec>; + +/// HTTP `Headers`, which is called [`Fields`] in RFC9110. +/// +/// Fields are sent and received within the header and trailer sections of messages. +/// +/// [`Fields`]: https://httpwg.org/specs/rfc9110.html#fields +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::Headers; +/// +/// let mut headers = Headers::new(); +/// headers.insert("Accept", "text/html").unwrap(); +/// headers.insert("Content-Length", "3495").unwrap(); +/// headers.append("Accept", "text/plain").unwrap(); +/// +/// assert_eq!(headers.get("accept").unwrap().to_str().unwrap(), "text/html, text/plain"); +/// assert_eq!(headers.get("content-length").unwrap().to_str().unwrap(), "3495"); +/// ``` +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Headers { + map: HashMap, +} + +impl Headers { + /// Creates a new, empty `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let headers = Headers::new(); + /// assert!(headers.is_empty()); + /// ``` + pub fn new() -> Self { + Headers { + map: HashMap::new(), + } + } + + /// Returns the number of header in the `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// assert_eq!(headers.len(), 0); + /// + /// headers.insert("accept", "text/html").unwrap(); + /// assert_eq!(headers.len(), 1); + /// ``` + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns `true` if the `Headers` contains no headers. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// assert!(headers.is_empty()); + /// + /// headers.insert("accept", "text/html").unwrap(); + /// assert!(!headers.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Returns an immutable reference to the `HeaderValue` corresponding to + /// the `HeaderName`. + /// + /// This method returns `None` if the input argument could not be + /// successfully converted to a `HeaderName` or the `HeaderName` is not in + /// `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept","text/html").unwrap(); + /// + /// let value = headers.get("accept"); + /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// ``` + pub fn get(&self, name: T) -> Option<&HeaderValue> + where + HeaderName: TryFrom, + { + HeaderName::try_from(name) + .ok() + .and_then(|name| self.map.get(&name)) + } + + /// Returns a mutable reference to the `HeaderValue` corresponding to + /// the `HeaderName`. + /// + /// This method returns `None` if the input argument could not be + /// successfully converted to a `HeaderName` or the `HeaderName` is not in + /// `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept", "text/html").unwrap(); + /// + /// let value = headers.get_mut("accept"); + /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// ``` + pub fn get_mut(&mut self, name: T) -> Option<&mut HeaderValue> + where + HeaderName: TryFrom, + { + HeaderName::try_from(name) + .ok() + .and_then(move |name| self.map.get_mut(&name)) + } + + /// Inserts a `Header` into the `Headers`. + /// + /// If the input argument could not be successfully converted to a `Header`, + /// `Err` is returned. + /// + /// If the `Headers` did not have this `HeaderName` present, `None` is returned. + /// + /// If the `Headers` did have this `HeaderName` present, the new + /// `HeaderValue` is updated, and the old `HeaderValue` is returned. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// assert!(headers.insert("\0", "illegal header").is_err()); + /// + /// assert_eq!(headers.insert("accept", "text/html"), Ok(None)); + /// + /// let old_value = headers.insert("accept", "text/plain").unwrap(); + /// assert_eq!(old_value.unwrap().to_str().unwrap(), "text/html"); + /// ``` + pub fn insert(&mut self, name: N, value: V) -> Result, HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + let name = HeaderName::try_from(name).map_err(Into::into)?; + let value = HeaderValue::try_from(value).map_err(Into::into)?; + Ok(self.map.insert(name, value)) + } + + /// Appends a `Header` to the `Headers`. + /// + /// If the input argument could not be successfully converted to a `Header`, + /// `Err` is returned. + /// + /// If the `Headers` did not have this `HeaderName` present, this `Header` + /// is inserted into the `Headers`. + /// + /// If the `Headers` did have this `HeaderName` present, the new + /// `HeaderValue` is appended to the old `HeaderValue`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// assert!(headers.append("\0", "illegal header").is_err()); + /// + /// headers.append("accept", "text/html").unwrap(); + /// headers.append("accept", "text/plain").unwrap(); + /// + /// let value = headers.get("accept"); + /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html, text/plain"); + /// ``` + pub fn append(&mut self, name: N, value: V) -> Result<(), HttpError> + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + let name = HeaderName::try_from(name).map_err(Into::into)?; + let value = HeaderValue::try_from(value).map_err(Into::into)?; + + match self.map.entry(name) { + Entry::Occupied(o) => { + o.into_mut().append(value); + } + Entry::Vacant(v) => { + let _ = v.insert(value); + } + }; + Ok(()) + } + + /// Removes `Header` from `Headers` by `HeaderName`, returning the + /// `HeaderValue` at the `HeaderName` if the `HeaderName` was previously + /// in the `Headers`. + /// + /// If the input argument could not be successfully converted to a `Header`, + /// `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept", "text/html").unwrap(); + /// + /// let value = headers.remove("accept"); + /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// ``` + pub fn remove(&mut self, name: T) -> Option + where + HeaderName: TryFrom, + { + HeaderName::try_from(name) + .ok() + .and_then(|name| self.map.remove(&name)) + } + + /// Returns an iterator over the `Headers`. The iterator element type is + /// `(&'a HeaderName, &'a HeaderValue)`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept","text/html").unwrap(); + /// + /// for (_name, _value) in headers.iter() { + /// // Operate on each `HeaderName` and `HeaderValue` pair. + /// } + /// ``` + pub fn iter(&self) -> HeadersIter<'_> { + self.map.iter() + } + + /// Returns an iterator over the `Headers`. The iterator element type is + /// `(&'a HeaderName, &'a mut HeaderValue)`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept","text/html").unwrap(); + /// + /// for (_name, _value) in headers.iter_mut() { + /// // Operate on each `HeaderName` and `HeaderValue` pair. + /// } + /// ``` + pub fn iter_mut(&mut self) -> HeadersIterMut<'_> { + self.map.iter_mut() + } +} + +impl IntoIterator for Headers { + type Item = (HeaderName, HeaderValue); + type IntoIter = HeadersIntoIter; + + /// Creates a consuming iterator, that is, one that moves each `HeaderName` + /// and `HeaderValue` pair out of the `Headers` in arbitrary order. The + /// `Headers` cannot be used after calling this. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// + /// let mut headers = Headers::new(); + /// headers.append("accept","text/html").unwrap(); + /// + /// for (_name, _value) in headers.into_iter() { + /// // Operate on each `HeaderName` and `HeaderValue` pair. + /// } + /// ``` + fn into_iter(self) -> Self::IntoIter { + self.map.into_iter() + } +} + +impl<'a> IntoIterator for &'a Headers { + type Item = (&'a HeaderName, &'a HeaderValue); + type IntoIter = HeadersIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut Headers { + type Item = (&'a HeaderName, &'a mut HeaderValue); + type IntoIter = HeadersIterMut<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// Immutable `Headers` iterator. +/// +/// This struct is created by [`Headers::iter`]. +/// +/// [`Headers::iter`]: Headers::iter +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::Headers; +/// +/// let mut headers = Headers::new(); +/// headers.append("accept","text/html").unwrap(); +/// +/// for (_name, _value) in headers.iter() { +/// // Operate on each `HeaderName` and `HeaderValue` pair. +/// } +/// ``` +pub type HeadersIter<'a> = hash_map::Iter<'a, HeaderName, HeaderValue>; + +/// Mutable `Headers` iterator. +/// +/// This struct is created by [`Headers::iter_mut`]. +/// +/// [`Headers::iter_mut`]: Headers::iter_mut +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::Headers; +/// +/// let mut headers = Headers::new(); +/// headers.append("accept","text/html").unwrap(); +/// +/// for (_name, _value) in headers.iter_mut() { +/// // Operate on each `HeaderName` and `HeaderValue` pair. +/// } +/// ``` +pub type HeadersIterMut<'a> = hash_map::IterMut<'a, HeaderName, HeaderValue>; + +/// An owning iterator over the entries of a `Headers`. +/// +/// This struct is created by [`Headers::into_iter`]. +/// +/// [`Headers::into_iter`]: crate::headers::Headers::into_iter +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::Headers; +/// +/// let mut headers = Headers::new(); +/// headers.append("accept","text/html").unwrap(); +/// +/// for (_name, _value) in headers.into_iter() { +/// // Operate on each `HeaderName` and `HeaderValue` pair. +/// } +/// ``` +pub type HeadersIntoIter = hash_map::IntoIter; + +// HEADER_CHARS is used to check whether char is correct and transfer to lowercase. +#[rustfmt::skip] +const HEADER_CHARS: [u8; 256] = [ +// 0 1 2 3 4 5 6 7 8 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x + 0, 0, b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', // 3x + 0, 0, b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', // 4x + b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, // 5x + 0, 0, 0, 0, 0, b'a', b'b', b'c', b'd', b'e', // 6x + b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 7x + b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', // 8x + b'z', 0, 0, 0, b'^', b'_', b'`', b'a', b'b', b'c', // 9x + b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x + b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x + b'x', b'y', b'z', 0, b'|', 0, b'~', 0, 0, 0, // 12x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x + 0, 0, 0, 0, 0, 0, // 25x +]; + +#[cfg(test)] +mod ut_headers { + use crate::headers::{Header, HeaderName, HeaderValue, Headers}; + use std::collections::HashMap; + + /// UT test cases for `HeaderName::from_bytes`. + /// + /// # Brief + /// 1. Creates a `HeaderName` by calling `HeaderName::from_bytes`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_header_name_from_bytes() { + let name = String::from("accept"); + assert_eq!( + HeaderName::from_bytes(b"ACCEPT"), + Ok(HeaderName { name: name.clone() }) + ); + assert_eq!(HeaderName::from_bytes(b"accept"), Ok(HeaderName { name })); + } + + /// UT test cases for `HeaderName::as_bytes`. + /// + /// # Brief + /// 1. Creates a `HeaderName`. + /// 2. Fetches content from `HeaderName` by calling `HeaderName::as_bytes`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_header_name_as_bytes() { + let name = HeaderName { + name: "accept".to_string(), + }; + assert_eq!(name.as_bytes(), b"accept"); + } + + /// UT test cases for `HeaderValue::from_bytes`. + /// + /// # Brief + /// 1. Creates a `HeaderValue` by calling `HeaderValue::from_bytes`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_header_value_from_bytes() { + let value = HeaderValue::from_bytes(b"teXt/hTml, APPLICATION/xhtml+xml, application/xml"); + let result = Ok(HeaderValue { + inner: vec![b"teXt/hTml, APPLICATION/xhtml+xml, application/xml".to_vec()], + is_sensitive: false, + }); + assert_eq!(value, result); + } + + /// UT test cases for `Header::from_raw_parts, name, value, into_parts`. + /// + /// # Brief + /// 1. Creates a `Header`. + /// 2. Calls Header::from_raw_parts, name, value and into_parts respectively. + /// 3. Checks if the test results are corrent. + #[test] + fn ut_header_methods() { + // from_raw_parts + let name = HeaderName::from_bytes(b"John-Doe").unwrap(); + let value = HeaderValue::from_bytes(b"Foo").unwrap(); + let header = Header::from_raw_parts(name, value); + assert_eq!(header.name().as_bytes(), b"john-doe"); + assert_eq!(header.value().to_str().unwrap(), "Foo"); + assert_ne!(header.name().as_bytes(), b"John-Doe"); + assert_ne!(header.value().to_str().unwrap(), "foo"); + + // name + let name = header.name(); + assert_eq!(name.as_bytes(), b"john-doe"); + assert_ne!(name.as_bytes(), b"John-Doe"); + assert_ne!(name.as_bytes(), b"jane-doe"); + + // value + let value = header.value(); + assert_eq!(value.to_str().unwrap(), "Foo"); + assert_ne!(value.to_str().unwrap(), "foo"); + assert_ne!(value.to_str().unwrap(), "oof"); + + // into_parts + let (name, value) = header.into_parts(); + assert_eq!(name.as_bytes(), b"john-doe"); + assert_eq!(value.to_str().unwrap(), "Foo"); + assert_ne!(name.as_bytes(), b"John-Doe"); + assert_ne!(value.to_str().unwrap(), "foo"); + } + + /// UT test cases for `HeaderValue::iter`. + /// + /// # Brief + /// 1. Creates a `HeaderValue`. + /// 2. Loops through the values by calling `HeaderValue::iter`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_header_value_iter() { + let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); + value.append_bytes(b"application/xml").unwrap(); + let value_to_compare = vec!["text/html", "application/xml"]; + + for (index, sub_value) in value.iter().enumerate() { + assert_eq!(sub_value, value_to_compare[index].as_bytes()); + } + + for (index, sub_value) in value.iter_mut().enumerate() { + assert_eq!(sub_value, value_to_compare[index].as_bytes()); + } + } + + /// UT test cases for `HeaderValue::is_sensitive`. + /// + /// # Brief + /// 1. Creates a `HeaderValue`. + /// 2. Calls `HeaderValue::is_sensitive` to check if the test results are correct. + #[test] + fn ut_header_value_is_sensitive() { + let mut value = HeaderValue { + inner: vec![b"text/html, application/xhtml+xml".to_vec()], + is_sensitive: true, + }; + assert!(value.is_sensitive()); + value.is_sensitive = false; + assert!(!value.is_sensitive()); + } + + /// UT test cases for `Headers::get_mut`. + /// + /// # Brief + /// 1. Creates a `Headers`. + /// 2. Gets the mutable `HeaderValue` by calling `HeaderValue::append_bytes`. + /// 3. Modifies `HeaderValue`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_headers_get_mut() { + let mut headers = Headers::new(); + headers.append("accept", "text/css").unwrap(); + let value = headers.get_mut("accept").unwrap(); + assert!(!value.is_sensitive()); + value.is_sensitive = true; + assert!(value.is_sensitive()); + } + + /// UT test cases for `HeaderValue::append_bytes`. + /// + /// # Brief + /// 1. Creates a `HeaderValue`. + /// 2. Adds new value content into `HeaderValue` by calling `HeaderValue::append_bytes`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_header_value_append_bytes() { + let mut value = HeaderValue { + inner: vec![b"text/html, application/xhtml+xml".to_vec()], + is_sensitive: false, + }; + assert!(value.append_bytes(b"teXt/plain, teXt/css").is_ok()); + assert!(value.append_bytes(b"application/xml").is_ok()); + + let res = HeaderValue { + inner: vec![ + b"text/html, application/xhtml+xml".to_vec(), + b"teXt/plain, teXt/css".to_vec(), + b"application/xml".to_vec(), + ], + is_sensitive: false, + }; + assert_eq!(value, res); + } + + /// UT test cases for `HeaderValue::to_string`. + /// + /// # Brief + /// 1. Creates a `HeaderValue`. + /// 2. Gets content of `HeaderValue` by calling `HeaderName::to_string`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_header_value_to_string() { + let value = HeaderValue { + inner: vec![ + b"text/html, application/xhtml+xml".to_vec(), + b"text/plain, text/css".to_vec(), + b"application/xml".to_vec(), + ], + is_sensitive: false, + }; + + let result = + "text/html, application/xhtml+xml, text/plain, text/css, application/xml".to_string(); + assert_eq!(value.to_str(), Ok(result)); + } + + /// UT test cases for `HeaderValue::set_sensitive`. + /// + /// # Brief + /// 1. Creates a `HeaderValue`. + /// 2. Sets content of `HeaderValue` by calling `HeaderName::set_sensitive`. + /// 3. Checks if the test results are correct. + #[test] + fn ut_header_value_set_sensitive() { + let mut value = HeaderValue { + inner: vec![], + is_sensitive: false, + }; + + value.set_sensitive(true); + assert!(value.is_sensitive); + } + + /// UT test cases for `Headers::new`. + /// + /// # Brief + /// 1. Creates `Headers` by calling `Headers::new`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_headers_new() { + assert_eq!( + Headers::new(), + Headers { + map: HashMap::new() + } + ); + } + + /// UT test cases for `ut_change_headers_info`. + /// + /// # Brief + /// 1. Creates Headers + /// 2. Adds content type `(&str, &str)` by calling append(). + /// 3. Uses new content to replace old content by calling insert(). + /// 4. Uses `HeaderName` to fetch `HeaderValue` by calling get(). + /// 5. Uses `HeaderNam`e to remove `HeaderValu`e by calling remove(). + /// 6. Checks if the test result is correct by assert_eq!(). + #[test] + fn ut_change_headers_info() { + let mut new_headers = Headers::new(); + if new_headers.is_empty() { + let _ = new_headers.append("ACCEPT", "text/html"); + } + let map_len = new_headers.len(); + assert_eq!(map_len, 1); + + let mut verify_map = HashMap::new(); + verify_map.insert( + HeaderName { + name: "accept".to_string(), + }, + HeaderValue { + inner: [b"text/html".to_vec()].to_vec(), + is_sensitive: false, + }, + ); + let headers_map = &new_headers.map; + assert_eq!(headers_map, &verify_map); + + let mut value_vec = Vec::new(); + let inner_vec = b"text/html, application/xhtml+xml, application/xml".to_vec(); + value_vec.push(inner_vec); + let _ = new_headers.insert( + "accept", + "text/html, application/xhtml+xml, application/xml", + ); + + let header_value = new_headers.get("accept").unwrap(); + let verify_value = HeaderValue { + inner: value_vec, + is_sensitive: false, + }; + assert_eq!(header_value, &verify_value); + + let remove_value = new_headers.remove("accept").unwrap(); + assert_eq!( + remove_value, + HeaderValue { + inner: [b"text/html, application/xhtml+xml, application/xml".to_vec()].to_vec(), + is_sensitive: false + } + ); + } + + /// UT test cases for `Headers::iter`. + /// + /// # Brief + /// 1. Creates a `Headers`. + /// 2. Creates an iterator by calling `Headers::iter`. + /// 3. Fetches `HeaderValue` content by calling `HeadersIter::next`. + /// 4. Checks if the test results are correct. + #[test] + fn ut_headers_iter() { + let mut headers = Headers::new(); + assert!(headers.append("ACCEPT", "text/html").is_ok()); + + let mut iter = headers.iter(); + assert_eq!( + iter.next(), + Some(( + &HeaderName { + name: "accept".to_string() + }, + &HeaderValue { + inner: [b"text/html".to_vec()].to_vec(), + is_sensitive: false + } + )) + ); + assert_eq!(iter.next(), None); + } +} diff --git a/ylong_http/src/huffman/consts.rs b/ylong_http/src/huffman/consts.rs new file mode 100644 index 0000000..196fdac --- /dev/null +++ b/ylong_http/src/huffman/consts.rs @@ -0,0 +1,1628 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// The following Huffman code is used when encoding string literals with a +/// Huffman coding. +#[rustfmt::skip] +pub(crate) const HUFFMAN_ENCODE: [(u8, u64); 257] = [ + // (`len in bits`, `code as hex aligned to LSB`) + (0x0d, 0x0000_1ff8), (0x17, 0x007f_ffd8), (0x1c, 0x0fff_ffe2), (0x1c, 0x0fff_ffe3), + (0x1c, 0x0fff_ffe4), (0x1c, 0x0fff_ffe5), (0x1c, 0x0fff_ffe6), (0x1c, 0x0fff_ffe7), + (0x1c, 0x0fff_ffe8), (0x18, 0x00ff_ffea), (0x1e, 0x3fff_fffc), (0x1c, 0x0fff_ffe9), + (0x1c, 0x0fff_ffea), (0x1e, 0x3fff_fffd), (0x1c, 0x0fff_ffeb), (0x1c, 0x0fff_ffec), + (0x1c, 0x0fff_ffed), (0x1c, 0x0fff_ffee), (0x1c, 0x0fff_ffef), (0x1c, 0x0fff_fff0), + (0x1c, 0x0fff_fff1), (0x1c, 0x0fff_fff2), (0x1e, 0x3fff_fffe), (0x1c, 0x0fff_fff3), + (0x1c, 0x0fff_fff4), (0x1c, 0x0fff_fff5), (0x1c, 0x0fff_fff6), (0x1c, 0x0fff_fff7), + (0x1c, 0x0fff_fff8), (0x1c, 0x0fff_fff9), (0x1c, 0x0fff_fffa), (0x1c, 0x0fff_fffb), + (0x06, 0x0000_0014), (0x0a, 0x0000_03f8), (0x0a, 0x0000_03f9), (0x0c, 0x0000_0ffa), + (0x0d, 0x0000_1ff9), (0x06, 0x0000_0015), (0x08, 0x0000_00f8), (0x0b, 0x0000_07fa), + (0x0a, 0x0000_03fa), (0x0a, 0x0000_03fb), (0x08, 0x0000_00f9), (0x0b, 0x0000_07fb), + (0x08, 0x0000_00fa), (0x06, 0x0000_0016), (0x06, 0x0000_0017), (0x06, 0x0000_0018), + (0x05, 0x0000_0000), (0x05, 0x0000_0001), (0x05, 0x0000_0002), (0x06, 0x0000_0019), + (0x06, 0x0000_001a), (0x06, 0x0000_001b), (0x06, 0x0000_001c), (0x06, 0x0000_001d), + (0x06, 0x0000_001e), (0x06, 0x0000_001f), (0x07, 0x0000_005c), (0x08, 0x0000_00fb), + (0x0f, 0x0000_7ffc), (0x06, 0x0000_0020), (0x0c, 0x0000_0ffb), (0x0a, 0x0000_03fc), + (0x0d, 0x0000_1ffa), (0x06, 0x0000_0021), (0x07, 0x0000_005d), (0x07, 0x0000_005e), + (0x07, 0x0000_005f), (0x07, 0x0000_0060), (0x07, 0x0000_0061), (0x07, 0x0000_0062), + (0x07, 0x0000_0063), (0x07, 0x0000_0064), (0x07, 0x0000_0065), (0x07, 0x0000_0066), + (0x07, 0x0000_0067), (0x07, 0x0000_0068), (0x07, 0x0000_0069), (0x07, 0x0000_006a), + (0x07, 0x0000_006b), (0x07, 0x0000_006c), (0x07, 0x0000_006d), (0x07, 0x0000_006e), + (0x07, 0x0000_006f), (0x07, 0x0000_0070), (0x07, 0x0000_0071), (0x07, 0x0000_0072), + (0x08, 0x0000_00fc), (0x07, 0x0000_0073), (0x08, 0x0000_00fd), (0x0d, 0x0000_1ffb), + (0x13, 0x0007_fff0), (0x0d, 0x0000_1ffc), (0x0e, 0x0000_3ffc), (0x06, 0x0000_0022), + (0x0f, 0x0000_7ffd), (0x05, 0x0000_0003), (0x06, 0x0000_0023), (0x05, 0x0000_0004), + (0x06, 0x0000_0024), (0x05, 0x0000_0005), (0x06, 0x0000_0025), (0x06, 0x0000_0026), + (0x06, 0x0000_0027), (0x05, 0x0000_0006), (0x07, 0x0000_0074), (0x07, 0x0000_0075), + (0x06, 0x0000_0028), (0x06, 0x0000_0029), (0x06, 0x0000_002a), (0x05, 0x0000_0007), + (0x06, 0x0000_002b), (0x07, 0x0000_0076), (0x06, 0x0000_002c), (0x05, 0x0000_0008), + (0x05, 0x0000_0009), (0x06, 0x0000_002d), (0x07, 0x0000_0077), (0x07, 0x0000_0078), + (0x07, 0x0000_0079), (0x07, 0x0000_007a), (0x07, 0x0000_007b), (0x0f, 0x0000_7ffe), + (0x0b, 0x0000_07fc), (0x0e, 0x0000_3ffd), (0x0d, 0x0000_1ffd), (0x1c, 0x0fff_fffc), + (0x14, 0x000f_ffe6), (0x16, 0x003f_ffd2), (0x14, 0x000f_ffe7), (0x14, 0x000f_ffe8), + (0x16, 0x003f_ffd3), (0x16, 0x003f_ffd4), (0x16, 0x003f_ffd5), (0x17, 0x007f_ffd9), + (0x16, 0x003f_ffd6), (0x17, 0x007f_ffda), (0x17, 0x007f_ffdb), (0x17, 0x007f_ffdc), + (0x17, 0x007f_ffdd), (0x17, 0x007f_ffde), (0x18, 0x00ff_ffeb), (0x17, 0x007f_ffdf), + (0x18, 0x00ff_ffec), (0x18, 0x00ff_ffed), (0x16, 0x003f_ffd7), (0x17, 0x007f_ffe0), + (0x18, 0x00ff_ffee), (0x17, 0x007f_ffe1), (0x17, 0x007f_ffe2), (0x17, 0x007f_ffe3), + (0x17, 0x007f_ffe4), (0x15, 0x001f_ffdc), (0x16, 0x003f_ffd8), (0x17, 0x007f_ffe5), + (0x16, 0x003f_ffd9), (0x17, 0x007f_ffe6), (0x17, 0x007f_ffe7), (0x18, 0x00ff_ffef), + (0x16, 0x003f_ffda), (0x15, 0x001f_ffdd), (0x14, 0x000f_ffe9), (0x16, 0x003f_ffdb), + (0x16, 0x003f_ffdc), (0x17, 0x007f_ffe8), (0x17, 0x007f_ffe9), (0x15, 0x001f_ffde), + (0x17, 0x007f_ffea), (0x16, 0x003f_ffdd), (0x16, 0x003f_ffde), (0x18, 0x00ff_fff0), + (0x15, 0x001f_ffdf), (0x16, 0x003f_ffdf), (0x17, 0x007f_ffeb), (0x17, 0x007f_ffec), + (0x15, 0x001f_ffe0), (0x15, 0x001f_ffe1), (0x16, 0x003f_ffe0), (0x15, 0x001f_ffe2), + (0x17, 0x007f_ffed), (0x16, 0x003f_ffe1), (0x17, 0x007f_ffee), (0x17, 0x007f_ffef), + (0x14, 0x000f_ffea), (0x16, 0x003f_ffe2), (0x16, 0x003f_ffe3), (0x16, 0x003f_ffe4), + (0x17, 0x007f_fff0), (0x16, 0x003f_ffe5), (0x16, 0x003f_ffe6), (0x17, 0x007f_fff1), + (0x1a, 0x03ff_ffe0), (0x1a, 0x03ff_ffe1), (0x14, 0x000f_ffeb), (0x13, 0x0007_fff1), + (0x16, 0x003f_ffe7), (0x17, 0x007f_fff2), (0x16, 0x003f_ffe8), (0x19, 0x01ff_ffec), + (0x1a, 0x03ff_ffe2), (0x1a, 0x03ff_ffe3), (0x1a, 0x03ff_ffe4), (0x1b, 0x07ff_ffde), + (0x1b, 0x07ff_ffdf), (0x1a, 0x03ff_ffe5), (0x18, 0x00ff_fff1), (0x19, 0x01ff_ffed), + (0x13, 0x0007_fff2), (0x15, 0x001f_ffe3), (0x1a, 0x03ff_ffe6), (0x1b, 0x07ff_ffe0), + (0x1b, 0x07ff_ffe1), (0x1a, 0x03ff_ffe7), (0x1b, 0x07ff_ffe2), (0x18, 0x00ff_fff2), + (0x15, 0x001f_ffe4), (0x15, 0x001f_ffe5), (0x1a, 0x03ff_ffe8), (0x1a, 0x03ff_ffe9), + (0x1c, 0x0fff_fffd), (0x1b, 0x07ff_ffe3), (0x1b, 0x07ff_ffe4), (0x1b, 0x07ff_ffe5), + (0x14, 0x000f_ffec), (0x18, 0x00ff_fff3), (0x14, 0x000f_ffed), (0x15, 0x001f_ffe6), + (0x16, 0x003f_ffe9), (0x15, 0x001f_ffe7), (0x15, 0x001f_ffe8), (0x17, 0x007f_fff3), + (0x16, 0x003f_ffea), (0x16, 0x003f_ffeb), (0x19, 0x01ff_ffee), (0x19, 0x01ff_ffef), + (0x18, 0x00ff_fff4), (0x18, 0x00ff_fff5), (0x1a, 0x03ff_ffea), (0x17, 0x007f_fff4), + (0x1a, 0x03ff_ffeb), (0x1b, 0x07ff_ffe6), (0x1a, 0x03ff_ffec), (0x1a, 0x03ff_ffed), + (0x1b, 0x07ff_ffe7), (0x1b, 0x07ff_ffe8), (0x1b, 0x07ff_ffe9), (0x1b, 0x07ff_ffea), + (0x1b, 0x07ff_ffeb), (0x1c, 0x0fff_fffe), (0x1b, 0x07ff_ffec), (0x1b, 0x07ff_ffed), + (0x1b, 0x07ff_ffee), (0x1b, 0x07ff_ffef), (0x1b, 0x07ff_fff0), (0x1a, 0x03ff_ffee), + (0x1e, 0x3fff_ffff), +]; + +/// The following table is used to decode Huffman code. +#[rustfmt::skip] +pub(crate) const HUFFMAN_DECODE: [[(u8, u8, u8); 16]; 256] = [ + // (next-state, byte, flags) + [ // 0x00 + (0x04, 0x00, 0x00), (0x05, 0x00, 0x00), (0x07, 0x00, 0x00), (0x08, 0x00, 0x00), + (0x0b, 0x00, 0x00), (0x0c, 0x00, 0x00), (0x10, 0x00, 0x00), (0x13, 0x00, 0x00), + (0x19, 0x00, 0x00), (0x1c, 0x00, 0x00), (0x20, 0x00, 0x00), (0x23, 0x00, 0x00), + (0x2a, 0x00, 0x00), (0x31, 0x00, 0x00), (0x39, 0x00, 0x00), (0x40, 0x00, 0x01), + ], + [ // 0x01 + (0x00, 0x30, 0x02), (0x00, 0x31, 0x02), (0x00, 0x32, 0x02), (0x00, 0x61, 0x02), + (0x00, 0x63, 0x02), (0x00, 0x65, 0x02), (0x00, 0x69, 0x02), (0x00, 0x6f, 0x02), + (0x00, 0x73, 0x02), (0x00, 0x74, 0x02), (0x0d, 0x00, 0x00), (0x0e, 0x00, 0x00), + (0x11, 0x00, 0x00), (0x12, 0x00, 0x00), (0x14, 0x00, 0x00), (0x15, 0x00, 0x00), + ], + [ // 0x02 + (0x01, 0x30, 0x02), (0x16, 0x30, 0x03), (0x01, 0x31, 0x02), (0x16, 0x31, 0x03), + (0x01, 0x32, 0x02), (0x16, 0x32, 0x03), (0x01, 0x61, 0x02), (0x16, 0x61, 0x03), + (0x01, 0x63, 0x02), (0x16, 0x63, 0x03), (0x01, 0x65, 0x02), (0x16, 0x65, 0x03), + (0x01, 0x69, 0x02), (0x16, 0x69, 0x03), (0x01, 0x6f, 0x02), (0x16, 0x6f, 0x03), + ], + [ // 0x03 + (0x02, 0x30, 0x02), (0x09, 0x30, 0x02), (0x17, 0x30, 0x02), (0x28, 0x30, 0x03), + (0x02, 0x31, 0x02), (0x09, 0x31, 0x02), (0x17, 0x31, 0x02), (0x28, 0x31, 0x03), + (0x02, 0x32, 0x02), (0x09, 0x32, 0x02), (0x17, 0x32, 0x02), (0x28, 0x32, 0x03), + (0x02, 0x61, 0x02), (0x09, 0x61, 0x02), (0x17, 0x61, 0x02), (0x28, 0x61, 0x03), + ], + [ // 0x04 + (0x03, 0x30, 0x02), (0x06, 0x30, 0x02), (0x0a, 0x30, 0x02), (0x0f, 0x30, 0x02), + (0x18, 0x30, 0x02), (0x1f, 0x30, 0x02), (0x29, 0x30, 0x02), (0x38, 0x30, 0x03), + (0x03, 0x31, 0x02), (0x06, 0x31, 0x02), (0x0a, 0x31, 0x02), (0x0f, 0x31, 0x02), + (0x18, 0x31, 0x02), (0x1f, 0x31, 0x02), (0x29, 0x31, 0x02), (0x38, 0x31, 0x03), + ], + [ // 0x05 + (0x03, 0x32, 0x02), (0x06, 0x32, 0x02), (0x0a, 0x32, 0x02), (0x0f, 0x32, 0x02), + (0x18, 0x32, 0x02), (0x1f, 0x32, 0x02), (0x29, 0x32, 0x02), (0x38, 0x32, 0x03), + (0x03, 0x61, 0x02), (0x06, 0x61, 0x02), (0x0a, 0x61, 0x02), (0x0f, 0x61, 0x02), + (0x18, 0x61, 0x02), (0x1f, 0x61, 0x02), (0x29, 0x61, 0x02), (0x38, 0x61, 0x03), + ], + [ // 0x06 + (0x02, 0x63, 0x02), (0x09, 0x63, 0x02), (0x17, 0x63, 0x02), (0x28, 0x63, 0x03), + (0x02, 0x65, 0x02), (0x09, 0x65, 0x02), (0x17, 0x65, 0x02), (0x28, 0x65, 0x03), + (0x02, 0x69, 0x02), (0x09, 0x69, 0x02), (0x17, 0x69, 0x02), (0x28, 0x69, 0x03), + (0x02, 0x6f, 0x02), (0x09, 0x6f, 0x02), (0x17, 0x6f, 0x02), (0x28, 0x6f, 0x03), + ], + [ // 0x07 + (0x03, 0x63, 0x02), (0x06, 0x63, 0x02), (0x0a, 0x63, 0x02), (0x0f, 0x63, 0x02), + (0x18, 0x63, 0x02), (0x1f, 0x63, 0x02), (0x29, 0x63, 0x02), (0x38, 0x63, 0x03), + (0x03, 0x65, 0x02), (0x06, 0x65, 0x02), (0x0a, 0x65, 0x02), (0x0f, 0x65, 0x02), + (0x18, 0x65, 0x02), (0x1f, 0x65, 0x02), (0x29, 0x65, 0x02), (0x38, 0x65, 0x03), + ], + [ // 0x08 + (0x03, 0x69, 0x02), (0x06, 0x69, 0x02), (0x0a, 0x69, 0x02), (0x0f, 0x69, 0x02), + (0x18, 0x69, 0x02), (0x1f, 0x69, 0x02), (0x29, 0x69, 0x02), (0x38, 0x69, 0x03), + (0x03, 0x6f, 0x02), (0x06, 0x6f, 0x02), (0x0a, 0x6f, 0x02), (0x0f, 0x6f, 0x02), + (0x18, 0x6f, 0x02), (0x1f, 0x6f, 0x02), (0x29, 0x6f, 0x02), (0x38, 0x6f, 0x03), + ], + [ // 0x09 + (0x01, 0x73, 0x02), (0x16, 0x73, 0x03), (0x01, 0x74, 0x02), (0x16, 0x74, 0x03), + (0x00, 0x20, 0x02), (0x00, 0x25, 0x02), (0x00, 0x2d, 0x02), (0x00, 0x2e, 0x02), + (0x00, 0x2f, 0x02), (0x00, 0x33, 0x02), (0x00, 0x34, 0x02), (0x00, 0x35, 0x02), + (0x00, 0x36, 0x02), (0x00, 0x37, 0x02), (0x00, 0x38, 0x02), (0x00, 0x39, 0x02), + ], + [ // 0x0a + (0x02, 0x73, 0x02), (0x09, 0x73, 0x02), (0x17, 0x73, 0x02), (0x28, 0x73, 0x03), + (0x02, 0x74, 0x02), (0x09, 0x74, 0x02), (0x17, 0x74, 0x02), (0x28, 0x74, 0x03), + (0x01, 0x20, 0x02), (0x16, 0x20, 0x03), (0x01, 0x25, 0x02), (0x16, 0x25, 0x03), + (0x01, 0x2d, 0x02), (0x16, 0x2d, 0x03), (0x01, 0x2e, 0x02), (0x16, 0x2e, 0x03), + ], + [ // 0x0b + (0x03, 0x73, 0x02), (0x06, 0x73, 0x02), (0x0a, 0x73, 0x02), (0x0f, 0x73, 0x02), + (0x18, 0x73, 0x02), (0x1f, 0x73, 0x02), (0x29, 0x73, 0x02), (0x38, 0x73, 0x03), + (0x03, 0x74, 0x02), (0x06, 0x74, 0x02), (0x0a, 0x74, 0x02), (0x0f, 0x74, 0x02), + (0x18, 0x74, 0x02), (0x1f, 0x74, 0x02), (0x29, 0x74, 0x02), (0x38, 0x74, 0x03), + ], + [ // 0x0c + (0x02, 0x20, 0x02), (0x09, 0x20, 0x02), (0x17, 0x20, 0x02), (0x28, 0x20, 0x03), + (0x02, 0x25, 0x02), (0x09, 0x25, 0x02), (0x17, 0x25, 0x02), (0x28, 0x25, 0x03), + (0x02, 0x2d, 0x02), (0x09, 0x2d, 0x02), (0x17, 0x2d, 0x02), (0x28, 0x2d, 0x03), + (0x02, 0x2e, 0x02), (0x09, 0x2e, 0x02), (0x17, 0x2e, 0x02), (0x28, 0x2e, 0x03), + ], + [ // 0x0d + (0x03, 0x20, 0x02), (0x06, 0x20, 0x02), (0x0a, 0x20, 0x02), (0x0f, 0x20, 0x02), + (0x18, 0x20, 0x02), (0x1f, 0x20, 0x02), (0x29, 0x20, 0x02), (0x38, 0x20, 0x03), + (0x03, 0x25, 0x02), (0x06, 0x25, 0x02), (0x0a, 0x25, 0x02), (0x0f, 0x25, 0x02), + (0x18, 0x25, 0x02), (0x1f, 0x25, 0x02), (0x29, 0x25, 0x02), (0x38, 0x25, 0x03), + ], + [ // 0x0e + (0x03, 0x2d, 0x02), (0x06, 0x2d, 0x02), (0x0a, 0x2d, 0x02), (0x0f, 0x2d, 0x02), + (0x18, 0x2d, 0x02), (0x1f, 0x2d, 0x02), (0x29, 0x2d, 0x02), (0x38, 0x2d, 0x03), + (0x03, 0x2e, 0x02), (0x06, 0x2e, 0x02), (0x0a, 0x2e, 0x02), (0x0f, 0x2e, 0x02), + (0x18, 0x2e, 0x02), (0x1f, 0x2e, 0x02), (0x29, 0x2e, 0x02), (0x38, 0x2e, 0x03), + ], + [ // 0x0f + (0x01, 0x2f, 0x02), (0x16, 0x2f, 0x03), (0x01, 0x33, 0x02), (0x16, 0x33, 0x03), + (0x01, 0x34, 0x02), (0x16, 0x34, 0x03), (0x01, 0x35, 0x02), (0x16, 0x35, 0x03), + (0x01, 0x36, 0x02), (0x16, 0x36, 0x03), (0x01, 0x37, 0x02), (0x16, 0x37, 0x03), + (0x01, 0x38, 0x02), (0x16, 0x38, 0x03), (0x01, 0x39, 0x02), (0x16, 0x39, 0x03), + ], + [ // 0x10 + (0x02, 0x2f, 0x02), (0x09, 0x2f, 0x02), (0x17, 0x2f, 0x02), (0x28, 0x2f, 0x03), + (0x02, 0x33, 0x02), (0x09, 0x33, 0x02), (0x17, 0x33, 0x02), (0x28, 0x33, 0x03), + (0x02, 0x34, 0x02), (0x09, 0x34, 0x02), (0x17, 0x34, 0x02), (0x28, 0x34, 0x03), + (0x02, 0x35, 0x02), (0x09, 0x35, 0x02), (0x17, 0x35, 0x02), (0x28, 0x35, 0x03), + ], + [ // 0x11 + (0x03, 0x2f, 0x02), (0x06, 0x2f, 0x02), (0x0a, 0x2f, 0x02), (0x0f, 0x2f, 0x02), + (0x18, 0x2f, 0x02), (0x1f, 0x2f, 0x02), (0x29, 0x2f, 0x02), (0x38, 0x2f, 0x03), + (0x03, 0x33, 0x02), (0x06, 0x33, 0x02), (0x0a, 0x33, 0x02), (0x0f, 0x33, 0x02), + (0x18, 0x33, 0x02), (0x1f, 0x33, 0x02), (0x29, 0x33, 0x02), (0x38, 0x33, 0x03), + ], + [ // 0x12 + (0x03, 0x34, 0x02), (0x06, 0x34, 0x02), (0x0a, 0x34, 0x02), (0x0f, 0x34, 0x02), + (0x18, 0x34, 0x02), (0x1f, 0x34, 0x02), (0x29, 0x34, 0x02), (0x38, 0x34, 0x03), + (0x03, 0x35, 0x02), (0x06, 0x35, 0x02), (0x0a, 0x35, 0x02), (0x0f, 0x35, 0x02), + (0x18, 0x35, 0x02), (0x1f, 0x35, 0x02), (0x29, 0x35, 0x02), (0x38, 0x35, 0x03), + ], + [ // 0x13 + (0x02, 0x36, 0x02), (0x09, 0x36, 0x02), (0x17, 0x36, 0x02), (0x28, 0x36, 0x03), + (0x02, 0x37, 0x02), (0x09, 0x37, 0x02), (0x17, 0x37, 0x02), (0x28, 0x37, 0x03), + (0x02, 0x38, 0x02), (0x09, 0x38, 0x02), (0x17, 0x38, 0x02), (0x28, 0x38, 0x03), + (0x02, 0x39, 0x02), (0x09, 0x39, 0x02), (0x17, 0x39, 0x02), (0x28, 0x39, 0x03), + ], + [ // 0x14 + (0x03, 0x36, 0x02), (0x06, 0x36, 0x02), (0x0a, 0x36, 0x02), (0x0f, 0x36, 0x02), + (0x18, 0x36, 0x02), (0x1f, 0x36, 0x02), (0x29, 0x36, 0x02), (0x38, 0x36, 0x03), + (0x03, 0x37, 0x02), (0x06, 0x37, 0x02), (0x0a, 0x37, 0x02), (0x0f, 0x37, 0x02), + (0x18, 0x37, 0x02), (0x1f, 0x37, 0x02), (0x29, 0x37, 0x02), (0x38, 0x37, 0x03), + ], + [ // 0x15 + (0x03, 0x38, 0x02), (0x06, 0x38, 0x02), (0x0a, 0x38, 0x02), (0x0f, 0x38, 0x02), + (0x18, 0x38, 0x02), (0x1f, 0x38, 0x02), (0x29, 0x38, 0x02), (0x38, 0x38, 0x03), + (0x03, 0x39, 0x02), (0x06, 0x39, 0x02), (0x0a, 0x39, 0x02), (0x0f, 0x39, 0x02), + (0x18, 0x39, 0x02), (0x1f, 0x39, 0x02), (0x29, 0x39, 0x02), (0x38, 0x39, 0x03), + ], + [ // 0x16 + (0x1a, 0x00, 0x00), (0x1b, 0x00, 0x00), (0x1d, 0x00, 0x00), (0x1e, 0x00, 0x00), + (0x21, 0x00, 0x00), (0x22, 0x00, 0x00), (0x24, 0x00, 0x00), (0x25, 0x00, 0x00), + (0x2b, 0x00, 0x00), (0x2e, 0x00, 0x00), (0x32, 0x00, 0x00), (0x35, 0x00, 0x00), + (0x3a, 0x00, 0x00), (0x3d, 0x00, 0x00), (0x41, 0x00, 0x00), (0x44, 0x00, 0x01), + ], + [ // 0x17 + (0x00, 0x3d, 0x02), (0x00, 0x41, 0x02), (0x00, 0x5f, 0x02), (0x00, 0x62, 0x02), + (0x00, 0x64, 0x02), (0x00, 0x66, 0x02), (0x00, 0x67, 0x02), (0x00, 0x68, 0x02), + (0x00, 0x6c, 0x02), (0x00, 0x6d, 0x02), (0x00, 0x6e, 0x02), (0x00, 0x70, 0x02), + (0x00, 0x72, 0x02), (0x00, 0x75, 0x02), (0x26, 0x00, 0x00), (0x27, 0x00, 0x00), + ], + [ // 0x18 + (0x01, 0x3d, 0x02), (0x16, 0x3d, 0x03), (0x01, 0x41, 0x02), (0x16, 0x41, 0x03), + (0x01, 0x5f, 0x02), (0x16, 0x5f, 0x03), (0x01, 0x62, 0x02), (0x16, 0x62, 0x03), + (0x01, 0x64, 0x02), (0x16, 0x64, 0x03), (0x01, 0x66, 0x02), (0x16, 0x66, 0x03), + (0x01, 0x67, 0x02), (0x16, 0x67, 0x03), (0x01, 0x68, 0x02), (0x16, 0x68, 0x03), + ], + [ // 0x19 + (0x02, 0x3d, 0x02), (0x09, 0x3d, 0x02), (0x17, 0x3d, 0x02), (0x28, 0x3d, 0x03), + (0x02, 0x41, 0x02), (0x09, 0x41, 0x02), (0x17, 0x41, 0x02), (0x28, 0x41, 0x03), + (0x02, 0x5f, 0x02), (0x09, 0x5f, 0x02), (0x17, 0x5f, 0x02), (0x28, 0x5f, 0x03), + (0x02, 0x62, 0x02), (0x09, 0x62, 0x02), (0x17, 0x62, 0x02), (0x28, 0x62, 0x03), + ], + [ // 0x1a + (0x03, 0x3d, 0x02), (0x06, 0x3d, 0x02), (0x0a, 0x3d, 0x02), (0x0f, 0x3d, 0x02), + (0x18, 0x3d, 0x02), (0x1f, 0x3d, 0x02), (0x29, 0x3d, 0x02), (0x38, 0x3d, 0x03), + (0x03, 0x41, 0x02), (0x06, 0x41, 0x02), (0x0a, 0x41, 0x02), (0x0f, 0x41, 0x02), + (0x18, 0x41, 0x02), (0x1f, 0x41, 0x02), (0x29, 0x41, 0x02), (0x38, 0x41, 0x03), + ], + [ // 0x1b + (0x03, 0x5f, 0x02), (0x06, 0x5f, 0x02), (0x0a, 0x5f, 0x02), (0x0f, 0x5f, 0x02), + (0x18, 0x5f, 0x02), (0x1f, 0x5f, 0x02), (0x29, 0x5f, 0x02), (0x38, 0x5f, 0x03), + (0x03, 0x62, 0x02), (0x06, 0x62, 0x02), (0x0a, 0x62, 0x02), (0x0f, 0x62, 0x02), + (0x18, 0x62, 0x02), (0x1f, 0x62, 0x02), (0x29, 0x62, 0x02), (0x38, 0x62, 0x03), + ], + [ // 0x1c + (0x02, 0x64, 0x02), (0x09, 0x64, 0x02), (0x17, 0x64, 0x02), (0x28, 0x64, 0x03), + (0x02, 0x66, 0x02), (0x09, 0x66, 0x02), (0x17, 0x66, 0x02), (0x28, 0x66, 0x03), + (0x02, 0x67, 0x02), (0x09, 0x67, 0x02), (0x17, 0x67, 0x02), (0x28, 0x67, 0x03), + (0x02, 0x68, 0x02), (0x09, 0x68, 0x02), (0x17, 0x68, 0x02), (0x28, 0x68, 0x03), + ], + [ // 0x1d + (0x03, 0x64, 0x02), (0x06, 0x64, 0x02), (0x0a, 0x64, 0x02), (0x0f, 0x64, 0x02), + (0x18, 0x64, 0x02), (0x1f, 0x64, 0x02), (0x29, 0x64, 0x02), (0x38, 0x64, 0x03), + (0x03, 0x66, 0x02), (0x06, 0x66, 0x02), (0x0a, 0x66, 0x02), (0x0f, 0x66, 0x02), + (0x18, 0x66, 0x02), (0x1f, 0x66, 0x02), (0x29, 0x66, 0x02), (0x38, 0x66, 0x03), + ], + [ // 0x1e + (0x03, 0x67, 0x02), (0x06, 0x67, 0x02), (0x0a, 0x67, 0x02), (0x0f, 0x67, 0x02), + (0x18, 0x67, 0x02), (0x1f, 0x67, 0x02), (0x29, 0x67, 0x02), (0x38, 0x67, 0x03), + (0x03, 0x68, 0x02), (0x06, 0x68, 0x02), (0x0a, 0x68, 0x02), (0x0f, 0x68, 0x02), + (0x18, 0x68, 0x02), (0x1f, 0x68, 0x02), (0x29, 0x68, 0x02), (0x38, 0x68, 0x03), + ], + [ // 0x1f + (0x01, 0x6c, 0x02), (0x16, 0x6c, 0x03), (0x01, 0x6d, 0x02), (0x16, 0x6d, 0x03), + (0x01, 0x6e, 0x02), (0x16, 0x6e, 0x03), (0x01, 0x70, 0x02), (0x16, 0x70, 0x03), + (0x01, 0x72, 0x02), (0x16, 0x72, 0x03), (0x01, 0x75, 0x02), (0x16, 0x75, 0x03), + (0x00, 0x3a, 0x02), (0x00, 0x42, 0x02), (0x00, 0x43, 0x02), (0x00, 0x44, 0x02), + ], + [ // 0x20 + (0x02, 0x6c, 0x02), (0x09, 0x6c, 0x02), (0x17, 0x6c, 0x02), (0x28, 0x6c, 0x03), + (0x02, 0x6d, 0x02), (0x09, 0x6d, 0x02), (0x17, 0x6d, 0x02), (0x28, 0x6d, 0x03), + (0x02, 0x6e, 0x02), (0x09, 0x6e, 0x02), (0x17, 0x6e, 0x02), (0x28, 0x6e, 0x03), + (0x02, 0x70, 0x02), (0x09, 0x70, 0x02), (0x17, 0x70, 0x02), (0x28, 0x70, 0x03), + ], + [ // 0x21 + (0x03, 0x6c, 0x02), (0x06, 0x6c, 0x02), (0x0a, 0x6c, 0x02), (0x0f, 0x6c, 0x02), + (0x18, 0x6c, 0x02), (0x1f, 0x6c, 0x02), (0x29, 0x6c, 0x02), (0x38, 0x6c, 0x03), + (0x03, 0x6d, 0x02), (0x06, 0x6d, 0x02), (0x0a, 0x6d, 0x02), (0x0f, 0x6d, 0x02), + (0x18, 0x6d, 0x02), (0x1f, 0x6d, 0x02), (0x29, 0x6d, 0x02), (0x38, 0x6d, 0x03), + ], + [ // 0x22 + (0x03, 0x6e, 0x02), (0x06, 0x6e, 0x02), (0x0a, 0x6e, 0x02), (0x0f, 0x6e, 0x02), + (0x18, 0x6e, 0x02), (0x1f, 0x6e, 0x02), (0x29, 0x6e, 0x02), (0x38, 0x6e, 0x03), + (0x03, 0x70, 0x02), (0x06, 0x70, 0x02), (0x0a, 0x70, 0x02), (0x0f, 0x70, 0x02), + (0x18, 0x70, 0x02), (0x1f, 0x70, 0x02), (0x29, 0x70, 0x02), (0x38, 0x70, 0x03), + ], + [ // 0x23 + (0x02, 0x72, 0x02), (0x09, 0x72, 0x02), (0x17, 0x72, 0x02), (0x28, 0x72, 0x03), + (0x02, 0x75, 0x02), (0x09, 0x75, 0x02), (0x17, 0x75, 0x02), (0x28, 0x75, 0x03), + (0x01, 0x3a, 0x02), (0x16, 0x3a, 0x03), (0x01, 0x42, 0x02), (0x16, 0x42, 0x03), + (0x01, 0x43, 0x02), (0x16, 0x43, 0x03), (0x01, 0x44, 0x02), (0x16, 0x44, 0x03), + ], + [ // 0x24 + (0x03, 0x72, 0x02), (0x06, 0x72, 0x02), (0x0a, 0x72, 0x02), (0x0f, 0x72, 0x02), + (0x18, 0x72, 0x02), (0x1f, 0x72, 0x02), (0x29, 0x72, 0x02), (0x38, 0x72, 0x03), + (0x03, 0x75, 0x02), (0x06, 0x75, 0x02), (0x0a, 0x75, 0x02), (0x0f, 0x75, 0x02), + (0x18, 0x75, 0x02), (0x1f, 0x75, 0x02), (0x29, 0x75, 0x02), (0x38, 0x75, 0x03), + ], + [ // 0x25 + (0x02, 0x3a, 0x02), (0x09, 0x3a, 0x02), (0x17, 0x3a, 0x02), (0x28, 0x3a, 0x03), + (0x02, 0x42, 0x02), (0x09, 0x42, 0x02), (0x17, 0x42, 0x02), (0x28, 0x42, 0x03), + (0x02, 0x43, 0x02), (0x09, 0x43, 0x02), (0x17, 0x43, 0x02), (0x28, 0x43, 0x03), + (0x02, 0x44, 0x02), (0x09, 0x44, 0x02), (0x17, 0x44, 0x02), (0x28, 0x44, 0x03), + ], + [ // 0x26 + (0x03, 0x3a, 0x02), (0x06, 0x3a, 0x02), (0x0a, 0x3a, 0x02), (0x0f, 0x3a, 0x02), + (0x18, 0x3a, 0x02), (0x1f, 0x3a, 0x02), (0x29, 0x3a, 0x02), (0x38, 0x3a, 0x03), + (0x03, 0x42, 0x02), (0x06, 0x42, 0x02), (0x0a, 0x42, 0x02), (0x0f, 0x42, 0x02), + (0x18, 0x42, 0x02), (0x1f, 0x42, 0x02), (0x29, 0x42, 0x02), (0x38, 0x42, 0x03), + ], + [ // 0x27 + (0x03, 0x43, 0x02), (0x06, 0x43, 0x02), (0x0a, 0x43, 0x02), (0x0f, 0x43, 0x02), + (0x18, 0x43, 0x02), (0x1f, 0x43, 0x02), (0x29, 0x43, 0x02), (0x38, 0x43, 0x03), + (0x03, 0x44, 0x02), (0x06, 0x44, 0x02), (0x0a, 0x44, 0x02), (0x0f, 0x44, 0x02), + (0x18, 0x44, 0x02), (0x1f, 0x44, 0x02), (0x29, 0x44, 0x02), (0x38, 0x44, 0x03), + ], + [ // 0x28 + (0x2c, 0x00, 0x00), (0x2d, 0x00, 0x00), (0x2f, 0x00, 0x00), (0x30, 0x00, 0x00), + (0x33, 0x00, 0x00), (0x34, 0x00, 0x00), (0x36, 0x00, 0x00), (0x37, 0x00, 0x00), + (0x3b, 0x00, 0x00), (0x3c, 0x00, 0x00), (0x3e, 0x00, 0x00), (0x3f, 0x00, 0x00), + (0x42, 0x00, 0x00), (0x43, 0x00, 0x00), (0x45, 0x00, 0x00), (0x48, 0x00, 0x01), + ], + [ // 0x29 + (0x00, 0x45, 0x02), (0x00, 0x46, 0x02), (0x00, 0x47, 0x02), (0x00, 0x48, 0x02), + (0x00, 0x49, 0x02), (0x00, 0x4a, 0x02), (0x00, 0x4b, 0x02), (0x00, 0x4c, 0x02), + (0x00, 0x4d, 0x02), (0x00, 0x4e, 0x02), (0x00, 0x4f, 0x02), (0x00, 0x50, 0x02), + (0x00, 0x51, 0x02), (0x00, 0x52, 0x02), (0x00, 0x53, 0x02), (0x00, 0x54, 0x02), + ], + [ // 0x2a + (0x01, 0x45, 0x02), (0x16, 0x45, 0x03), (0x01, 0x46, 0x02), (0x16, 0x46, 0x03), + (0x01, 0x47, 0x02), (0x16, 0x47, 0x03), (0x01, 0x48, 0x02), (0x16, 0x48, 0x03), + (0x01, 0x49, 0x02), (0x16, 0x49, 0x03), (0x01, 0x4a, 0x02), (0x16, 0x4a, 0x03), + (0x01, 0x4b, 0x02), (0x16, 0x4b, 0x03), (0x01, 0x4c, 0x02), (0x16, 0x4c, 0x03), + ], + [ // 0x2b + (0x02, 0x45, 0x02), (0x09, 0x45, 0x02), (0x17, 0x45, 0x02), (0x28, 0x45, 0x03), + (0x02, 0x46, 0x02), (0x09, 0x46, 0x02), (0x17, 0x46, 0x02), (0x28, 0x46, 0x03), + (0x02, 0x47, 0x02), (0x09, 0x47, 0x02), (0x17, 0x47, 0x02), (0x28, 0x47, 0x03), + (0x02, 0x48, 0x02), (0x09, 0x48, 0x02), (0x17, 0x48, 0x02), (0x28, 0x48, 0x03), + ], + [ // 0x2c + (0x03, 0x45, 0x02), (0x06, 0x45, 0x02), (0x0a, 0x45, 0x02), (0x0f, 0x45, 0x02), + (0x18, 0x45, 0x02), (0x1f, 0x45, 0x02), (0x29, 0x45, 0x02), (0x38, 0x45, 0x03), + (0x03, 0x46, 0x02), (0x06, 0x46, 0x02), (0x0a, 0x46, 0x02), (0x0f, 0x46, 0x02), + (0x18, 0x46, 0x02), (0x1f, 0x46, 0x02), (0x29, 0x46, 0x02), (0x38, 0x46, 0x03), + ], + [ // 0x2d + (0x03, 0x47, 0x02), (0x06, 0x47, 0x02), (0x0a, 0x47, 0x02), (0x0f, 0x47, 0x02), + (0x18, 0x47, 0x02), (0x1f, 0x47, 0x02), (0x29, 0x47, 0x02), (0x38, 0x47, 0x03), + (0x03, 0x48, 0x02), (0x06, 0x48, 0x02), (0x0a, 0x48, 0x02), (0x0f, 0x48, 0x02), + (0x18, 0x48, 0x02), (0x1f, 0x48, 0x02), (0x29, 0x48, 0x02), (0x38, 0x48, 0x03), + ], + [ // 0x2e + (0x02, 0x49, 0x02), (0x09, 0x49, 0x02), (0x17, 0x49, 0x02), (0x28, 0x49, 0x03), + (0x02, 0x4a, 0x02), (0x09, 0x4a, 0x02), (0x17, 0x4a, 0x02), (0x28, 0x4a, 0x03), + (0x02, 0x4b, 0x02), (0x09, 0x4b, 0x02), (0x17, 0x4b, 0x02), (0x28, 0x4b, 0x03), + (0x02, 0x4c, 0x02), (0x09, 0x4c, 0x02), (0x17, 0x4c, 0x02), (0x28, 0x4c, 0x03), + ], + [ // 0x2f + (0x03, 0x49, 0x02), (0x06, 0x49, 0x02), (0x0a, 0x49, 0x02), (0x0f, 0x49, 0x02), + (0x18, 0x49, 0x02), (0x1f, 0x49, 0x02), (0x29, 0x49, 0x02), (0x38, 0x49, 0x03), + (0x03, 0x4a, 0x02), (0x06, 0x4a, 0x02), (0x0a, 0x4a, 0x02), (0x0f, 0x4a, 0x02), + (0x18, 0x4a, 0x02), (0x1f, 0x4a, 0x02), (0x29, 0x4a, 0x02), (0x38, 0x4a, 0x03), + ], + [ // 0x30 + (0x03, 0x4b, 0x02), (0x06, 0x4b, 0x02), (0x0a, 0x4b, 0x02), (0x0f, 0x4b, 0x02), + (0x18, 0x4b, 0x02), (0x1f, 0x4b, 0x02), (0x29, 0x4b, 0x02), (0x38, 0x4b, 0x03), + (0x03, 0x4c, 0x02), (0x06, 0x4c, 0x02), (0x0a, 0x4c, 0x02), (0x0f, 0x4c, 0x02), + (0x18, 0x4c, 0x02), (0x1f, 0x4c, 0x02), (0x29, 0x4c, 0x02), (0x38, 0x4c, 0x03), + ], + [ // 0x31 + (0x01, 0x4d, 0x02), (0x16, 0x4d, 0x03), (0x01, 0x4e, 0x02), (0x16, 0x4e, 0x03), + (0x01, 0x4f, 0x02), (0x16, 0x4f, 0x03), (0x01, 0x50, 0x02), (0x16, 0x50, 0x03), + (0x01, 0x51, 0x02), (0x16, 0x51, 0x03), (0x01, 0x52, 0x02), (0x16, 0x52, 0x03), + (0x01, 0x53, 0x02), (0x16, 0x53, 0x03), (0x01, 0x54, 0x02), (0x16, 0x54, 0x03), + ], + [ // 0x32 + (0x02, 0x4d, 0x02), (0x09, 0x4d, 0x02), (0x17, 0x4d, 0x02), (0x28, 0x4d, 0x03), + (0x02, 0x4e, 0x02), (0x09, 0x4e, 0x02), (0x17, 0x4e, 0x02), (0x28, 0x4e, 0x03), + (0x02, 0x4f, 0x02), (0x09, 0x4f, 0x02), (0x17, 0x4f, 0x02), (0x28, 0x4f, 0x03), + (0x02, 0x50, 0x02), (0x09, 0x50, 0x02), (0x17, 0x50, 0x02), (0x28, 0x50, 0x03), + ], + [ // 0x33 + (0x03, 0x4d, 0x02), (0x06, 0x4d, 0x02), (0x0a, 0x4d, 0x02), (0x0f, 0x4d, 0x02), + (0x18, 0x4d, 0x02), (0x1f, 0x4d, 0x02), (0x29, 0x4d, 0x02), (0x38, 0x4d, 0x03), + (0x03, 0x4e, 0x02), (0x06, 0x4e, 0x02), (0x0a, 0x4e, 0x02), (0x0f, 0x4e, 0x02), + (0x18, 0x4e, 0x02), (0x1f, 0x4e, 0x02), (0x29, 0x4e, 0x02), (0x38, 0x4e, 0x03), + ], + [ // 0x34 + (0x03, 0x4f, 0x02), (0x06, 0x4f, 0x02), (0x0a, 0x4f, 0x02), (0x0f, 0x4f, 0x02), + (0x18, 0x4f, 0x02), (0x1f, 0x4f, 0x02), (0x29, 0x4f, 0x02), (0x38, 0x4f, 0x03), + (0x03, 0x50, 0x02), (0x06, 0x50, 0x02), (0x0a, 0x50, 0x02), (0x0f, 0x50, 0x02), + (0x18, 0x50, 0x02), (0x1f, 0x50, 0x02), (0x29, 0x50, 0x02), (0x38, 0x50, 0x03), + ], + [ // 0x35 + (0x02, 0x51, 0x02), (0x09, 0x51, 0x02), (0x17, 0x51, 0x02), (0x28, 0x51, 0x03), + (0x02, 0x52, 0x02), (0x09, 0x52, 0x02), (0x17, 0x52, 0x02), (0x28, 0x52, 0x03), + (0x02, 0x53, 0x02), (0x09, 0x53, 0x02), (0x17, 0x53, 0x02), (0x28, 0x53, 0x03), + (0x02, 0x54, 0x02), (0x09, 0x54, 0x02), (0x17, 0x54, 0x02), (0x28, 0x54, 0x03), + ], + [ // 0x36 + (0x03, 0x51, 0x02), (0x06, 0x51, 0x02), (0x0a, 0x51, 0x02), (0x0f, 0x51, 0x02), + (0x18, 0x51, 0x02), (0x1f, 0x51, 0x02), (0x29, 0x51, 0x02), (0x38, 0x51, 0x03), + (0x03, 0x52, 0x02), (0x06, 0x52, 0x02), (0x0a, 0x52, 0x02), (0x0f, 0x52, 0x02), + (0x18, 0x52, 0x02), (0x1f, 0x52, 0x02), (0x29, 0x52, 0x02), (0x38, 0x52, 0x03), + ], + [ // 0x37 + (0x03, 0x53, 0x02), (0x06, 0x53, 0x02), (0x0a, 0x53, 0x02), (0x0f, 0x53, 0x02), + (0x18, 0x53, 0x02), (0x1f, 0x53, 0x02), (0x29, 0x53, 0x02), (0x38, 0x53, 0x03), + (0x03, 0x54, 0x02), (0x06, 0x54, 0x02), (0x0a, 0x54, 0x02), (0x0f, 0x54, 0x02), + (0x18, 0x54, 0x02), (0x1f, 0x54, 0x02), (0x29, 0x54, 0x02), (0x38, 0x54, 0x03), + ], + [ // 0x38 + (0x00, 0x55, 0x02), (0x00, 0x56, 0x02), (0x00, 0x57, 0x02), (0x00, 0x59, 0x02), + (0x00, 0x6a, 0x02), (0x00, 0x6b, 0x02), (0x00, 0x71, 0x02), (0x00, 0x76, 0x02), + (0x00, 0x77, 0x02), (0x00, 0x78, 0x02), (0x00, 0x79, 0x02), (0x00, 0x7a, 0x02), + (0x46, 0x00, 0x00), (0x47, 0x00, 0x00), (0x49, 0x00, 0x00), (0x4a, 0x00, 0x01), + ], + [ // 0x39 + (0x01, 0x55, 0x02), (0x16, 0x55, 0x03), (0x01, 0x56, 0x02), (0x16, 0x56, 0x03), + (0x01, 0x57, 0x02), (0x16, 0x57, 0x03), (0x01, 0x59, 0x02), (0x16, 0x59, 0x03), + (0x01, 0x6a, 0x02), (0x16, 0x6a, 0x03), (0x01, 0x6b, 0x02), (0x16, 0x6b, 0x03), + (0x01, 0x71, 0x02), (0x16, 0x71, 0x03), (0x01, 0x76, 0x02), (0x16, 0x76, 0x03), + ], + [ // 0x3a + (0x02, 0x55, 0x02), (0x09, 0x55, 0x02), (0x17, 0x55, 0x02), (0x28, 0x55, 0x03), + (0x02, 0x56, 0x02), (0x09, 0x56, 0x02), (0x17, 0x56, 0x02), (0x28, 0x56, 0x03), + (0x02, 0x57, 0x02), (0x09, 0x57, 0x02), (0x17, 0x57, 0x02), (0x28, 0x57, 0x03), + (0x02, 0x59, 0x02), (0x09, 0x59, 0x02), (0x17, 0x59, 0x02), (0x28, 0x59, 0x03), + ], + [ // 0x3b + (0x03, 0x55, 0x02), (0x06, 0x55, 0x02), (0x0a, 0x55, 0x02), (0x0f, 0x55, 0x02), + (0x18, 0x55, 0x02), (0x1f, 0x55, 0x02), (0x29, 0x55, 0x02), (0x38, 0x55, 0x03), + (0x03, 0x56, 0x02), (0x06, 0x56, 0x02), (0x0a, 0x56, 0x02), (0x0f, 0x56, 0x02), + (0x18, 0x56, 0x02), (0x1f, 0x56, 0x02), (0x29, 0x56, 0x02), (0x38, 0x56, 0x03), + ], + [ // 0x3c + (0x03, 0x57, 0x02), (0x06, 0x57, 0x02), (0x0a, 0x57, 0x02), (0x0f, 0x57, 0x02), + (0x18, 0x57, 0x02), (0x1f, 0x57, 0x02), (0x29, 0x57, 0x02), (0x38, 0x57, 0x03), + (0x03, 0x59, 0x02), (0x06, 0x59, 0x02), (0x0a, 0x59, 0x02), (0x0f, 0x59, 0x02), + (0x18, 0x59, 0x02), (0x1f, 0x59, 0x02), (0x29, 0x59, 0x02), (0x38, 0x59, 0x03), + ], + [ // 0x3d + (0x02, 0x6a, 0x02), (0x09, 0x6a, 0x02), (0x17, 0x6a, 0x02), (0x28, 0x6a, 0x03), + (0x02, 0x6b, 0x02), (0x09, 0x6b, 0x02), (0x17, 0x6b, 0x02), (0x28, 0x6b, 0x03), + (0x02, 0x71, 0x02), (0x09, 0x71, 0x02), (0x17, 0x71, 0x02), (0x28, 0x71, 0x03), + (0x02, 0x76, 0x02), (0x09, 0x76, 0x02), (0x17, 0x76, 0x02), (0x28, 0x76, 0x03), + ], + [ // 0x3e + (0x03, 0x6a, 0x02), (0x06, 0x6a, 0x02), (0x0a, 0x6a, 0x02), (0x0f, 0x6a, 0x02), + (0x18, 0x6a, 0x02), (0x1f, 0x6a, 0x02), (0x29, 0x6a, 0x02), (0x38, 0x6a, 0x03), + (0x03, 0x6b, 0x02), (0x06, 0x6b, 0x02), (0x0a, 0x6b, 0x02), (0x0f, 0x6b, 0x02), + (0x18, 0x6b, 0x02), (0x1f, 0x6b, 0x02), (0x29, 0x6b, 0x02), (0x38, 0x6b, 0x03), + ], + [ // 0x3f + (0x03, 0x71, 0x02), (0x06, 0x71, 0x02), (0x0a, 0x71, 0x02), (0x0f, 0x71, 0x02), + (0x18, 0x71, 0x02), (0x1f, 0x71, 0x02), (0x29, 0x71, 0x02), (0x38, 0x71, 0x03), + (0x03, 0x76, 0x02), (0x06, 0x76, 0x02), (0x0a, 0x76, 0x02), (0x0f, 0x76, 0x02), + (0x18, 0x76, 0x02), (0x1f, 0x76, 0x02), (0x29, 0x76, 0x02), (0x38, 0x76, 0x03), + ], + [ // 0x40 + (0x01, 0x77, 0x02), (0x16, 0x77, 0x03), (0x01, 0x78, 0x02), (0x16, 0x78, 0x03), + (0x01, 0x79, 0x02), (0x16, 0x79, 0x03), (0x01, 0x7a, 0x02), (0x16, 0x7a, 0x03), + (0x00, 0x26, 0x02), (0x00, 0x2a, 0x02), (0x00, 0x2c, 0x02), (0x00, 0x3b, 0x02), + (0x00, 0x58, 0x02), (0x00, 0x5a, 0x02), (0x4b, 0x00, 0x00), (0x4e, 0x00, 0x00), + ], + [ // 0x41 + (0x02, 0x77, 0x02), (0x09, 0x77, 0x02), (0x17, 0x77, 0x02), (0x28, 0x77, 0x03), + (0x02, 0x78, 0x02), (0x09, 0x78, 0x02), (0x17, 0x78, 0x02), (0x28, 0x78, 0x03), + (0x02, 0x79, 0x02), (0x09, 0x79, 0x02), (0x17, 0x79, 0x02), (0x28, 0x79, 0x03), + (0x02, 0x7a, 0x02), (0x09, 0x7a, 0x02), (0x17, 0x7a, 0x02), (0x28, 0x7a, 0x03), + ], + [ // 0x42 + (0x03, 0x77, 0x02), (0x06, 0x77, 0x02), (0x0a, 0x77, 0x02), (0x0f, 0x77, 0x02), + (0x18, 0x77, 0x02), (0x1f, 0x77, 0x02), (0x29, 0x77, 0x02), (0x38, 0x77, 0x03), + (0x03, 0x78, 0x02), (0x06, 0x78, 0x02), (0x0a, 0x78, 0x02), (0x0f, 0x78, 0x02), + (0x18, 0x78, 0x02), (0x1f, 0x78, 0x02), (0x29, 0x78, 0x02), (0x38, 0x78, 0x03), + ], + [ // 0x43 + (0x03, 0x79, 0x02), (0x06, 0x79, 0x02), (0x0a, 0x79, 0x02), (0x0f, 0x79, 0x02), + (0x18, 0x79, 0x02), (0x1f, 0x79, 0x02), (0x29, 0x79, 0x02), (0x38, 0x79, 0x03), + (0x03, 0x7a, 0x02), (0x06, 0x7a, 0x02), (0x0a, 0x7a, 0x02), (0x0f, 0x7a, 0x02), + (0x18, 0x7a, 0x02), (0x1f, 0x7a, 0x02), (0x29, 0x7a, 0x02), (0x38, 0x7a, 0x03), + ], + [ // 0x44 + (0x01, 0x26, 0x02), (0x16, 0x26, 0x03), (0x01, 0x2a, 0x02), (0x16, 0x2a, 0x03), + (0x01, 0x2c, 0x02), (0x16, 0x2c, 0x03), (0x01, 0x3b, 0x02), (0x16, 0x3b, 0x03), + (0x01, 0x58, 0x02), (0x16, 0x58, 0x03), (0x01, 0x5a, 0x02), (0x16, 0x5a, 0x03), + (0x4c, 0x00, 0x00), (0x4d, 0x00, 0x00), (0x4f, 0x00, 0x00), (0x51, 0x00, 0x00), + ], + [ // 0x45 + (0x02, 0x26, 0x02), (0x09, 0x26, 0x02), (0x17, 0x26, 0x02), (0x28, 0x26, 0x03), + (0x02, 0x2a, 0x02), (0x09, 0x2a, 0x02), (0x17, 0x2a, 0x02), (0x28, 0x2a, 0x03), + (0x02, 0x2c, 0x02), (0x09, 0x2c, 0x02), (0x17, 0x2c, 0x02), (0x28, 0x2c, 0x03), + (0x02, 0x3b, 0x02), (0x09, 0x3b, 0x02), (0x17, 0x3b, 0x02), (0x28, 0x3b, 0x03), + ], + [ // 0x46 + (0x03, 0x26, 0x02), (0x06, 0x26, 0x02), (0x0a, 0x26, 0x02), (0x0f, 0x26, 0x02), + (0x18, 0x26, 0x02), (0x1f, 0x26, 0x02), (0x29, 0x26, 0x02), (0x38, 0x26, 0x03), + (0x03, 0x2a, 0x02), (0x06, 0x2a, 0x02), (0x0a, 0x2a, 0x02), (0x0f, 0x2a, 0x02), + (0x18, 0x2a, 0x02), (0x1f, 0x2a, 0x02), (0x29, 0x2a, 0x02), (0x38, 0x2a, 0x03), + ], + [ // 0x47 + (0x03, 0x2c, 0x02), (0x06, 0x2c, 0x02), (0x0a, 0x2c, 0x02), (0x0f, 0x2c, 0x02), + (0x18, 0x2c, 0x02), (0x1f, 0x2c, 0x02), (0x29, 0x2c, 0x02), (0x38, 0x2c, 0x03), + (0x03, 0x3b, 0x02), (0x06, 0x3b, 0x02), (0x0a, 0x3b, 0x02), (0x0f, 0x3b, 0x02), + (0x18, 0x3b, 0x02), (0x1f, 0x3b, 0x02), (0x29, 0x3b, 0x02), (0x38, 0x3b, 0x03), + ], + [ // 0x48 + (0x02, 0x58, 0x02), (0x09, 0x58, 0x02), (0x17, 0x58, 0x02), (0x28, 0x58, 0x03), + (0x02, 0x5a, 0x02), (0x09, 0x5a, 0x02), (0x17, 0x5a, 0x02), (0x28, 0x5a, 0x03), + (0x00, 0x21, 0x02), (0x00, 0x22, 0x02), (0x00, 0x28, 0x02), (0x00, 0x29, 0x02), + (0x00, 0x3f, 0x02), (0x50, 0x00, 0x00), (0x52, 0x00, 0x00), (0x54, 0x00, 0x00), + ], + [ // 0x49 + (0x03, 0x58, 0x02), (0x06, 0x58, 0x02), (0x0a, 0x58, 0x02), (0x0f, 0x58, 0x02), + (0x18, 0x58, 0x02), (0x1f, 0x58, 0x02), (0x29, 0x58, 0x02), (0x38, 0x58, 0x03), + (0x03, 0x5a, 0x02), (0x06, 0x5a, 0x02), (0x0a, 0x5a, 0x02), (0x0f, 0x5a, 0x02), + (0x18, 0x5a, 0x02), (0x1f, 0x5a, 0x02), (0x29, 0x5a, 0x02), (0x38, 0x5a, 0x03), + ], + [ // 0x4a + (0x01, 0x21, 0x02), (0x16, 0x21, 0x03), (0x01, 0x22, 0x02), (0x16, 0x22, 0x03), + (0x01, 0x28, 0x02), (0x16, 0x28, 0x03), (0x01, 0x29, 0x02), (0x16, 0x29, 0x03), + (0x01, 0x3f, 0x02), (0x16, 0x3f, 0x03), (0x00, 0x27, 0x02), (0x00, 0x2b, 0x02), + (0x00, 0x7c, 0x02), (0x53, 0x00, 0x00), (0x55, 0x00, 0x00), (0x58, 0x00, 0x00), + ], + [ // 0x4b + (0x02, 0x21, 0x02), (0x09, 0x21, 0x02), (0x17, 0x21, 0x02), (0x28, 0x21, 0x03), + (0x02, 0x22, 0x02), (0x09, 0x22, 0x02), (0x17, 0x22, 0x02), (0x28, 0x22, 0x03), + (0x02, 0x28, 0x02), (0x09, 0x28, 0x02), (0x17, 0x28, 0x02), (0x28, 0x28, 0x03), + (0x02, 0x29, 0x02), (0x09, 0x29, 0x02), (0x17, 0x29, 0x02), (0x28, 0x29, 0x03), + ], + [ // 0x4c + (0x03, 0x21, 0x02), (0x06, 0x21, 0x02), (0x0a, 0x21, 0x02), (0x0f, 0x21, 0x02), + (0x18, 0x21, 0x02), (0x1f, 0x21, 0x02), (0x29, 0x21, 0x02), (0x38, 0x21, 0x03), + (0x03, 0x22, 0x02), (0x06, 0x22, 0x02), (0x0a, 0x22, 0x02), (0x0f, 0x22, 0x02), + (0x18, 0x22, 0x02), (0x1f, 0x22, 0x02), (0x29, 0x22, 0x02), (0x38, 0x22, 0x03), + ], + [ // 0x4d + (0x03, 0x28, 0x02), (0x06, 0x28, 0x02), (0x0a, 0x28, 0x02), (0x0f, 0x28, 0x02), + (0x18, 0x28, 0x02), (0x1f, 0x28, 0x02), (0x29, 0x28, 0x02), (0x38, 0x28, 0x03), + (0x03, 0x29, 0x02), (0x06, 0x29, 0x02), (0x0a, 0x29, 0x02), (0x0f, 0x29, 0x02), + (0x18, 0x29, 0x02), (0x1f, 0x29, 0x02), (0x29, 0x29, 0x02), (0x38, 0x29, 0x03), + ], + [ // 0x4e + (0x02, 0x3f, 0x02), (0x09, 0x3f, 0x02), (0x17, 0x3f, 0x02), (0x28, 0x3f, 0x03), + (0x01, 0x27, 0x02), (0x16, 0x27, 0x03), (0x01, 0x2b, 0x02), (0x16, 0x2b, 0x03), + (0x01, 0x7c, 0x02), (0x16, 0x7c, 0x03), (0x00, 0x23, 0x02), (0x00, 0x3e, 0x02), + (0x56, 0x00, 0x00), (0x57, 0x00, 0x00), (0x59, 0x00, 0x00), (0x5a, 0x00, 0x00), + ], + [ // 0x4f + (0x03, 0x3f, 0x02), (0x06, 0x3f, 0x02), (0x0a, 0x3f, 0x02), (0x0f, 0x3f, 0x02), + (0x18, 0x3f, 0x02), (0x1f, 0x3f, 0x02), (0x29, 0x3f, 0x02), (0x38, 0x3f, 0x03), + (0x02, 0x27, 0x02), (0x09, 0x27, 0x02), (0x17, 0x27, 0x02), (0x28, 0x27, 0x03), + (0x02, 0x2b, 0x02), (0x09, 0x2b, 0x02), (0x17, 0x2b, 0x02), (0x28, 0x2b, 0x03), + ], + [ // 0x50 + (0x03, 0x27, 0x02), (0x06, 0x27, 0x02), (0x0a, 0x27, 0x02), (0x0f, 0x27, 0x02), + (0x18, 0x27, 0x02), (0x1f, 0x27, 0x02), (0x29, 0x27, 0x02), (0x38, 0x27, 0x03), + (0x03, 0x2b, 0x02), (0x06, 0x2b, 0x02), (0x0a, 0x2b, 0x02), (0x0f, 0x2b, 0x02), + (0x18, 0x2b, 0x02), (0x1f, 0x2b, 0x02), (0x29, 0x2b, 0x02), (0x38, 0x2b, 0x03), + ], + [ // 0x51 + (0x02, 0x7c, 0x02), (0x09, 0x7c, 0x02), (0x17, 0x7c, 0x02), (0x28, 0x7c, 0x03), + (0x01, 0x23, 0x02), (0x16, 0x23, 0x03), (0x01, 0x3e, 0x02), (0x16, 0x3e, 0x03), + (0x00, 0x00, 0x02), (0x00, 0x24, 0x02), (0x00, 0x40, 0x02), (0x00, 0x5b, 0x02), + (0x00, 0x5d, 0x02), (0x00, 0x7e, 0x02), (0x5b, 0x00, 0x00), (0x5c, 0x00, 0x00), + ], + [ // 0x52 + (0x03, 0x7c, 0x02), (0x06, 0x7c, 0x02), (0x0a, 0x7c, 0x02), (0x0f, 0x7c, 0x02), + (0x18, 0x7c, 0x02), (0x1f, 0x7c, 0x02), (0x29, 0x7c, 0x02), (0x38, 0x7c, 0x03), + (0x02, 0x23, 0x02), (0x09, 0x23, 0x02), (0x17, 0x23, 0x02), (0x28, 0x23, 0x03), + (0x02, 0x3e, 0x02), (0x09, 0x3e, 0x02), (0x17, 0x3e, 0x02), (0x28, 0x3e, 0x03), + ], + [ // 0x53 + (0x03, 0x23, 0x02), (0x06, 0x23, 0x02), (0x0a, 0x23, 0x02), (0x0f, 0x23, 0x02), + (0x18, 0x23, 0x02), (0x1f, 0x23, 0x02), (0x29, 0x23, 0x02), (0x38, 0x23, 0x03), + (0x03, 0x3e, 0x02), (0x06, 0x3e, 0x02), (0x0a, 0x3e, 0x02), (0x0f, 0x3e, 0x02), + (0x18, 0x3e, 0x02), (0x1f, 0x3e, 0x02), (0x29, 0x3e, 0x02), (0x38, 0x3e, 0x03), + ], + [ // 0x54 + (0x01, 0x00, 0x02), (0x16, 0x00, 0x03), (0x01, 0x24, 0x02), (0x16, 0x24, 0x03), + (0x01, 0x40, 0x02), (0x16, 0x40, 0x03), (0x01, 0x5b, 0x02), (0x16, 0x5b, 0x03), + (0x01, 0x5d, 0x02), (0x16, 0x5d, 0x03), (0x01, 0x7e, 0x02), (0x16, 0x7e, 0x03), + (0x00, 0x5e, 0x02), (0x00, 0x7d, 0x02), (0x5d, 0x00, 0x00), (0x5e, 0x00, 0x00), + ], + [ // 0x55 + (0x02, 0x00, 0x02), (0x09, 0x00, 0x02), (0x17, 0x00, 0x02), (0x28, 0x00, 0x03), + (0x02, 0x24, 0x02), (0x09, 0x24, 0x02), (0x17, 0x24, 0x02), (0x28, 0x24, 0x03), + (0x02, 0x40, 0x02), (0x09, 0x40, 0x02), (0x17, 0x40, 0x02), (0x28, 0x40, 0x03), + (0x02, 0x5b, 0x02), (0x09, 0x5b, 0x02), (0x17, 0x5b, 0x02), (0x28, 0x5b, 0x03), + ], + [ // 0x56 + (0x03, 0x00, 0x02), (0x06, 0x00, 0x02), (0x0a, 0x00, 0x02), (0x0f, 0x00, 0x02), + (0x18, 0x00, 0x02), (0x1f, 0x00, 0x02), (0x29, 0x00, 0x02), (0x38, 0x00, 0x03), + (0x03, 0x24, 0x02), (0x06, 0x24, 0x02), (0x0a, 0x24, 0x02), (0x0f, 0x24, 0x02), + (0x18, 0x24, 0x02), (0x1f, 0x24, 0x02), (0x29, 0x24, 0x02), (0x38, 0x24, 0x03), + ], + [ // 0x57 + (0x03, 0x40, 0x02), (0x06, 0x40, 0x02), (0x0a, 0x40, 0x02), (0x0f, 0x40, 0x02), + (0x18, 0x40, 0x02), (0x1f, 0x40, 0x02), (0x29, 0x40, 0x02), (0x38, 0x40, 0x03), + (0x03, 0x5b, 0x02), (0x06, 0x5b, 0x02), (0x0a, 0x5b, 0x02), (0x0f, 0x5b, 0x02), + (0x18, 0x5b, 0x02), (0x1f, 0x5b, 0x02), (0x29, 0x5b, 0x02), (0x38, 0x5b, 0x03), + ], + [ // 0x58 + (0x02, 0x5d, 0x02), (0x09, 0x5d, 0x02), (0x17, 0x5d, 0x02), (0x28, 0x5d, 0x03), + (0x02, 0x7e, 0x02), (0x09, 0x7e, 0x02), (0x17, 0x7e, 0x02), (0x28, 0x7e, 0x03), + (0x01, 0x5e, 0x02), (0x16, 0x5e, 0x03), (0x01, 0x7d, 0x02), (0x16, 0x7d, 0x03), + (0x00, 0x3c, 0x02), (0x00, 0x60, 0x02), (0x00, 0x7b, 0x02), (0x5f, 0x00, 0x00), + ], + [ // 0x59 + (0x03, 0x5d, 0x02), (0x06, 0x5d, 0x02), (0x0a, 0x5d, 0x02), (0x0f, 0x5d, 0x02), + (0x18, 0x5d, 0x02), (0x1f, 0x5d, 0x02), (0x29, 0x5d, 0x02), (0x38, 0x5d, 0x03), + (0x03, 0x7e, 0x02), (0x06, 0x7e, 0x02), (0x0a, 0x7e, 0x02), (0x0f, 0x7e, 0x02), + (0x18, 0x7e, 0x02), (0x1f, 0x7e, 0x02), (0x29, 0x7e, 0x02), (0x38, 0x7e, 0x03), + ], + [ // 0x5a + (0x02, 0x5e, 0x02), (0x09, 0x5e, 0x02), (0x17, 0x5e, 0x02), (0x28, 0x5e, 0x03), + (0x02, 0x7d, 0x02), (0x09, 0x7d, 0x02), (0x17, 0x7d, 0x02), (0x28, 0x7d, 0x03), + (0x01, 0x3c, 0x02), (0x16, 0x3c, 0x03), (0x01, 0x60, 0x02), (0x16, 0x60, 0x03), + (0x01, 0x7b, 0x02), (0x16, 0x7b, 0x03), (0x60, 0x00, 0x00), (0x6e, 0x00, 0x00), + ], + [ // 0x5b + (0x03, 0x5e, 0x02), (0x06, 0x5e, 0x02), (0x0a, 0x5e, 0x02), (0x0f, 0x5e, 0x02), + (0x18, 0x5e, 0x02), (0x1f, 0x5e, 0x02), (0x29, 0x5e, 0x02), (0x38, 0x5e, 0x03), + (0x03, 0x7d, 0x02), (0x06, 0x7d, 0x02), (0x0a, 0x7d, 0x02), (0x0f, 0x7d, 0x02), + (0x18, 0x7d, 0x02), (0x1f, 0x7d, 0x02), (0x29, 0x7d, 0x02), (0x38, 0x7d, 0x03), + ], + [ // 0x5c + (0x02, 0x3c, 0x02), (0x09, 0x3c, 0x02), (0x17, 0x3c, 0x02), (0x28, 0x3c, 0x03), + (0x02, 0x60, 0x02), (0x09, 0x60, 0x02), (0x17, 0x60, 0x02), (0x28, 0x60, 0x03), + (0x02, 0x7b, 0x02), (0x09, 0x7b, 0x02), (0x17, 0x7b, 0x02), (0x28, 0x7b, 0x03), + (0x61, 0x00, 0x00), (0x65, 0x00, 0x00), (0x6f, 0x00, 0x00), (0x85, 0x00, 0x00), + ], + [ // 0x5d + (0x03, 0x3c, 0x02), (0x06, 0x3c, 0x02), (0x0a, 0x3c, 0x02), (0x0f, 0x3c, 0x02), + (0x18, 0x3c, 0x02), (0x1f, 0x3c, 0x02), (0x29, 0x3c, 0x02), (0x38, 0x3c, 0x03), + (0x03, 0x60, 0x02), (0x06, 0x60, 0x02), (0x0a, 0x60, 0x02), (0x0f, 0x60, 0x02), + (0x18, 0x60, 0x02), (0x1f, 0x60, 0x02), (0x29, 0x60, 0x02), (0x38, 0x60, 0x03), + ], + [ // 0x5e + (0x03, 0x7b, 0x02), (0x06, 0x7b, 0x02), (0x0a, 0x7b, 0x02), (0x0f, 0x7b, 0x02), + (0x18, 0x7b, 0x02), (0x1f, 0x7b, 0x02), (0x29, 0x7b, 0x02), (0x38, 0x7b, 0x03), + (0x62, 0x00, 0x00), (0x63, 0x00, 0x00), (0x66, 0x00, 0x00), (0x69, 0x00, 0x00), + (0x70, 0x00, 0x00), (0x77, 0x00, 0x00), (0x86, 0x00, 0x00), (0x99, 0x00, 0x00), + ], + [ // 0x5f + (0x00, 0x5c, 0x02), (0x00, 0xc3, 0x02), (0x00, 0xd0, 0x02), (0x64, 0x00, 0x00), + (0x67, 0x00, 0x00), (0x68, 0x00, 0x00), (0x6a, 0x00, 0x00), (0x6b, 0x00, 0x00), + (0x71, 0x00, 0x00), (0x74, 0x00, 0x00), (0x78, 0x00, 0x00), (0x7e, 0x00, 0x00), + (0x87, 0x00, 0x00), (0x8e, 0x00, 0x00), (0x9a, 0x00, 0x00), (0xa9, 0x00, 0x00), + ], + [ // 0x60 + (0x01, 0x5c, 0x02), (0x16, 0x5c, 0x03), (0x01, 0xc3, 0x02), (0x16, 0xc3, 0x03), + (0x01, 0xd0, 0x02), (0x16, 0xd0, 0x03), (0x00, 0x80, 0x02), (0x00, 0x82, 0x02), + (0x00, 0x83, 0x02), (0x00, 0xa2, 0x02), (0x00, 0xb8, 0x02), (0x00, 0xc2, 0x02), + (0x00, 0xe0, 0x02), (0x00, 0xe2, 0x02), (0x6c, 0x00, 0x00), (0x6d, 0x00, 0x00), + ], + [ // 0x61 + (0x02, 0x5c, 0x02), (0x09, 0x5c, 0x02), (0x17, 0x5c, 0x02), (0x28, 0x5c, 0x03), + (0x02, 0xc3, 0x02), (0x09, 0xc3, 0x02), (0x17, 0xc3, 0x02), (0x28, 0xc3, 0x03), + (0x02, 0xd0, 0x02), (0x09, 0xd0, 0x02), (0x17, 0xd0, 0x02), (0x28, 0xd0, 0x03), + (0x01, 0x80, 0x02), (0x16, 0x80, 0x03), (0x01, 0x82, 0x02), (0x16, 0x82, 0x03), + ], + [ // 0x62 + (0x03, 0x5c, 0x02), (0x06, 0x5c, 0x02), (0x0a, 0x5c, 0x02), (0x0f, 0x5c, 0x02), + (0x18, 0x5c, 0x02), (0x1f, 0x5c, 0x02), (0x29, 0x5c, 0x02), (0x38, 0x5c, 0x03), + (0x03, 0xc3, 0x02), (0x06, 0xc3, 0x02), (0x0a, 0xc3, 0x02), (0x0f, 0xc3, 0x02), + (0x18, 0xc3, 0x02), (0x1f, 0xc3, 0x02), (0x29, 0xc3, 0x02), (0x38, 0xc3, 0x03), + ], + [ // 0x63 + (0x03, 0xd0, 0x02), (0x06, 0xd0, 0x02), (0x0a, 0xd0, 0x02), (0x0f, 0xd0, 0x02), + (0x18, 0xd0, 0x02), (0x1f, 0xd0, 0x02), (0x29, 0xd0, 0x02), (0x38, 0xd0, 0x03), + (0x02, 0x80, 0x02), (0x09, 0x80, 0x02), (0x17, 0x80, 0x02), (0x28, 0x80, 0x03), + (0x02, 0x82, 0x02), (0x09, 0x82, 0x02), (0x17, 0x82, 0x02), (0x28, 0x82, 0x03), + ], + [ // 0x64 + (0x03, 0x80, 0x02), (0x06, 0x80, 0x02), (0x0a, 0x80, 0x02), (0x0f, 0x80, 0x02), + (0x18, 0x80, 0x02), (0x1f, 0x80, 0x02), (0x29, 0x80, 0x02), (0x38, 0x80, 0x03), + (0x03, 0x82, 0x02), (0x06, 0x82, 0x02), (0x0a, 0x82, 0x02), (0x0f, 0x82, 0x02), + (0x18, 0x82, 0x02), (0x1f, 0x82, 0x02), (0x29, 0x82, 0x02), (0x38, 0x82, 0x03), + ], + [ // 0x65 + (0x01, 0x83, 0x02), (0x16, 0x83, 0x03), (0x01, 0xa2, 0x02), (0x16, 0xa2, 0x03), + (0x01, 0xb8, 0x02), (0x16, 0xb8, 0x03), (0x01, 0xc2, 0x02), (0x16, 0xc2, 0x03), + (0x01, 0xe0, 0x02), (0x16, 0xe0, 0x03), (0x01, 0xe2, 0x02), (0x16, 0xe2, 0x03), + (0x00, 0x99, 0x02), (0x00, 0xa1, 0x02), (0x00, 0xa7, 0x02), (0x00, 0xac, 0x02), + ], + [ // 0x66 + (0x02, 0x83, 0x02), (0x09, 0x83, 0x02), (0x17, 0x83, 0x02), (0x28, 0x83, 0x03), + (0x02, 0xa2, 0x02), (0x09, 0xa2, 0x02), (0x17, 0xa2, 0x02), (0x28, 0xa2, 0x03), + (0x02, 0xb8, 0x02), (0x09, 0xb8, 0x02), (0x17, 0xb8, 0x02), (0x28, 0xb8, 0x03), + (0x02, 0xc2, 0x02), (0x09, 0xc2, 0x02), (0x17, 0xc2, 0x02), (0x28, 0xc2, 0x03), + ], + [ // 0x67 + (0x03, 0x83, 0x02), (0x06, 0x83, 0x02), (0x0a, 0x83, 0x02), (0x0f, 0x83, 0x02), + (0x18, 0x83, 0x02), (0x1f, 0x83, 0x02), (0x29, 0x83, 0x02), (0x38, 0x83, 0x03), + (0x03, 0xa2, 0x02), (0x06, 0xa2, 0x02), (0x0a, 0xa2, 0x02), (0x0f, 0xa2, 0x02), + (0x18, 0xa2, 0x02), (0x1f, 0xa2, 0x02), (0x29, 0xa2, 0x02), (0x38, 0xa2, 0x03), + ], + [ // 0x68 + (0x03, 0xb8, 0x02), (0x06, 0xb8, 0x02), (0x0a, 0xb8, 0x02), (0x0f, 0xb8, 0x02), + (0x18, 0xb8, 0x02), (0x1f, 0xb8, 0x02), (0x29, 0xb8, 0x02), (0x38, 0xb8, 0x03), + (0x03, 0xc2, 0x02), (0x06, 0xc2, 0x02), (0x0a, 0xc2, 0x02), (0x0f, 0xc2, 0x02), + (0x18, 0xc2, 0x02), (0x1f, 0xc2, 0x02), (0x29, 0xc2, 0x02), (0x38, 0xc2, 0x03), + ], + [ // 0x69 + (0x02, 0xe0, 0x02), (0x09, 0xe0, 0x02), (0x17, 0xe0, 0x02), (0x28, 0xe0, 0x03), + (0x02, 0xe2, 0x02), (0x09, 0xe2, 0x02), (0x17, 0xe2, 0x02), (0x28, 0xe2, 0x03), + (0x01, 0x99, 0x02), (0x16, 0x99, 0x03), (0x01, 0xa1, 0x02), (0x16, 0xa1, 0x03), + (0x01, 0xa7, 0x02), (0x16, 0xa7, 0x03), (0x01, 0xac, 0x02), (0x16, 0xac, 0x03), + ], + [ // 0x6a + (0x03, 0xe0, 0x02), (0x06, 0xe0, 0x02), (0x0a, 0xe0, 0x02), (0x0f, 0xe0, 0x02), + (0x18, 0xe0, 0x02), (0x1f, 0xe0, 0x02), (0x29, 0xe0, 0x02), (0x38, 0xe0, 0x03), + (0x03, 0xe2, 0x02), (0x06, 0xe2, 0x02), (0x0a, 0xe2, 0x02), (0x0f, 0xe2, 0x02), + (0x18, 0xe2, 0x02), (0x1f, 0xe2, 0x02), (0x29, 0xe2, 0x02), (0x38, 0xe2, 0x03), + ], + [ // 0x6b + (0x02, 0x99, 0x02), (0x09, 0x99, 0x02), (0x17, 0x99, 0x02), (0x28, 0x99, 0x03), + (0x02, 0xa1, 0x02), (0x09, 0xa1, 0x02), (0x17, 0xa1, 0x02), (0x28, 0xa1, 0x03), + (0x02, 0xa7, 0x02), (0x09, 0xa7, 0x02), (0x17, 0xa7, 0x02), (0x28, 0xa7, 0x03), + (0x02, 0xac, 0x02), (0x09, 0xac, 0x02), (0x17, 0xac, 0x02), (0x28, 0xac, 0x03), + ], + [ // 0x6c + (0x03, 0x99, 0x02), (0x06, 0x99, 0x02), (0x0a, 0x99, 0x02), (0x0f, 0x99, 0x02), + (0x18, 0x99, 0x02), (0x1f, 0x99, 0x02), (0x29, 0x99, 0x02), (0x38, 0x99, 0x03), + (0x03, 0xa1, 0x02), (0x06, 0xa1, 0x02), (0x0a, 0xa1, 0x02), (0x0f, 0xa1, 0x02), + (0x18, 0xa1, 0x02), (0x1f, 0xa1, 0x02), (0x29, 0xa1, 0x02), (0x38, 0xa1, 0x03), + ], + [ // 0x6d + (0x03, 0xa7, 0x02), (0x06, 0xa7, 0x02), (0x0a, 0xa7, 0x02), (0x0f, 0xa7, 0x02), + (0x18, 0xa7, 0x02), (0x1f, 0xa7, 0x02), (0x29, 0xa7, 0x02), (0x38, 0xa7, 0x03), + (0x03, 0xac, 0x02), (0x06, 0xac, 0x02), (0x0a, 0xac, 0x02), (0x0f, 0xac, 0x02), + (0x18, 0xac, 0x02), (0x1f, 0xac, 0x02), (0x29, 0xac, 0x02), (0x38, 0xac, 0x03), + ], + [ // 0x6e + (0x72, 0x00, 0x00), (0x73, 0x00, 0x00), (0x75, 0x00, 0x00), (0x76, 0x00, 0x00), + (0x79, 0x00, 0x00), (0x7b, 0x00, 0x00), (0x7f, 0x00, 0x00), (0x82, 0x00, 0x00), + (0x88, 0x00, 0x00), (0x8b, 0x00, 0x00), (0x8f, 0x00, 0x00), (0x92, 0x00, 0x00), + (0x9b, 0x00, 0x00), (0xa2, 0x00, 0x00), (0xaa, 0x00, 0x00), (0xb4, 0x00, 0x00), + ], + [ // 0x6f + (0x00, 0xb0, 0x02), (0x00, 0xb1, 0x02), (0x00, 0xb3, 0x02), (0x00, 0xd1, 0x02), + (0x00, 0xd8, 0x02), (0x00, 0xd9, 0x02), (0x00, 0xe3, 0x02), (0x00, 0xe5, 0x02), + (0x00, 0xe6, 0x02), (0x7a, 0x00, 0x00), (0x7c, 0x00, 0x00), (0x7d, 0x00, 0x00), + (0x80, 0x00, 0x00), (0x81, 0x00, 0x00), (0x83, 0x00, 0x00), (0x84, 0x00, 0x00), + ], + [ // 0x70 + (0x01, 0xb0, 0x02), (0x16, 0xb0, 0x03), (0x01, 0xb1, 0x02), (0x16, 0xb1, 0x03), + (0x01, 0xb3, 0x02), (0x16, 0xb3, 0x03), (0x01, 0xd1, 0x02), (0x16, 0xd1, 0x03), + (0x01, 0xd8, 0x02), (0x16, 0xd8, 0x03), (0x01, 0xd9, 0x02), (0x16, 0xd9, 0x03), + (0x01, 0xe3, 0x02), (0x16, 0xe3, 0x03), (0x01, 0xe5, 0x02), (0x16, 0xe5, 0x03), + ], + [ // 0x71 + (0x02, 0xb0, 0x02), (0x09, 0xb0, 0x02), (0x17, 0xb0, 0x02), (0x28, 0xb0, 0x03), + (0x02, 0xb1, 0x02), (0x09, 0xb1, 0x02), (0x17, 0xb1, 0x02), (0x28, 0xb1, 0x03), + (0x02, 0xb3, 0x02), (0x09, 0xb3, 0x02), (0x17, 0xb3, 0x02), (0x28, 0xb3, 0x03), + (0x02, 0xd1, 0x02), (0x09, 0xd1, 0x02), (0x17, 0xd1, 0x02), (0x28, 0xd1, 0x03), + ], + [ // 0x72 + (0x03, 0xb0, 0x02), (0x06, 0xb0, 0x02), (0x0a, 0xb0, 0x02), (0x0f, 0xb0, 0x02), + (0x18, 0xb0, 0x02), (0x1f, 0xb0, 0x02), (0x29, 0xb0, 0x02), (0x38, 0xb0, 0x03), + (0x03, 0xb1, 0x02), (0x06, 0xb1, 0x02), (0x0a, 0xb1, 0x02), (0x0f, 0xb1, 0x02), + (0x18, 0xb1, 0x02), (0x1f, 0xb1, 0x02), (0x29, 0xb1, 0x02), (0x38, 0xb1, 0x03), + ], + [ // 0x73 + (0x03, 0xb3, 0x02), (0x06, 0xb3, 0x02), (0x0a, 0xb3, 0x02), (0x0f, 0xb3, 0x02), + (0x18, 0xb3, 0x02), (0x1f, 0xb3, 0x02), (0x29, 0xb3, 0x02), (0x38, 0xb3, 0x03), + (0x03, 0xd1, 0x02), (0x06, 0xd1, 0x02), (0x0a, 0xd1, 0x02), (0x0f, 0xd1, 0x02), + (0x18, 0xd1, 0x02), (0x1f, 0xd1, 0x02), (0x29, 0xd1, 0x02), (0x38, 0xd1, 0x03), + ], + [ // 0x74 + (0x02, 0xd8, 0x02), (0x09, 0xd8, 0x02), (0x17, 0xd8, 0x02), (0x28, 0xd8, 0x03), + (0x02, 0xd9, 0x02), (0x09, 0xd9, 0x02), (0x17, 0xd9, 0x02), (0x28, 0xd9, 0x03), + (0x02, 0xe3, 0x02), (0x09, 0xe3, 0x02), (0x17, 0xe3, 0x02), (0x28, 0xe3, 0x03), + (0x02, 0xe5, 0x02), (0x09, 0xe5, 0x02), (0x17, 0xe5, 0x02), (0x28, 0xe5, 0x03), + ], + [ // 0x75 + (0x03, 0xd8, 0x02), (0x06, 0xd8, 0x02), (0x0a, 0xd8, 0x02), (0x0f, 0xd8, 0x02), + (0x18, 0xd8, 0x02), (0x1f, 0xd8, 0x02), (0x29, 0xd8, 0x02), (0x38, 0xd8, 0x03), + (0x03, 0xd9, 0x02), (0x06, 0xd9, 0x02), (0x0a, 0xd9, 0x02), (0x0f, 0xd9, 0x02), + (0x18, 0xd9, 0x02), (0x1f, 0xd9, 0x02), (0x29, 0xd9, 0x02), (0x38, 0xd9, 0x03), + ], + [ // 0x76 + (0x03, 0xe3, 0x02), (0x06, 0xe3, 0x02), (0x0a, 0xe3, 0x02), (0x0f, 0xe3, 0x02), + (0x18, 0xe3, 0x02), (0x1f, 0xe3, 0x02), (0x29, 0xe3, 0x02), (0x38, 0xe3, 0x03), + (0x03, 0xe5, 0x02), (0x06, 0xe5, 0x02), (0x0a, 0xe5, 0x02), (0x0f, 0xe5, 0x02), + (0x18, 0xe5, 0x02), (0x1f, 0xe5, 0x02), (0x29, 0xe5, 0x02), (0x38, 0xe5, 0x03), + ], + [ // 0x77 + (0x01, 0xe6, 0x02), (0x16, 0xe6, 0x03), (0x00, 0x81, 0x02), (0x00, 0x84, 0x02), + (0x00, 0x85, 0x02), (0x00, 0x86, 0x02), (0x00, 0x88, 0x02), (0x00, 0x92, 0x02), + (0x00, 0x9a, 0x02), (0x00, 0x9c, 0x02), (0x00, 0xa0, 0x02), (0x00, 0xa3, 0x02), + (0x00, 0xa4, 0x02), (0x00, 0xa9, 0x02), (0x00, 0xaa, 0x02), (0x00, 0xad, 0x02), + ], + [ // 0x78 + (0x02, 0xe6, 0x02), (0x09, 0xe6, 0x02), (0x17, 0xe6, 0x02), (0x28, 0xe6, 0x03), + (0x01, 0x81, 0x02), (0x16, 0x81, 0x03), (0x01, 0x84, 0x02), (0x16, 0x84, 0x03), + (0x01, 0x85, 0x02), (0x16, 0x85, 0x03), (0x01, 0x86, 0x02), (0x16, 0x86, 0x03), + (0x01, 0x88, 0x02), (0x16, 0x88, 0x03), (0x01, 0x92, 0x02), (0x16, 0x92, 0x03), + ], + [ // 0x79 + (0x03, 0xe6, 0x02), (0x06, 0xe6, 0x02), (0x0a, 0xe6, 0x02), (0x0f, 0xe6, 0x02), + (0x18, 0xe6, 0x02), (0x1f, 0xe6, 0x02), (0x29, 0xe6, 0x02), (0x38, 0xe6, 0x03), + (0x02, 0x81, 0x02), (0x09, 0x81, 0x02), (0x17, 0x81, 0x02), (0x28, 0x81, 0x03), + (0x02, 0x84, 0x02), (0x09, 0x84, 0x02), (0x17, 0x84, 0x02), (0x28, 0x84, 0x03), + ], + [ // 0x7a + (0x03, 0x81, 0x02), (0x06, 0x81, 0x02), (0x0a, 0x81, 0x02), (0x0f, 0x81, 0x02), + (0x18, 0x81, 0x02), (0x1f, 0x81, 0x02), (0x29, 0x81, 0x02), (0x38, 0x81, 0x03), + (0x03, 0x84, 0x02), (0x06, 0x84, 0x02), (0x0a, 0x84, 0x02), (0x0f, 0x84, 0x02), + (0x18, 0x84, 0x02), (0x1f, 0x84, 0x02), (0x29, 0x84, 0x02), (0x38, 0x84, 0x03), + ], + [ // 0x7b + (0x02, 0x85, 0x02), (0x09, 0x85, 0x02), (0x17, 0x85, 0x02), (0x28, 0x85, 0x03), + (0x02, 0x86, 0x02), (0x09, 0x86, 0x02), (0x17, 0x86, 0x02), (0x28, 0x86, 0x03), + (0x02, 0x88, 0x02), (0x09, 0x88, 0x02), (0x17, 0x88, 0x02), (0x28, 0x88, 0x03), + (0x02, 0x92, 0x02), (0x09, 0x92, 0x02), (0x17, 0x92, 0x02), (0x28, 0x92, 0x03), + ], + [ // 0x7c + (0x03, 0x85, 0x02), (0x06, 0x85, 0x02), (0x0a, 0x85, 0x02), (0x0f, 0x85, 0x02), + (0x18, 0x85, 0x02), (0x1f, 0x85, 0x02), (0x29, 0x85, 0x02), (0x38, 0x85, 0x03), + (0x03, 0x86, 0x02), (0x06, 0x86, 0x02), (0x0a, 0x86, 0x02), (0x0f, 0x86, 0x02), + (0x18, 0x86, 0x02), (0x1f, 0x86, 0x02), (0x29, 0x86, 0x02), (0x38, 0x86, 0x03), + ], + [ // 0x7d + (0x03, 0x88, 0x02), (0x06, 0x88, 0x02), (0x0a, 0x88, 0x02), (0x0f, 0x88, 0x02), + (0x18, 0x88, 0x02), (0x1f, 0x88, 0x02), (0x29, 0x88, 0x02), (0x38, 0x88, 0x03), + (0x03, 0x92, 0x02), (0x06, 0x92, 0x02), (0x0a, 0x92, 0x02), (0x0f, 0x92, 0x02), + (0x18, 0x92, 0x02), (0x1f, 0x92, 0x02), (0x29, 0x92, 0x02), (0x38, 0x92, 0x03), + ], + [ // 0x7e + (0x01, 0x9a, 0x02), (0x16, 0x9a, 0x03), (0x01, 0x9c, 0x02), (0x16, 0x9c, 0x03), + (0x01, 0xa0, 0x02), (0x16, 0xa0, 0x03), (0x01, 0xa3, 0x02), (0x16, 0xa3, 0x03), + (0x01, 0xa4, 0x02), (0x16, 0xa4, 0x03), (0x01, 0xa9, 0x02), (0x16, 0xa9, 0x03), + (0x01, 0xaa, 0x02), (0x16, 0xaa, 0x03), (0x01, 0xad, 0x02), (0x16, 0xad, 0x03), + ], + [ // 0x7f + (0x02, 0x9a, 0x02), (0x09, 0x9a, 0x02), (0x17, 0x9a, 0x02), (0x28, 0x9a, 0x03), + (0x02, 0x9c, 0x02), (0x09, 0x9c, 0x02), (0x17, 0x9c, 0x02), (0x28, 0x9c, 0x03), + (0x02, 0xa0, 0x02), (0x09, 0xa0, 0x02), (0x17, 0xa0, 0x02), (0x28, 0xa0, 0x03), + (0x02, 0xa3, 0x02), (0x09, 0xa3, 0x02), (0x17, 0xa3, 0x02), (0x28, 0xa3, 0x03), + ], + [ // 0x80 + (0x03, 0x9a, 0x02), (0x06, 0x9a, 0x02), (0x0a, 0x9a, 0x02), (0x0f, 0x9a, 0x02), + (0x18, 0x9a, 0x02), (0x1f, 0x9a, 0x02), (0x29, 0x9a, 0x02), (0x38, 0x9a, 0x03), + (0x03, 0x9c, 0x02), (0x06, 0x9c, 0x02), (0x0a, 0x9c, 0x02), (0x0f, 0x9c, 0x02), + (0x18, 0x9c, 0x02), (0x1f, 0x9c, 0x02), (0x29, 0x9c, 0x02), (0x38, 0x9c, 0x03), + ], + [ // 0x81 + (0x03, 0xa0, 0x02), (0x06, 0xa0, 0x02), (0x0a, 0xa0, 0x02), (0x0f, 0xa0, 0x02), + (0x18, 0xa0, 0x02), (0x1f, 0xa0, 0x02), (0x29, 0xa0, 0x02), (0x38, 0xa0, 0x03), + (0x03, 0xa3, 0x02), (0x06, 0xa3, 0x02), (0x0a, 0xa3, 0x02), (0x0f, 0xa3, 0x02), + (0x18, 0xa3, 0x02), (0x1f, 0xa3, 0x02), (0x29, 0xa3, 0x02), (0x38, 0xa3, 0x03), + ], + [ // 0x82 + (0x02, 0xa4, 0x02), (0x09, 0xa4, 0x02), (0x17, 0xa4, 0x02), (0x28, 0xa4, 0x03), + (0x02, 0xa9, 0x02), (0x09, 0xa9, 0x02), (0x17, 0xa9, 0x02), (0x28, 0xa9, 0x03), + (0x02, 0xaa, 0x02), (0x09, 0xaa, 0x02), (0x17, 0xaa, 0x02), (0x28, 0xaa, 0x03), + (0x02, 0xad, 0x02), (0x09, 0xad, 0x02), (0x17, 0xad, 0x02), (0x28, 0xad, 0x03), + ], + [ // 0x83 + (0x03, 0xa4, 0x02), (0x06, 0xa4, 0x02), (0x0a, 0xa4, 0x02), (0x0f, 0xa4, 0x02), + (0x18, 0xa4, 0x02), (0x1f, 0xa4, 0x02), (0x29, 0xa4, 0x02), (0x38, 0xa4, 0x03), + (0x03, 0xa9, 0x02), (0x06, 0xa9, 0x02), (0x0a, 0xa9, 0x02), (0x0f, 0xa9, 0x02), + (0x18, 0xa9, 0x02), (0x1f, 0xa9, 0x02), (0x29, 0xa9, 0x02), (0x38, 0xa9, 0x03), + ], + [ // 0x84 + (0x03, 0xaa, 0x02), (0x06, 0xaa, 0x02), (0x0a, 0xaa, 0x02), (0x0f, 0xaa, 0x02), + (0x18, 0xaa, 0x02), (0x1f, 0xaa, 0x02), (0x29, 0xaa, 0x02), (0x38, 0xaa, 0x03), + (0x03, 0xad, 0x02), (0x06, 0xad, 0x02), (0x0a, 0xad, 0x02), (0x0f, 0xad, 0x02), + (0x18, 0xad, 0x02), (0x1f, 0xad, 0x02), (0x29, 0xad, 0x02), (0x38, 0xad, 0x03), + ], + [ // 0x85 + (0x89, 0x00, 0x00), (0x8a, 0x00, 0x00), (0x8c, 0x00, 0x00), (0x8d, 0x00, 0x00), + (0x90, 0x00, 0x00), (0x91, 0x00, 0x00), (0x93, 0x00, 0x00), (0x96, 0x00, 0x00), + (0x9c, 0x00, 0x00), (0x9f, 0x00, 0x00), (0xa3, 0x00, 0x00), (0xa6, 0x00, 0x00), + (0xab, 0x00, 0x00), (0xae, 0x00, 0x00), (0xb5, 0x00, 0x00), (0xbe, 0x00, 0x00), + ], + [ // 0x86 + (0x00, 0xb2, 0x02), (0x00, 0xb5, 0x02), (0x00, 0xb9, 0x02), (0x00, 0xba, 0x02), + (0x00, 0xbb, 0x02), (0x00, 0xbd, 0x02), (0x00, 0xbe, 0x02), (0x00, 0xc4, 0x02), + (0x00, 0xc6, 0x02), (0x00, 0xe4, 0x02), (0x00, 0xe8, 0x02), (0x00, 0xe9, 0x02), + (0x94, 0x00, 0x00), (0x95, 0x00, 0x00), (0x97, 0x00, 0x00), (0x98, 0x00, 0x00), + ], + [ // 0x87 + (0x01, 0xb2, 0x02), (0x16, 0xb2, 0x03), (0x01, 0xb5, 0x02), (0x16, 0xb5, 0x03), + (0x01, 0xb9, 0x02), (0x16, 0xb9, 0x03), (0x01, 0xba, 0x02), (0x16, 0xba, 0x03), + (0x01, 0xbb, 0x02), (0x16, 0xbb, 0x03), (0x01, 0xbd, 0x02), (0x16, 0xbd, 0x03), + (0x01, 0xbe, 0x02), (0x16, 0xbe, 0x03), (0x01, 0xc4, 0x02), (0x16, 0xc4, 0x03), + ], + [ // 0x88 + (0x02, 0xb2, 0x02), (0x09, 0xb2, 0x02), (0x17, 0xb2, 0x02), (0x28, 0xb2, 0x03), + (0x02, 0xb5, 0x02), (0x09, 0xb5, 0x02), (0x17, 0xb5, 0x02), (0x28, 0xb5, 0x03), + (0x02, 0xb9, 0x02), (0x09, 0xb9, 0x02), (0x17, 0xb9, 0x02), (0x28, 0xb9, 0x03), + (0x02, 0xba, 0x02), (0x09, 0xba, 0x02), (0x17, 0xba, 0x02), (0x28, 0xba, 0x03), + ], + [ // 0x89 + (0x03, 0xb2, 0x02), (0x06, 0xb2, 0x02), (0x0a, 0xb2, 0x02), (0x0f, 0xb2, 0x02), + (0x18, 0xb2, 0x02), (0x1f, 0xb2, 0x02), (0x29, 0xb2, 0x02), (0x38, 0xb2, 0x03), + (0x03, 0xb5, 0x02), (0x06, 0xb5, 0x02), (0x0a, 0xb5, 0x02), (0x0f, 0xb5, 0x02), + (0x18, 0xb5, 0x02), (0x1f, 0xb5, 0x02), (0x29, 0xb5, 0x02), (0x38, 0xb5, 0x03), + ], + [ // 0x8a + (0x03, 0xb9, 0x02), (0x06, 0xb9, 0x02), (0x0a, 0xb9, 0x02), (0x0f, 0xb9, 0x02), + (0x18, 0xb9, 0x02), (0x1f, 0xb9, 0x02), (0x29, 0xb9, 0x02), (0x38, 0xb9, 0x03), + (0x03, 0xba, 0x02), (0x06, 0xba, 0x02), (0x0a, 0xba, 0x02), (0x0f, 0xba, 0x02), + (0x18, 0xba, 0x02), (0x1f, 0xba, 0x02), (0x29, 0xba, 0x02), (0x38, 0xba, 0x03), + ], + [ // 0x8b + (0x02, 0xbb, 0x02), (0x09, 0xbb, 0x02), (0x17, 0xbb, 0x02), (0x28, 0xbb, 0x03), + (0x02, 0xbd, 0x02), (0x09, 0xbd, 0x02), (0x17, 0xbd, 0x02), (0x28, 0xbd, 0x03), + (0x02, 0xbe, 0x02), (0x09, 0xbe, 0x02), (0x17, 0xbe, 0x02), (0x28, 0xbe, 0x03), + (0x02, 0xc4, 0x02), (0x09, 0xc4, 0x02), (0x17, 0xc4, 0x02), (0x28, 0xc4, 0x03), + ], + [ // 0x8c + (0x03, 0xbb, 0x02), (0x06, 0xbb, 0x02), (0x0a, 0xbb, 0x02), (0x0f, 0xbb, 0x02), + (0x18, 0xbb, 0x02), (0x1f, 0xbb, 0x02), (0x29, 0xbb, 0x02), (0x38, 0xbb, 0x03), + (0x03, 0xbd, 0x02), (0x06, 0xbd, 0x02), (0x0a, 0xbd, 0x02), (0x0f, 0xbd, 0x02), + (0x18, 0xbd, 0x02), (0x1f, 0xbd, 0x02), (0x29, 0xbd, 0x02), (0x38, 0xbd, 0x03), + ], + [ // 0x8d + (0x03, 0xbe, 0x02), (0x06, 0xbe, 0x02), (0x0a, 0xbe, 0x02), (0x0f, 0xbe, 0x02), + (0x18, 0xbe, 0x02), (0x1f, 0xbe, 0x02), (0x29, 0xbe, 0x02), (0x38, 0xbe, 0x03), + (0x03, 0xc4, 0x02), (0x06, 0xc4, 0x02), (0x0a, 0xc4, 0x02), (0x0f, 0xc4, 0x02), + (0x18, 0xc4, 0x02), (0x1f, 0xc4, 0x02), (0x29, 0xc4, 0x02), (0x38, 0xc4, 0x03), + ], + [ // 0x8e + (0x01, 0xc6, 0x02), (0x16, 0xc6, 0x03), (0x01, 0xe4, 0x02), (0x16, 0xe4, 0x03), + (0x01, 0xe8, 0x02), (0x16, 0xe8, 0x03), (0x01, 0xe9, 0x02), (0x16, 0xe9, 0x03), + (0x00, 0x01, 0x02), (0x00, 0x87, 0x02), (0x00, 0x89, 0x02), (0x00, 0x8a, 0x02), + (0x00, 0x8b, 0x02), (0x00, 0x8c, 0x02), (0x00, 0x8d, 0x02), (0x00, 0x8f, 0x02), + ], + [ // 0x8f + (0x02, 0xc6, 0x02), (0x09, 0xc6, 0x02), (0x17, 0xc6, 0x02), (0x28, 0xc6, 0x03), + (0x02, 0xe4, 0x02), (0x09, 0xe4, 0x02), (0x17, 0xe4, 0x02), (0x28, 0xe4, 0x03), + (0x02, 0xe8, 0x02), (0x09, 0xe8, 0x02), (0x17, 0xe8, 0x02), (0x28, 0xe8, 0x03), + (0x02, 0xe9, 0x02), (0x09, 0xe9, 0x02), (0x17, 0xe9, 0x02), (0x28, 0xe9, 0x03), + ], + [ // 0x90 + (0x03, 0xc6, 0x02), (0x06, 0xc6, 0x02), (0x0a, 0xc6, 0x02), (0x0f, 0xc6, 0x02), + (0x18, 0xc6, 0x02), (0x1f, 0xc6, 0x02), (0x29, 0xc6, 0x02), (0x38, 0xc6, 0x03), + (0x03, 0xe4, 0x02), (0x06, 0xe4, 0x02), (0x0a, 0xe4, 0x02), (0x0f, 0xe4, 0x02), + (0x18, 0xe4, 0x02), (0x1f, 0xe4, 0x02), (0x29, 0xe4, 0x02), (0x38, 0xe4, 0x03), + ], + [ // 0x91 + (0x03, 0xe8, 0x02), (0x06, 0xe8, 0x02), (0x0a, 0xe8, 0x02), (0x0f, 0xe8, 0x02), + (0x18, 0xe8, 0x02), (0x1f, 0xe8, 0x02), (0x29, 0xe8, 0x02), (0x38, 0xe8, 0x03), + (0x03, 0xe9, 0x02), (0x06, 0xe9, 0x02), (0x0a, 0xe9, 0x02), (0x0f, 0xe9, 0x02), + (0x18, 0xe9, 0x02), (0x1f, 0xe9, 0x02), (0x29, 0xe9, 0x02), (0x38, 0xe9, 0x03), + ], + [ // 0x92 + (0x01, 0x01, 0x02), (0x16, 0x01, 0x03), (0x01, 0x87, 0x02), (0x16, 0x87, 0x03), + (0x01, 0x89, 0x02), (0x16, 0x89, 0x03), (0x01, 0x8a, 0x02), (0x16, 0x8a, 0x03), + (0x01, 0x8b, 0x02), (0x16, 0x8b, 0x03), (0x01, 0x8c, 0x02), (0x16, 0x8c, 0x03), + (0x01, 0x8d, 0x02), (0x16, 0x8d, 0x03), (0x01, 0x8f, 0x02), (0x16, 0x8f, 0x03), + ], + [ // 0x93 + (0x02, 0x01, 0x02), (0x09, 0x01, 0x02), (0x17, 0x01, 0x02), (0x28, 0x01, 0x03), + (0x02, 0x87, 0x02), (0x09, 0x87, 0x02), (0x17, 0x87, 0x02), (0x28, 0x87, 0x03), + (0x02, 0x89, 0x02), (0x09, 0x89, 0x02), (0x17, 0x89, 0x02), (0x28, 0x89, 0x03), + (0x02, 0x8a, 0x02), (0x09, 0x8a, 0x02), (0x17, 0x8a, 0x02), (0x28, 0x8a, 0x03), + ], + [ // 0x94 + (0x03, 0x01, 0x02), (0x06, 0x01, 0x02), (0x0a, 0x01, 0x02), (0x0f, 0x01, 0x02), + (0x18, 0x01, 0x02), (0x1f, 0x01, 0x02), (0x29, 0x01, 0x02), (0x38, 0x01, 0x03), + (0x03, 0x87, 0x02), (0x06, 0x87, 0x02), (0x0a, 0x87, 0x02), (0x0f, 0x87, 0x02), + (0x18, 0x87, 0x02), (0x1f, 0x87, 0x02), (0x29, 0x87, 0x02), (0x38, 0x87, 0x03), + ], + [ // 0x95 + (0x03, 0x89, 0x02), (0x06, 0x89, 0x02), (0x0a, 0x89, 0x02), (0x0f, 0x89, 0x02), + (0x18, 0x89, 0x02), (0x1f, 0x89, 0x02), (0x29, 0x89, 0x02), (0x38, 0x89, 0x03), + (0x03, 0x8a, 0x02), (0x06, 0x8a, 0x02), (0x0a, 0x8a, 0x02), (0x0f, 0x8a, 0x02), + (0x18, 0x8a, 0x02), (0x1f, 0x8a, 0x02), (0x29, 0x8a, 0x02), (0x38, 0x8a, 0x03), + ], + [ // 0x96 + (0x02, 0x8b, 0x02), (0x09, 0x8b, 0x02), (0x17, 0x8b, 0x02), (0x28, 0x8b, 0x03), + (0x02, 0x8c, 0x02), (0x09, 0x8c, 0x02), (0x17, 0x8c, 0x02), (0x28, 0x8c, 0x03), + (0x02, 0x8d, 0x02), (0x09, 0x8d, 0x02), (0x17, 0x8d, 0x02), (0x28, 0x8d, 0x03), + (0x02, 0x8f, 0x02), (0x09, 0x8f, 0x02), (0x17, 0x8f, 0x02), (0x28, 0x8f, 0x03), + ], + [ // 0x97 + (0x03, 0x8b, 0x02), (0x06, 0x8b, 0x02), (0x0a, 0x8b, 0x02), (0x0f, 0x8b, 0x02), + (0x18, 0x8b, 0x02), (0x1f, 0x8b, 0x02), (0x29, 0x8b, 0x02), (0x38, 0x8b, 0x03), + (0x03, 0x8c, 0x02), (0x06, 0x8c, 0x02), (0x0a, 0x8c, 0x02), (0x0f, 0x8c, 0x02), + (0x18, 0x8c, 0x02), (0x1f, 0x8c, 0x02), (0x29, 0x8c, 0x02), (0x38, 0x8c, 0x03), + ], + [ // 0x98 + (0x03, 0x8d, 0x02), (0x06, 0x8d, 0x02), (0x0a, 0x8d, 0x02), (0x0f, 0x8d, 0x02), + (0x18, 0x8d, 0x02), (0x1f, 0x8d, 0x02), (0x29, 0x8d, 0x02), (0x38, 0x8d, 0x03), + (0x03, 0x8f, 0x02), (0x06, 0x8f, 0x02), (0x0a, 0x8f, 0x02), (0x0f, 0x8f, 0x02), + (0x18, 0x8f, 0x02), (0x1f, 0x8f, 0x02), (0x29, 0x8f, 0x02), (0x38, 0x8f, 0x03), + ], + [ // 0x99 + (0x9d, 0x00, 0x00), (0x9e, 0x00, 0x00), (0xa0, 0x00, 0x00), (0xa1, 0x00, 0x00), + (0xa4, 0x00, 0x00), (0xa5, 0x00, 0x00), (0xa7, 0x00, 0x00), (0xa8, 0x00, 0x00), + (0xac, 0x00, 0x00), (0xad, 0x00, 0x00), (0xaf, 0x00, 0x00), (0xb1, 0x00, 0x00), + (0xb6, 0x00, 0x00), (0xb9, 0x00, 0x00), (0xbf, 0x00, 0x00), (0xcf, 0x00, 0x00), + ], + [ // 0x9a + (0x00, 0x93, 0x02), (0x00, 0x95, 0x02), (0x00, 0x96, 0x02), (0x00, 0x97, 0x02), + (0x00, 0x98, 0x02), (0x00, 0x9b, 0x02), (0x00, 0x9d, 0x02), (0x00, 0x9e, 0x02), + (0x00, 0xa5, 0x02), (0x00, 0xa6, 0x02), (0x00, 0xa8, 0x02), (0x00, 0xae, 0x02), + (0x00, 0xaf, 0x02), (0x00, 0xb4, 0x02), (0x00, 0xb6, 0x02), (0x00, 0xb7, 0x02), + ], + [ // 0x9b + (0x01, 0x93, 0x02), (0x16, 0x93, 0x03), (0x01, 0x95, 0x02), (0x16, 0x95, 0x03), + (0x01, 0x96, 0x02), (0x16, 0x96, 0x03), (0x01, 0x97, 0x02), (0x16, 0x97, 0x03), + (0x01, 0x98, 0x02), (0x16, 0x98, 0x03), (0x01, 0x9b, 0x02), (0x16, 0x9b, 0x03), + (0x01, 0x9d, 0x02), (0x16, 0x9d, 0x03), (0x01, 0x9e, 0x02), (0x16, 0x9e, 0x03), + ], + [ // 0x9c + (0x02, 0x93, 0x02), (0x09, 0x93, 0x02), (0x17, 0x93, 0x02), (0x28, 0x93, 0x03), + (0x02, 0x95, 0x02), (0x09, 0x95, 0x02), (0x17, 0x95, 0x02), (0x28, 0x95, 0x03), + (0x02, 0x96, 0x02), (0x09, 0x96, 0x02), (0x17, 0x96, 0x02), (0x28, 0x96, 0x03), + (0x02, 0x97, 0x02), (0x09, 0x97, 0x02), (0x17, 0x97, 0x02), (0x28, 0x97, 0x03), + ], + [ // 0x9d + (0x03, 0x93, 0x02), (0x06, 0x93, 0x02), (0x0a, 0x93, 0x02), (0x0f, 0x93, 0x02), + (0x18, 0x93, 0x02), (0x1f, 0x93, 0x02), (0x29, 0x93, 0x02), (0x38, 0x93, 0x03), + (0x03, 0x95, 0x02), (0x06, 0x95, 0x02), (0x0a, 0x95, 0x02), (0x0f, 0x95, 0x02), + (0x18, 0x95, 0x02), (0x1f, 0x95, 0x02), (0x29, 0x95, 0x02), (0x38, 0x95, 0x03), + ], + [ // 0x9e + (0x03, 0x96, 0x02), (0x06, 0x96, 0x02), (0x0a, 0x96, 0x02), (0x0f, 0x96, 0x02), + (0x18, 0x96, 0x02), (0x1f, 0x96, 0x02), (0x29, 0x96, 0x02), (0x38, 0x96, 0x03), + (0x03, 0x97, 0x02), (0x06, 0x97, 0x02), (0x0a, 0x97, 0x02), (0x0f, 0x97, 0x02), + (0x18, 0x97, 0x02), (0x1f, 0x97, 0x02), (0x29, 0x97, 0x02), (0x38, 0x97, 0x03), + ], + [ // 0x9f + (0x02, 0x98, 0x02), (0x09, 0x98, 0x02), (0x17, 0x98, 0x02), (0x28, 0x98, 0x03), + (0x02, 0x9b, 0x02), (0x09, 0x9b, 0x02), (0x17, 0x9b, 0x02), (0x28, 0x9b, 0x03), + (0x02, 0x9d, 0x02), (0x09, 0x9d, 0x02), (0x17, 0x9d, 0x02), (0x28, 0x9d, 0x03), + (0x02, 0x9e, 0x02), (0x09, 0x9e, 0x02), (0x17, 0x9e, 0x02), (0x28, 0x9e, 0x03), + ], + [ // 0xa0 + (0x03, 0x98, 0x02), (0x06, 0x98, 0x02), (0x0a, 0x98, 0x02), (0x0f, 0x98, 0x02), + (0x18, 0x98, 0x02), (0x1f, 0x98, 0x02), (0x29, 0x98, 0x02), (0x38, 0x98, 0x03), + (0x03, 0x9b, 0x02), (0x06, 0x9b, 0x02), (0x0a, 0x9b, 0x02), (0x0f, 0x9b, 0x02), + (0x18, 0x9b, 0x02), (0x1f, 0x9b, 0x02), (0x29, 0x9b, 0x02), (0x38, 0x9b, 0x03), + ], + [ // 0xa1 + (0x03, 0x9d, 0x02), (0x06, 0x9d, 0x02), (0x0a, 0x9d, 0x02), (0x0f, 0x9d, 0x02), + (0x18, 0x9d, 0x02), (0x1f, 0x9d, 0x02), (0x29, 0x9d, 0x02), (0x38, 0x9d, 0x03), + (0x03, 0x9e, 0x02), (0x06, 0x9e, 0x02), (0x0a, 0x9e, 0x02), (0x0f, 0x9e, 0x02), + (0x18, 0x9e, 0x02), (0x1f, 0x9e, 0x02), (0x29, 0x9e, 0x02), (0x38, 0x9e, 0x03), + ], + [ // 0xa2 + (0x01, 0xa5, 0x02), (0x16, 0xa5, 0x03), (0x01, 0xa6, 0x02), (0x16, 0xa6, 0x03), + (0x01, 0xa8, 0x02), (0x16, 0xa8, 0x03), (0x01, 0xae, 0x02), (0x16, 0xae, 0x03), + (0x01, 0xaf, 0x02), (0x16, 0xaf, 0x03), (0x01, 0xb4, 0x02), (0x16, 0xb4, 0x03), + (0x01, 0xb6, 0x02), (0x16, 0xb6, 0x03), (0x01, 0xb7, 0x02), (0x16, 0xb7, 0x03), + ], + [ // 0xa3 + (0x02, 0xa5, 0x02), (0x09, 0xa5, 0x02), (0x17, 0xa5, 0x02), (0x28, 0xa5, 0x03), + (0x02, 0xa6, 0x02), (0x09, 0xa6, 0x02), (0x17, 0xa6, 0x02), (0x28, 0xa6, 0x03), + (0x02, 0xa8, 0x02), (0x09, 0xa8, 0x02), (0x17, 0xa8, 0x02), (0x28, 0xa8, 0x03), + (0x02, 0xae, 0x02), (0x09, 0xae, 0x02), (0x17, 0xae, 0x02), (0x28, 0xae, 0x03), + ], + [ // 0xa4 + (0x03, 0xa5, 0x02), (0x06, 0xa5, 0x02), (0x0a, 0xa5, 0x02), (0x0f, 0xa5, 0x02), + (0x18, 0xa5, 0x02), (0x1f, 0xa5, 0x02), (0x29, 0xa5, 0x02), (0x38, 0xa5, 0x03), + (0x03, 0xa6, 0x02), (0x06, 0xa6, 0x02), (0x0a, 0xa6, 0x02), (0x0f, 0xa6, 0x02), + (0x18, 0xa6, 0x02), (0x1f, 0xa6, 0x02), (0x29, 0xa6, 0x02), (0x38, 0xa6, 0x03), + ], + [ // 0xa5 + (0x03, 0xa8, 0x02), (0x06, 0xa8, 0x02), (0x0a, 0xa8, 0x02), (0x0f, 0xa8, 0x02), + (0x18, 0xa8, 0x02), (0x1f, 0xa8, 0x02), (0x29, 0xa8, 0x02), (0x38, 0xa8, 0x03), + (0x03, 0xae, 0x02), (0x06, 0xae, 0x02), (0x0a, 0xae, 0x02), (0x0f, 0xae, 0x02), + (0x18, 0xae, 0x02), (0x1f, 0xae, 0x02), (0x29, 0xae, 0x02), (0x38, 0xae, 0x03), + ], + [ // 0xa6 + (0x02, 0xaf, 0x02), (0x09, 0xaf, 0x02), (0x17, 0xaf, 0x02), (0x28, 0xaf, 0x03), + (0x02, 0xb4, 0x02), (0x09, 0xb4, 0x02), (0x17, 0xb4, 0x02), (0x28, 0xb4, 0x03), + (0x02, 0xb6, 0x02), (0x09, 0xb6, 0x02), (0x17, 0xb6, 0x02), (0x28, 0xb6, 0x03), + (0x02, 0xb7, 0x02), (0x09, 0xb7, 0x02), (0x17, 0xb7, 0x02), (0x28, 0xb7, 0x03), + ], + [ // 0xa7 + (0x03, 0xaf, 0x02), (0x06, 0xaf, 0x02), (0x0a, 0xaf, 0x02), (0x0f, 0xaf, 0x02), + (0x18, 0xaf, 0x02), (0x1f, 0xaf, 0x02), (0x29, 0xaf, 0x02), (0x38, 0xaf, 0x03), + (0x03, 0xb4, 0x02), (0x06, 0xb4, 0x02), (0x0a, 0xb4, 0x02), (0x0f, 0xb4, 0x02), + (0x18, 0xb4, 0x02), (0x1f, 0xb4, 0x02), (0x29, 0xb4, 0x02), (0x38, 0xb4, 0x03), + ], + [ // 0xa8 + (0x03, 0xb6, 0x02), (0x06, 0xb6, 0x02), (0x0a, 0xb6, 0x02), (0x0f, 0xb6, 0x02), + (0x18, 0xb6, 0x02), (0x1f, 0xb6, 0x02), (0x29, 0xb6, 0x02), (0x38, 0xb6, 0x03), + (0x03, 0xb7, 0x02), (0x06, 0xb7, 0x02), (0x0a, 0xb7, 0x02), (0x0f, 0xb7, 0x02), + (0x18, 0xb7, 0x02), (0x1f, 0xb7, 0x02), (0x29, 0xb7, 0x02), (0x38, 0xb7, 0x03), + ], + [ // 0xa9 + (0x00, 0xbc, 0x02), (0x00, 0xbf, 0x02), (0x00, 0xc5, 0x02), (0x00, 0xe7, 0x02), + (0x00, 0xef, 0x02), (0xb0, 0x00, 0x00), (0xb2, 0x00, 0x00), (0xb3, 0x00, 0x00), + (0xb7, 0x00, 0x00), (0xb8, 0x00, 0x00), (0xba, 0x00, 0x00), (0xbb, 0x00, 0x00), + (0xc0, 0x00, 0x00), (0xc7, 0x00, 0x00), (0xd0, 0x00, 0x00), (0xdf, 0x00, 0x00), + ], + [ // 0xaa + (0x01, 0xbc, 0x02), (0x16, 0xbc, 0x03), (0x01, 0xbf, 0x02), (0x16, 0xbf, 0x03), + (0x01, 0xc5, 0x02), (0x16, 0xc5, 0x03), (0x01, 0xe7, 0x02), (0x16, 0xe7, 0x03), + (0x01, 0xef, 0x02), (0x16, 0xef, 0x03), (0x00, 0x09, 0x02), (0x00, 0x8e, 0x02), + (0x00, 0x90, 0x02), (0x00, 0x91, 0x02), (0x00, 0x94, 0x02), (0x00, 0x9f, 0x02), + ], + [ // 0xab + (0x02, 0xbc, 0x02), (0x09, 0xbc, 0x02), (0x17, 0xbc, 0x02), (0x28, 0xbc, 0x03), + (0x02, 0xbf, 0x02), (0x09, 0xbf, 0x02), (0x17, 0xbf, 0x02), (0x28, 0xbf, 0x03), + (0x02, 0xc5, 0x02), (0x09, 0xc5, 0x02), (0x17, 0xc5, 0x02), (0x28, 0xc5, 0x03), + (0x02, 0xe7, 0x02), (0x09, 0xe7, 0x02), (0x17, 0xe7, 0x02), (0x28, 0xe7, 0x03), + ], + [ // 0xac + (0x03, 0xbc, 0x02), (0x06, 0xbc, 0x02), (0x0a, 0xbc, 0x02), (0x0f, 0xbc, 0x02), + (0x18, 0xbc, 0x02), (0x1f, 0xbc, 0x02), (0x29, 0xbc, 0x02), (0x38, 0xbc, 0x03), + (0x03, 0xbf, 0x02), (0x06, 0xbf, 0x02), (0x0a, 0xbf, 0x02), (0x0f, 0xbf, 0x02), + (0x18, 0xbf, 0x02), (0x1f, 0xbf, 0x02), (0x29, 0xbf, 0x02), (0x38, 0xbf, 0x03), + ], + [ // 0xad + (0x03, 0xc5, 0x02), (0x06, 0xc5, 0x02), (0x0a, 0xc5, 0x02), (0x0f, 0xc5, 0x02), + (0x18, 0xc5, 0x02), (0x1f, 0xc5, 0x02), (0x29, 0xc5, 0x02), (0x38, 0xc5, 0x03), + (0x03, 0xe7, 0x02), (0x06, 0xe7, 0x02), (0x0a, 0xe7, 0x02), (0x0f, 0xe7, 0x02), + (0x18, 0xe7, 0x02), (0x1f, 0xe7, 0x02), (0x29, 0xe7, 0x02), (0x38, 0xe7, 0x03), + ], + [ // 0xae + (0x02, 0xef, 0x02), (0x09, 0xef, 0x02), (0x17, 0xef, 0x02), (0x28, 0xef, 0x03), + (0x01, 0x09, 0x02), (0x16, 0x09, 0x03), (0x01, 0x8e, 0x02), (0x16, 0x8e, 0x03), + (0x01, 0x90, 0x02), (0x16, 0x90, 0x03), (0x01, 0x91, 0x02), (0x16, 0x91, 0x03), + (0x01, 0x94, 0x02), (0x16, 0x94, 0x03), (0x01, 0x9f, 0x02), (0x16, 0x9f, 0x03), + ], + [ // 0xaf + (0x03, 0xef, 0x02), (0x06, 0xef, 0x02), (0x0a, 0xef, 0x02), (0x0f, 0xef, 0x02), + (0x18, 0xef, 0x02), (0x1f, 0xef, 0x02), (0x29, 0xef, 0x02), (0x38, 0xef, 0x03), + (0x02, 0x09, 0x02), (0x09, 0x09, 0x02), (0x17, 0x09, 0x02), (0x28, 0x09, 0x03), + (0x02, 0x8e, 0x02), (0x09, 0x8e, 0x02), (0x17, 0x8e, 0x02), (0x28, 0x8e, 0x03), + ], + [ // 0xb0 + (0x03, 0x09, 0x02), (0x06, 0x09, 0x02), (0x0a, 0x09, 0x02), (0x0f, 0x09, 0x02), + (0x18, 0x09, 0x02), (0x1f, 0x09, 0x02), (0x29, 0x09, 0x02), (0x38, 0x09, 0x03), + (0x03, 0x8e, 0x02), (0x06, 0x8e, 0x02), (0x0a, 0x8e, 0x02), (0x0f, 0x8e, 0x02), + (0x18, 0x8e, 0x02), (0x1f, 0x8e, 0x02), (0x29, 0x8e, 0x02), (0x38, 0x8e, 0x03), + ], + [ // 0xb1 + (0x02, 0x90, 0x02), (0x09, 0x90, 0x02), (0x17, 0x90, 0x02), (0x28, 0x90, 0x03), + (0x02, 0x91, 0x02), (0x09, 0x91, 0x02), (0x17, 0x91, 0x02), (0x28, 0x91, 0x03), + (0x02, 0x94, 0x02), (0x09, 0x94, 0x02), (0x17, 0x94, 0x02), (0x28, 0x94, 0x03), + (0x02, 0x9f, 0x02), (0x09, 0x9f, 0x02), (0x17, 0x9f, 0x02), (0x28, 0x9f, 0x03), + ], + [ // 0xb2 + (0x03, 0x90, 0x02), (0x06, 0x90, 0x02), (0x0a, 0x90, 0x02), (0x0f, 0x90, 0x02), + (0x18, 0x90, 0x02), (0x1f, 0x90, 0x02), (0x29, 0x90, 0x02), (0x38, 0x90, 0x03), + (0x03, 0x91, 0x02), (0x06, 0x91, 0x02), (0x0a, 0x91, 0x02), (0x0f, 0x91, 0x02), + (0x18, 0x91, 0x02), (0x1f, 0x91, 0x02), (0x29, 0x91, 0x02), (0x38, 0x91, 0x03), + ], + [ // 0xb3 + (0x03, 0x94, 0x02), (0x06, 0x94, 0x02), (0x0a, 0x94, 0x02), (0x0f, 0x94, 0x02), + (0x18, 0x94, 0x02), (0x1f, 0x94, 0x02), (0x29, 0x94, 0x02), (0x38, 0x94, 0x03), + (0x03, 0x9f, 0x02), (0x06, 0x9f, 0x02), (0x0a, 0x9f, 0x02), (0x0f, 0x9f, 0x02), + (0x18, 0x9f, 0x02), (0x1f, 0x9f, 0x02), (0x29, 0x9f, 0x02), (0x38, 0x9f, 0x03), + ], + [ // 0xb4 + (0x00, 0xab, 0x02), (0x00, 0xce, 0x02), (0x00, 0xd7, 0x02), (0x00, 0xe1, 0x02), + (0x00, 0xec, 0x02), (0x00, 0xed, 0x02), (0xbc, 0x00, 0x00), (0xbd, 0x00, 0x00), + (0xc1, 0x00, 0x00), (0xc4, 0x00, 0x00), (0xc8, 0x00, 0x00), (0xcb, 0x00, 0x00), + (0xd1, 0x00, 0x00), (0xd8, 0x00, 0x00), (0xe0, 0x00, 0x00), (0xee, 0x00, 0x00), + ], + [ // 0xb5 + (0x01, 0xab, 0x02), (0x16, 0xab, 0x03), (0x01, 0xce, 0x02), (0x16, 0xce, 0x03), + (0x01, 0xd7, 0x02), (0x16, 0xd7, 0x03), (0x01, 0xe1, 0x02), (0x16, 0xe1, 0x03), + (0x01, 0xec, 0x02), (0x16, 0xec, 0x03), (0x01, 0xed, 0x02), (0x16, 0xed, 0x03), + (0x00, 0xc7, 0x02), (0x00, 0xcf, 0x02), (0x00, 0xea, 0x02), (0x00, 0xeb, 0x02), + ], + [ // 0xb6 + (0x02, 0xab, 0x02), (0x09, 0xab, 0x02), (0x17, 0xab, 0x02), (0x28, 0xab, 0x03), + (0x02, 0xce, 0x02), (0x09, 0xce, 0x02), (0x17, 0xce, 0x02), (0x28, 0xce, 0x03), + (0x02, 0xd7, 0x02), (0x09, 0xd7, 0x02), (0x17, 0xd7, 0x02), (0x28, 0xd7, 0x03), + (0x02, 0xe1, 0x02), (0x09, 0xe1, 0x02), (0x17, 0xe1, 0x02), (0x28, 0xe1, 0x03), + ], + [ // 0xb7 + (0x03, 0xab, 0x02), (0x06, 0xab, 0x02), (0x0a, 0xab, 0x02), (0x0f, 0xab, 0x02), + (0x18, 0xab, 0x02), (0x1f, 0xab, 0x02), (0x29, 0xab, 0x02), (0x38, 0xab, 0x03), + (0x03, 0xce, 0x02), (0x06, 0xce, 0x02), (0x0a, 0xce, 0x02), (0x0f, 0xce, 0x02), + (0x18, 0xce, 0x02), (0x1f, 0xce, 0x02), (0x29, 0xce, 0x02), (0x38, 0xce, 0x03), + ], + [ // 0xb8 + (0x03, 0xd7, 0x02), (0x06, 0xd7, 0x02), (0x0a, 0xd7, 0x02), (0x0f, 0xd7, 0x02), + (0x18, 0xd7, 0x02), (0x1f, 0xd7, 0x02), (0x29, 0xd7, 0x02), (0x38, 0xd7, 0x03), + (0x03, 0xe1, 0x02), (0x06, 0xe1, 0x02), (0x0a, 0xe1, 0x02), (0x0f, 0xe1, 0x02), + (0x18, 0xe1, 0x02), (0x1f, 0xe1, 0x02), (0x29, 0xe1, 0x02), (0x38, 0xe1, 0x03), + ], + [ // 0xb9 + (0x02, 0xec, 0x02), (0x09, 0xec, 0x02), (0x17, 0xec, 0x02), (0x28, 0xec, 0x03), + (0x02, 0xed, 0x02), (0x09, 0xed, 0x02), (0x17, 0xed, 0x02), (0x28, 0xed, 0x03), + (0x01, 0xc7, 0x02), (0x16, 0xc7, 0x03), (0x01, 0xcf, 0x02), (0x16, 0xcf, 0x03), + (0x01, 0xea, 0x02), (0x16, 0xea, 0x03), (0x01, 0xeb, 0x02), (0x16, 0xeb, 0x03), + ], + [ // 0xba + (0x03, 0xec, 0x02), (0x06, 0xec, 0x02), (0x0a, 0xec, 0x02), (0x0f, 0xec, 0x02), + (0x18, 0xec, 0x02), (0x1f, 0xec, 0x02), (0x29, 0xec, 0x02), (0x38, 0xec, 0x03), + (0x03, 0xed, 0x02), (0x06, 0xed, 0x02), (0x0a, 0xed, 0x02), (0x0f, 0xed, 0x02), + (0x18, 0xed, 0x02), (0x1f, 0xed, 0x02), (0x29, 0xed, 0x02), (0x38, 0xed, 0x03), + ], + [ // 0xbb + (0x02, 0xc7, 0x02), (0x09, 0xc7, 0x02), (0x17, 0xc7, 0x02), (0x28, 0xc7, 0x03), + (0x02, 0xcf, 0x02), (0x09, 0xcf, 0x02), (0x17, 0xcf, 0x02), (0x28, 0xcf, 0x03), + (0x02, 0xea, 0x02), (0x09, 0xea, 0x02), (0x17, 0xea, 0x02), (0x28, 0xea, 0x03), + (0x02, 0xeb, 0x02), (0x09, 0xeb, 0x02), (0x17, 0xeb, 0x02), (0x28, 0xeb, 0x03), + ], + [ // 0xbc + (0x03, 0xc7, 0x02), (0x06, 0xc7, 0x02), (0x0a, 0xc7, 0x02), (0x0f, 0xc7, 0x02), + (0x18, 0xc7, 0x02), (0x1f, 0xc7, 0x02), (0x29, 0xc7, 0x02), (0x38, 0xc7, 0x03), + (0x03, 0xcf, 0x02), (0x06, 0xcf, 0x02), (0x0a, 0xcf, 0x02), (0x0f, 0xcf, 0x02), + (0x18, 0xcf, 0x02), (0x1f, 0xcf, 0x02), (0x29, 0xcf, 0x02), (0x38, 0xcf, 0x03), + ], + [ // 0xbd + (0x03, 0xea, 0x02), (0x06, 0xea, 0x02), (0x0a, 0xea, 0x02), (0x0f, 0xea, 0x02), + (0x18, 0xea, 0x02), (0x1f, 0xea, 0x02), (0x29, 0xea, 0x02), (0x38, 0xea, 0x03), + (0x03, 0xeb, 0x02), (0x06, 0xeb, 0x02), (0x0a, 0xeb, 0x02), (0x0f, 0xeb, 0x02), + (0x18, 0xeb, 0x02), (0x1f, 0xeb, 0x02), (0x29, 0xeb, 0x02), (0x38, 0xeb, 0x03), + ], + [ // 0xbe + (0xc2, 0x00, 0x00), (0xc3, 0x00, 0x00), (0xc5, 0x00, 0x00), (0xc6, 0x00, 0x00), + (0xc9, 0x00, 0x00), (0xca, 0x00, 0x00), (0xcc, 0x00, 0x00), (0xcd, 0x00, 0x00), + (0xd2, 0x00, 0x00), (0xd5, 0x00, 0x00), (0xd9, 0x00, 0x00), (0xdc, 0x00, 0x00), + (0xe1, 0x00, 0x00), (0xe7, 0x00, 0x00), (0xef, 0x00, 0x00), (0xf6, 0x00, 0x00), + ], + [ // 0xbf + (0x00, 0xc0, 0x02), (0x00, 0xc1, 0x02), (0x00, 0xc8, 0x02), (0x00, 0xc9, 0x02), + (0x00, 0xca, 0x02), (0x00, 0xcd, 0x02), (0x00, 0xd2, 0x02), (0x00, 0xd5, 0x02), + (0x00, 0xda, 0x02), (0x00, 0xdb, 0x02), (0x00, 0xee, 0x02), (0x00, 0xf0, 0x02), + (0x00, 0xf2, 0x02), (0x00, 0xf3, 0x02), (0x00, 0xff, 0x02), (0xce, 0x00, 0x00), + ], + [ // 0xc0 + (0x01, 0xc0, 0x02), (0x16, 0xc0, 0x03), (0x01, 0xc1, 0x02), (0x16, 0xc1, 0x03), + (0x01, 0xc8, 0x02), (0x16, 0xc8, 0x03), (0x01, 0xc9, 0x02), (0x16, 0xc9, 0x03), + (0x01, 0xca, 0x02), (0x16, 0xca, 0x03), (0x01, 0xcd, 0x02), (0x16, 0xcd, 0x03), + (0x01, 0xd2, 0x02), (0x16, 0xd2, 0x03), (0x01, 0xd5, 0x02), (0x16, 0xd5, 0x03), + ], + [ // 0xc1 + (0x02, 0xc0, 0x02), (0x09, 0xc0, 0x02), (0x17, 0xc0, 0x02), (0x28, 0xc0, 0x03), + (0x02, 0xc1, 0x02), (0x09, 0xc1, 0x02), (0x17, 0xc1, 0x02), (0x28, 0xc1, 0x03), + (0x02, 0xc8, 0x02), (0x09, 0xc8, 0x02), (0x17, 0xc8, 0x02), (0x28, 0xc8, 0x03), + (0x02, 0xc9, 0x02), (0x09, 0xc9, 0x02), (0x17, 0xc9, 0x02), (0x28, 0xc9, 0x03), + ], + [ // 0xc2 + (0x03, 0xc0, 0x02), (0x06, 0xc0, 0x02), (0x0a, 0xc0, 0x02), (0x0f, 0xc0, 0x02), + (0x18, 0xc0, 0x02), (0x1f, 0xc0, 0x02), (0x29, 0xc0, 0x02), (0x38, 0xc0, 0x03), + (0x03, 0xc1, 0x02), (0x06, 0xc1, 0x02), (0x0a, 0xc1, 0x02), (0x0f, 0xc1, 0x02), + (0x18, 0xc1, 0x02), (0x1f, 0xc1, 0x02), (0x29, 0xc1, 0x02), (0x38, 0xc1, 0x03), + ], + [ // 0xc3 + (0x03, 0xc8, 0x02), (0x06, 0xc8, 0x02), (0x0a, 0xc8, 0x02), (0x0f, 0xc8, 0x02), + (0x18, 0xc8, 0x02), (0x1f, 0xc8, 0x02), (0x29, 0xc8, 0x02), (0x38, 0xc8, 0x03), + (0x03, 0xc9, 0x02), (0x06, 0xc9, 0x02), (0x0a, 0xc9, 0x02), (0x0f, 0xc9, 0x02), + (0x18, 0xc9, 0x02), (0x1f, 0xc9, 0x02), (0x29, 0xc9, 0x02), (0x38, 0xc9, 0x03), + ], + [ // 0xc4 + (0x02, 0xca, 0x02), (0x09, 0xca, 0x02), (0x17, 0xca, 0x02), (0x28, 0xca, 0x03), + (0x02, 0xcd, 0x02), (0x09, 0xcd, 0x02), (0x17, 0xcd, 0x02), (0x28, 0xcd, 0x03), + (0x02, 0xd2, 0x02), (0x09, 0xd2, 0x02), (0x17, 0xd2, 0x02), (0x28, 0xd2, 0x03), + (0x02, 0xd5, 0x02), (0x09, 0xd5, 0x02), (0x17, 0xd5, 0x02), (0x28, 0xd5, 0x03), + ], + [ // 0xc5 + (0x03, 0xca, 0x02), (0x06, 0xca, 0x02), (0x0a, 0xca, 0x02), (0x0f, 0xca, 0x02), + (0x18, 0xca, 0x02), (0x1f, 0xca, 0x02), (0x29, 0xca, 0x02), (0x38, 0xca, 0x03), + (0x03, 0xcd, 0x02), (0x06, 0xcd, 0x02), (0x0a, 0xcd, 0x02), (0x0f, 0xcd, 0x02), + (0x18, 0xcd, 0x02), (0x1f, 0xcd, 0x02), (0x29, 0xcd, 0x02), (0x38, 0xcd, 0x03), + ], + [ // 0xc6 + (0x03, 0xd2, 0x02), (0x06, 0xd2, 0x02), (0x0a, 0xd2, 0x02), (0x0f, 0xd2, 0x02), + (0x18, 0xd2, 0x02), (0x1f, 0xd2, 0x02), (0x29, 0xd2, 0x02), (0x38, 0xd2, 0x03), + (0x03, 0xd5, 0x02), (0x06, 0xd5, 0x02), (0x0a, 0xd5, 0x02), (0x0f, 0xd5, 0x02), + (0x18, 0xd5, 0x02), (0x1f, 0xd5, 0x02), (0x29, 0xd5, 0x02), (0x38, 0xd5, 0x03), + ], + [ // 0xc7 + (0x01, 0xda, 0x02), (0x16, 0xda, 0x03), (0x01, 0xdb, 0x02), (0x16, 0xdb, 0x03), + (0x01, 0xee, 0x02), (0x16, 0xee, 0x03), (0x01, 0xf0, 0x02), (0x16, 0xf0, 0x03), + (0x01, 0xf2, 0x02), (0x16, 0xf2, 0x03), (0x01, 0xf3, 0x02), (0x16, 0xf3, 0x03), + (0x01, 0xff, 0x02), (0x16, 0xff, 0x03), (0x00, 0xcb, 0x02), (0x00, 0xcc, 0x02), + ], + [ // 0xc8 + (0x02, 0xda, 0x02), (0x09, 0xda, 0x02), (0x17, 0xda, 0x02), (0x28, 0xda, 0x03), + (0x02, 0xdb, 0x02), (0x09, 0xdb, 0x02), (0x17, 0xdb, 0x02), (0x28, 0xdb, 0x03), + (0x02, 0xee, 0x02), (0x09, 0xee, 0x02), (0x17, 0xee, 0x02), (0x28, 0xee, 0x03), + (0x02, 0xf0, 0x02), (0x09, 0xf0, 0x02), (0x17, 0xf0, 0x02), (0x28, 0xf0, 0x03), + ], + [ // 0xc9 + (0x03, 0xda, 0x02), (0x06, 0xda, 0x02), (0x0a, 0xda, 0x02), (0x0f, 0xda, 0x02), + (0x18, 0xda, 0x02), (0x1f, 0xda, 0x02), (0x29, 0xda, 0x02), (0x38, 0xda, 0x03), + (0x03, 0xdb, 0x02), (0x06, 0xdb, 0x02), (0x0a, 0xdb, 0x02), (0x0f, 0xdb, 0x02), + (0x18, 0xdb, 0x02), (0x1f, 0xdb, 0x02), (0x29, 0xdb, 0x02), (0x38, 0xdb, 0x03), + ], + [ // 0xca + (0x03, 0xee, 0x02), (0x06, 0xee, 0x02), (0x0a, 0xee, 0x02), (0x0f, 0xee, 0x02), + (0x18, 0xee, 0x02), (0x1f, 0xee, 0x02), (0x29, 0xee, 0x02), (0x38, 0xee, 0x03), + (0x03, 0xf0, 0x02), (0x06, 0xf0, 0x02), (0x0a, 0xf0, 0x02), (0x0f, 0xf0, 0x02), + (0x18, 0xf0, 0x02), (0x1f, 0xf0, 0x02), (0x29, 0xf0, 0x02), (0x38, 0xf0, 0x03), + ], + [ // 0xcb + (0x02, 0xf2, 0x02), (0x09, 0xf2, 0x02), (0x17, 0xf2, 0x02), (0x28, 0xf2, 0x03), + (0x02, 0xf3, 0x02), (0x09, 0xf3, 0x02), (0x17, 0xf3, 0x02), (0x28, 0xf3, 0x03), + (0x02, 0xff, 0x02), (0x09, 0xff, 0x02), (0x17, 0xff, 0x02), (0x28, 0xff, 0x03), + (0x01, 0xcb, 0x02), (0x16, 0xcb, 0x03), (0x01, 0xcc, 0x02), (0x16, 0xcc, 0x03), + ], + [ // 0xcc + (0x03, 0xf2, 0x02), (0x06, 0xf2, 0x02), (0x0a, 0xf2, 0x02), (0x0f, 0xf2, 0x02), + (0x18, 0xf2, 0x02), (0x1f, 0xf2, 0x02), (0x29, 0xf2, 0x02), (0x38, 0xf2, 0x03), + (0x03, 0xf3, 0x02), (0x06, 0xf3, 0x02), (0x0a, 0xf3, 0x02), (0x0f, 0xf3, 0x02), + (0x18, 0xf3, 0x02), (0x1f, 0xf3, 0x02), (0x29, 0xf3, 0x02), (0x38, 0xf3, 0x03), + ], + [ // 0xcd + (0x03, 0xff, 0x02), (0x06, 0xff, 0x02), (0x0a, 0xff, 0x02), (0x0f, 0xff, 0x02), + (0x18, 0xff, 0x02), (0x1f, 0xff, 0x02), (0x29, 0xff, 0x02), (0x38, 0xff, 0x03), + (0x02, 0xcb, 0x02), (0x09, 0xcb, 0x02), (0x17, 0xcb, 0x02), (0x28, 0xcb, 0x03), + (0x02, 0xcc, 0x02), (0x09, 0xcc, 0x02), (0x17, 0xcc, 0x02), (0x28, 0xcc, 0x03), + ], + [ // 0xce + (0x03, 0xcb, 0x02), (0x06, 0xcb, 0x02), (0x0a, 0xcb, 0x02), (0x0f, 0xcb, 0x02), + (0x18, 0xcb, 0x02), (0x1f, 0xcb, 0x02), (0x29, 0xcb, 0x02), (0x38, 0xcb, 0x03), + (0x03, 0xcc, 0x02), (0x06, 0xcc, 0x02), (0x0a, 0xcc, 0x02), (0x0f, 0xcc, 0x02), + (0x18, 0xcc, 0x02), (0x1f, 0xcc, 0x02), (0x29, 0xcc, 0x02), (0x38, 0xcc, 0x03), + ], + [ // 0xcf + (0xd3, 0x00, 0x00), (0xd4, 0x00, 0x00), (0xd6, 0x00, 0x00), (0xd7, 0x00, 0x00), + (0xda, 0x00, 0x00), (0xdb, 0x00, 0x00), (0xdd, 0x00, 0x00), (0xde, 0x00, 0x00), + (0xe2, 0x00, 0x00), (0xe4, 0x00, 0x00), (0xe8, 0x00, 0x00), (0xeb, 0x00, 0x00), + (0xf0, 0x00, 0x00), (0xf3, 0x00, 0x00), (0xf7, 0x00, 0x00), (0xfa, 0x00, 0x00), + ], + [ // 0xd0 + (0x00, 0xd3, 0x02), (0x00, 0xd4, 0x02), (0x00, 0xd6, 0x02), (0x00, 0xdd, 0x02), + (0x00, 0xde, 0x02), (0x00, 0xdf, 0x02), (0x00, 0xf1, 0x02), (0x00, 0xf4, 0x02), + (0x00, 0xf5, 0x02), (0x00, 0xf6, 0x02), (0x00, 0xf7, 0x02), (0x00, 0xf8, 0x02), + (0x00, 0xfa, 0x02), (0x00, 0xfb, 0x02), (0x00, 0xfc, 0x02), (0x00, 0xfd, 0x02), + ], + [ // 0xd1 + (0x01, 0xd3, 0x02), (0x16, 0xd3, 0x03), (0x01, 0xd4, 0x02), (0x16, 0xd4, 0x03), + (0x01, 0xd6, 0x02), (0x16, 0xd6, 0x03), (0x01, 0xdd, 0x02), (0x16, 0xdd, 0x03), + (0x01, 0xde, 0x02), (0x16, 0xde, 0x03), (0x01, 0xdf, 0x02), (0x16, 0xdf, 0x03), + (0x01, 0xf1, 0x02), (0x16, 0xf1, 0x03), (0x01, 0xf4, 0x02), (0x16, 0xf4, 0x03), + ], + [ // 0xd2 + (0x02, 0xd3, 0x02), (0x09, 0xd3, 0x02), (0x17, 0xd3, 0x02), (0x28, 0xd3, 0x03), + (0x02, 0xd4, 0x02), (0x09, 0xd4, 0x02), (0x17, 0xd4, 0x02), (0x28, 0xd4, 0x03), + (0x02, 0xd6, 0x02), (0x09, 0xd6, 0x02), (0x17, 0xd6, 0x02), (0x28, 0xd6, 0x03), + (0x02, 0xdd, 0x02), (0x09, 0xdd, 0x02), (0x17, 0xdd, 0x02), (0x28, 0xdd, 0x03), + ], + [ // 0xd3 + (0x03, 0xd3, 0x02), (0x06, 0xd3, 0x02), (0x0a, 0xd3, 0x02), (0x0f, 0xd3, 0x02), + (0x18, 0xd3, 0x02), (0x1f, 0xd3, 0x02), (0x29, 0xd3, 0x02), (0x38, 0xd3, 0x03), + (0x03, 0xd4, 0x02), (0x06, 0xd4, 0x02), (0x0a, 0xd4, 0x02), (0x0f, 0xd4, 0x02), + (0x18, 0xd4, 0x02), (0x1f, 0xd4, 0x02), (0x29, 0xd4, 0x02), (0x38, 0xd4, 0x03), + ], + [ // 0xd4 + (0x03, 0xd6, 0x02), (0x06, 0xd6, 0x02), (0x0a, 0xd6, 0x02), (0x0f, 0xd6, 0x02), + (0x18, 0xd6, 0x02), (0x1f, 0xd6, 0x02), (0x29, 0xd6, 0x02), (0x38, 0xd6, 0x03), + (0x03, 0xdd, 0x02), (0x06, 0xdd, 0x02), (0x0a, 0xdd, 0x02), (0x0f, 0xdd, 0x02), + (0x18, 0xdd, 0x02), (0x1f, 0xdd, 0x02), (0x29, 0xdd, 0x02), (0x38, 0xdd, 0x03), + ], + [ // 0xd5 + (0x02, 0xde, 0x02), (0x09, 0xde, 0x02), (0x17, 0xde, 0x02), (0x28, 0xde, 0x03), + (0x02, 0xdf, 0x02), (0x09, 0xdf, 0x02), (0x17, 0xdf, 0x02), (0x28, 0xdf, 0x03), + (0x02, 0xf1, 0x02), (0x09, 0xf1, 0x02), (0x17, 0xf1, 0x02), (0x28, 0xf1, 0x03), + (0x02, 0xf4, 0x02), (0x09, 0xf4, 0x02), (0x17, 0xf4, 0x02), (0x28, 0xf4, 0x03), + ], + [ // 0xd6 + (0x03, 0xde, 0x02), (0x06, 0xde, 0x02), (0x0a, 0xde, 0x02), (0x0f, 0xde, 0x02), + (0x18, 0xde, 0x02), (0x1f, 0xde, 0x02), (0x29, 0xde, 0x02), (0x38, 0xde, 0x03), + (0x03, 0xdf, 0x02), (0x06, 0xdf, 0x02), (0x0a, 0xdf, 0x02), (0x0f, 0xdf, 0x02), + (0x18, 0xdf, 0x02), (0x1f, 0xdf, 0x02), (0x29, 0xdf, 0x02), (0x38, 0xdf, 0x03), + ], + [ // 0xd7 + (0x03, 0xf1, 0x02), (0x06, 0xf1, 0x02), (0x0a, 0xf1, 0x02), (0x0f, 0xf1, 0x02), + (0x18, 0xf1, 0x02), (0x1f, 0xf1, 0x02), (0x29, 0xf1, 0x02), (0x38, 0xf1, 0x03), + (0x03, 0xf4, 0x02), (0x06, 0xf4, 0x02), (0x0a, 0xf4, 0x02), (0x0f, 0xf4, 0x02), + (0x18, 0xf4, 0x02), (0x1f, 0xf4, 0x02), (0x29, 0xf4, 0x02), (0x38, 0xf4, 0x03), + ], + [ // 0xd8 + (0x01, 0xf5, 0x02), (0x16, 0xf5, 0x03), (0x01, 0xf6, 0x02), (0x16, 0xf6, 0x03), + (0x01, 0xf7, 0x02), (0x16, 0xf7, 0x03), (0x01, 0xf8, 0x02), (0x16, 0xf8, 0x03), + (0x01, 0xfa, 0x02), (0x16, 0xfa, 0x03), (0x01, 0xfb, 0x02), (0x16, 0xfb, 0x03), + (0x01, 0xfc, 0x02), (0x16, 0xfc, 0x03), (0x01, 0xfd, 0x02), (0x16, 0xfd, 0x03), + ], + [ // 0xd9 + (0x02, 0xf5, 0x02), (0x09, 0xf5, 0x02), (0x17, 0xf5, 0x02), (0x28, 0xf5, 0x03), + (0x02, 0xf6, 0x02), (0x09, 0xf6, 0x02), (0x17, 0xf6, 0x02), (0x28, 0xf6, 0x03), + (0x02, 0xf7, 0x02), (0x09, 0xf7, 0x02), (0x17, 0xf7, 0x02), (0x28, 0xf7, 0x03), + (0x02, 0xf8, 0x02), (0x09, 0xf8, 0x02), (0x17, 0xf8, 0x02), (0x28, 0xf8, 0x03), + ], + [ // 0xda + (0x03, 0xf5, 0x02), (0x06, 0xf5, 0x02), (0x0a, 0xf5, 0x02), (0x0f, 0xf5, 0x02), + (0x18, 0xf5, 0x02), (0x1f, 0xf5, 0x02), (0x29, 0xf5, 0x02), (0x38, 0xf5, 0x03), + (0x03, 0xf6, 0x02), (0x06, 0xf6, 0x02), (0x0a, 0xf6, 0x02), (0x0f, 0xf6, 0x02), + (0x18, 0xf6, 0x02), (0x1f, 0xf6, 0x02), (0x29, 0xf6, 0x02), (0x38, 0xf6, 0x03), + ], + [ // 0xdb + (0x03, 0xf7, 0x02), (0x06, 0xf7, 0x02), (0x0a, 0xf7, 0x02), (0x0f, 0xf7, 0x02), + (0x18, 0xf7, 0x02), (0x1f, 0xf7, 0x02), (0x29, 0xf7, 0x02), (0x38, 0xf7, 0x03), + (0x03, 0xf8, 0x02), (0x06, 0xf8, 0x02), (0x0a, 0xf8, 0x02), (0x0f, 0xf8, 0x02), + (0x18, 0xf8, 0x02), (0x1f, 0xf8, 0x02), (0x29, 0xf8, 0x02), (0x38, 0xf8, 0x03), + ], + [ // 0xdc + (0x02, 0xfa, 0x02), (0x09, 0xfa, 0x02), (0x17, 0xfa, 0x02), (0x28, 0xfa, 0x03), + (0x02, 0xfb, 0x02), (0x09, 0xfb, 0x02), (0x17, 0xfb, 0x02), (0x28, 0xfb, 0x03), + (0x02, 0xfc, 0x02), (0x09, 0xfc, 0x02), (0x17, 0xfc, 0x02), (0x28, 0xfc, 0x03), + (0x02, 0xfd, 0x02), (0x09, 0xfd, 0x02), (0x17, 0xfd, 0x02), (0x28, 0xfd, 0x03), + ], + [ // 0xdd + (0x03, 0xfa, 0x02), (0x06, 0xfa, 0x02), (0x0a, 0xfa, 0x02), (0x0f, 0xfa, 0x02), + (0x18, 0xfa, 0x02), (0x1f, 0xfa, 0x02), (0x29, 0xfa, 0x02), (0x38, 0xfa, 0x03), + (0x03, 0xfb, 0x02), (0x06, 0xfb, 0x02), (0x0a, 0xfb, 0x02), (0x0f, 0xfb, 0x02), + (0x18, 0xfb, 0x02), (0x1f, 0xfb, 0x02), (0x29, 0xfb, 0x02), (0x38, 0xfb, 0x03), + ], + [ // 0xde + (0x03, 0xfc, 0x02), (0x06, 0xfc, 0x02), (0x0a, 0xfc, 0x02), (0x0f, 0xfc, 0x02), + (0x18, 0xfc, 0x02), (0x1f, 0xfc, 0x02), (0x29, 0xfc, 0x02), (0x38, 0xfc, 0x03), + (0x03, 0xfd, 0x02), (0x06, 0xfd, 0x02), (0x0a, 0xfd, 0x02), (0x0f, 0xfd, 0x02), + (0x18, 0xfd, 0x02), (0x1f, 0xfd, 0x02), (0x29, 0xfd, 0x02), (0x38, 0xfd, 0x03), + ], + [ // 0xdf + (0x00, 0xfe, 0x02), (0xe3, 0x00, 0x00), (0xe5, 0x00, 0x00), (0xe6, 0x00, 0x00), + (0xe9, 0x00, 0x00), (0xea, 0x00, 0x00), (0xec, 0x00, 0x00), (0xed, 0x00, 0x00), + (0xf1, 0x00, 0x00), (0xf2, 0x00, 0x00), (0xf4, 0x00, 0x00), (0xf5, 0x00, 0x00), + (0xf8, 0x00, 0x00), (0xf9, 0x00, 0x00), (0xfb, 0x00, 0x00), (0xfc, 0x00, 0x00), + ], + [ // 0xe0 + (0x01, 0xfe, 0x02), (0x16, 0xfe, 0x03), (0x00, 0x02, 0x02), (0x00, 0x03, 0x02), + (0x00, 0x04, 0x02), (0x00, 0x05, 0x02), (0x00, 0x06, 0x02), (0x00, 0x07, 0x02), + (0x00, 0x08, 0x02), (0x00, 0x0b, 0x02), (0x00, 0x0c, 0x02), (0x00, 0x0e, 0x02), + (0x00, 0x0f, 0x02), (0x00, 0x10, 0x02), (0x00, 0x11, 0x02), (0x00, 0x12, 0x02), + ], + [ // 0xe1 + (0x02, 0xfe, 0x02), (0x09, 0xfe, 0x02), (0x17, 0xfe, 0x02), (0x28, 0xfe, 0x03), + (0x01, 0x02, 0x02), (0x16, 0x02, 0x03), (0x01, 0x03, 0x02), (0x16, 0x03, 0x03), + (0x01, 0x04, 0x02), (0x16, 0x04, 0x03), (0x01, 0x05, 0x02), (0x16, 0x05, 0x03), + (0x01, 0x06, 0x02), (0x16, 0x06, 0x03), (0x01, 0x07, 0x02), (0x16, 0x07, 0x03), + ], + [ // 0xe2 + (0x03, 0xfe, 0x02), (0x06, 0xfe, 0x02), (0x0a, 0xfe, 0x02), (0x0f, 0xfe, 0x02), + (0x18, 0xfe, 0x02), (0x1f, 0xfe, 0x02), (0x29, 0xfe, 0x02), (0x38, 0xfe, 0x03), + (0x02, 0x02, 0x02), (0x09, 0x02, 0x02), (0x17, 0x02, 0x02), (0x28, 0x02, 0x03), + (0x02, 0x03, 0x02), (0x09, 0x03, 0x02), (0x17, 0x03, 0x02), (0x28, 0x03, 0x03), + ], + [ // 0xe3 + (0x03, 0x02, 0x02), (0x06, 0x02, 0x02), (0x0a, 0x02, 0x02), (0x0f, 0x02, 0x02), + (0x18, 0x02, 0x02), (0x1f, 0x02, 0x02), (0x29, 0x02, 0x02), (0x38, 0x02, 0x03), + (0x03, 0x03, 0x02), (0x06, 0x03, 0x02), (0x0a, 0x03, 0x02), (0x0f, 0x03, 0x02), + (0x18, 0x03, 0x02), (0x1f, 0x03, 0x02), (0x29, 0x03, 0x02), (0x38, 0x03, 0x03), + ], + [ // 0xe4 + (0x02, 0x04, 0x02), (0x09, 0x04, 0x02), (0x17, 0x04, 0x02), (0x28, 0x04, 0x03), + (0x02, 0x05, 0x02), (0x09, 0x05, 0x02), (0x17, 0x05, 0x02), (0x28, 0x05, 0x03), + (0x02, 0x06, 0x02), (0x09, 0x06, 0x02), (0x17, 0x06, 0x02), (0x28, 0x06, 0x03), + (0x02, 0x07, 0x02), (0x09, 0x07, 0x02), (0x17, 0x07, 0x02), (0x28, 0x07, 0x03), + ], + [ // 0xe5 + (0x03, 0x04, 0x02), (0x06, 0x04, 0x02), (0x0a, 0x04, 0x02), (0x0f, 0x04, 0x02), + (0x18, 0x04, 0x02), (0x1f, 0x04, 0x02), (0x29, 0x04, 0x02), (0x38, 0x04, 0x03), + (0x03, 0x05, 0x02), (0x06, 0x05, 0x02), (0x0a, 0x05, 0x02), (0x0f, 0x05, 0x02), + (0x18, 0x05, 0x02), (0x1f, 0x05, 0x02), (0x29, 0x05, 0x02), (0x38, 0x05, 0x03), + ], + [ // 0xe6 + (0x03, 0x06, 0x02), (0x06, 0x06, 0x02), (0x0a, 0x06, 0x02), (0x0f, 0x06, 0x02), + (0x18, 0x06, 0x02), (0x1f, 0x06, 0x02), (0x29, 0x06, 0x02), (0x38, 0x06, 0x03), + (0x03, 0x07, 0x02), (0x06, 0x07, 0x02), (0x0a, 0x07, 0x02), (0x0f, 0x07, 0x02), + (0x18, 0x07, 0x02), (0x1f, 0x07, 0x02), (0x29, 0x07, 0x02), (0x38, 0x07, 0x03), + ], + [ // 0xe7 + (0x01, 0x08, 0x02), (0x16, 0x08, 0x03), (0x01, 0x0b, 0x02), (0x16, 0x0b, 0x03), + (0x01, 0x0c, 0x02), (0x16, 0x0c, 0x03), (0x01, 0x0e, 0x02), (0x16, 0x0e, 0x03), + (0x01, 0x0f, 0x02), (0x16, 0x0f, 0x03), (0x01, 0x10, 0x02), (0x16, 0x10, 0x03), + (0x01, 0x11, 0x02), (0x16, 0x11, 0x03), (0x01, 0x12, 0x02), (0x16, 0x12, 0x03), + ], + [ // 0xe8 + (0x02, 0x08, 0x02), (0x09, 0x08, 0x02), (0x17, 0x08, 0x02), (0x28, 0x08, 0x03), + (0x02, 0x0b, 0x02), (0x09, 0x0b, 0x02), (0x17, 0x0b, 0x02), (0x28, 0x0b, 0x03), + (0x02, 0x0c, 0x02), (0x09, 0x0c, 0x02), (0x17, 0x0c, 0x02), (0x28, 0x0c, 0x03), + (0x02, 0x0e, 0x02), (0x09, 0x0e, 0x02), (0x17, 0x0e, 0x02), (0x28, 0x0e, 0x03), + ], + [ // 0xe9 + (0x03, 0x08, 0x02), (0x06, 0x08, 0x02), (0x0a, 0x08, 0x02), (0x0f, 0x08, 0x02), + (0x18, 0x08, 0x02), (0x1f, 0x08, 0x02), (0x29, 0x08, 0x02), (0x38, 0x08, 0x03), + (0x03, 0x0b, 0x02), (0x06, 0x0b, 0x02), (0x0a, 0x0b, 0x02), (0x0f, 0x0b, 0x02), + (0x18, 0x0b, 0x02), (0x1f, 0x0b, 0x02), (0x29, 0x0b, 0x02), (0x38, 0x0b, 0x03), + ], + [ // 0xea + (0x03, 0x0c, 0x02), (0x06, 0x0c, 0x02), (0x0a, 0x0c, 0x02), (0x0f, 0x0c, 0x02), + (0x18, 0x0c, 0x02), (0x1f, 0x0c, 0x02), (0x29, 0x0c, 0x02), (0x38, 0x0c, 0x03), + (0x03, 0x0e, 0x02), (0x06, 0x0e, 0x02), (0x0a, 0x0e, 0x02), (0x0f, 0x0e, 0x02), + (0x18, 0x0e, 0x02), (0x1f, 0x0e, 0x02), (0x29, 0x0e, 0x02), (0x38, 0x0e, 0x03), + ], + [ // 0xeb + (0x02, 0x0f, 0x02), (0x09, 0x0f, 0x02), (0x17, 0x0f, 0x02), (0x28, 0x0f, 0x03), + (0x02, 0x10, 0x02), (0x09, 0x10, 0x02), (0x17, 0x10, 0x02), (0x28, 0x10, 0x03), + (0x02, 0x11, 0x02), (0x09, 0x11, 0x02), (0x17, 0x11, 0x02), (0x28, 0x11, 0x03), + (0x02, 0x12, 0x02), (0x09, 0x12, 0x02), (0x17, 0x12, 0x02), (0x28, 0x12, 0x03), + ], + [ // 0xec + (0x03, 0x0f, 0x02), (0x06, 0x0f, 0x02), (0x0a, 0x0f, 0x02), (0x0f, 0x0f, 0x02), + (0x18, 0x0f, 0x02), (0x1f, 0x0f, 0x02), (0x29, 0x0f, 0x02), (0x38, 0x0f, 0x03), + (0x03, 0x10, 0x02), (0x06, 0x10, 0x02), (0x0a, 0x10, 0x02), (0x0f, 0x10, 0x02), + (0x18, 0x10, 0x02), (0x1f, 0x10, 0x02), (0x29, 0x10, 0x02), (0x38, 0x10, 0x03), + ], + [ // 0xed + (0x03, 0x11, 0x02), (0x06, 0x11, 0x02), (0x0a, 0x11, 0x02), (0x0f, 0x11, 0x02), + (0x18, 0x11, 0x02), (0x1f, 0x11, 0x02), (0x29, 0x11, 0x02), (0x38, 0x11, 0x03), + (0x03, 0x12, 0x02), (0x06, 0x12, 0x02), (0x0a, 0x12, 0x02), (0x0f, 0x12, 0x02), + (0x18, 0x12, 0x02), (0x1f, 0x12, 0x02), (0x29, 0x12, 0x02), (0x38, 0x12, 0x03), + ], + [ // 0xee + (0x00, 0x13, 0x02), (0x00, 0x14, 0x02), (0x00, 0x15, 0x02), (0x00, 0x17, 0x02), + (0x00, 0x18, 0x02), (0x00, 0x19, 0x02), (0x00, 0x1a, 0x02), (0x00, 0x1b, 0x02), + (0x00, 0x1c, 0x02), (0x00, 0x1d, 0x02), (0x00, 0x1e, 0x02), (0x00, 0x1f, 0x02), + (0x00, 0x7f, 0x02), (0x00, 0xdc, 0x02), (0x00, 0xf9, 0x02), (0xfd, 0x00, 0x00), + ], + [ // 0xef + (0x01, 0x13, 0x02), (0x16, 0x13, 0x03), (0x01, 0x14, 0x02), (0x16, 0x14, 0x03), + (0x01, 0x15, 0x02), (0x16, 0x15, 0x03), (0x01, 0x17, 0x02), (0x16, 0x17, 0x03), + (0x01, 0x18, 0x02), (0x16, 0x18, 0x03), (0x01, 0x19, 0x02), (0x16, 0x19, 0x03), + (0x01, 0x1a, 0x02), (0x16, 0x1a, 0x03), (0x01, 0x1b, 0x02), (0x16, 0x1b, 0x03), + ], + [ // 0xf0 + (0x02, 0x13, 0x02), (0x09, 0x13, 0x02), (0x17, 0x13, 0x02), (0x28, 0x13, 0x03), + (0x02, 0x14, 0x02), (0x09, 0x14, 0x02), (0x17, 0x14, 0x02), (0x28, 0x14, 0x03), + (0x02, 0x15, 0x02), (0x09, 0x15, 0x02), (0x17, 0x15, 0x02), (0x28, 0x15, 0x03), + (0x02, 0x17, 0x02), (0x09, 0x17, 0x02), (0x17, 0x17, 0x02), (0x28, 0x17, 0x03), + ], + [ // 0xf1 + (0x03, 0x13, 0x02), (0x06, 0x13, 0x02), (0x0a, 0x13, 0x02), (0x0f, 0x13, 0x02), + (0x18, 0x13, 0x02), (0x1f, 0x13, 0x02), (0x29, 0x13, 0x02), (0x38, 0x13, 0x03), + (0x03, 0x14, 0x02), (0x06, 0x14, 0x02), (0x0a, 0x14, 0x02), (0x0f, 0x14, 0x02), + (0x18, 0x14, 0x02), (0x1f, 0x14, 0x02), (0x29, 0x14, 0x02), (0x38, 0x14, 0x03), + ], + [ // 0xf2 + (0x03, 0x15, 0x02), (0x06, 0x15, 0x02), (0x0a, 0x15, 0x02), (0x0f, 0x15, 0x02), + (0x18, 0x15, 0x02), (0x1f, 0x15, 0x02), (0x29, 0x15, 0x02), (0x38, 0x15, 0x03), + (0x03, 0x17, 0x02), (0x06, 0x17, 0x02), (0x0a, 0x17, 0x02), (0x0f, 0x17, 0x02), + (0x18, 0x17, 0x02), (0x1f, 0x17, 0x02), (0x29, 0x17, 0x02), (0x38, 0x17, 0x03), + ], + [ // 0xf3 + (0x02, 0x18, 0x02), (0x09, 0x18, 0x02), (0x17, 0x18, 0x02), (0x28, 0x18, 0x03), + (0x02, 0x19, 0x02), (0x09, 0x19, 0x02), (0x17, 0x19, 0x02), (0x28, 0x19, 0x03), + (0x02, 0x1a, 0x02), (0x09, 0x1a, 0x02), (0x17, 0x1a, 0x02), (0x28, 0x1a, 0x03), + (0x02, 0x1b, 0x02), (0x09, 0x1b, 0x02), (0x17, 0x1b, 0x02), (0x28, 0x1b, 0x03), + ], + [ // 0xf4 + (0x03, 0x18, 0x02), (0x06, 0x18, 0x02), (0x0a, 0x18, 0x02), (0x0f, 0x18, 0x02), + (0x18, 0x18, 0x02), (0x1f, 0x18, 0x02), (0x29, 0x18, 0x02), (0x38, 0x18, 0x03), + (0x03, 0x19, 0x02), (0x06, 0x19, 0x02), (0x0a, 0x19, 0x02), (0x0f, 0x19, 0x02), + (0x18, 0x19, 0x02), (0x1f, 0x19, 0x02), (0x29, 0x19, 0x02), (0x38, 0x19, 0x03), + ], + [ // 0xf5 + (0x03, 0x1a, 0x02), (0x06, 0x1a, 0x02), (0x0a, 0x1a, 0x02), (0x0f, 0x1a, 0x02), + (0x18, 0x1a, 0x02), (0x1f, 0x1a, 0x02), (0x29, 0x1a, 0x02), (0x38, 0x1a, 0x03), + (0x03, 0x1b, 0x02), (0x06, 0x1b, 0x02), (0x0a, 0x1b, 0x02), (0x0f, 0x1b, 0x02), + (0x18, 0x1b, 0x02), (0x1f, 0x1b, 0x02), (0x29, 0x1b, 0x02), (0x38, 0x1b, 0x03), + ], + [ // 0xf6 + (0x01, 0x1c, 0x02), (0x16, 0x1c, 0x03), (0x01, 0x1d, 0x02), (0x16, 0x1d, 0x03), + (0x01, 0x1e, 0x02), (0x16, 0x1e, 0x03), (0x01, 0x1f, 0x02), (0x16, 0x1f, 0x03), + (0x01, 0x7f, 0x02), (0x16, 0x7f, 0x03), (0x01, 0xdc, 0x02), (0x16, 0xdc, 0x03), + (0x01, 0xf9, 0x02), (0x16, 0xf9, 0x03), (0xfe, 0x00, 0x00), (0xff, 0x00, 0x00), + ], + [ // 0xf7 + (0x02, 0x1c, 0x02), (0x09, 0x1c, 0x02), (0x17, 0x1c, 0x02), (0x28, 0x1c, 0x03), + (0x02, 0x1d, 0x02), (0x09, 0x1d, 0x02), (0x17, 0x1d, 0x02), (0x28, 0x1d, 0x03), + (0x02, 0x1e, 0x02), (0x09, 0x1e, 0x02), (0x17, 0x1e, 0x02), (0x28, 0x1e, 0x03), + (0x02, 0x1f, 0x02), (0x09, 0x1f, 0x02), (0x17, 0x1f, 0x02), (0x28, 0x1f, 0x03), + ], + [ // 0xf8 + (0x03, 0x1c, 0x02), (0x06, 0x1c, 0x02), (0x0a, 0x1c, 0x02), (0x0f, 0x1c, 0x02), + (0x18, 0x1c, 0x02), (0x1f, 0x1c, 0x02), (0x29, 0x1c, 0x02), (0x38, 0x1c, 0x03), + (0x03, 0x1d, 0x02), (0x06, 0x1d, 0x02), (0x0a, 0x1d, 0x02), (0x0f, 0x1d, 0x02), + (0x18, 0x1d, 0x02), (0x1f, 0x1d, 0x02), (0x29, 0x1d, 0x02), (0x38, 0x1d, 0x03), + ], + [ // 0xf9 + (0x03, 0x1e, 0x02), (0x06, 0x1e, 0x02), (0x0a, 0x1e, 0x02), (0x0f, 0x1e, 0x02), + (0x18, 0x1e, 0x02), (0x1f, 0x1e, 0x02), (0x29, 0x1e, 0x02), (0x38, 0x1e, 0x03), + (0x03, 0x1f, 0x02), (0x06, 0x1f, 0x02), (0x0a, 0x1f, 0x02), (0x0f, 0x1f, 0x02), + (0x18, 0x1f, 0x02), (0x1f, 0x1f, 0x02), (0x29, 0x1f, 0x02), (0x38, 0x1f, 0x03), + ], + [ // 0xfa + (0x02, 0x7f, 0x02), (0x09, 0x7f, 0x02), (0x17, 0x7f, 0x02), (0x28, 0x7f, 0x03), + (0x02, 0xdc, 0x02), (0x09, 0xdc, 0x02), (0x17, 0xdc, 0x02), (0x28, 0xdc, 0x03), + (0x02, 0xf9, 0x02), (0x09, 0xf9, 0x02), (0x17, 0xf9, 0x02), (0x28, 0xf9, 0x03), + (0x00, 0x0a, 0x02), (0x00, 0x0d, 0x02), (0x00, 0x16, 0x02), (0x00, 0x00, 0x04), + ], + [ // 0xfb + (0x03, 0x7f, 0x02), (0x06, 0x7f, 0x02), (0x0a, 0x7f, 0x02), (0x0f, 0x7f, 0x02), + (0x18, 0x7f, 0x02), (0x1f, 0x7f, 0x02), (0x29, 0x7f, 0x02), (0x38, 0x7f, 0x03), + (0x03, 0xdc, 0x02), (0x06, 0xdc, 0x02), (0x0a, 0xdc, 0x02), (0x0f, 0xdc, 0x02), + (0x18, 0xdc, 0x02), (0x1f, 0xdc, 0x02), (0x29, 0xdc, 0x02), (0x38, 0xdc, 0x03), + ], + [ // 0xfc + (0x03, 0xf9, 0x02), (0x06, 0xf9, 0x02), (0x0a, 0xf9, 0x02), (0x0f, 0xf9, 0x02), + (0x18, 0xf9, 0x02), (0x1f, 0xf9, 0x02), (0x29, 0xf9, 0x02), (0x38, 0xf9, 0x03), + (0x01, 0x0a, 0x02), (0x16, 0x0a, 0x03), (0x01, 0x0d, 0x02), (0x16, 0x0d, 0x03), + (0x01, 0x16, 0x02), (0x16, 0x16, 0x03), (0x00, 0x00, 0x04), (0x00, 0x00, 0x05), + ], + [ // 0xfd + (0x02, 0x0a, 0x02), (0x09, 0x0a, 0x02), (0x17, 0x0a, 0x02), (0x28, 0x0a, 0x03), + (0x02, 0x0d, 0x02), (0x09, 0x0d, 0x02), (0x17, 0x0d, 0x02), (0x28, 0x0d, 0x03), + (0x02, 0x16, 0x02), (0x09, 0x16, 0x02), (0x17, 0x16, 0x02), (0x28, 0x16, 0x03), + (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x05), + ], + [ // 0xfe + (0x03, 0x0a, 0x02), (0x06, 0x0a, 0x02), (0x0a, 0x0a, 0x02), (0x0f, 0x0a, 0x02), + (0x18, 0x0a, 0x02), (0x1f, 0x0a, 0x02), (0x29, 0x0a, 0x02), (0x38, 0x0a, 0x03), + (0x03, 0x0d, 0x02), (0x06, 0x0d, 0x02), (0x0a, 0x0d, 0x02), (0x0f, 0x0d, 0x02), + (0x18, 0x0d, 0x02), (0x1f, 0x0d, 0x02), (0x29, 0x0d, 0x02), (0x38, 0x0d, 0x03), + ], + [ // 0xff + (0x03, 0x16, 0x02), (0x06, 0x16, 0x02), (0x0a, 0x16, 0x02), (0x0f, 0x16, 0x02), + (0x18, 0x16, 0x02), (0x1f, 0x16, 0x02), (0x29, 0x16, 0x02), (0x38, 0x16, 0x03), + (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), + (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x04), (0x00, 0x00, 0x05), + ], +]; diff --git a/ylong_http/src/huffman/mod.rs b/ylong_http/src/huffman/mod.rs new file mode 100644 index 0000000..d0cfa1e --- /dev/null +++ b/ylong_http/src/huffman/mod.rs @@ -0,0 +1,412 @@ +//! [Huffman coding] implementation of the HTTP/2 protocol. +//! +//! [Huffman Coding]: https://en.wikipedia.org/wiki/Huffman_coding +//! +//! # What is Huffman coding +//! In computer science and information theory, a `Huffman code` is a particular +//! type of optimal prefix code that is commonly used for lossless data +//! compression. The process of finding or using such a code proceeds by means +//! of `Huffman coding`, an algorithm developed by David A. Huffman while he was +//! a Sc.D. student at MIT, and published in the 1952 paper "A Method for the +//! Construction of Minimum-Redundancy Codes". +//! +//! # Huffman code in Http/2 +//! There is a table of Huffman code in `RFC7541`. This [Huffman code] was +//! generated from statistics obtained on a large sample of HTTP headers. It is +//! a canonical Huffman code with some tweaking to ensure that no symbol has a +//! unique code length. +//! +//! [Huffman Code]: https://www.rfc-editor.org/rfc/rfc7541.html#ref-HUFFMAN + +// TODO: Introduction of `Huffman code in Http/3`. + +mod consts; + +use consts::{HUFFMAN_DECODE, HUFFMAN_ENCODE}; + +use core::cmp::Ordering; + +/// Converts a string to a Huffman code, and then put it into the +/// specified `Vec`. +pub(crate) fn huffman_encode(src: &[u8], dst: &mut Vec) { + // We use `state` to hold temporary encoding state. + // We use `unfilled` to represent the remaining number of bits that is not + // filled. Each time any bytes are encoded, we will store the result bits + // in `state`. + // + // When `state` is not full, we add the result bits to `Unfilled`. + // `state`: + // +----------+----------+----------------------------+ + // | Result A | Result B | Unfilled | + // +----------+----------+----------------------------+ + // |<------------------- 64 bits -------------------> + // + // When the length of the result bits is greater than the length of `Unfilled`, + // we will truncate it. + // `state`: + // +---------------------+----------------------------+ + // | | A part of Result C | -> Output it. + // +---------------------+----------------------------+ + // |<-------------- full 64 bits -------------------> + // + // Final `state`: + // +--------------------------------+-----------------+ + // | The remaining part of Result C | Unfilled | + // +--------------------------------+-----------------+ + + let mut state = 0u64; + // The initial value of `unfilled` is equal to the number of bits in the `state`. + let mut unfilled = 64; + + for byte in src.iter() { + let (nbits, code) = HUFFMAN_ENCODE[*byte as usize]; + match unfilled.cmp(&nbits) { + Ordering::Greater => { + state |= code << (unfilled - nbits); + unfilled -= nbits; + } + Ordering::Equal => { + state |= code; + dst.extend_from_slice(&state.to_be_bytes()); + state = 0; + unfilled = 64; + } + // We rotate the `code` to the right, and we will get `rotate`. + // `rotate`: + // +---------+-----------------+----------+ + // | Parts A | | Parts B | + // +---------+-----------------+----------+ + // `mask`: + // +---------+-----------------+----------+ + // | 000...0 | 111...1 | + // +---------+-----------------+----------+ + // `rotate` & mask => Parts B + // `rotate` & !mask => Parts A + Ordering::Less => { + let rotate = code.rotate_right((nbits - unfilled) as u32); + let mask = u64::MAX >> (64 - unfilled); + state |= rotate & mask; + dst.extend_from_slice(&state.to_be_bytes()); + state = rotate & !mask; + unfilled = 64 - (nbits - unfilled); + } + } + } + + // At the end of character decoding, if the last byte is not completely + // filled, it needs to be filled with `0b1`. + if unfilled != 64 { + state |= u64::MAX >> (64 - unfilled); + let bytes = &state.to_be_bytes(); + // Here we only need to output the filled bytes, not all the `state`. + let len = (8 - (unfilled >> 3)) as usize; + dst.extend_from_slice(&bytes.as_slice()[..len]); + } +} + +/// Converts a Huffman code into a literal string at one time, and then put it into the +/// specified `Vec`. +/// +/// The algorithm comes from crate [h2]. +/// +/// [h2]: https://crates.io/crates/h2 +pub(crate) fn huffman_decode(src: &[u8], dst: &mut Vec) -> Result<(), HuffmanDecodeError> { + // We use a state machine to parse Huffman code. + // We have a `HUFFMAN_DECODE` table, which contains three elements: + // `State`, `Decoded Byte`, and `Flags`. + // + // `State` represents the current state. It can be considered as the value + // composed of the remaining unparsed bits after the last parsing is completed. + // + // `Decoded Byte` represents the decoded character when the `Decoded` bit + // of `Flags` is set to `0b1`. + // + // `Flags` contains three bits: `MAYBE_EOS`(0x1), `DECODED`(0x2), `ERROR`(0x4). + // + // When `MAYBE_EOS` is set, it means that the current character may be the + // end of the literal string. + // + // When `DECODED` is set, it means that the character at `Decoded Bytes` is + // the current decoded character. + // + // When `ERROR` is set, it means that there is a wrong Huffman code in the + // byte sequence. + // + // We use a variable called `state` to hold the current state. Its initial + // value is 0. Every time we take out 4 bits from the byte sequence that + // needs to be parsed and look it up in the `HUFFMAN_DECODE` table. Then we + // will get `state`, `byte` and `flags`. We can judge the current state + // through `flags` and choose to continue decoding or return an error. + + let (state, flags) = huffman_decode_inner(src, dst, 0, 0)?; + + // The decoding operation succeeds in the following two cases: + // 1. `state` is 0. It means that all bits are parsed normally. + // 2. `state` is not 0, but `maybe_eos` is true. It means that not all bits + // are parsed and the last byte can be the terminator. The remaining bits are + // allowed because a part of the padding is done when http/2 performs + // Huffman encoding. + if state != 0 && (flags & 0x1) == 0 { + return Err(HuffmanDecodeError::InvalidHuffmanCode); + } + + Ok(()) +} + +fn huffman_decode_inner( + src: &[u8], + dst: &mut Vec, + state: u8, + flags: u8, +) -> Result<(u8, u8), HuffmanDecodeError> { + let (mut state, mut _result, mut flags) = (state, 0, flags); + + for byte in src.iter() { + let left = byte >> 4; + let right = byte & 0xf; + + (state, _result, flags) = HUFFMAN_DECODE[state as usize][left as usize]; + if (flags & 0x4) == 0x4 { + return Err(HuffmanDecodeError::InvalidHuffmanCode); + } + if (flags & 0x2) == 0x2 { + dst.push(_result); + } + + (state, _result, flags) = HUFFMAN_DECODE[state as usize][right as usize]; + if (flags & 0x4) == 0x4 { + return Err(HuffmanDecodeError::InvalidHuffmanCode); + } + if (flags & 0x2) == 0x2 { + dst.push(_result); + } + } + Ok((state, flags)) +} + +/// Converts a Huffman code into a literal string, and then put it into the +/// specified `Vec`. Users can split the string into multiple slices and +/// then pass them into `HuffmanDecoder` to get the result. +/// +/// The algorithm comes from crate [h2]. +/// +/// [h2]: https://crates.io/crates/h2 +pub(crate) struct HuffmanDecoder { + state: u8, + flags: u8, + vec: Vec, +} + +impl HuffmanDecoder { + /// Creates a new, empty `HuffmanDecoder`. + pub(crate) fn new() -> Self { + Self { + state: 0, + flags: 0, + vec: Vec::new(), + } + } + + /// Decodes input string. Stop when the `src` is used up. + pub(crate) fn decode(&mut self, src: &[u8]) -> Result<(), HuffmanDecodeError> { + (self.state, self.flags) = + huffman_decode_inner(src, &mut self.vec, self.state, self.flags)?; + Ok(()) + } + + /// Finishes decoding and get the decoded result. + pub(crate) fn finish(self) -> Result, HuffmanDecodeError> { + if self.state != 0 && (self.flags & 0x1) == 0 { + return Err(HuffmanDecodeError::InvalidHuffmanCode); + } + Ok(self.vec) + } +} + +/// Possible errors in Huffman decoding operations. +#[derive(Debug)] +pub(crate) enum HuffmanDecodeError { + InvalidHuffmanCode, +} + +#[cfg(test)] +mod ut_huffman { + use super::{huffman_decode, huffman_encode, HuffmanDecoder}; + use crate::test_util::decode; + + /// UT test cases for `huffman_encode`. + /// + /// # Brief + /// 1. Calls `huffman_encode` function, passing in the specified parameters. + /// 2. Checks if the test results are correct. + #[test] + fn ut_huffman_encode() { + rfc7541_test_cases(); + + macro_rules! huffman_test_case { + ($ctn: expr, $res: expr $(,)?) => { + let mut vec = Vec::new(); + huffman_encode($ctn.as_bytes(), &mut vec); + assert_eq!(vec, decode($res).unwrap()) + }; + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.4.1 First Request + huffman_test_case!("www.example.com", "f1e3c2e5f23a6ba0ab90f4ff"); + + // C.4.2 Second Request + huffman_test_case!("no-cache", "a8eb10649cbf"); + + // C.4.3 Third Request + huffman_test_case!("custom-value", "25a849e95bb8e8b4bf"); + + // C.6.1 First Response + huffman_test_case!("302", "6402"); + huffman_test_case!("private", "aec3771a4b"); + huffman_test_case!( + "Mon, 21 Oct 2013 20:13:21 GMT", + "d07abe941054d444a8200595040b8166e082a62d1bff" + ); + huffman_test_case!( + "https://www.example.com", + "9d29ad171863c78f0b97c8e9ae82ae43d3" + ); + + // C.6.2 Second Response + huffman_test_case!("307", "640eff"); + + // C.6.3 Third Response + huffman_test_case!("gzip", "9bd9ab"); + huffman_test_case!( + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + "94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007", + ); + } + } + + /// UT test cases for `huffman_decode`. + /// + /// # Brief + /// 1. Calls `huffman_decode` function, passing in the specified parameters. + /// 2. Checks if the test results are correct. + #[test] + fn ut_huffman_decode() { + rfc7541_test_cases(); + + macro_rules! huffman_test_case { + ($ctn: expr, $res: expr $(,)?) => { + let mut vec = Vec::new(); + huffman_decode(decode($ctn).unwrap().as_slice(), &mut vec).unwrap(); + assert_eq!(vec.as_slice(), $res.as_bytes()) + }; + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.4.1 First Request + huffman_test_case!("f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"); + + // C.4.2 Second Request + huffman_test_case!("a8eb10649cbf", "no-cache"); + + // C.4.3 Third Request + huffman_test_case!("25a849e95bb8e8b4bf", "custom-value"); + + // C.6.1 First Response + huffman_test_case!("6402", "302"); + huffman_test_case!("aec3771a4b", "private"); + huffman_test_case!( + "d07abe941054d444a8200595040b8166e082a62d1bff", + "Mon, 21 Oct 2013 20:13:21 GMT" + ); + huffman_test_case!( + "9d29ad171863c78f0b97c8e9ae82ae43d3", + "https://www.example.com", + ); + + // C.6.2 Second Response + huffman_test_case!("640eff", "307"); + + // C.6.3 Third Response + huffman_test_case!("9bd9ab", "gzip"); + huffman_test_case!( + "94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1" + ); + } + } + + /// UT test cases for `HuffmanDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `HuffmanDecoder`. + /// 1. Calls `decode` and `finish` function, passing in the specified parameters. + /// 2. Checks if the test results are correct. + #[test] + fn ut_huffman_decoder() { + rfc7541_test_cases(); + slices_test(); + + macro_rules! huffman_test_case { + ($content: expr, $result: expr) => {{ + let mut decoder = HuffmanDecoder::new(); + for cont in $content.as_slice().iter() { + let bytes = decode(cont).unwrap(); + assert!(decoder.decode(&bytes).is_ok()); + } + match decoder.finish() { + Ok(vec) => vec == $result.as_bytes(), + _ => panic!(), + } + }}; + } + + /// The following test cases are from RFC7541. + fn rfc7541_test_cases() { + // C.4.1 First Request + huffman_test_case!(["f1e3c2e5f23a6ba0ab90f4ff"], "www.example.com"); + + // C.4.2 Second Request + huffman_test_case!(["a8eb10649cbf"], "no-cache"); + + // C.4.3 Third Request + huffman_test_case!(["25a849e95bb8e8b4bf"], "custom-value"); + + // C.6.1 First Response + huffman_test_case!(["6402"], "302"); + huffman_test_case!(["aec3771a4b"], "private"); + huffman_test_case!( + ["d07abe941054d444a8200595040b8166e082a62d1bff"], + "Mon, 21 Oct 2013 20:13:21 GMT" + ); + huffman_test_case!( + ["9d29ad171863c78f0b97c8e9ae82ae43d3"], + "https://www.example.com" + ); + + // C.6.2 Second Response + huffman_test_case!(["640eff"], "307"); + + // C.6.3 Third Response + huffman_test_case!(["9bd9ab"], "gzip"); + huffman_test_case!( + ["94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007"], + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1" + ); + } + + /// The following test cases is for testing segmented byte slices. + fn slices_test() { + // Fragmentation + huffman_test_case!(["a8", "eb", "10", "64", "9c", "bf"], "no-cache"); + + // Fragmentation + Blank + huffman_test_case!( + ["", "", "", "", "a8", "", "eb", "10", "", "64", "9c", "", "bf", "", ""], + "no-cache" + ); + } + } +} diff --git a/ylong_http/src/lib.rs b/ylong_http/src/lib.rs new file mode 100644 index 0000000..45b3d8d --- /dev/null +++ b/ylong_http/src/lib.rs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![allow(dead_code)] +#![allow(unused_imports)] + +//! `ylong_http` provides various basic components that `HTTP` needs to use. +//! You can use these components to build a HTTP client, a HTTP server, etc. +//! +//! # Support HTTP Version +//! - `HTTP1.1` +//! +// TODO: Need doc. + +#[cfg(feature = "http1_1")] +pub mod h1; + +#[cfg(feature = "http2")] +pub mod h2; + +/// Module that contains the functionality for HTTP/3 support. +#[cfg(feature = "http3")] +pub mod h3; + +#[cfg(feature = "huffman")] +mod huffman; + +pub mod body; +pub mod error; +pub mod headers; +pub mod request; +pub mod response; +pub mod version; + +#[cfg(test)] +pub(crate) mod test_util; diff --git a/ylong_http/src/request/method.rs b/ylong_http/src/request/method.rs new file mode 100644 index 0000000..377810f --- /dev/null +++ b/ylong_http/src/request/method.rs @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Method`]. +//! +//! The request method token is the primary source of request semantics; +//! it indicates the purpose for which the client has made this request and what +//! is expected by the client as a successful result. +//! +//! [`Method`]: https://httpwg.org/specs/rfc9110.html#methods +//! +//! # Examples +//! +//! ``` +//! use ylong_http::request::method::Method; +//! +//! assert_eq!(Method::GET.as_str(), "GET"); +//! ``` + +use crate::error::{ErrorKind, HttpError}; +use core::convert::TryFrom; + +/// HTTP `Method` implementation. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::request::method::Method; +/// +/// assert_eq!(Method::GET.as_str(), "GET"); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Method(Inner); + +impl Method { + /// Transfer a current representation of the target resource. + /// + /// See [`RFC9110 9.3.1`] for more details. + /// + /// [`RFC9110 9.3.1`]: (https://httpwg.org/specs/rfc9110.html#GET) + pub const GET: Self = Self(Inner::Get); + + /// Same as `GET`, but do not transfer the response content. + /// + /// See [`RFC9110 9.3.2`] for more details. + /// + /// [`RFC9110 9.3.2`]: https://httpwg.org/specs/rfc9110.html#HEAD + pub const HEAD: Self = Self(Inner::Head); + + /// Perform resource-specific processing on the request content. + /// + /// See [`RFC9110 9.3.3`] for more details. + /// + /// [`RFC9110 9.3.3`]: https://httpwg.org/specs/rfc9110.html#POST + pub const POST: Self = Self(Inner::Post); + + /// Replace all current representations of the target resource with the + /// request content. + /// + /// See [`RFC9110 9.3.4`] for more details. + /// + /// [`RFC9110 9.3.4`]: https://httpwg.org/specs/rfc9110.html#PUT + pub const PUT: Self = Self(Inner::Put); + + /// Remove all current representations of the target resource. + /// + /// See [`RFC9110 9.3.5`] for more details. + /// + /// [`RFC9110 9.3.5`]: https://httpwg.org/specs/rfc9110.html#DELETE + pub const DELETE: Self = Self(Inner::Delete); + + /// Establish a tunnel to the server identified by the target resource. + /// + /// See [`RFC9110 9.3.6`] for more details. + /// + /// [`RFC9110 9.3.6`]: https://httpwg.org/specs/rfc9110.html#CONNECT + pub const CONNECT: Self = Self(Inner::Connect); + + /// Describe the communication options for the target resource. + /// + /// See [`RFC9110 9.3.7`] for more details. + /// + /// [`RFC9110 9.3.7`]: https://httpwg.org/specs/rfc9110.html#OPTIONS + pub const OPTIONS: Self = Self(Inner::Options); + + /// Perform a message loop-back test along the path to the target resource. + /// + /// See [`RFC9110 9.3.8`] for more details. + /// + /// [`RFC9110 9.3.8`]: https://httpwg.org/specs/rfc9110.html#TRACE + pub const TRACE: Self = Self(Inner::Trace); + + /// Tries converting &[u8] to `Method`. Only uppercase letters are supported. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::method::Method; + /// + /// let method = Method::from_bytes(b"GET").unwrap(); + /// assert_eq!(method.as_str(), "GET"); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 3 || bytes.len() > 7 { + return Err(ErrorKind::InvalidInput.into()); + } + match bytes[0] { + b'G' if b"ET" == &bytes[1..] => Ok(Method::GET), + b'P' => match bytes[1] { + b'U' if b"T" == &bytes[2..] => Ok(Method::PUT), + b'O' if b"ST" == &bytes[2..] => Ok(Method::POST), + _ => Err(ErrorKind::InvalidInput.into()), + }, + b'H' if b"EAD" == &bytes[1..] => Ok(Method::HEAD), + b'T' if b"RACE" == &bytes[1..] => Ok(Method::TRACE), + b'D' if b"ELETE" == &bytes[1..] => Ok(Method::DELETE), + b'O' if b"PTIONS" == &bytes[1..] => Ok(Method::OPTIONS), + b'C' if b"ONNECT" == &bytes[1..] => Ok(Method::CONNECT), + _ => Err(ErrorKind::InvalidInput.into()), + } + } + + /// Converts `Method` to `&str` in uppercase. + /// + /// # Examples + /// ``` + /// use ylong_http::request::method::Method; + /// + /// assert_eq!(Method::GET.as_str(), "GET"); + /// ``` + pub fn as_str(&self) -> &str { + match self.0 { + Inner::Get => "GET", + Inner::Head => "HEAD", + Inner::Post => "POST", + Inner::Put => "PUT", + Inner::Delete => "DELETE", + Inner::Options => "OPTIONS", + Inner::Trace => "TRACE", + Inner::Connect => "CONNECT", + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum Inner { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, +} + +impl<'a> TryFrom<&'a [u8]> for Method { + type Error = HttpError; + + fn try_from(t: &'a [u8]) -> Result { + Self::from_bytes(t) + } +} + +impl<'a> TryFrom<&'a str> for Method { + type Error = HttpError; + + fn try_from(t: &'a str) -> Result { + Self::from_bytes(t.as_bytes()) + } +} + +#[cfg(test)] +mod ut_method { + use super::Method; + use crate::error::{ErrorKind, HttpError}; + + /// UT test cases for `Method::as_str`. + /// + /// # Brief + /// 1. Calls `as_str` for all method kinds. + /// 2. Checks the results. + #[test] + fn ut_method_as_str() { + assert_eq!(Method::GET.as_str(), "GET"); + assert_eq!(Method::HEAD.as_str(), "HEAD"); + assert_eq!(Method::POST.as_str(), "POST"); + assert_eq!(Method::PUT.as_str(), "PUT"); + assert_eq!(Method::DELETE.as_str(), "DELETE"); + assert_eq!(Method::OPTIONS.as_str(), "OPTIONS"); + assert_eq!(Method::TRACE.as_str(), "TRACE"); + assert_eq!(Method::CONNECT.as_str(), "CONNECT"); + } + + /// UT test cases for `Method::from_bytes`. + /// + /// # Brief + /// 1. Calls `from_bytes` and pass in various types of parameters. + /// 2. Checks the results. + #[test] + fn ut_method_from_bytes() { + // Normal Test Cases: + assert_eq!(Method::from_bytes(b"GET").unwrap(), Method::GET); + assert_eq!(Method::from_bytes(b"HEAD").unwrap(), Method::HEAD); + assert_eq!(Method::from_bytes(b"POST").unwrap(), Method::POST); + assert_eq!(Method::from_bytes(b"PUT").unwrap(), Method::PUT); + assert_eq!(Method::from_bytes(b"DELETE").unwrap(), Method::DELETE); + assert_eq!(Method::from_bytes(b"OPTIONS").unwrap(), Method::OPTIONS); + assert_eq!(Method::from_bytes(b"TRACE").unwrap(), Method::TRACE); + assert_eq!(Method::from_bytes(b"CONNECT").unwrap(), Method::CONNECT); + + // Exception Test Cases: + // 1. Empty bytes slice. + assert_eq!( + Method::from_bytes(b""), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 2. The length of bytes slice is less than 3. + assert_eq!( + Method::from_bytes(b"G"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 3. The length of bytes slice is more than 7. + assert_eq!( + Method::from_bytes(b"CONNECTT"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 4. Mixed case letters inside the bytes slice. + assert_eq!( + Method::from_bytes(b"Get"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 5. Other error branch coverage test cases. + assert_eq!( + Method::from_bytes(b"PATC"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + Method::from_bytes(b"PATCHH"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + Method::from_bytes(b"ABCDEFG"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } +} diff --git a/ylong_http/src/request/mod.rs b/ylong_http/src/request/mod.rs new file mode 100644 index 0000000..011ee0f --- /dev/null +++ b/ylong_http/src/request/mod.rs @@ -0,0 +1,872 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Request`][http_request]. +//! +//! This module provides [`Request`][my_request], [`RequestBuilder`] and +//! [`RequestPart`]. +//! +//! [http_request]: https://www.rfc-editor.org/rfc/rfc9112.html#request.line +//! [my_request]: Request +//! [`RequestBuilder`]: RequestBuilder +//! [`RequestPart`]: RequestPart +//! +//! # Examples +//! +//! ``` +//! use ylong_http::request::method::Method; +//! use ylong_http::request::{Request, RequestBuilder}; +//! use ylong_http::version::Version; +//! +//! // Uses `RequestBuilder` to construct a `Request`. +//! let request = Request::builder() +//! .method("GET") +//! .url("www.example.com") +//! .version("HTTP/1.1") +//! .header("ACCEPT","text/html") +//! .append_header("ACCEPT","application/xml") +//! .body(()) +//! .unwrap(); +//! +//! assert_eq!(request.method(), &Method::GET); +//! assert_eq!(request.uri().to_string(), "www.example.com"); +//! assert_eq!(request.version(), &Version::HTTP1_1); +//! assert_eq!( +//! request.headers().get("accept").unwrap().to_str().unwrap(), +//! "text/html, application/xml" +//! ); +//! ``` + +pub mod method; +pub mod uri; + +use method::Method; +use uri::Uri; + +use crate::body::MultiPart; +use crate::error::{ErrorKind, HttpError}; +use crate::headers::{Header, HeaderName, HeaderValue, Headers}; +use crate::version::Version; +use core::convert::TryFrom; + +/// HTTP `Request`. A `Request` consists of a request line and a body. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::request::Request; +/// +/// let request = Request::new("this is a body"); +/// assert_eq!(request.body(), &"this is a body"); +/// ``` +pub struct Request { + part: RequestPart, + body: T, +} + +impl Request<()> { + /// Creates a new, default `RequestBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let builder = Request::builder(); + /// ``` + pub fn builder() -> RequestBuilder { + RequestBuilder::new() + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `GET`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::get("www.example.com").body(()).unwrap(); + /// ``` + pub fn get(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::GET).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `HEAD`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::head("www.example.com").body(()).unwrap(); + /// ``` + pub fn head(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::HEAD).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `POST`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::post("www.example.com").body(()).unwrap(); + /// ``` + pub fn post(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::POST).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `PUT`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::put("www.example.com").body(()).unwrap(); + /// ``` + pub fn put(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::PUT).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `DELETE`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::delete("www.example.com").body(()).unwrap(); + /// ``` + pub fn delete(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::DELETE).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `CONNECT`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::connect("www.example.com").body(()).unwrap(); + /// ``` + pub fn connect(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::CONNECT).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `OPTIONS`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::options("www.example.com").body(()).unwrap(); + /// ``` + pub fn options(uri: T) -> RequestBuilder + where + Uri: TryFrom, + >::Error: Into, + { + RequestBuilder::new().method(Method::OPTIONS).url(uri) + } + + /// Creates a `RequestBuilder` for the given `Uri` with method set to `TRACE`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::trace("www.example.com").body(()).unwrap(); + /// ``` + pub fn trace(uri: T) -> RequestBuilder + where + Uri: TryFrom, + HttpError: From<>::Error>, + { + RequestBuilder::new().method(Method::TRACE).url(uri) + } +} + +impl Request { + /// Creates a new, default `Request` with options set default. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let builder = RequestBuilder::new(); + /// ``` + pub fn new(body: T) -> Self { + Request { + part: Default::default(), + body, + } + } + + /// Gets an immutable reference to the `Method`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let method = request.method(); + /// ``` + pub fn method(&self) -> &Method { + &self.part.method + } + + /// Gets a mutable reference to the `Method`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let method = request.method_mut(); + /// ``` + pub fn method_mut(&mut self) -> &mut Method { + &mut self.part.method + } + + /// Gets an immutable reference to the `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let uri = request.uri(); + /// ``` + pub fn uri(&self) -> &Uri { + &self.part.uri + } + + /// Gets a mutable reference to the `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let uri = request.uri_mut(); + /// ``` + pub fn uri_mut(&mut self) -> &mut Uri { + &mut self.part.uri + } + + /// Gets an immutable reference to the `Version`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let version = request.version(); + /// ``` + pub fn version(&self) -> &Version { + &self.part.version + } + + /// Gets a mutable reference to the `Version`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let version = request.version_mut(); + /// ``` + pub fn version_mut(&mut self) -> &mut Version { + &mut self.part.version + } + + /// Gets an immutable reference to the `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let headers = request.headers(); + /// ``` + pub fn headers(&self) -> &Headers { + &self.part.headers + } + + /// Gets a mutable reference to the `Headers`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let headers = request.headers_mut(); + /// ``` + pub fn headers_mut(&mut self) -> &mut Headers { + &mut self.part.headers + } + + /// Gets an immutable reference to the `RequestPart`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let part = request.part(); + /// ``` + pub fn part(&self) -> &RequestPart { + &self.part + } + + /// Gets a mutable reference to the `RequestPart`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let part = request.part_mut(); + /// ``` + pub fn part_mut(&mut self) -> &RequestPart { + &mut self.part + } + + /// Gets an immutable reference to the `Body`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let request = Request::new(()); + /// let body = request.body(); + /// ``` + pub fn body(&self) -> &T { + &self.body + } + + /// Gets a mutable reference to the `Body`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::Request; + /// + /// let mut request = Request::new(()); + /// let body = request.body_mut(); + /// ``` + pub fn body_mut(&mut self) -> &mut T { + &mut self.body + } + + /// Splits `Request` into `RequestPart` and `Body`. + /// + /// # Examples + /// ``` + /// use ylong_http::request::{Request, RequestPart}; + /// + /// let request = Request::new(()); + /// let (part, body) = request.into_parts(); + /// ``` + pub fn into_parts(self) -> (RequestPart, T) { + (self.part, self.body) + } + + /// Combines `RequestPart` and `Body` into a `Request`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::{Request, RequestPart}; + /// + /// let part = RequestPart::default(); + /// let body = (); + /// let request = Request::from_raw_parts(part, body); + /// ``` + pub fn from_raw_parts(part: RequestPart, body: T) -> Request { + Request { part, body } + } +} + +impl Clone for Request { + fn clone(&self) -> Self { + Request::from_raw_parts(self.part.clone(), self.body.clone()) + } +} + +/// A builder which is used to construct `Request`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http::headers::Headers; +/// use ylong_http::request::method::Method; +/// use ylong_http::request::RequestBuilder; +/// use ylong_http::version::Version; +/// +/// let request = RequestBuilder::new() +/// .method("GET") +/// .url("www.example.com") +/// .version("HTTP/1.1") +/// .header("ACCEPT","text/html") +/// .append_header("ACCEPT","application/xml") +/// .body(()) +/// .unwrap(); +/// +/// assert_eq!(request.method(), &Method::GET); +/// assert_eq!(request.uri().to_string(), "www.example.com"); +/// assert_eq!(request.version(), &Version::HTTP1_1); +/// assert_eq!( +/// request.headers().get("accept").unwrap().to_str().unwrap(), +/// "text/html, application/xml" +/// ); +/// ``` +pub struct RequestBuilder { + part: Result, +} + +impl RequestBuilder { + /// Creates a new, default `RequestBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let builder = RequestBuilder::new(); + /// ``` + pub fn new() -> Self { + RequestBuilder { + part: Ok(RequestPart::default()), + } + } + + /// Sets the `Method` of the `Request`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let builder = RequestBuilder::new().method("GET"); + /// ``` + pub fn method(mut self, method: T) -> Self + where + Method: TryFrom, + >::Error: Into, + { + self.part = self.part.and_then(move |mut part| { + part.method = Method::try_from(method).map_err(Into::into)?; + Ok(part) + }); + self + } + + /// Sets the `Uri` of the `Request`. `Uri` does not provide a default value, + /// so it must be set. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let builder = RequestBuilder::new().url("www.example.com"); + /// ``` + pub fn url(mut self, uri: T) -> Self + where + Uri: TryFrom, + >::Error: Into, + { + self.part = self.part.and_then(move |mut part| { + part.uri = Uri::try_from(uri).map_err(Into::into)?; + Ok(part) + }); + self + } + + /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().version("HTTP/1.1"); + /// ``` + pub fn version(mut self, version: T) -> Self + where + Version: TryFrom, + >::Error: Into, + { + self.part = self.part.and_then(move |mut part| { + part.version = Version::try_from(version).map_err(Into::into)?; + Ok(part) + }); + self + } + + /// Adds a `Header` to `Request`. Overwrites `HeaderValue` if the + /// `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().header("ACCEPT","text/html"); + /// ``` + pub fn header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.part = self.part.and_then(move |mut part| { + part.headers.insert(name, value)?; + Ok(part) + }); + self + } + + /// Adds a `Header` to `Request`. Appends `HeaderValue` to the end of + /// previous `HeaderValue` if the `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().append_header("ACCEPT","text/html"); + /// ``` + pub fn append_header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.part = self.part.and_then(move |mut part| { + part.headers.append(name, value)?; + Ok(part) + }); + self + } + + /// Try to create a `Request` based on the incoming `body`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().body(()).unwrap(); + /// ``` + pub fn body(self, body: T) -> Result, HttpError> { + Ok(Request { + part: self.part?, + body, + }) + } + + /// Creates a `Request` that uses this `RequestBuilder` configuration and + /// the provided `Multipart`. You can also provide a `Uploader` + /// as the body. + /// + /// # Error + /// + /// This method fails if some configurations are wrong. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::{MultiPart, Part}; + /// # use ylong_http::request::RequestBuilder; + /// + /// # fn create_request_with_multipart(multipart: MultiPart) { + /// let request = RequestBuilder::new().multipart(multipart).unwrap(); + /// # } + /// ``` + pub fn multipart(self, body: T) -> Result, HttpError> + where + T: AsRef, + { + let value = format!("multipart/form-data; boundary={}", body.as_ref().boundary()); + + let mut part = self.part?; + let _ = part.headers.insert( + "Content-Type", + HeaderValue::try_from(value.as_str()) + .map_err(|_| HttpError::from(ErrorKind::InvalidInput))?, + ); + + if let Some(size) = body.as_ref().total_bytes() { + let _ = part.headers.insert( + "Content-Length", + HeaderValue::try_from(format!("{size}").as_str()) + .map_err(|_| HttpError::from(ErrorKind::InvalidInput))?, + ); + } + + Ok(Request { part, body }) + } +} + +impl Default for RequestBuilder { + fn default() -> Self { + Self::new() + } +} + +/// `RequestPart`, which is called [`Request Line`] in [`RFC9112`]. +/// +/// A request-line begins with a method token, followed by a single space (SP), +/// the request-target, and another single space (SP), and ends with the protocol +/// version. +/// +/// [`RFC9112`]: https://httpwg.org/specs/rfc9112.html +/// [`Request Line`]: https://httpwg.org/specs/rfc9112.html#request.line +/// +/// # Examples +/// +/// ``` +/// use ylong_http::request::Request; +/// +/// let request = Request::new(()); +/// +/// // Uses `Request::into_parts` to get a `RequestPart`. +/// let (part, _) = request.into_parts(); +/// ``` +#[derive(Clone, Debug)] +pub struct RequestPart { + /// HTTP URI implementation + pub uri: Uri, + /// HTTP Method implementation + pub method: Method, + /// HTTP Version implementation + pub version: Version, + /// HTTP Headers, which is called Fields in RFC9110. + pub headers: Headers, +} + +impl Default for RequestPart { + fn default() -> Self { + Self { + uri: Uri::http(), + method: Method::GET, + version: Version::HTTP1_1, + headers: Headers::new(), + } + } +} + +#[cfg(test)] +mod ut_request { + use super::{Method, Request, RequestBuilder, RequestPart, Uri}; + use crate::headers::Headers; + use crate::version::Version; + use core::convert::TryFrom; + + /// UT test cases for `RequestBuilder::build`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `RequestBuilder::build`. + /// 2. Sets method by calling `RequestBuilder::method`. + /// 3. Sets uri by calling `RequestBuilder::uri`. + /// 4. Sets version by calling `RequestBuilder::version`. + /// 5. Sets header by calling `RequestBuilder::insert_header`. + /// 6. Sets header by calling `RequestBuilder::append_header`. + /// 7. Gets method by calling `Request::method`. + /// 8. Gets uri by calling `Request::uri`. + /// 9. Gets version by calling `Request::version`. + /// 10. Gets headers by calling `Request::headers`. + /// 11. Checks if the test result is correct. + #[test] + fn ut_request_builder_build() { + let request = RequestBuilder::new() + .method("GET") + .url("www.baidu.com") + .version("HTTP/1.1") + .header("ACCEPT", "text/html") + .append_header("ACCEPT", "application/xml") + .body(()) + .unwrap(); + + let mut new_headers = Headers::new(); + let _ = new_headers.insert("accept", "text/html"); + let _ = new_headers.append("accept", "application/xml"); + + assert_eq!(request.method().as_str(), "GET"); + assert_eq!(request.uri().to_string().as_str(), "www.baidu.com"); + assert_eq!(request.version().as_str(), "HTTP/1.1"); + assert_eq!(request.headers(), &new_headers); + } + + /// UT test cases for `RequestBuilder::build`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `RequestBuilder::build`. + /// 2. Sets method by calling `RequestBuilder.method`. + /// 3. Sets uri by calling `RequestBuilder.uri`. + /// 4. Sets version by calling `RequestBuilder.version`. + /// 5. Sets header by calling `RequestBuilder.insert_header`. + /// 6. Sets header by calling `RequestBuilder.append_header`. + /// 7. Changes method by calling `Request.method_mut`. + /// 8. Changes uri by calling `Request.uri_mut`. + /// 9. Changes version by calling `Request.version_mut`. + /// 10. Changes headers by calling `Request.headers_mut`. + /// 11. Gets method by calling `Request.method`. + /// 12. Gets uri by calling `Request.uri`. + /// 13. Gets version by calling `Request.version`. + /// 14. Gets headers by calling `Request.headers`. + /// 15. Checks if the test result is correct. + #[test] + fn ut_request_builder_build_2() { + let mut request = RequestBuilder::new() + .method("GET") + .url("www.baidu.com") + .version("HTTP/1.1") + .header("ACCEPT", "text/html") + .body(()) + .unwrap(); + + *request.method_mut() = Method::POST; + *request.uri_mut() = Uri::try_from("www.google.com").unwrap(); + *request.version_mut() = Version::HTTP2; + let _ = request.headers_mut().insert("accept", "application/xml"); + + let mut new_headers = Headers::new(); + let _ = new_headers.insert("accept", "application/xml"); + + assert_eq!(request.method().as_str(), "POST"); + assert_eq!(request.uri().to_string().as_str(), "www.google.com"); + assert_eq!(request.version().as_str(), "HTTP/2.0"); + assert_eq!(request.headers(), &new_headers); + } + + /// UT test cases for `Request::new`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `Request::new`. + /// 2. Gets body by calling `Request.body`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_new() { + let request = Request::new(String::from("
")); + assert_eq!( + request.body().to_owned().as_str(), + "
" + ); + } + + /// UT test cases for `Request::into_parts`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `Request::new`. + /// 2. Gets request part and body by calling `Request.into_parts`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_into_parts() { + let request = Request::new(String::from("
")); + let (part, body) = request.into_parts(); + assert_eq!(part.method.as_str(), "GET"); + assert_eq!(body.as_str(), "
"); + } + + /// UT test cases for `Request::part`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `Request::new`. + /// 2. Gets request part and body by calling `Request.part`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_part() { + let request = Request::new(()); + let part = request.part(); + assert_eq!(part.method.as_str(), "GET"); + assert_eq!(part.version.as_str(), "HTTP/1.1"); + } + + /// UT test cases for `Request::from_raw_parts`. + /// + /// # Brief + /// 1. Creates a `RequestPart` and a body. + /// 2. Gets the request by calling `Request::from_raw_parts`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_from_raw_parts() { + let part = RequestPart::default(); + let body = String::from("
"); + let request = Request::from_raw_parts(part, body); + assert_eq!(request.part.method.as_str(), "GET"); + assert_eq!(request.body, "
"); + } + + /// UT test cases for `Request::get`. + /// + /// # Brief + /// 1. Creates a `Request` by calling `Request::get`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_request_get() { + let request = Request::get("www.example.com").body("".as_bytes()).unwrap(); + assert_eq!(request.part.uri.to_string(), "www.example.com"); + assert_eq!(request.part.method.as_str(), "GET"); + assert_eq!(request.part.version.as_str(), "HTTP/1.1"); + } +} diff --git a/ylong_http/src/request/uri.rs b/ylong_http/src/request/uri.rs new file mode 100644 index 0000000..5d42e33 --- /dev/null +++ b/ylong_http/src/request/uri.rs @@ -0,0 +1,1536 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`URI`]. +//! +//! URI references are used to target requests, indicate redirects, and define +//! relationships. +//! +//! [`URI`]: https://httpwg.org/specs/rfc9110.html#uri.references + +use crate::error::{ErrorKind, HttpError}; +use core::convert::{Infallible, TryFrom, TryInto}; + +// Maximum uri length. +const MAX_URI_LEN: usize = (u16::MAX - 1) as usize; + +/// HTTP [`URI`] implementation. +/// +/// The complete structure of the uri is as follows: +/// +/// ```text +/// | scheme://authority path ?query | +/// ``` +/// +/// `URI` currently only supports `HTTP` and `HTTPS` schemes. +/// +/// According to [RFC9110, Section 4.2], the userinfo parameter before authority +/// is forbidden. Fragment information in query is not stored in uri. +/// +/// So, the `URI` shown below is illegal: +/// +/// ```text +/// http://username:password@example.com:80/ +/// ``` +/// +/// [`URI`]: https://httpwg.org/specs/rfc9110.html#uri.references +/// [RFC9110, Section 4.2]: https://httpwg.org/specs/rfc9110.html#uri.schemes +/// +/// # Examples +/// +/// ``` +/// use ylong_http::request::uri::Uri; +/// +/// let uri = Uri::builder() +/// .scheme("http") +/// .authority("example.com:80") +/// .path("/foo") +/// .query("a=1") +/// .build() +/// .unwrap(); +/// +/// assert_eq!(uri.scheme().unwrap().as_str(), "http"); +/// assert_eq!(uri.host().unwrap().as_str(), "example.com"); +/// assert_eq!(uri.port().unwrap().as_str(), "80"); +/// assert_eq!(uri.path().unwrap().as_str(), "/foo"); +/// assert_eq!(uri.query().unwrap().as_str(), "a=1"); +/// assert_eq!(uri.to_string(), "http://example.com:80/foo?a=1"); +/// +/// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); +/// assert_eq!(uri.to_string(), "http://example.com:80/foo?a=1"); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct Uri { + /// The scheme can be `None` when the relative uri is used. + scheme: Option, + + /// The authority can be `None` when the relative uri is used. + authority: Option, + + /// The path can be `None` when the path is "/". + path: Option, + + /// The query can be `None` when the query is not set. + query: Option, +} + +impl Uri { + /// Creates a HTTP-compliant default `Uri` with `Path` set to '/'. + pub(crate) fn http() -> Uri { + Uri { + scheme: None, + authority: None, + path: Path::from_bytes(b"/").ok(), + query: None, + } + } + + /// Creates a new default [`UriBuilder`]. + /// + /// [`UriBuilder`]: UriBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let builder = Uri::builder(); + /// ``` + pub fn builder() -> UriBuilder { + UriBuilder::new() + } + + /// Gets a immutable reference to `Scheme`. + /// + /// Returns `None` if there is no `Scheme`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.scheme().unwrap().as_str(), "http"); + /// ``` + pub fn scheme(&self) -> Option<&Scheme> { + self.scheme.as_ref() + } + + /// Gets a immutable reference to `Authority`. + /// + /// Returns `None` if there is no `Authority`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// let authority = uri.authority().unwrap(); + /// assert_eq!(authority.host().as_str(), "example.com"); + /// assert_eq!(authority.port().unwrap().as_str(), "80"); + /// ``` + pub fn authority(&self) -> Option<&Authority> { + self.authority.as_ref() + } + + /// Gets a immutable reference to `Host`. + /// + /// Returns `None` if there is no `Host`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.host().unwrap().as_str(), "example.com"); + /// ``` + pub fn host(&self) -> Option<&Host> { + self.authority.as_ref().map(|auth| auth.host()) + } + + /// Gets a immutable reference to `Port`. + /// + /// Returns `None` if there is no `Port`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.port().unwrap().as_str(), "80"); + /// ``` + pub fn port(&self) -> Option<&Port> { + self.authority.as_ref().and_then(|auth| auth.port()) + } + + /// Gets a immutable reference to `Path`. + /// + /// Returns `None` if there is no `Path`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.path().unwrap().as_str(), "/foo"); + /// ``` + pub fn path(&self) -> Option<&Path> { + self.path.as_ref() + } + + /// Gets a immutable reference to `Query`. + /// + /// Returns `None` if there is no `Query`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.query().unwrap().as_str(), "a=1"); + /// ``` + pub fn query(&self) -> Option<&Query> { + self.query.as_ref() + } + + /// Converts a bytes slice into a `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"/foo?a=1").unwrap(); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() > MAX_URI_LEN { + return Err(InvalidUri::UriTooLong.into()); + } + if bytes.is_empty() { + return Err(InvalidUri::InvalidFormat.into()); + } + let (scheme, rest) = scheme_token(bytes)?; + let (authority, rest) = authority_token(rest)?; + let (path, rest) = path_token(rest)?; + let query = query_token(rest)?; + let result = Uri { + scheme, + authority, + path, + query, + }; + validity_check(result).map_err(Into::into) + } + + /// Gets a `String`, which contains the path and query. + /// + /// Returns `None` if path and query are both empty. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// assert_eq!(uri.path_and_query().unwrap(), String::from("/foo?a=1")); + /// ``` + pub fn path_and_query(&self) -> Option { + let mut builder = String::new(); + if let Some(path) = self.path() { + builder.push_str(path.as_str()); + } + if let Some(query) = self.query() { + builder.push('?'); + builder.push_str(query.as_str()); + } + if builder.is_empty() { + return None; + } + Some(builder) + } + + /// Splits the `Uri` into its parts. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Scheme, Uri}; + /// + /// let uri = Uri::from_bytes(b"http://example.com:80/foo?a=1").unwrap(); + /// let (scheme, auth, path, query) = uri.into_parts(); + /// assert_eq!(scheme, Some(Scheme::HTTP)); + /// assert_eq!(auth.unwrap().to_string(), String::from("example.com:80")); + /// assert_eq!(path.unwrap().as_str(), "/foo"); + /// assert_eq!(query.unwrap().as_str(), "a=1"); + /// ``` + pub fn into_parts( + self, + ) -> ( + Option, + Option, + Option, + Option, + ) { + (self.scheme, self.authority, self.path, self.query) + } +} + +impl ToString for Uri { + fn to_string(&self) -> String { + let mut builder = String::new(); + if let Some(scheme) = self.scheme() { + builder.push_str(scheme.as_str()); + builder.push_str("://"); + } + if let Some(host) = self.host() { + builder.push_str(host.as_str()); + } + if let Some(port) = self.port() { + builder.push(':'); + builder.push_str(port.as_str()); + } + if let Some(path) = self.path() { + builder.push_str(path.as_str()); + } + if let Some(query) = self.query() { + builder.push('?'); + builder.push_str(query.as_str()); + } + builder + } +} + +impl<'a> TryFrom<&'a [u8]> for Uri { + type Error = HttpError; + + fn try_from(s: &'a [u8]) -> Result { + Self::from_bytes(s) + } +} + +impl<'a> TryFrom<&'a str> for Uri { + type Error = HttpError; + + fn try_from(s: &'a str) -> Result { + Self::from_bytes(s.as_bytes()) + } +} + +/// A builder of `Uri`, which you can use it to construct a [`Uri`]. +/// +/// [`Uri`]: Uri +/// +/// # Example +/// +/// ``` +/// use ylong_http::request::uri::Uri; +/// +/// let uri = Uri::builder() +/// .scheme("http") +/// .authority("example.com:80") +/// .path("/foo") +/// .query("a=1") +/// .build() +/// .unwrap(); +/// ``` +pub struct UriBuilder { + unprocessed: Result, +} + +impl UriBuilder { + /// Creates a new, default `UriBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// let uri = UriBuilder::new(); + /// ``` + pub fn new() -> Self { + UriBuilder::default() + } + + /// Sets the `Scheme` of `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// // This method takes a generic parameter that supports multiple types. + /// let builder = UriBuilder::new().scheme("http"); + /// let builder = UriBuilder::new().scheme("http".as_bytes()); + /// ``` + pub fn scheme(mut self, scheme: T) -> Self + where + Scheme: TryFrom, + InvalidUri: From<>::Error>, + { + self.unprocessed = self.unprocessed.and_then(move |mut unprocessed| { + let scheme = scheme.try_into()?; + unprocessed.scheme = Some(scheme); + Ok(unprocessed) + }); + self + } + + /// Sets the `Authority` of `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// // This method takes a generic parameter that supports multiple types. + /// let builder = UriBuilder::new().authority("example.com:80"); + /// let builder = UriBuilder::new().authority("example.com:80".as_bytes()); + /// ``` + pub fn authority(mut self, auth: T) -> Self + where + Authority: TryFrom, + InvalidUri: From<>::Error>, + { + self.unprocessed = self.unprocessed.and_then(move |mut unprocessed| { + let auth = auth.try_into()?; + unprocessed.authority = Some(auth); + Ok(unprocessed) + }); + self + } + + /// Sets the `Path` of `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// let mut builder = UriBuilder::new().path("/foo"); + /// let mut builder = UriBuilder::new().path("/foo".as_bytes()); + /// ``` + pub fn path(mut self, path: T) -> Self + where + Path: TryFrom, + InvalidUri: From<>::Error>, + { + self.unprocessed = self.unprocessed.and_then(move |mut unprocessed| { + let path = path.try_into()?; + unprocessed.path = Some(path); + Ok(unprocessed) + }); + self + } + + /// Sets the `Query` of `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// let builder = UriBuilder::new().query("a=1"); + /// let builder = UriBuilder::new().query("a=1".as_bytes()); + /// ``` + pub fn query(mut self, query: T) -> Self + where + Query: TryFrom, + InvalidUri: From<>::Error>, + { + self.unprocessed = self.unprocessed.and_then(move |mut unprocessed| { + let query = query.try_into()?; + unprocessed.query = Some(query); + Ok(unprocessed) + }); + self + } + + /// Consumes the builder and constructs a valid `Uri`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::UriBuilder; + /// + /// let uri = UriBuilder::new() + /// .scheme("http") + /// .authority("example.com:80") + /// .path("/foo") + /// .query("a=1") + /// .build() + /// .unwrap(); + /// ``` + pub fn build(self) -> Result { + self.unprocessed + .and_then(validity_check) + .map_err(Into::into) + } +} + +impl Default for UriBuilder { + fn default() -> UriBuilder { + UriBuilder { + unprocessed: Ok(Uri { + scheme: None, + authority: None, + path: None, + query: None, + }), + } + } +} + +/// Scheme component of [`Uri`]. +/// +/// [`Uri`]: Uri +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct Scheme { + proto: Protocol, +} + +impl Scheme { + /// HTTP protocol Scheme. + pub const HTTP: Scheme = Scheme { + proto: Protocol::Http, + }; + + /// HTTPS protocol Scheme. + pub const HTTPS: Scheme = Scheme { + proto: Protocol::Https, + }; + + /// Converts a byte slice into a `Scheme`. + /// + /// This method only accepts `b"http"` and `b"https"` as input. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Scheme; + /// + /// let scheme = Scheme::from_bytes(b"http").unwrap(); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.eq_ignore_ascii_case(b"http") { + Ok(Protocol::Http.into()) + } else if bytes.eq_ignore_ascii_case(b"https") { + Ok(Protocol::Https.into()) + } else { + Err(InvalidUri::InvalidScheme) + } + } + + /// Returns a string slice containing the entire `Scheme`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Scheme; + /// + /// let scheme = Scheme::from_bytes(b"http").unwrap(); + /// assert_eq!(scheme.as_str(), "http"); + /// ``` + pub fn as_str(&self) -> &str { + match &self.proto { + Protocol::Http => "http", + Protocol::Https => "https", + } + } +} + +impl From for Scheme { + fn from(proto: Protocol) -> Self { + Scheme { proto } + } +} + +impl<'a> TryFrom<&'a [u8]> for Scheme { + type Error = InvalidUri; + + fn try_from(bytes: &'a [u8]) -> Result { + Scheme::from_bytes(bytes) + } +} + +impl<'a> TryFrom<&'a str> for Scheme { + type Error = InvalidUri; + + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) + } +} + +/// Authority component of [`Uri`]. +/// +/// [`Uri`]: Uri +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] +pub struct Authority { + host: Host, + port: Option, +} + +impl Authority { + /// Converts a byte slice into a `Authority`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Authority; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(InvalidUri::UriMissAuthority); + } + let (authority, rest) = authority_token(bytes)?; + if !rest.is_empty() || authority.is_none() { + return Err(InvalidUri::InvalidAuthority); + } + Ok(authority.unwrap()) + } + + /// Gets a immutable reference to `Host`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Authority, Host}; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let host = authority.host(); + /// assert_eq!(host.as_str(), "example.com"); + /// ``` + pub fn host(&self) -> &Host { + &self.host + } + + /// Gets a immutable reference to `Port`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Authority, Port}; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let port = authority.port().unwrap(); + /// assert_eq!(port.as_str(), "80"); + /// ``` + pub fn port(&self) -> Option<&Port> { + self.port.as_ref() + } + + /// Returns a string containing the entire `Authority`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Authority, Port}; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// assert_eq!(authority.to_str(), "example.com:80".to_string()); + /// ``` + pub fn to_str(&self) -> String { + let mut auth = self.host.as_str().to_string(); + if let Some(ref p) = self.port { + auth.push(':'); + auth.push_str(p.as_str()); + }; + auth + } + + /// Splits the `Authority` into its parts. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Authority; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let (host, port) = authority.into_parts(); + /// assert_eq!(host.as_str(), "example.com"); + /// assert_eq!(port.unwrap().as_u16().unwrap(), 80); + /// ``` + pub fn into_parts(self) -> (Host, Option) { + (self.host, self.port) + } +} + +impl<'a> TryFrom<&'a [u8]> for Authority { + type Error = InvalidUri; + + fn try_from(bytes: &'a [u8]) -> Result { + Authority::from_bytes(bytes) + } +} + +impl<'a> TryFrom<&'a str> for Authority { + type Error = InvalidUri; + + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) + } +} + +impl ToString for Authority { + fn to_string(&self) -> String { + let mut builder = String::new(); + builder.push_str(self.host().as_str()); + if let Some(port) = self.port() { + builder.push(':'); + builder.push_str(port.as_str()); + } + builder + } +} + +/// Host part of [`Authority`]. +/// +/// [`Authority`]: Authority +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct Host(String); + +impl Host { + /// Returns a string slice containing the entire `Host`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Authority; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let host = authority.host(); + /// assert_eq!(host.as_str(), "example.com"); + /// ``` + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +/// Port part of [`Authority`]. +/// +/// [`Authority`]: Authority +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct Port(String); + +impl Port { + /// Returns a string slice containing the entire `Port`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Authority, Port}; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let port = authority.port().unwrap(); + /// assert_eq!(port.as_str(), "80"); + /// ``` + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Returns a u16 value of the `Port`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::{Authority, Port}; + /// + /// let authority = Authority::from_bytes(b"example.com:80").unwrap(); + /// let port = authority.port().unwrap(); + /// assert_eq!(port.as_u16().unwrap(), 80); + /// ``` + pub fn as_u16(&self) -> Result { + self.0 + .parse::() + .map_err(|_| ErrorKind::Uri(InvalidUri::InvalidPort).into()) + } +} + +/// Path component of [`Uri`]. +/// +/// [`Uri`]: Uri +#[derive(Clone, Debug, Default)] +pub struct Path(String); + +impl Path { + /// Converts a byte slice into a `Path`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Path; + /// + /// let path = Path::from_bytes(b"/foo").unwrap(); + /// assert_eq!(path.as_str(), "/foo"); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + let (path, rest) = path_token(bytes)?; + if rest.is_empty() { + path.ok_or(InvalidUri::UriMissPath) + } else { + Err(InvalidUri::InvalidPath) + } + } + + /// Returns a string slice containing the entire `Path`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Path; + /// + /// let path = Path::from_bytes(b"/foo").unwrap(); + /// assert_eq!(path.as_str(), "/foo"); + /// ``` + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl<'a> TryFrom<&'a [u8]> for Path { + type Error = InvalidUri; + + fn try_from(bytes: &'a [u8]) -> Result { + Path::from_bytes(bytes) + } +} + +impl<'a> TryFrom<&'a str> for Path { + type Error = InvalidUri; + + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) + } +} + +/// Query component of [`Uri`]. +/// +/// [`Uri`]: Uri +#[derive(Clone, Debug, Default)] +pub struct Query(String); + +impl Query { + /// Converts a byte slice into a `Query`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Query; + /// + /// let query = Query::from_bytes(b"a=1").unwrap(); + /// assert_eq!(query.as_str(), "a=1"); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + let query = query_token(bytes)?; + query.ok_or(InvalidUri::UriMissQuery) + } + + /// Returns a string slice containing the entire `Query`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Query; + /// + /// let query = Query::from_bytes(b"a=1").unwrap(); + /// assert_eq!(query.as_str(), "a=1"); + /// ``` + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl<'a> TryFrom<&'a [u8]> for Query { + type Error = InvalidUri; + + fn try_from(s: &'a [u8]) -> Result { + Query::from_bytes(s) + } +} + +impl<'a> TryFrom<&'a str> for Query { + type Error = InvalidUri; + + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) + } +} + +/// `Protocol` indicates the scheme type supported by [`Uri`]. +/// +/// [`Uri`]: Uri +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +enum Protocol { + Http, + Https, +} + +/// Error types generated during [`Uri`] construction due to different causes. +/// +/// [`Uri`]: Uri +#[derive(Debug, Eq, PartialEq)] +pub enum InvalidUri { + /// Invalid scheme + InvalidScheme, + /// Invalid authority + InvalidAuthority, + /// Invalid path + InvalidPath, + /// Invalid byte + InvalidByte, + /// Invalid format + InvalidFormat, + /// Invalid port + InvalidPort, + /// Missing scheme + UriMissScheme, + /// Missing path + UriMissPath, + /// Missing query + UriMissQuery, + /// Missing authority + UriMissAuthority, + /// Missing host + UriMissHost, + /// Contains Userinfo + UriContainUserinfo, + /// Too long + UriTooLong, +} + +impl From for InvalidUri { + fn from(_: Infallible) -> Self { + unimplemented!() + } +} + +fn bytes_to_str(bytes: &[u8]) -> &str { + unsafe { std::str::from_utf8_unchecked(bytes) } +} + +fn scheme_token(bytes: &[u8]) -> Result<(Option, &[u8]), InvalidUri> { + const HTTP_SCHEME_LENGTH: usize = "http://".len(); + const HTTPS_SCHEME_LENGTH: usize = "https://".len(); + // Obtains the position of colons that separate schemes. + let pos = match bytes.iter().enumerate().find(|(_, &b)| b == b':') { + Some((index, _)) + if index != 0 + && bytes[index..].len() > 2 + && bytes[index + 1..index + 3].eq_ignore_ascii_case(b"//") => + { + index + } + Some((0, _)) => return Err(InvalidUri::InvalidScheme), + _ => return Ok((None, bytes)), + }; + // Currently, only HTTP and HTTPS are supported. Therefore, you need to verify the scheme content. + if bytes[..pos].eq_ignore_ascii_case(b"http") { + Ok((Some(Protocol::Http.into()), &bytes[HTTP_SCHEME_LENGTH..])) + } else if bytes[..pos].eq_ignore_ascii_case(b"https") { + Ok((Some(Protocol::Https.into()), &bytes[HTTPS_SCHEME_LENGTH..])) + } else { + Err(InvalidUri::InvalidScheme) + } +} + +fn authority_token(bytes: &[u8]) -> Result<(Option, &[u8]), InvalidUri> { + let mut end = bytes.len(); + let mut colon_num = 0; + let mut left_bracket = false; + let mut right_bracket = false; + for (i, &b) in bytes.iter().enumerate() { + match b { + b'/' | b'?' | b'#' => { + end = i; + break; + } + b'[' => { + if i == 0 { + left_bracket = true; + } else if left_bracket { + return Err(InvalidUri::InvalidAuthority); + } + } + b']' => { + if left_bracket { + if right_bracket { + return Err(InvalidUri::InvalidAuthority); + } else { + right_bracket = true; + // The ':' between '[' and ']' is in ipv6 and should be ignored. + colon_num = 0; + } + } + } + // TODO According to RFC3986, the character @ can be one of the reserved characters, which needs to be improved after being familiar with the rules. + b'@' => { + return Err(InvalidUri::UriContainUserinfo); + } + b':' => { + colon_num += 1; + } + other => { + if !URI_VALUE_BYTES[other as usize] { + return Err(InvalidUri::InvalidByte); + } + } + } + } + // The authority does not exist. + if end == 0 { + return Ok((None, &bytes[end..])); + } + + // Incomplete square brackets + if left_bracket ^ right_bracket { + return Err(InvalidUri::InvalidAuthority); + } + // There are multiple colons in addition to IPv6. + if colon_num > 1 { + return Err(InvalidUri::InvalidAuthority); + } + let authority = host_port(&bytes[..end], colon_num)?; + Ok((Some(authority), &bytes[end..])) +} + +fn path_token(bytes: &[u8]) -> Result<(Option, &[u8]), InvalidUri> { + let mut end = bytes.len(); + for (i, &b) in bytes.iter().enumerate() { + match b { + b'?' | b'#' => { + end = i; + break; + } + _ => { + if !URI_VALUE_BYTES[b as usize] { + return Err(InvalidUri::InvalidByte); + } + } + } + } + if end != 0 { + let path = bytes_to_str(&bytes[..end]).to_string(); + Ok((Some(Path(path)), &bytes[end..])) + } else { + Ok((None, &bytes[end..])) + } +} + +fn query_token(s: &[u8]) -> Result, InvalidUri> { + if s.is_empty() { + return Ok(None); + } + let bytes = if s[0].eq_ignore_ascii_case(&b'?') { + &s[1..] + } else { + s + }; + let mut end = bytes.len(); + for (i, &b) in bytes.iter().enumerate() { + match b { + b'#' => { + end = i; + break; + } + _ => { + if !URI_VALUE_BYTES[b as usize] { + return Err(InvalidUri::InvalidByte); + } + } + } + } + if end == 0 { + return Ok(None); + } + let query = bytes_to_str(&bytes[..end]); + Ok(Some(Query(query.to_string()))) +} + +fn host_port(auth: &[u8], colon_num: i32) -> Result { + let authority = bytes_to_str(auth); + if colon_num != 0 { + match authority.rsplit_once(':') { + Some((host, port)) => { + if host.is_empty() { + Err(InvalidUri::UriMissHost) + } else if port.is_empty() { + Ok(Authority { + host: Host(host.to_string()), + port: None, + }) + } else { + port.parse::().map_err(|_| InvalidUri::InvalidPort)?; + Ok(Authority { + host: Host(host.to_string()), + port: Some(Port(port.to_string())), + }) + } + } + None => Err(InvalidUri::UriMissAuthority), + } + } else { + Ok(Authority { + host: Host(authority.to_string()), + port: None, + }) + } +} + +fn validity_check(unchecked_uri: Uri) -> Result { + match ( + &unchecked_uri.scheme, + &unchecked_uri.authority, + &unchecked_uri.path, + &unchecked_uri.query, + ) { + (Some(_), None, _, _) => Err(InvalidUri::UriMissAuthority), + (None, Some(_), Some(_), _) => Err(InvalidUri::UriMissScheme), + (None, Some(_), _, Some(_)) => Err(InvalidUri::UriMissScheme), + (None, None, None, None) => Err(InvalidUri::InvalidFormat), + _ => Ok(unchecked_uri), + } +} + +#[rustfmt::skip] +const URI_VALUE_BYTES: [bool; 256] = { + const __: bool = false; + const TT: bool = true; + [ +// \0 HT LF CR + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1F +// \w ! " # $ % & ' ( ) * + , - . / + __, TT, __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, // 2F +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, TT, __, TT, // 3F +// @ A B C D E F G H I J K L M N O + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, // 4F +// P Q R S T U V W X Y Z [ \ ] ^ _ + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, TT, __, TT, // 5F +// ` a b c d e f g h i j k l m n o + __, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, // 6F +// p q r s t u v w x y z { | } ~ del + TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, TT, __, __, __, TT, __, // 7F +// Expand ascii + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8F + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9F + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // AF + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // BF + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // CF + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // DF + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // EF + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // FF + ] +}; + +#[cfg(test)] +mod ut_uri { + use super::{InvalidUri, Scheme, Uri, UriBuilder}; + use crate::error::{ErrorKind, HttpError}; + + macro_rules! test_builder_valid { + ($res1:expr, $res2:expr) => {{ + let uri = UriBuilder::new() + .scheme($res1.0) + .authority($res1.1) + .path($res1.2) + .query($res1.3) + .build() + .unwrap(); + assert_eq!(uri.scheme().unwrap().as_str(), $res2.0); + assert_eq!(uri.host().unwrap().as_str(), $res2.1); + assert_eq!(uri.port().unwrap().as_str(), $res2.2); + assert_eq!(uri.path().unwrap().as_str(), $res2.3); + assert_eq!(uri.query().unwrap().as_str(), $res2.4); + assert_eq!(uri.to_string(), $res2.5) + }}; + } + + /// UT test cases for `build_from_builder`. + /// + /// # Brief + /// 1. Creates UriBuilder by calling UriBuilder::new(). + /// 2. Sets Scheme by calling scheme(). + /// 3. Sets authority by calling authority(). + /// 4. Sets path by calling path(). + /// 5. Sets query by calling query(). + /// 6. Creates Uri by calling build(). + /// 7. Gets string slice value of uri components by calling as_str(). + /// 8. Gets string value of uri by calling to_string(). + /// 9. Checks if the test result is correct by assert_eq!(). + #[test] + fn build_from_builder() { + test_builder_valid!( + ("http", "hyper.rs:80", "/foo", "a=1"), + ( + "http", + "hyper.rs", + "80", + "/foo", + "a=1", + "http://hyper.rs:80/foo?a=1" + ) + ); + test_builder_valid!( + (Scheme::HTTP, "hyper.rs:80", "/foo", "a=1"), + ( + "http", + "hyper.rs", + "80", + "/foo", + "a=1", + "http://hyper.rs:80/foo?a=1" + ) + ); + test_builder_valid!( + ("https", "hyper.rs:80", "/foo", "a=1"), + ( + "https", + "hyper.rs", + "80", + "/foo", + "a=1", + "https://hyper.rs:80/foo?a=1" + ) + ); + test_builder_valid!( + (Scheme::HTTPS, "hyper.rs:80", "/foo", "a=1"), + ( + "https", + "hyper.rs", + "80", + "/foo", + "a=1", + "https://hyper.rs:80/foo?a=1" + ) + ); + } + + /// UT test cases for `build_from_instance`. + /// + /// # Brief + /// 1. Creates UriBuilder by calling Uri::builder(). + /// 2. Sets Scheme by calling scheme(). + /// 3. Sets authority by calling authority(). + /// 4. Sets path by calling path(). + /// 5. Sets query by calling query(). + /// 6. Creates Uri by calling build(). + /// 7. Gets string slice value of uri components by calling as_str(). + /// 8. Gets string value of uri by calling to_string(). + /// 9. Checks if the test result is correct by assert_eq!(). + #[test] + fn build_from_instance() { + let uri = Uri::builder() + .scheme(Scheme::HTTP) + .authority("hyper.rs:80") + .path("/foo") + .query("a=1") + .build() + .unwrap(); + assert_eq!(uri.scheme().unwrap().as_str(), "http"); + assert_eq!(uri.host().unwrap().as_str(), "hyper.rs"); + assert_eq!(uri.port().unwrap().as_str(), "80"); + assert_eq!(uri.path().unwrap().as_str(), "/foo"); + assert_eq!(uri.query().unwrap().as_str(), "a=1"); + assert_eq!(uri.to_string(), "http://hyper.rs:80/foo?a=1") + } + + /// UT test cases for `build_from_str`. + /// + /// # Brief + /// 1. Creates Uri by calling from_bytes(). + /// 2. Gets string slice value of uri components by calling as_str(). + /// 3. Gets u16 value of port by call as_u16(). + /// 4. Gets string value of uri by calling to_string(). + /// 5. Checks if the test result is correct by assert_eq!(). + #[test] + fn build_from_str() { + let uri = Uri::from_bytes("http://hyper.rs:80/foo?a=1".as_bytes()).unwrap(); + assert_eq!(uri.scheme().unwrap().as_str(), "http"); + assert_eq!(uri.host().unwrap().as_str(), "hyper.rs"); + assert_eq!(uri.port().unwrap().as_str(), "80"); + assert_eq!(uri.port().unwrap().as_u16().unwrap(), 80); + assert_eq!(uri.path().unwrap().as_str(), "/foo"); + assert_eq!(uri.query().unwrap().as_str(), "a=1"); + assert_eq!(uri.to_string(), "http://hyper.rs:80/foo?a=1") + } + + /// UT test cases for `Uri::from_bytes`. + /// + /// # Brief + /// 1. Creates Uri by calling `Uri::from_bytes()`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_uri_from_bytes() { + macro_rules! uri_test_case { + ($raw: expr, $tar: expr, $(,)?) => { + match (Uri::from_bytes($raw), $tar) { + (Ok(res), Ok(tar)) => assert_eq!( + ( + res.scheme().map(|scheme| scheme.as_str()), + res.host().map(|host| host.as_str()), + res.port().map(|port| port.as_str()), + res.path().map(|path| path.as_str()), + res.query().map(|query| query.as_str()), + ), + tar, + ), + (Err(res), Err(tar)) => assert_eq!(res, tar), + _ => panic!("uri test case failed!"), + } + }; + } + + uri_test_case!( + b"httpss://www.example.com/", + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidScheme))), + ); + + uri_test_case!( + b"://www.example.com/", + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidScheme))), + ); + + uri_test_case!( + b"https://www.hu awei.com/", + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https://www.hu"awei.com/"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https://www.hu"awei.com/"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https://www.hu"<>\^`awei.com/"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https://www.example.com:a0/"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidPort))), + ); + + uri_test_case!( + br#"https://www.example.com:80/message/e<>mail"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https://www.example.com:80/message/email?name='\^'"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::InvalidByte))), + ); + + uri_test_case!( + br#"https:/www.example.com:80/message/email?name=arya"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::UriMissScheme))), + ); + + uri_test_case!( + br#"https:/www.example.com:80"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::UriMissScheme))), + ); + + uri_test_case!( + br#"https:/www.example.com"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::UriMissScheme))), + ); + + uri_test_case!( + br#"https://www.huaw:ei.com:80"#, + Err(HttpError::from(ErrorKind::Uri( + InvalidUri::InvalidAuthority + ))), + ); + + uri_test_case!( + br#"https://www.huaw:ei.com:80"#, + Err(HttpError::from(ErrorKind::Uri( + InvalidUri::InvalidAuthority + ))), + ); + + uri_test_case!( + br#"https://name=1234@www.example.com:80/message/email?name=arya"#, + Err(HttpError::from(ErrorKind::Uri( + InvalidUri::UriContainUserinfo + ))), + ); + + uri_test_case!( + br#"www.example.com:80/message/email?name=arya"#, + Err(HttpError::from(ErrorKind::Uri(InvalidUri::UriMissScheme))), + ); + + uri_test_case!( + br#"https://[0:0:0:0:0:0:0:0:80/message/email?name=arya"#, + Err(HttpError::from(ErrorKind::Uri( + InvalidUri::InvalidAuthority + ))), + ); + + uri_test_case!( + br#"https:///foo?a=1"#, + Err(HttpError::from(ErrorKind::Uri( + InvalidUri::UriMissAuthority + ))), + ); + + uri_test_case!( + b"https://www.example.com/", + Ok(( + Some("https"), + Some("www.example.com"), + None, + Some("/"), + None + )), + ); + + uri_test_case!( + b"https://www.example.com:80/foo?a=1", + Ok(( + Some("https"), + Some("www.example.com"), + Some("80"), + Some("/foo"), + Some("a=1"), + )), + ); + + uri_test_case!( + b"https://www.example.com:80/foo?a=1#fragment", + Ok(( + Some("https"), + Some("www.example.com"), + Some("80"), + Some("/foo"), + Some("a=1"), + )), + ); + + uri_test_case!( + b"https://www.example.com:80?a=1", + Ok(( + Some("https"), + Some("www.example.com"), + Some("80"), + None, + Some("a=1"), + )), + ); + + uri_test_case!( + b"https://www.example.com?a=1", + Ok(( + Some("https"), + Some("www.example.com"), + None, + None, + Some("a=1"), + )), + ); + + uri_test_case!( + b"https://www.example.com?", + Ok((Some("https"), Some("www.example.com"), None, None, None)), + ); + + uri_test_case!( + b"https://www.example.com:80", + Ok(( + Some("https"), + Some("www.example.com"), + Some("80"), + None, + None, + )), + ); + + uri_test_case!( + b"https://www.example.com", + Ok((Some("https"), Some("www.example.com"), None, None, None)), + ); + + uri_test_case!( + b"https://www.example.com#fragment", + Ok((Some("https"), Some("www.example.com"), None, None, None)), + ); + + uri_test_case!( + b"www.example.com", + Ok((None, Some("www.example.com"), None, None, None)), + ); + + uri_test_case!( + b"/foo?a=1", + Ok((None, None, None, Some("/foo"), Some("a=1"))), + ); + + uri_test_case!( + b"https://[0:0:0:0:0:0:0:0]", + Ok((Some("https"), Some("[0:0:0:0:0:0:0:0]"), None, None, None)), + ); + + uri_test_case!( + b"https://[0:0:0:0:0:0:0:0]:80", + Ok(( + Some("https"), + Some("[0:0:0:0:0:0:0:0]"), + Some("80"), + None, + None, + )), + ); + } + + /// UT test cases for `Uri::authority`. + /// + /// # Brief + /// 1. Creates Uri by calling `Uri::authority()`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_uri_authority() { + let uri = Uri::from_bytes(b"http://example.com:8080/foo?a=1").unwrap(); + let authority = uri.authority().unwrap(); + assert_eq!(authority.host.as_str(), "example.com"); + assert_eq!(authority.port().unwrap().as_str(), "8080"); + } + + /// UT test cases for `Uri::path_and_query`. + /// + /// # Brief + /// 1. Creates Uri by calling `Uri::path_and_query()`. + /// 2. Checks if the test results are correct. + #[test] + fn ut_uri_path_and_query() { + let uri = Uri::from_bytes(b"http://example.com:8080/foo?a=1").unwrap(); + assert_eq!(uri.path_and_query().unwrap(), "/foo?a=1"); + + let uri = Uri::from_bytes(b"http://example.com:8080").unwrap(); + assert_eq!(uri.path_and_query(), None); + } +} diff --git a/ylong_http/src/response/mod.rs b/ylong_http/src/response/mod.rs new file mode 100644 index 0000000..db4c5a9 --- /dev/null +++ b/ylong_http/src/response/mod.rs @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Response`]. + +pub mod status; +use status::StatusCode; + +use crate::headers::Headers; +use crate::version::Version; + +// TODO: `ResponseBuilder` implementation. + +/// HTTP `Response` Implementation. +/// +/// The status-line and field-line of a response-message are stored in `Response`. +/// +/// The body can be saved in the user-defined type. +/// +/// According to the [`RFC9112`], the origin reason-phrase of status-line is not +/// saved, the unified reason in `StatusCode` is used to indicate the status +/// code reason. +/// +/// [`RFC9112`]: https://httpwg.org/specs/rfc9112.html +/// [`Response`]: https://httpwg.org/specs/rfc9112.html#status.line +pub struct Response { + part: ResponsePart, + body: T, +} + +impl Response { + /// Gets an immutable reference to the `Version`. + pub fn version(&self) -> &Version { + &self.part.version + } + + /// Gets an immutable reference to the `StatusCode`. + // TODO: change this to `status_code`? + pub fn status(&self) -> &StatusCode { + &self.part.status + } + + /// Gets an immutable reference to the `Headers`. + pub fn headers(&self) -> &Headers { + &self.part.headers + } + + /// Gets an immutable reference to the `Body`. + pub fn body(&self) -> &T { + &self.body + } + + /// Gets a mutable reference to the `Body`. + pub fn body_mut(&mut self) -> &mut T { + &mut self.body + } + + /// Splits `Response` into `ResponsePart` and `Body`. + pub fn into_parts(self) -> (ResponsePart, T) { + (self.part, self.body) + } + + /// Response construction method with parameters + pub fn from_raw_parts(part: ResponsePart, body: T) -> Response { + Self { part, body } + } +} + +impl Clone for Response { + fn clone(&self) -> Self { + Self::from_raw_parts(self.part.clone(), self.body.clone()) + } +} + +/// `ResponsePart`, which is called [`Status Line`] in [`RFC9112`]. +/// +/// A request-line begins with a method token, followed by a single space (SP), +/// the request-target, and another single space (SP), and ends with the protocol +/// version. +/// +/// [`RFC9112`]: https://httpwg.org/specs/rfc9112.html +/// [`Status Line`]: https://httpwg.org/specs/rfc9112.html#status.line +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ResponsePart { + /// HTTP Version implementation. + pub version: Version, + /// HTTP Status Codes implementation. + pub status: StatusCode, + /// HTTP Headers, which is called Fields in RFC9110. + pub headers: Headers, +} + +#[cfg(test)] +mod ut_response { + use crate::h1::ResponseDecoder; + use crate::headers::Headers; + use crate::response::Response; + + const ERROR_HEADER: &str = "header append failed"; + + /// UT test cases for `Response::version`. + /// + /// # Brief + /// 1. Creates a `ResponsePart` by calling `ResponseDecoder::decode`. + /// 2. Gets the reference of a `Version` by calling `Response::version`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_version() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response { + part: result.0, + body: result.1, + }; + assert_eq!(response.version().as_str(), "HTTP/1.1") + } + + /// UT test cases for `Response::status_code`. + /// + /// # Brief + /// 1. Creates a `ResponsePart` by calling `ResponseDecoder::decode`. + /// 2. Gets the reference of a `StatusCode` by calling `Response::status_code`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_status_code() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response { + part: result.0, + body: result.1, + }; + assert_eq!(response.status().as_u16(), 304) + } + + /// UT test cases for `Response::headers`. + /// + /// # Brief + /// 1. Creates a `ResponsePart` by calling `ResponseDecoder::decode`. + /// 2. Gets the reference of a `Headers` by calling `Response::headers`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_headers() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response { + part: result.0, + body: result.1, + }; + let mut headers = Headers::new(); + headers.insert("age", "270646").expect(ERROR_HEADER); + headers + .insert("Date", "Mon, 19 Dec 2022 01:46:59 GMT") + .expect(ERROR_HEADER); + headers + .insert("Etag", "\"3147526947+gzip\"") + .expect(ERROR_HEADER); + assert_eq!(response.headers(), &headers) + } + + /// UT test cases for `Response::body`. + /// + /// # Brief + /// 1. Creates a body by calling `ResponseDecoder::decode`. + /// 2. Gets the reference of a `&[u8]` body by calling `Response::body`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_body() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response { + part: result.0, + body: result.1, + }; + assert_eq!(*response.body(), "body part".as_bytes()) + } + + /// UT test cases for `Response::body_mut`. + /// + /// # Brief + /// 1. Creates a body by calling `ResponseDecoder::decode`. + /// 2. Gets the reference of a `&[u8]` body by calling `Response::body_mut`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_body_mut() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let mut response = Response { + part: result.0, + body: result.1, + }; + assert_eq!(*response.body_mut(), "body part".as_bytes()) + } + + /// UT test cases for `Response::into_parts`. + /// + /// # Brief + /// 1. Creates a body by calling `ResponseDecoder::into_parts`. + /// 2. Checks if the test result is correct. + #[test] + fn ut_response_into_parts() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response { + part: result.0, + body: result.1, + }; + let (part, body) = response.into_parts(); + assert!(body.is_empty()); + assert_eq!(part.version.as_str(), "HTTP/1.1"); + } + + /// UT test cases for `Response::from_raw_parts`. + /// + /// # Brief + /// 1. Creates a body and a part by calling `ResponseDecoder::decode`. + /// 2. Creates a `Response` by calling `Response::from_raw_parts`. + /// 3. Creates a `Response` by calling `Response::clone`. + /// 3. Checks if the test result is correct. + #[test] + fn ut_response_from_raw_parts() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\nbody part".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response::<&[u8]>::from_raw_parts(result.0, result.1); + assert_eq!(response.version().as_str(), "HTTP/1.1"); + assert_eq!(response.status().as_u16(), 304); + let mut headers = Headers::new(); + headers.insert("age", "270646").expect(ERROR_HEADER); + headers + .insert("Date", "Mon, 19 Dec 2022 01:46:59 GMT") + .expect(ERROR_HEADER); + headers + .insert("Etag", "\"3147526947+gzip\"") + .expect(ERROR_HEADER); + assert_eq!(response.headers(), &headers); + assert_eq!(*response.body(), "body part".as_bytes()) + } +} diff --git a/ylong_http/src/response/status.rs b/ylong_http/src/response/status.rs new file mode 100644 index 0000000..cef3331 --- /dev/null +++ b/ylong_http/src/response/status.rs @@ -0,0 +1,661 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Status Codes`]. +//! +//! The status code of a response is a three-digit integer code that describes +//! the result of the request and the semantics of the response, including +//! whether the request was successful and what content is enclosed (if any). +//! All valid status codes are within the range of 100 to 599, inclusive. +//! +//! [`Status Codes`]: https://httpwg.org/specs/rfc9110.html#status.codes + +use crate::error::{ErrorKind, HttpError}; +use core::convert::TryFrom; +use core::fmt::{Display, Formatter}; + +/// HTTP [`Status Codes`] implementation. +/// +/// [`Status Codes`]: https://httpwg.org/specs/rfc9110.html#status.codes +/// +/// # Examples +/// +/// ``` +/// use ylong_http::response::status::StatusCode; +/// +/// let status = StatusCode::OK; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StatusCode(u16); + +impl StatusCode { + /// Converts a `u16` to a `StatusCode`. + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert_eq!(StatusCode::from_u16(200), Ok(StatusCode::OK)); + /// ``` + pub fn from_u16(code: u16) -> Result { + // Only three-digit status codes are valid. + if !(100..1000).contains(&code) { + return Err(ErrorKind::InvalidInput.into()); + } + + Ok(StatusCode(code)) + } + + /// Converts a `StatusCode` to a `u16`. + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert_eq!(StatusCode::OK.as_u16(), 200u16); + /// ``` + pub fn as_u16(&self) -> u16 { + self.0 + } + + /// Converts a `&[u8]` to a `StatusCode`. + /// + /// # Example + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert_eq!(StatusCode::from_bytes(b"200"), Ok(StatusCode::OK)); + /// assert!(StatusCode::from_bytes(b"0").is_err()); + /// ``` + pub fn from_bytes(bytes: &[u8]) -> Result { + // Only three-digit status codes are valid. + if bytes.len() != 3 { + return Err(ErrorKind::InvalidInput.into()); + } + + let a = bytes[0].wrapping_sub(b'0') as u16; + let b = bytes[1].wrapping_sub(b'0') as u16; + let c = bytes[2].wrapping_sub(b'0') as u16; + + if a == 0 || a > 9 || b > 9 || c > 9 { + return Err(ErrorKind::InvalidInput.into()); + } + + // Valid status code: 1 <= a <= 9 && 0 <= b <= 9 && 0 <= c <= 9 + Ok(StatusCode((a * 100) + (b * 10) + c)) + } + + /// Converts a `StatusCode` to a `[u8; 3]`. + /// + /// # Example + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert_eq!(StatusCode::OK.as_bytes(), *b"200"); + /// ``` + pub fn as_bytes(&self) -> [u8; 3] { + [ + ((self.0 / 100) as u8) + b'0', + (((self.0 % 100) / 10) as u8) + b'0', + ((self.0 % 10) as u8) + b'0', + ] + } + + /// StatusCode as str. + // TODO: Adapter, remove it later. + pub fn as_str(&self) -> String { + format!("{}", self.0) + } + + /// Determines whether the `StatusCode` is [`1xx (Informational)`]. + /// + /// The 1xx (Informational) class of status code indicates an interim + /// response for communicating connection status or request progress prior + /// to completing the requested action and sending a final response. + /// + /// [`1xx (Informational)`]: https://httpwg.org/specs/rfc9110.html#status.1xx + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert!(StatusCode::CONTINUE.is_informational()); + /// assert!(!StatusCode::OK.is_informational()); + /// ``` + pub fn is_informational(&self) -> bool { + self.0 >= 100 && 200 > self.0 + } + + /// Determines whether the `StatusCode` is [`2xx (Successful)`]. + /// + /// The 2xx (Successful) class of status code indicates that the client's + /// request was successfully received, understood, and accepted. + /// + /// [`2xx (Successful)`]: https://httpwg.org/specs/rfc9110.html#status.2xx + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert!(StatusCode::OK.is_successful()); + /// assert!(!StatusCode::CONTINUE.is_successful()); + /// ``` + pub fn is_successful(&self) -> bool { + self.0 >= 200 && 300 > self.0 + } + + /// Determines whether the `StatusCode` is [`3xx (Redirection)`]. + /// + /// The 3xx (Redirection) class of status code indicates that further action + /// needs to be taken by the user agent in order to fulfill the request. + /// + /// [`3xx (Redirection)`]: https://httpwg.org/specs/rfc9110.html#status.3xx + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert!(StatusCode::MULTIPLE_CHOICES.is_redirection()); + /// assert!(!StatusCode::OK.is_redirection()); + /// ``` + pub fn is_redirection(&self) -> bool { + self.0 >= 300 && 400 > self.0 + } + + /// Determines whether the `StatusCode` is [`4xx (Client Error)`]. + /// + /// The 4xx (Client Error) class of status code indicates that the client + /// seems to have erred. + /// + /// [`4xx (Client Error)`]: https://httpwg.org/specs/rfc9110.html#status.4xx + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert!(StatusCode::BAD_REQUEST.is_client_error()); + /// assert!(!StatusCode::OK.is_client_error()); + /// ``` + pub fn is_client_error(&self) -> bool { + self.0 >= 400 && 500 > self.0 + } + + /// Determines whether the `StatusCode` is [`5xx (Server Error)`]. + /// + /// The 5xx (Server Error) class of status code indicates that the server is + /// aware that it has erred or is incapable of performing the requested + /// method. + /// + /// [`5xx (Server Error)`]: https://httpwg.org/specs/rfc9110.html#status.5xx + /// + /// # Examples + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert!(StatusCode::INTERNAL_SERVER_ERROR.is_server_error()); + /// assert!(!StatusCode::OK.is_server_error()); + /// ``` + pub fn is_server_error(&self) -> bool { + self.0 >= 500 && 600 > self.0 + } +} + +impl TryFrom for StatusCode { + type Error = HttpError; + + fn try_from(value: u16) -> Result { + Self::from_u16(value) + } +} + +impl<'a> TryFrom<&'a [u8]> for StatusCode { + type Error = HttpError; + + fn try_from(value: &'a [u8]) -> Result { + Self::from_bytes(value) + } +} + +macro_rules! status_list { + ( + $( + $(#[$docs: meta])* + ($num:expr, $name: ident, $phrase: expr), + )* + ) => { + impl StatusCode { + $( + $(#[$docs])* + pub const $name: StatusCode = StatusCode($num as u16); + )* + + /// Gets the reason of the `StatusCode`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::response::status::StatusCode; + /// + /// assert_eq!(StatusCode::OK.reason(), Some("OK")); + /// ``` + pub fn reason(&self) -> Option<&'static str> { + match self.0 { + $( + $num => Some($phrase), + )* + _ => None, + } + } + } + + /// UT test cases for `StatusCode::reason`. + /// + /// # Brief + /// 1. Creates all the valid `StatusCode`s. + /// 2. Calls `StatusCode::reason` on them. + /// 3. Checks if the results are correct. + #[test] + pub fn ut_status_code_reason() { + $( + assert_eq!(StatusCode::from_u16($num as u16).unwrap().reason(), Some($phrase)); + )* + assert_eq!(StatusCode::from_u16(999).unwrap().reason(), None); + } + } +} + +// TODO: Adapter, remove this later. +impl Display for StatusCode { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{} {}", + self.as_u16(), + self.reason().unwrap_or("Unknown status code") + ) + } +} + +#[rustfmt::skip] +status_list!( + /// [`100 Continue`]: https://tools.ietf.org/html/rfc7231#section-6.2.1 + (100, CONTINUE, "Continue"), + + /// [`101 Switching Protocols`]: https://tools.ietf.org/html/rfc7231#section-6.2.2 + (101, SWITCHING_PROTOCOLS, "Switching Protocols"), + + /// [`102 Processing`]: https://tools.ietf.org/html/rfc2518 + (102, PROCESSING, "Processing"), + + /// [`200 OK`]: https://tools.ietf.org/html/rfc7231#section-6.3.1 + (200, OK, "OK"), + + /// [`201 Created`]: https://tools.ietf.org/html/rfc7231#section-6.3.2 + (201, CREATED, "Created"), + + /// [`202 Accepted`]: https://tools.ietf.org/html/rfc7231#section-6.3.3 + (202, ACCEPTED, "Accepted"), + + /// [`203 Non-Authoritative Information`]: https://tools.ietf.org/html/rfc7231#section-6.3.4 + (203, NON_AUTHORITATIVE_INFORMATION, "Non Authoritative Information"), + + /// [`204 No Content`]: https://tools.ietf.org/html/rfc7231#section-6.3.5 + (204, NO_CONTENT, "No Content"), + + /// [`205 Reset Content`]: https://tools.ietf.org/html/rfc7231#section-6.3.6 + (205, RESET_CONTENT, "Reset Content"), + + /// [`206 Partial Content`]: https://tools.ietf.org/html/rfc7233#section-4.1 + (206, PARTIAL_CONTENT, "Partial Content"), + + /// [`207 Multi-Status`]: https://tools.ietf.org/html/rfc4918 + (207, MULTI_STATUS, "Multi-Status"), + + /// [`208 Already Reported`]: https://tools.ietf.org/html/rfc5842 + (208, ALREADY_REPORTED, "Already Reported"), + + /// [`226 IM Used`]: https://tools.ietf.org/html/rfc3229 + (226, IM_USED, "IM Used"), + + /// [`300 Multiple Choices`]: https://tools.ietf.org/html/rfc7231#section-6.4.1 + (300, MULTIPLE_CHOICES, "Multiple Choices"), + + /// [`301 Moved Permanently`]: https://tools.ietf.org/html/rfc7231#section-6.4.2 + (301, MOVED_PERMANENTLY, "Moved Permanently"), + + /// [`302 Found`]: https://tools.ietf.org/html/rfc7231#section-6.4.3 + (302, FOUND, "Found"), + + /// [`303 See Other`]: https://tools.ietf.org/html/rfc7231#section-6.4.4 + (303, SEE_OTHER, "See Other"), + + /// [`304 Not Modified`]: https://tools.ietf.org/html/rfc7232#section-4.1 + (304, NOT_MODIFIED, "Not Modified"), + + /// [`305 Use Proxy`]: https://tools.ietf.org/html/rfc7231#section-6.4.5 + (305, USE_PROXY, "Use Proxy"), + + /// [`307 Temporary Redirect`]: https://tools.ietf.org/html/rfc7231#section-6.4.7 + (307, TEMPORARY_REDIRECT, "Temporary Redirect"), + + /// [`308 Permanent Redirect`]: https://tools.ietf.org/html/rfc7238 + (308, PERMANENT_REDIRECT, "Permanent Redirect"), + + /// [`400 Bad Request`]: https://tools.ietf.org/html/rfc7231#section-6.5.1 + (400, BAD_REQUEST, "Bad Request"), + + /// [`401 Unauthorized`]: https://tools.ietf.org/html/rfc7235#section-3.1 + (401, UNAUTHORIZED, "Unauthorized"), + + /// [`402 Payment Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.2 + (402, PAYMENT_REQUIRED, "Payment Required"), + + /// [`403 Forbidden`]: https://tools.ietf.org/html/rfc7231#section-6.5.3 + (403, FORBIDDEN, "Forbidden"), + + /// [`404 Not Found`]: https://tools.ietf.org/html/rfc7231#section-6.5.4 + (404, NOT_FOUND, "Not Found"), + + /// [`405 Method Not Allowed`]: https://tools.ietf.org/html/rfc7231#section-6.5.5 + (405, METHOD_NOT_ALLOWED, "Method Not Allowed"), + + /// [`406 Not Acceptable`]: https://tools.ietf.org/html/rfc7231#section-6.5.6 + (406, NOT_ACCEPTABLE, "Not Acceptable"), + + /// [`407 Proxy Authentication Required`]: https://tools.ietf.org/html/rfc7235#section-3.2 + (407, PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required"), + + /// [`408 Request Timeout`]: https://tools.ietf.org/html/rfc7231#section-6.5.7 + (408, REQUEST_TIMEOUT, "Request Timeout"), + + /// [`409 Conflict`]: https://tools.ietf.org/html/rfc7231#section-6.5.8 + (409, CONFLICT, "Conflict"), + + /// [`410 Gone`]: https://tools.ietf.org/html/rfc7231#section-6.5.9 + (410, GONE, "Gone"), + + /// [`411 Length Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.10 + (411, LENGTH_REQUIRED, "Length Required"), + + /// [`412 Precondition Failed`]: https://tools.ietf.org/html/rfc7232#section-4.2 + (412, PRECONDITION_FAILED, "Precondition Failed"), + + /// [`413 Payload Too Large`]: https://tools.ietf.org/html/rfc7231#section-6.5.11 + (413, PAYLOAD_TOO_LARGE, "Payload Too Large"), + + /// [`414 URI Too Long`]: https://tools.ietf.org/html/rfc7231#section-6.5.12 + (414, URI_TOO_LONG, "URI Too Long"), + + /// [`415 Unsupported Media Type`]: https://tools.ietf.org/html/rfc7231#section-6.5.13 + (415, UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"), + + /// [`416 Range Not Satisfiable`]: https://tools.ietf.org/html/rfc7233#section-4.4 + (416, RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"), + + /// [`417 Expectation Failed`]: https://tools.ietf.org/html/rfc7231#section-6.5.14 + (417, EXPECTATION_FAILED, "Expectation Failed"), + + /// [`418 I'm a teapot`]: https://tools.ietf.org/html/rfc2324 + (418, IM_A_TEAPOT, "I'm a teapot"), + + /// [`421 Misdirected Request`]: http://tools.ietf.org/html/rfc7540#section-9.1.2 + (421, MISDIRECTED_REQUEST, "Misdirected Request"), + + /// [`422 Unprocessable Entity`]: https://tools.ietf.org/html/rfc4918 + (422, UNPROCESSABLE_ENTITY, "Unprocessable Entity"), + + /// [`423 Locked`]: https://tools.ietf.org/html/rfc4918 + (423, LOCKED, "Locked"), + + /// [`424 Failed Dependency`]: https://tools.ietf.org/html/rfc4918 + (424, FAILED_DEPENDENCY, "Failed Dependency"), + + /// [`426 Upgrade Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.15 + (426, UPGRADE_REQUIRED, "Upgrade Required"), + + /// [`428 Precondition Required`]: https://tools.ietf.org/html/rfc6585 + (428, PRECONDITION_REQUIRED, "Precondition Required"), + + /// [`429 Too Many Requests`]: https://tools.ietf.org/html/rfc6585 + (429, TOO_MANY_REQUESTS, "Too Many Requests"), + + /// [`431 Request Header Fields Too Large`]: https://tools.ietf.org/html/rfc6585 + (431, REQUEST_HEADER_FIELDS_TOO_LARGE, "Request Header Fields Too Large"), + + /// [`451 Unavailable For Legal Reasons`]: http://tools.ietf.org/html/rfc7725 + (451, UNAVAILABLE_FOR_LEGAL_REASONS, "Unavailable For Legal Reasons"), + + /// [`500 Internal Server Error`]: https://tools.ietf.org/html/rfc7231#section-6.6.1 + (500, INTERNAL_SERVER_ERROR, "Internal Server Error"), + + /// [`501 Not Implemented`]: https://tools.ietf.org/html/rfc7231#section-6.6.2 + (501, NOT_IMPLEMENTED, "Not Implemented"), + + /// [`502 Bad Gateway`]: https://tools.ietf.org/html/rfc7231#section-6.6.3 + (502, BAD_GATEWAY, "Bad Gateway"), + + /// [`503 Service Unavailable`]: https://tools.ietf.org/html/rfc7231#section-6.6.4 + (503, SERVICE_UNAVAILABLE, "Service Unavailable"), + + /// [`504 Gateway Timeout`]: https://tools.ietf.org/html/rfc7231#section-6.6.5 + (504, GATEWAY_TIMEOUT, "Gateway Timeout"), + + /// [`505 HTTP Version Not Supported`]: https://tools.ietf.org/html/rfc7231#section-6.6.6 + (505, HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported"), + + /// [`506 Variant Also Negotiates`]: https://tools.ietf.org/html/rfc2295 + (506, VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates"), + + /// [`507 Insufficient Storage`]: https://tools.ietf.org/html/rfc4918 + (507, INSUFFICIENT_STORAGE, "Insufficient Storage"), + + /// [`508 Loop Detected`]: https://tools.ietf.org/html/rfc5842 + (508, LOOP_DETECTED, "Loop Detected"), + + /// [`510 Not Extended`]: https://tools.ietf.org/html/rfc2774 + (510, NOT_EXTENDED, "Not Extended"), + + /// [`511 Network Authentication Required`]: https://tools.ietf.org/html/rfc6585 + (511, NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required"), +); + +#[cfg(test)] +mod ut_status_code { + use super::StatusCode; + use crate::error::{ErrorKind, HttpError}; + + /// UT test cases for `StatusCode::from_bytes`. + /// + /// # Brief + /// 1. Calls `StatusCode::from_bytes` with various inputs. + /// 2. Checks if the results are correct. + #[test] + fn ut_status_code_from_u16() { + // Normal Test Cases: + assert_eq!(StatusCode::from_u16(100), Ok(StatusCode::CONTINUE)); + assert!(StatusCode::from_u16(999).is_ok()); + + // Exception Test Cases: + // 1. The given number is not in the range of 100 to 1000. + assert_eq!( + StatusCode::from_u16(0), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_u16(u16::MAX), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } + + /// UT test cases for `StatusCode::as_u16`. + /// + /// # Brief + /// 1. Creates a `StatusCode`. + /// 2. Calls `StatusCode::as_u16` on it. + /// 3. Checks if the result is correct. + #[test] + fn ut_status_code_as_u16() { + assert_eq!(StatusCode::OK.as_u16(), 200); + } + + /// UT test cases for `StatusCode::from_bytes`. + /// + /// # Brief + /// 1. Calls `StatusCode::from_bytes` with various inputs. + /// 2. Checks if the results are correct. + #[test] + fn ut_status_code_from_bytes() { + // Normal Test Cases: + assert_eq!(StatusCode::from_bytes(b"100"), Ok(StatusCode::CONTINUE)); + assert_eq!( + StatusCode::from_bytes(b"500"), + Ok(StatusCode::INTERNAL_SERVER_ERROR) + ); + assert!(StatusCode::from_bytes(b"999").is_ok()); + + // Exception Test Cases: + // 1. Empty bytes slice. + assert_eq!( + StatusCode::from_bytes(b""), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 2. The length of the bytes slice is not 3. + assert_eq!( + StatusCode::from_bytes(b"1"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_bytes(b"1000"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + + // 3. Other error branch coverage test cases. + assert_eq!( + StatusCode::from_bytes(b"099"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_bytes(b"a99"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_bytes(b"1a9"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_bytes(b"19a"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + assert_eq!( + StatusCode::from_bytes(b"\n\n\n"), + Err(HttpError::from(ErrorKind::InvalidInput)) + ); + } + + /// UT test cases for `StatusCode::is_informational`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::is_informational` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_is_informational() { + assert!(StatusCode::CONTINUE.is_informational()); + assert!(!StatusCode::OK.is_informational()); + assert!(!StatusCode::MULTIPLE_CHOICES.is_informational()); + assert!(!StatusCode::BAD_REQUEST.is_informational()); + assert!(!StatusCode::INTERNAL_SERVER_ERROR.is_informational()); + assert!(!StatusCode::from_u16(999).unwrap().is_informational()); + } + + /// UT test cases for `StatusCode::is_successful`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::is_successful` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_is_successful() { + assert!(!StatusCode::CONTINUE.is_successful()); + assert!(StatusCode::OK.is_successful()); + assert!(!StatusCode::MULTIPLE_CHOICES.is_successful()); + assert!(!StatusCode::BAD_REQUEST.is_successful()); + assert!(!StatusCode::INTERNAL_SERVER_ERROR.is_successful()); + assert!(!StatusCode::from_u16(999).unwrap().is_successful()); + } + + /// UT test cases for `StatusCode::is_redirection`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::is_redirection` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_is_redirection() { + assert!(!StatusCode::CONTINUE.is_redirection()); + assert!(!StatusCode::OK.is_redirection()); + assert!(StatusCode::MULTIPLE_CHOICES.is_redirection()); + assert!(!StatusCode::BAD_REQUEST.is_redirection()); + assert!(!StatusCode::INTERNAL_SERVER_ERROR.is_redirection()); + assert!(!StatusCode::from_u16(999).unwrap().is_redirection()); + } + + /// UT test cases for `StatusCode::is_client_error`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::is_client_error` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_is_client_error() { + assert!(!StatusCode::CONTINUE.is_client_error()); + assert!(!StatusCode::OK.is_client_error()); + assert!(!StatusCode::MULTIPLE_CHOICES.is_client_error()); + assert!(StatusCode::BAD_REQUEST.is_client_error()); + assert!(!StatusCode::INTERNAL_SERVER_ERROR.is_client_error()); + assert!(!StatusCode::from_u16(999).unwrap().is_client_error()); + } + + /// UT test cases for `StatusCode::is_server_error`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::is_server_error` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_is_server_error() { + assert!(!StatusCode::CONTINUE.is_server_error()); + assert!(!StatusCode::OK.is_server_error()); + assert!(!StatusCode::MULTIPLE_CHOICES.is_server_error()); + assert!(!StatusCode::BAD_REQUEST.is_server_error()); + assert!(StatusCode::INTERNAL_SERVER_ERROR.is_server_error()); + assert!(!StatusCode::from_u16(999).unwrap().is_server_error()); + } + + /// UT test cases for `StatusCode::as_bytes`. + /// + /// # Brief + /// 1. Creates some `StatusCode`s that have different type with each other. + /// 2. Calls `StatusCode::as_bytes` on them. + /// 3. Checks if the results are correct. + #[test] + fn ut_status_code_as_bytes() { + assert_eq!(StatusCode::OK.as_bytes(), *b"200"); + assert_eq!(StatusCode::FOUND.as_bytes(), *b"302"); + assert_eq!(StatusCode::NOT_FOUND.as_bytes(), *b"404"); + assert_eq!(StatusCode::GATEWAY_TIMEOUT.as_bytes(), *b"504"); + } +} diff --git a/ylong_http/src/test_util.rs b/ylong_http/src/test_util.rs new file mode 100644 index 0000000..a68a885 --- /dev/null +++ b/ylong_http/src/test_util.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pub(crate) fn decode(str: &str) -> Option> { + if str.len() % 2 != 0 { + return None; + } + let mut vec = Vec::new(); + let mut remained = str; + while !remained.is_empty() { + let (left, right) = remained.split_at(2); + match u8::from_str_radix(left, 16) { + Ok(num) => vec.push(num), + Err(_) => return None, + } + remained = right; + } + Some(vec) +} diff --git a/ylong_http/src/version.rs b/ylong_http/src/version.rs new file mode 100644 index 0000000..d73e630 --- /dev/null +++ b/ylong_http/src/version.rs @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP [`Version`]. +//! +//! HTTP's version number consists of two decimal digits separated by a "." +//! (period or decimal point). The first digit (major version) indicates the +//! messaging syntax, whereas the second digit (minor version) indicates the +//! highest minor version within that major version to which the sender is +//! conformant (able to understand for future communication). +//! +//! [`Version`]: https://httpwg.org/specs/rfc9110.html#protocol.version + +use crate::error::{ErrorKind, HttpError}; +use core::convert::TryFrom; + +/// HTTP [`Version`] implementation. +/// +/// [`Version`]: https://httpwg.org/specs/rfc9110.html#protocol.version +/// +/// # Examples +/// +/// ``` +/// use ylong_http::version::Version; +/// +/// assert_eq!(Version::HTTP1_1.as_str(), "HTTP/1.1"); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Version(Inner); + +impl Version { + /// HTTP1.1 + pub const HTTP1_1: Self = Self(Inner::Http11); + /// HTTP2 + pub const HTTP2: Self = Self(Inner::Http2); + /// HTTP3 + pub const HTTP3: Self = Self(Inner::Http3); + + /// Converts a `Version` to a `&str`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::version::Version; + /// + /// assert_eq!(Version::HTTP1_1.as_str(), "HTTP/1.1"); + /// ``` + pub fn as_str(&self) -> &str { + match self.0 { + Inner::Http11 => "HTTP/1.1", + Inner::Http2 => "HTTP/2.0", + Inner::Http3 => "HTTP/3.0", + } + } +} + +impl<'a> TryFrom<&'a str> for Version { + type Error = HttpError; + + fn try_from(str: &'a str) -> Result { + match str { + "HTTP/1.1" => Ok(Version::HTTP1_1), + "HTTP/2.0" => Ok(Version::HTTP2), + "HTTP/3.0" => Ok(Version::HTTP3), + _ => Err(ErrorKind::InvalidInput.into()), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Inner { + Http11, + Http2, + Http3, +} + +#[cfg(test)] +mod ut_version { + use super::Version; + use std::convert::TryFrom; + + /// UT test cases for `Version::as_str`. + /// + /// # Brief + /// 1. Checks whether `Version::as_str` is correct. + #[test] + fn ut_version_as_str() { + assert_eq!(Version::HTTP1_1.as_str(), "HTTP/1.1"); + assert_eq!(Version::HTTP2.as_str(), "HTTP/2.0"); + assert_eq!(Version::HTTP3.as_str(), "HTTP/3.0"); + } + + /// UT test cases for `Version::try_from`. + /// + /// # Brief + /// 1. Checks whether `Version::try_from` is correct. + #[test] + fn ut_version_try_from() { + assert_eq!(Version::try_from("HTTP/1.1").unwrap(), Version::HTTP1_1); + assert_eq!(Version::try_from("HTTP/2.0").unwrap(), Version::HTTP2); + assert_eq!(Version::try_from("HTTP/3.0").unwrap(), Version::HTTP3); + assert!(Version::try_from("http/1.1").is_err()); + assert!(Version::try_from("http/2.0").is_err()); + assert!(Version::try_from("http/3.0").is_err()); + } +} diff --git a/ylong_http_client/Cargo.toml b/ylong_http_client/Cargo.toml new file mode 100644 index 0000000..d7602dd --- /dev/null +++ b/ylong_http_client/Cargo.toml @@ -0,0 +1,100 @@ +[package] +name = "ylong_http_client" +version = "1.9.0" +edition = "2018" +description = "http client" +readme = "README.md" +license = "Apache-2.0" +repository = "https://open.codehub.huawei.com/innersource/Ylong_Rust/ylong_rs/files?ref=master&filePath=src%2Fweb%2Fylong_http_client" +keywords = ["ylong", "http", "client"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ylong_http = { path = "../ylong_http", version = "1.9.0", features = ["full"] } +tokio = { version = "1.20.1", features = ["io-util", "net", "rt", "rt-multi-thread", "macros", "sync", "time"] } +libc = { version = "0.2.134", optional = true } + +[dev-dependencies] +hyper = { version = "0.14.23", features = ["http1", "tcp", "server"] } +openssl = { version = "0.10.29" } +tokio-openssl = { version = "0.6.3" } + +[features] +default = [] +full = [ + "sync", + "async", + "http1_1", + "tls_default", +] + +sync = [] # Uses sync interfaces. +async = [] # Uses async interfaces. +http1_1 = ["ylong_http/http1_1"] # Uses HTTP/1.1. +http2 = ["ylong_http/http2"] # Uses HTTP/2. +http3 = [] # Uses HTTP/3. +tokio_rt = ["tokio/rt", "tokio/rt-multi-thread"] # Uses tokio runtime. +# ylong_rt = ["ylong_sched"] # Uses ylong runtime. + +tls_default = ["c_openssl_3_0"] +__tls = [] # Not open to user, only mark to use tls for developer. +__c_openssl = ["__tls", "libc"] # Not open to user, only mark to use tls by C-openssl for developer. +c_openssl_1_1 = ["__c_openssl"] # Uses TLS by FFI of C-openssl 1.1. +c_openssl_3_0 = ["__c_openssl"] # Uses TLS by FFI of C-openssl 3.0. + +[[example]] +name = "async_http" +path = "examples/async_http.rs" +required-features = ["async", "http1_1"] + +[[example]] +name = "sync_http" +path = "examples/sync_http.rs" +required-features = ["sync", "http1_1"] + +[[example]] +name = "async_redirect_http" +path = "examples/async_redirect_http.rs" +required-features = ["async", "http1_1"] + +[[example]] +name = "sync_redirect_http" +path = "examples/sync_redirect_http.rs" +required-features = ["sync", "http1_1"] + +[[example]] +name = "async_proxy_http" +path = "examples/async_proxy_http.rs" +required-features = ["async", "http1_1"] + +[[example]] +name = "sync_proxy_http" +path = "examples/sync_proxy_http.rs" +required-features = ["sync", "http1_1"] + +[[example]] +name = "sync_http2" +path = "./examples/async_http2.rs" +required-features = ["async", "http2"] + +[[example]] +name = "sync_http2_multi" +path = "./examples/async_http2_multi.rs" +required-features = ["async", "http2"] + +[[test]] +name = "sdv_client" +path = "./tests/sdv_client.rs" +required-features = ["sync", "async", "http1_1"] + +[[test]] +name = "sdv_https_c_ssl" +path = "./tests/sdv_https_c_ssl.rs" +required-features = ["sync", "async", "http1_1", "c_openssl_1_1"] + +[[example]] +name = "async_https_outside" +path = "./examples/async_https_outside.rs" +required-features = ["async", "http1_1", "c_openssl_3_0"] + diff --git a/ylong_http_client/README.md b/ylong_http_client/README.md new file mode 100644 index 0000000..92fce44 --- /dev/null +++ b/ylong_http_client/README.md @@ -0,0 +1,40 @@ +# ylong_http_client + +### 简介 + +ylong_http_client 支持用户构建 HTTP 客户端,支持用户使用该客户端向服务器发送请求,接收并解析服务器返回的响应。 + +##### Client + +ylong_http_client 支持用户创建同步或者异步 HTTP 客户端,用户可以使用 mod 区分两种客户端。 + +- `sync_impl::Client`:同步 HTTP 客户端,整体流程使用同步接口。 + +- `async_impl::Client`:异步 HTTP 客户端,整体流程使用异步接口。 + +不论是同步还是异步客户端,都具有相同的功能,例如:连接复用、自动重定向、自动重试、设置代理等功能。 + +ylong_http_client 创建的客户端支持以下 HTTP 版本: + +- `HTTP/1.1` + +- `HTTP/2` + +- `HTTP/3` + +##### Request 和 Response + +ylong_http_client 使用 ylong_http 库提供的 `Request` 结构,支持用户自定义请求内容。 + +在使用客户端发送完请求后,接收到的响应会以 ylong_http 库提供的 `Response` + `HttpBody` 的结构返回。 + +用户可以使用 `Response` 提供的接口来获取请求信息,并且可以使用 ylong_http 提供的 `Body` trait 读取响应的内容。用户也可以使用 ylong_http_client 提供的 `BodyReader` 读取内容。 + +### 编译构建 + +在 ```Cargo.toml``` 下添加依赖。添加后使用 ```cargo``` 进行编译和构建: + +```toml +[dependencies] +ylong_http_client = "1.9.0" +``` \ No newline at end of file diff --git a/ylong_http_client/build.rs b/ylong_http_client/build.rs new file mode 100644 index 0000000..ad4d95d --- /dev/null +++ b/ylong_http_client/build.rs @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This crate depends on Openssl3.0. +//! Sets environment variables when use the feature `c_openssl_3_0`. +//! Needs export ``OPENSSL_LIB_DIR`` and ``OPENSSL_INCLUDE_DIR``. +//! ``OPENSSL_LIB_DIR`` is the path for ``libssl.so`` and ``libcrypto.so``. +//! ``OPENSSL_INCLUDE_DIR`` is the path for the Openssl header file. + +use std::env; + +fn main() { + let lib_dir = env::var("OPENSSL_LIB_DIR"); + let include_dir = env::var("OPENSSL_INCLUDE_DIR"); + + if let Ok(lib_dir) = lib_dir { + println!("cargo:rustc-link-lib=ssl"); + println!("cargo:rustc-link-lib=crypto"); + println!("cargo:rustc-link-search=native={lib_dir}"); + } + + if let Ok(include_dir) = include_dir { + println!("cargo:include={include_dir}"); + } +} diff --git a/ylong_http_client/examples/async_http.rs b/ylong_http_client/examples/async_http.rs new file mode 100644 index 0000000..bb0ea56 --- /dev/null +++ b/ylong_http_client/examples/async_http.rs @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple asynchronous HTTP client example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response asynchronously. + +use ylong_http_client::async_impl::{Client, Downloader}; +use ylong_http_client::Request; + +#[tokio::main] +async fn main() { + // Creates a `async_impl::Client` + let client = Client::new(); + + // Creates a `Request`. + let request = Request::get("127.0.0.1:3000").body("".as_bytes()).unwrap(); + + // Sends request and receives a `Response`. + let response = client.request(request).await.unwrap(); + + // Reads the body of `Response` by using `BodyReader`. + let _ = Downloader::console(response).download().await; +} diff --git a/ylong_http_client/examples/async_http2.rs b/ylong_http_client/examples/async_http2.rs new file mode 100644 index 0000000..04fd3a0 --- /dev/null +++ b/ylong_http_client/examples/async_http2.rs @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple asynchronous HTTP2 client example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response asynchronously. + +use ylong_http_client::async_impl::{Body, ClientBuilder}; +use ylong_http_client::{RequestBuilder, StatusCode, TextBody, Version}; + +fn main() { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .expect("Build runtime failed."); + + let client = ClientBuilder::new() + .http2_prior_knowledge() + .build() + .unwrap(); + + let request = RequestBuilder::new() + .version("HTTP/2.0") + .url("127.0.0.1:5678") + .method("GET") + .header("host", "127.0.0.1") + .body(TextBody::from_bytes("Hi".as_bytes())) + .unwrap(); + + rt.block_on(async move { + let mut response = client.request(request).await.unwrap(); + assert_eq!(response.version(), &Version::HTTP2); + assert_eq!(response.status(), &StatusCode::OK); + + let mut buf = [0u8; 4096]; + let mut size = 0; + + loop { + let read = response + .body_mut() + .data(&mut buf[size..]) + .await + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!( + &buf[..size], + "hello world".as_bytes(), + "Assert response body failed" + ); + }); +} diff --git a/ylong_http_client/examples/async_http2_multi.rs b/ylong_http_client/examples/async_http2_multi.rs new file mode 100644 index 0000000..cc5dfd1 --- /dev/null +++ b/ylong_http_client/examples/async_http2_multi.rs @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple asynchronous HTTP client example in concurrent scenarios using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response asynchronously. + +use std::sync::Arc; +use ylong_http_client::async_impl::{Body, ClientBuilder}; +use ylong_http_client::{RequestBuilder, StatusCode, TextBody, Version}; + +fn main() { + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .expect("Build runtime failed."); + + let client = ClientBuilder::new() + .http2_prior_knowledge() + .build() + .unwrap(); + + let client_interface = Arc::new(client); + let mut shut_downs = vec![]; + + for i in 0..5 { + let client = client_interface.clone(); + let handle = rt.spawn(async move { + let body_text = format!("hello {i}"); + let request = RequestBuilder::new() + .version("HTTP/2.0") + .url("127.0.0.1:5678") + .method("GET") + .header("host", "127.0.0.1") + .body(TextBody::from_bytes(body_text.as_bytes())) + .unwrap(); + + let mut response = client.request(request).await.unwrap(); + assert_eq!(response.version(), &Version::HTTP2); + assert_eq!(response.status(), &StatusCode::OK); + + let mut buf = [0u8; 4096]; + let mut size = 0; + + loop { + let read = response + .body_mut() + .data(&mut buf[size..]) + .await + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!( + &buf[..size], + "hello world".as_bytes(), + "Assert response body failed" + ); + }); + + shut_downs.push(handle); + } + + for shut_down in shut_downs { + rt.block_on(shut_down) + .expect("Runtime wait for server shutdown failed"); + } +} diff --git a/ylong_http_client/examples/async_https_outside.rs b/ylong_http_client/examples/async_https_outside.rs new file mode 100644 index 0000000..58dc042 --- /dev/null +++ b/ylong_http_client/examples/async_https_outside.rs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use ylong_http_client::async_impl::{Client, Downloader}; +use ylong_http_client::util::Redirect; +use ylong_http_client::{Certificate, Request}; + +#[tokio::main] +async fn main() { + let v = include_bytes!("./test.pem"); + let cert = Certificate::from_pem(v); + // Creates a `async_impl::Client` + let client = Client::builder() + .redirect(Redirect::default()) + .add_root_certificate(cert.unwrap()) + .build() + .unwrap(); + + // Creates a `Request`. + let request = Request::get("https://www.huawei.com") + .body("".as_bytes()) + .unwrap(); + + // Sends request and receives a `Response`. + let response = client.request(request).await; + assert!(response.is_ok()); + + // Reads the body of `Response` by using `BodyReader`. + let _ = Downloader::console(response.unwrap()).download().await; +} diff --git a/ylong_http_client/examples/async_proxy_http.rs b/ylong_http_client/examples/async_proxy_http.rs new file mode 100644 index 0000000..e9a64ea --- /dev/null +++ b/ylong_http_client/examples/async_proxy_http.rs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple asynchronous HTTP client example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response asynchronously. +use ylong_http_client::async_impl::{ClientBuilder, Downloader}; +use ylong_http_client::{EmptyBody, Proxy, Request}; + +#[tokio::main] +async fn main() { + // Creates a `async_impl::Client` + let client = ClientBuilder::new() + .proxy(Proxy::all("http://proxy.example.com").build().unwrap()) + .build() + .unwrap(); + // Creates a `Request`. + let request = Request::get("http://127.0.0.1:3000") + .body(EmptyBody) + .unwrap(); + // Sends request and receives a `Response`. + let response = client.request(request).await.unwrap(); + // Reads the body of `Response` by using `BodyReader`. + let _ = Downloader::console(response).download().await; +} diff --git a/ylong_http_client/examples/async_redirect_http.rs b/ylong_http_client/examples/async_redirect_http.rs new file mode 100644 index 0000000..5c7a72f --- /dev/null +++ b/ylong_http_client/examples/async_redirect_http.rs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple asynchronous HTTP client redirect example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response asynchronously. + +use ylong_http_client::async_impl::{ClientBuilder, Downloader}; +use ylong_http_client::{Redirect, Request}; + +#[tokio::main] +async fn main() { + // Creates a `async_impl::Client` + let client = ClientBuilder::new() + .redirect(Redirect::default()) + .build() + .unwrap(); + // Creates a `Request`. + let request = Request::get("127.0.0.1:3000").body("".as_bytes()).unwrap(); + + // Sends request and receives a `Response`. + let response = client.request(request).await.unwrap(); + // Reads the body of `Response` by using `BodyReader`. + let _ = Downloader::console(response).download().await; +} diff --git a/ylong_http_client/examples/sync_http.rs b/ylong_http_client/examples/sync_http.rs new file mode 100644 index 0000000..ca35836 --- /dev/null +++ b/ylong_http_client/examples/sync_http.rs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple synchronous HTTP client example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response. + +use ylong_http_client::sync_impl::{BodyReader, Client}; +use ylong_http_client::Request; + +fn main() { + // Creates a `sync_impl::Client` + let client = Client::new(); + + // Creates a `Request`. + let request = Request::get("127.0.0.1:3000").body("".as_bytes()).unwrap(); + + // Sends request and receives a `Response`. + let mut response = client.request(request).unwrap(); + + // Reads the body of `Response` by using `BodyReader`. + let _ = BodyReader::default().read_all(response.body_mut()); +} diff --git a/ylong_http_client/examples/sync_proxy_http.rs b/ylong_http_client/examples/sync_proxy_http.rs new file mode 100644 index 0000000..2ccc318 --- /dev/null +++ b/ylong_http_client/examples/sync_proxy_http.rs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple synchronous HTTP client example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response. +use ylong_http_client::sync_impl::{BodyReader, ClientBuilder}; +use ylong_http_client::util::Proxy; +use ylong_http_client::{EmptyBody, Request}; + +fn main() { + // Creates a `sync_impl::Client` + let client = ClientBuilder::new() + .proxy(Proxy::http("https://proxy.example.com").build().unwrap()) + .build() + .unwrap(); + // Creates a `Request`. + + let request = Request::get("http://127.0.0.1:3000") + .body(EmptyBody) + .unwrap(); + + // Sends request and receives a `Response`. + let mut response = client.request(request).unwrap(); + // Reads the body of `Response` by using `BodyReader`. + let _ = BodyReader::default().read_all(response.body_mut()); +} diff --git a/ylong_http_client/examples/sync_redirect_http.rs b/ylong_http_client/examples/sync_redirect_http.rs new file mode 100644 index 0000000..0e1c373 --- /dev/null +++ b/ylong_http_client/examples/sync_redirect_http.rs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This is a simple synchronous HTTP client redirect example using the ylong_http_client crate. +//! It demonstrates creating a client, making a request, and reading the response. + +use ylong_http_client::sync_impl::{BodyReader, ClientBuilder}; +use ylong_http_client::util::Redirect; +use ylong_http_client::Request; + +fn main() { + // Creates a `sync_impl::Client` + let client = ClientBuilder::new() + .redirect(Redirect::default()) + .build() + .unwrap(); + + // Creates a `Request`. + let request = Request::get("127.0.0.1:3000").body("".as_bytes()).unwrap(); + + // Sends request and receives a `Response`. + let mut response = client.request(request).unwrap(); + + // Reads the body of `Response` by using `BodyReader`. + let _ = BodyReader::default().read_all(response.body_mut()); +} diff --git a/ylong_http_client/src/async_impl/adapter.rs b/ylong_http_client/src/async_impl/adapter.rs new file mode 100644 index 0000000..a029558 --- /dev/null +++ b/ylong_http_client/src/async_impl/adapter.rs @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::HttpBody; +use crate::{ErrorKind, HttpClientError}; +use std::convert::TryFrom; +use std::ops::{Deref, DerefMut}; +use ylong_http::body::async_impl::Body; +use ylong_http::body::MultiPart; +use ylong_http::error::HttpError; +use ylong_http::headers::{HeaderName, HeaderValue}; +use ylong_http::request::method::Method; +use ylong_http::request::uri::Uri; +use ylong_http::request::{Request, RequestBuilder as ReqBuilder}; +use ylong_http::response::Response as Resp; +use ylong_http::version::Version; + +/// Response Adapter. +pub struct Response { + response: Resp, +} + +impl Response { + pub(crate) fn new(response: Resp) -> Self { + Self { response } + } + + /// `text()` adapter. + pub async fn text(self) -> Result { + let mut buf = [0u8; 1024]; + let mut vec = Vec::new(); + let mut response = self.response; + loop { + let size = response.body_mut().data(&mut buf).await?; + if size == 0 { + break; + } + vec.extend_from_slice(&buf[..size]); + } + String::from_utf8(vec).map_err(|_| { + HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "The body content is not valid utf8.", + ) + }) + } +} + +impl Deref for Response { + type Target = Resp; + + fn deref(&self) -> &Self::Target { + &self.response + } +} + +impl DerefMut for Response { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.response + } +} + +/// RequestBuilder Adapter +pub struct RequestBuilder(ReqBuilder); + +impl RequestBuilder { + /// Creates a new, default `RequestBuilder`. + pub fn new() -> Self { + Self(ReqBuilder::new()) + } + + /// Sets the `Method` of the `Request`. + pub fn method(self, method: T) -> Self + where + Method: TryFrom, + >::Error: Into, + { + Self(self.0.method(method)) + } + + /// Sets the `Uri` of the `Request`. `Uri` does not provide a default value, + /// so it must be set. + pub fn url(self, uri: T) -> Self + where + Uri: TryFrom, + >::Error: Into, + { + Self(self.0.url(uri)) + } + + /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default. + pub fn version(mut self, version: T) -> Self + where + Version: TryFrom, + >::Error: Into, + { + self.0 = self.0.version(version); + self + } + + /// Adds a `Header` to `Request`. Overwrites `HeaderValue` if the + /// `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().header("ACCEPT","text/html"); + /// ``` + pub fn header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.0 = self.0.header(name, value); + self + } + + /// Adds a `Header` to `Request`. Appends `HeaderValue` to the end of + /// previous `HeaderValue` if the `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::headers::Headers; + /// use ylong_http::request::RequestBuilder; + /// + /// let request = RequestBuilder::new().append_header("ACCEPT","text/html"); + /// ``` + pub fn append_header(mut self, name: N, value: V) -> Self + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, + { + self.0 = self.0.append_header(name, value); + self + } + + /// Try to create a `Request` based on the incoming `body`. + pub fn body(self, body: T) -> Result, HttpClientError> { + self.0 + .body(body) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e))) + } + + /// Creates a `Request` that uses this `RequestBuilder` configuration and + /// the provided `Multipart`. You can also provide a `Uploader` + /// as the body. + /// + /// # Error + /// + /// This method fails if some configurations are wrong. + pub fn multipart(self, body: T) -> Result, HttpClientError> + where + T: AsRef, + { + self.0 + .multipart(body) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e))) + } +} + +impl Default for RequestBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs new file mode 100644 index 0000000..59b289b --- /dev/null +++ b/ylong_http_client/src/async_impl/client.rs @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{conn, Body, ConnPool, Connector, HttpBody, HttpConnector}; +use crate::async_impl::timeout::TimeoutFuture; +use crate::util::normalizer::{RequestFormatter, UriFormatter}; +use crate::util::proxy::Proxies; +use crate::util::redirect::TriggerKind; +use crate::util::{ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Redirect}; +use crate::{ErrorKind, HttpClientError, Proxy, Request, Timeout, Uri}; +use ylong_http::body::{ChunkBody, TextBody}; +use ylong_http::response::Response; + +#[cfg(feature = "http2")] +use crate::H2Config; + +/// HTTP asynchronous client implementation. Users can use `async_impl::Client` to +/// send `Request` asynchronously. `async_impl::Client` depends on a +/// [`async_impl::Connector`] that can be customized by the user. +/// +/// [`async_impl::Connector`]: Connector +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::async_impl::Client; +/// use ylong_http_client::{Request, EmptyBody}; +/// +/// async fn async_client() { +/// // Creates a new `Client`. +/// let client = Client::new(); +/// +/// // Creates a new `Request`. +/// let request = Request::new(EmptyBody); +/// +/// // Sends `Request` and wait for the `Response` to return asynchronously. +/// let response = client.request(request).await.unwrap(); +/// +/// // Gets the content of `Response`. +/// let status = response.status(); +/// } +/// ``` +pub struct Client { + inner: ConnPool, + client_config: ClientConfig, + http_config: HttpConfig, +} + +impl Client { + /// Creates a new, default `AsyncClient`, which uses [`async_impl::HttpConnector`]. + /// + /// [`async_impl::HttpConnector`]: HttpConnector + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Client; + /// + /// let client = Client::new(); + /// ``` + pub fn new() -> Self { + Self::with_connector(HttpConnector::default()) + } + + /// Creates a new, default [`async_impl::ClientBuilder`]. + /// + /// [`async_impl::ClientBuilder`]: ClientBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Client; + /// + /// let builder = Client::builder(); + /// ``` + pub fn builder() -> ClientBuilder { + ClientBuilder::new() + } +} + +impl Client { + /// Creates a new, default `AsyncClient` with a given connector. + pub fn with_connector(connector: C) -> Self { + let http_config = HttpConfig::default(); + Self { + inner: ConnPool::new(http_config.clone(), connector), + client_config: ClientConfig::default(), + http_config, + } + } + + /// Sends HTTP `Request` asynchronously. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Client; + /// use ylong_http_client::{Request, EmptyBody}; + /// + /// async fn async_client() { + /// let client = Client::new(); + /// let response = client.request(Request::new(EmptyBody)).await; + /// } + /// ``` + // TODO: change result to `Response` later. + pub async fn request( + &self, + request: Request, + ) -> Result { + let (part, body) = request.into_parts(); + + let content_length = part + .headers + .get("Content-Length") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.parse::().ok()) + .is_some(); + + let transfer_encoding = part + .headers + .get("Transfer-Encoding") + .and_then(|v| v.to_str().ok()) + .map(|v| v.contains("chunked")) + .unwrap_or(false); + + let response = match (content_length, transfer_encoding) { + (_, true) => { + let request = Request::from_raw_parts(part, ChunkBody::from_async_body(body)); + self.retry_send_request(request).await + } + (true, false) => { + let request = Request::from_raw_parts(part, TextBody::from_async_body(body)); + self.retry_send_request(request).await + } + (false, false) => { + let request = Request::from_raw_parts(part, body); + self.retry_send_request(request).await + } + }; + response.map(super::Response::new) + } + + async fn retry_send_request( + &self, + mut request: Request, + ) -> Result, HttpClientError> { + let mut retries = self.client_config.retry.times().unwrap_or(0); + loop { + let response = self.send_request_retryable(&mut request).await; + if response.is_ok() || retries == 0 { + return response; + } + retries -= 1; + } + } + + async fn send_request_retryable( + &self, + request: &mut Request, + ) -> Result, HttpClientError> { + let response = self + .send_request_with_uri(request.uri().clone(), request) + .await?; + self.redirect_request(response, request).await + } + + async fn redirect_request( + &self, + mut response: Response, + request: &mut Request, + ) -> Result, HttpClientError> { + let mut redirected_list = vec![]; + let mut dst_uri = Uri::default(); + loop { + if Redirect::is_redirect(response.status().clone(), request) { + redirected_list.push(request.uri().clone()); + let trigger = Redirect::get_redirect( + &mut dst_uri, + &self.client_config.redirect, + &redirected_list, + &response, + request, + )?; + + UriFormatter::new().format(&mut dst_uri)?; + let _ = request + .headers_mut() + .insert("Host", dst_uri.authority().unwrap().to_string().as_bytes()); + match trigger { + TriggerKind::NextLink => { + response = self.send_request_with_uri(dst_uri.clone(), request).await?; + continue; + } + TriggerKind::Stop => { + return Ok(response); + } + } + } else { + return Ok(response); + } + } + } + + async fn send_request_with_uri( + &self, + mut uri: Uri, + request: &mut Request, + ) -> Result, HttpClientError> { + UriFormatter::new().format(&mut uri)?; + RequestFormatter::new(request).normalize()?; + + match self.http_config.version { + #[cfg(feature = "http2")] + HttpVersion::Http2PriorKnowledge => self.http2_request(uri, request).await, + HttpVersion::Http11 => { + let conn = if let Some(timeout) = self.client_config.connect_timeout.inner() { + match tokio::time::timeout(timeout, self.inner.connect_to(uri)).await { + Err(_elapsed) => { + return Err(HttpClientError::new_with_message( + ErrorKind::Timeout, + "Connect timeout", + )) + } + Ok(Ok(conn)) => conn, + Ok(Err(e)) => return Err(e), + } + } else { + self.inner.connect_to(uri).await? + }; + + let mut retryable = Retryable::default(); + if let Some(timeout) = self.client_config.request_timeout.inner() { + TimeoutFuture { + timeout: Some(Box::pin(tokio::time::sleep(timeout))), + future: Box::pin(conn::request(conn, request, &mut retryable)), + } + .await + } else { + conn::request(conn, request, &mut retryable).await + } + } + } + } + + #[cfg(feature = "http2")] + async fn http2_request( + &self, + uri: Uri, + request: &mut Request, + ) -> Result, HttpClientError> { + let mut retryable = Retryable::default(); + + const RETRY: usize = 1; + let mut times = 0; + loop { + retryable.set_retry(false); + let conn = self.inner.connect_to(uri.clone()).await?; + let response = conn::request(conn, request, &mut retryable).await; + if retryable.retry() && times < RETRY { + times += 1; + continue; + } + return response; + } + } +} + +impl Default for Client { + fn default() -> Self { + Self::new() + } +} + +#[derive(Default)] +pub(crate) struct Retryable { + #[cfg(feature = "http2")] + retry: bool, +} + +#[cfg(feature = "http2")] +impl Retryable { + pub(crate) fn set_retry(&mut self, retryable: bool) { + self.retry = retryable + } + + pub(crate) fn retry(&self) -> bool { + self.retry + } +} + +/// A builder which is used to construct `async_impl::Client`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::async_impl::ClientBuilder; +/// +/// let client = ClientBuilder::new().build(); +/// ``` +pub struct ClientBuilder { + /// Options and flags that is related to `HTTP`. + http: HttpConfig, + + /// Options and flags that is related to `Client`. + client: ClientConfig, + + /// Options and flags that is related to `Proxy`. + proxies: Proxies, + + /// Options and flags that is related to `TLS`. + #[cfg(feature = "__tls")] + tls: crate::util::TlsConfigBuilder, +} + +impl ClientBuilder { + /// Creates a new, default `ClientBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { + http: HttpConfig::default(), + client: ClientConfig::default(), + proxies: Proxies::default(), + + #[cfg(feature = "__tls")] + tls: crate::util::TlsConfig::builder(), + } + } + + /// Only use HTTP/1. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new().http1_only(); + /// ``` + pub fn http1_only(mut self) -> Self { + self.http.version = HttpVersion::Http11; + self + } + + /// Only use HTTP/2. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new().http2_prior_knowledge(); + /// ``` + #[cfg(feature = "http2")] + pub fn http2_prior_knowledge(mut self) -> Self { + self.http.version = HttpVersion::Http2PriorKnowledge; + self + } + + /// HTTP/2 settings. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::H2Config; + /// + /// let builder = ClientBuilder::new().http2_settings(H2Config::default()); + /// ``` + #[cfg(feature = "http2")] + pub fn http2_settings(mut self, config: H2Config) -> Self { + self.http.http2_config = config; + self + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request starts connection util the + /// response body has finished. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::Timeout; + /// + /// let builder = ClientBuilder::new().request_timeout(Timeout::none()); + /// ``` + pub fn request_timeout(mut self, timeout: Timeout) -> Self { + self.client.request_timeout = timeout; + self + } + + /// Sets a timeout for only the connect phase of `Client`. + /// + /// Default is `Timeout::none()`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::Timeout; + /// + /// let builder = ClientBuilder::new().connect_timeout(Timeout::none()); + /// ``` + pub fn connect_timeout(mut self, timeout: Timeout) -> Self { + self.client.connect_timeout = timeout; + self + } + + /// Sets a `Redirect` for this client. + /// + /// Default will follow redirects up to a maximum of 10. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::Redirect; + /// + /// let builder = ClientBuilder::new().redirect(Redirect::none()); + /// ``` + pub fn redirect(mut self, redirect: Redirect) -> Self { + self.client.redirect = redirect; + self + } + + /// Adds a `Proxy` to the list of proxies the `Client` will use. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::ClientBuilder; + /// # use ylong_http_client::{HttpClientError, Proxy}; + /// + /// # fn add_proxy() -> Result<(), HttpClientError> { + /// let builder = ClientBuilder::new().proxy(Proxy::http("http://www.example.com").build()?); + /// # Ok(()) + /// # } + /// ``` + pub fn proxy(mut self, proxy: Proxy) -> Self { + self.proxies.add_proxy(proxy.inner()); + self + } + + /// Sets the maximum allowed TLS version for connections. + /// + /// By default there's no maximum. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::TlsVersion; + /// + /// let builder = ClientBuilder::new().max_tls_version(TlsVersion::TLS_1_2); + /// ``` + #[cfg(feature = "__tls")] + pub fn max_tls_version(mut self, version: crate::util::TlsVersion) -> Self { + self.tls = self.tls.set_max_proto_version(version); + self + } + + /// Sets the minimum required TLS version for connections. + /// + /// By default the TLS backend's own default is used. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::TlsVersion; + /// + /// let builder = ClientBuilder::new().min_tls_version(TlsVersion::TLS_1_2); + /// ``` + #[cfg(feature = "__tls")] + pub fn min_tls_version(mut self, version: crate::util::TlsVersion) -> Self { + self.tls = self.tls.set_min_proto_version(version); + self + } + + /// Adds a custom root certificate. + /// + /// This can be used to connect to a server that has a self-signed. + /// certificate for example. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::Certificate; + /// + /// # fn set_cert(cert: Certificate) { + /// let builder = ClientBuilder::new().add_root_certificate(cert); + /// # } + /// ``` + #[cfg(feature = "__tls")] + pub fn add_root_certificate(mut self, certs: crate::util::Certificate) -> Self { + self.tls = self.tls.add_root_certificates(certs); + self + } + + /// Loads trusted root certificates from a file. The file should contain a + /// sequence of PEM-formatted CA certificates. + #[cfg(feature = "__tls")] + pub fn set_ca_file(mut self, path: &str) -> Self { + self.tls = self.tls.set_ca_file(path); + self + } + + /// Sets the list of supported ciphers for protocols before `TLSv1.3`. + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new() + /// .set_cipher_list( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + #[cfg(feature = "__tls")] + pub fn set_cipher_list(mut self, list: &str) -> Self { + self.tls = self.tls.set_cipher_list(list); + self + } + + /// Sets the list of supported ciphers for the `TLSv1.3` protocol. + /// + /// The format consists of TLSv1.3 cipher suite names separated by `:` + /// characters in order of preference. + /// + /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new() + /// .set_cipher_suites( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + #[cfg(feature = "__tls")] + pub fn set_cipher_suites(mut self, list: &str) -> Self { + self.tls = self.tls.set_cipher_suites(list); + self + } + + /// Controls the use of built-in system certificates during certificate validation. + /// Default to `true` -- uses built-in system certs. + #[cfg(feature = "__tls")] + pub fn tls_built_in_root_certs(mut self, is_use: bool) -> ClientBuilder { + self.tls = self.tls.build_in_root_certs(is_use); + self + } + + /// Constructs a `Client` based on the given settings. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let client = ClientBuilder::new().build(); + /// ``` + pub fn build(self) -> Result, HttpClientError> { + let config = ConnectorConfig { + proxies: self.proxies, + #[cfg(feature = "__tls")] + tls: self.tls.build()?, + }; + + let connector = HttpConnector::new(config); + + Ok(Client { + inner: ConnPool::new(self.http.clone(), connector), + client_config: self.client, + http_config: self.http, + }) + } +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod ut_async_impl_client { + use crate::async_impl::Client; + + /// UT test cases for `Client::builder`. + /// + /// # Brief + /// 1. Creates a ClientBuilder by calling `Client::Builder`. + /// 2. Calls `http_config`, `client_config`, `build` on the builder respectively. + /// 3. Checks if the result is as expected. + #[test] + fn ut_client_builder() { + let builder = Client::builder().http1_only().build(); + assert!(builder.is_ok()) + } + + /// UT test cases for `ClientBuilder::default`. + /// + /// # Brief + /// 1. Creates a `ClientBuilder` by calling `ClientBuilder::default`. + /// 2. Calls `http_config`, `client_config`, `tls_config` and `build` respectively. + /// 3. Checks if the result is as expected. + #[cfg(feature = "__tls")] + #[test] + fn ut_client_builder_default() { + use crate::async_impl::ClientBuilder; + use crate::util::{Redirect, Timeout}; + + let builder = ClientBuilder::default() + .redirect(Redirect::none()) + .connect_timeout(Timeout::from_secs(9)) + .build(); + assert!(builder.is_ok()) + } +} diff --git a/ylong_http_client/src/async_impl/conn/http1.rs b/ylong_http_client/src/async_impl/conn/http1.rs new file mode 100644 index 0000000..bd6fd78 --- /dev/null +++ b/ylong_http_client/src/async_impl/conn/http1.rs @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::{Body, HttpBody, StreamData}; +use crate::error::{ErrorKind, HttpClientError}; +use crate::util::dispatcher::http1::Http1Conn; +use crate::Request; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; +use ylong_http::h1::{RequestEncoder, ResponseDecoder}; +use ylong_http::response::Response; + +const TEMP_BUF_SIZE: usize = 16 * 1024; + +pub(crate) async fn request( + mut conn: Http1Conn, + request: &mut Request, +) -> Result, HttpClientError> +where + T: Body, + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +{ + let mut buf = vec![0u8; TEMP_BUF_SIZE]; + + // Encodes and sends Request-line and Headers(non-body fields). + let mut non_body = RequestEncoder::new(request.part().clone()); + loop { + match non_body.encode(&mut buf[..]) { + Ok(0) => break, + Ok(written) => { + // RequestEncoder writes `buf` as much as possible. + conn.raw_mut() + .write_all(&buf[..written]) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + } + Err(e) => return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))), + } + } + + // Encodes Request Body. + let body = request.body_mut(); + let mut written = 0; + let mut end_body = false; + while !end_body { + if written < buf.len() { + match body.data(&mut buf[written..]).await { + Ok(0) => end_body = true, + Ok(size) => written += size, + Err(e) => { + return Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + )) + } + } + } + if written == buf.len() || end_body { + conn.raw_mut() + .write_all(&buf[..written]) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyTransfer, Some(e)))?; + written = 0; + } + } + + // Decodes response part. + let (part, pre) = { + let mut decoder = ResponseDecoder::new(); + loop { + let size = conn + .raw_mut() + .read(buf.as_mut_slice()) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + match decoder.decode(&buf[..size]) { + Ok(None) => {} + Ok(Some((part, rem))) => break (part, rem), + Err(e) => return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))), + } + } + }; + + // Generates response body. + let body = { + let chunked = part + .headers + .get("Transfer-Encoding") + .map(|v| v.to_str().unwrap_or(String::new())) + .and_then(|s| s.find("chunked")) + .is_some(); + let content_length = part + .headers + .get("Content-Length") + .map(|v| v.to_str().unwrap_or(String::new())) + .and_then(|s| s.parse::().ok()); + + let is_trailer = part.headers.get("Trailer").is_some(); + + match (chunked, content_length, pre.is_empty()) { + (true, None, _) => HttpBody::chunk(pre, Box::new(conn), is_trailer), + (false, Some(0), _) => HttpBody::empty(), + (false, Some(len), _) => HttpBody::text(len, pre, Box::new(conn)), + (false, None, true) => HttpBody::empty(), + // TODO: Need more information about this error. + _ => { + return Err(HttpClientError::new_with_message( + ErrorKind::Request, + "Invalid Response Format", + )) + } + } + }; + Ok(Response::from_raw_parts(part, body)) +} + +impl AsyncRead for Http1Conn { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(self.raw_mut()).poll_read(cx, buf) + } +} + +impl StreamData for Http1Conn { + fn shutdown(&self) { + Self::shutdown(self) + } +} diff --git a/ylong_http_client/src/async_impl/conn/http2.rs b/ylong_http_client/src/async_impl/conn/http2.rs new file mode 100644 index 0000000..49f9b76 --- /dev/null +++ b/ylong_http_client/src/async_impl/conn/http2.rs @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::client::Retryable; +use crate::async_impl::conn::HttpBody; +use crate::async_impl::StreamData; +use crate::error::{ErrorKind, HttpClientError}; +use crate::util::dispatcher::http2::Http2Conn; +use std::cmp::min; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use ylong_http::body::async_impl::Body; +use ylong_http::error::HttpError; +use ylong_http::h2; +use ylong_http::h2::{ErrorCode, Frame, FrameFlags, H2Error, Payload, PseudoHeaders}; +use ylong_http::headers::Headers; +use ylong_http::request::uri::Scheme; +use ylong_http::request::{Request, RequestPart}; +use ylong_http::response::status::StatusCode; +use ylong_http::response::{Response, ResponsePart}; + +const UNUSED_FLAG: u8 = 0x0; + +pub(crate) async fn request( + mut conn: Http2Conn, + request: &mut Request, + retryable: &mut Retryable, +) -> Result, HttpClientError> +where + T: Body, + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +{ + let part = request.part().clone(); + let body = request.body_mut(); + + // TODO Due to the reason of the Body structure, the use of the trailer is not implemented here for the time being, and it needs to be completed after the Body trait is provided to obtain the trailer interface + match build_data_frame(conn.id as usize, body).await? { + None => { + let headers = build_headers_frame(conn.id, part, true) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + conn.send_frame_to_controller(headers).map_err(|e| { + retryable.set_retry(true); + HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + })?; + } + Some(data) => { + let headers = build_headers_frame(conn.id, part, false) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + conn.send_frame_to_controller(headers).map_err(|e| { + retryable.set_retry(true); + HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + })?; + conn.send_frame_to_controller(data).map_err(|e| { + retryable.set_retry(true); + HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + })?; + } + } + let frame = Pin::new(&mut conn.stream_info) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + frame_2_response(conn, frame, retryable) +} + +fn frame_2_response( + conn: Http2Conn, + headers_frame: Frame, + retryable: &mut Retryable, +) -> Result, HttpClientError> +where + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +{ + let part = match headers_frame.payload() { + Payload::Headers(headers) => { + let (pseudo, fields) = headers.parts(); + let status_code = match pseudo.status() { + Some(status) => StatusCode::from_bytes(status.as_bytes()) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?, + None => { + return Err(HttpClientError::new_with_cause( + ErrorKind::Request, + Some(HttpError::from(H2Error::StreamError( + conn.id, + ErrorCode::ProtocolError, + ))), + )); + } + }; + ResponsePart { + version: ylong_http::version::Version::HTTP2, + status: status_code, + headers: fields.clone(), + } + } + Payload::RstStream(reset) => { + return Err(HttpClientError::new_with_cause( + ErrorKind::Request, + Some(HttpError::from(reset.error(conn.id).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + })?)), + )); + } + Payload::Goaway(_) => { + // return Err(HttpClientError::from(ErrorKind::Resend)); + retryable.set_retry(true); + return Err(HttpClientError::new_with_message( + ErrorKind::Request, + "GoAway", + )); + } + _ => { + return Err(HttpClientError::new_with_cause( + ErrorKind::Request, + Some(HttpError::from(H2Error::StreamError( + conn.id, + ErrorCode::ProtocolError, + ))), + )); + } + }; + + let body = { + if headers_frame.flags().is_end_stream() { + HttpBody::empty() + } else { + // TODO Can Content-Length in h2 be null? + let content_length = part + .headers + .get("Content-Length") + .map(|v| v.to_str().unwrap_or(String::new())) + .and_then(|s| s.parse::().ok()); + match content_length { + None => HttpBody::empty(), + Some(0) => HttpBody::empty(), + Some(size) => { + let text_io = TextIo::new(conn); + HttpBody::text(size, &[0u8; 0], Box::new(text_io)) + } + } + } + }; + Ok(Response::from_raw_parts(part, body)) +} + +pub(crate) async fn build_data_frame( + id: usize, + body: &mut T, +) -> Result, HttpClientError> { + let mut data_vec = vec![]; + let mut buf = [0u8; 1024]; + loop { + let size = body + .data(&mut buf) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + if size == 0 { + break; + } + data_vec.extend_from_slice(&buf[..size]); + } + if data_vec.is_empty() { + Ok(None) + } else { + // TODO When the Body trait supports trailer, END_STREAM_FLAG needs to be modified + let mut flag = FrameFlags::new(UNUSED_FLAG); + flag.set_end_stream(true); + Ok(Some(Frame::new( + id, + flag, + Payload::Data(h2::Data::new(data_vec)), + ))) + } +} + +pub(crate) fn build_headers_frame( + id: u32, + part: RequestPart, + is_end_stream: bool, +) -> Result { + check_connection_specific_headers(id, &part.headers)?; + let pseudo = build_pseudo_headers(&part); + let mut header_part = h2::Parts::new(); + header_part.set_header_lines(part.headers); + header_part.set_pseudo(pseudo); + let headers_payload = h2::Headers::new(header_part); + + let mut flag = FrameFlags::new(UNUSED_FLAG); + flag.set_end_headers(true); + if is_end_stream { + flag.set_end_stream(true); + } + Ok(Frame::new( + id as usize, + flag, + Payload::Headers(headers_payload), + )) +} + +// Illegal headers validation in http2. +// [`Connection-Specific Headers`] implementation. +// +// [`Connection-Specific Headers`]: https://www.rfc-editor.org/rfc/rfc9113.html#name-connection-specific-header- +fn check_connection_specific_headers(id: u32, headers: &Headers) -> Result<(), HttpError> { + const CONNECTION_SPECIFIC_HEADERS: &[&str; 5] = &[ + "connection", + "keep-alive", + "proxy-connection", + "upgrade", + "transfer-encoding", + ]; + for specific_header in CONNECTION_SPECIFIC_HEADERS.iter() { + if headers.get(*specific_header).is_some() { + return Err(H2Error::StreamError(id, ErrorCode::ProtocolError).into()); + } + } + if let Some(te_value) = headers.get("te") { + if te_value.to_str()? != "trailers" { + return Err(H2Error::StreamError(id, ErrorCode::ProtocolError).into()); + } + } + Ok(()) +} + +fn build_pseudo_headers(request_part: &RequestPart) -> PseudoHeaders { + let mut pseudo = PseudoHeaders::default(); + match request_part.uri.scheme() { + Some(scheme) => { + pseudo.set_scheme(Some(String::from(scheme.as_str()))); + } + None => pseudo.set_scheme(Some(String::from(Scheme::HTTP.as_str()))), + } + pseudo.set_method(Some(String::from(request_part.method.as_str()))); + pseudo.set_path( + request_part + .uri + .path_and_query() + .or_else(|| Some(String::from("/"))), + ); + // TODO Validity verification is required, for example: `Authority` must be consistent with the `Host` header + pseudo.set_authority(request_part.uri.authority().map(|auth| auth.to_string())); + pseudo +} + +struct TextIo { + pub(crate) handle: Http2Conn, + pub(crate) offset: usize, + pub(crate) remain: Option, + pub(crate) is_closed: bool, +} + +impl TextIo { + pub(crate) fn new(handle: Http2Conn) -> Self { + Self { + handle, + offset: 0, + remain: None, + is_closed: false, + } + } +} + +impl StreamData for TextIo { + fn shutdown(&self) { + todo!() + } +} + +impl AsyncRead for TextIo { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let text_io = self.get_mut(); + + if buf.remaining() == 0 || text_io.is_closed { + return Poll::Ready(Ok(())); + } + while buf.remaining() != 0 { + if let Some(frame) = &text_io.remain { + match frame.payload() { + Payload::Headers(_) => { + break; + } + Payload::Data(data) => { + let data = data.data(); + let unfilled_len = buf.remaining(); + let data_len = data.len() - text_io.offset; + let fill_len = min(unfilled_len, data_len); + if unfilled_len < data_len { + buf.put_slice(&data[text_io.offset..text_io.offset + fill_len]); + text_io.offset += fill_len; + break; + } else { + buf.put_slice(&data[text_io.offset..text_io.offset + fill_len]); + text_io.offset += fill_len; + if frame.flags().is_end_stream() { + text_io.is_closed = true; + break; + } + } + } + _ => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::Other, + HttpError::from(H2Error::ConnectionError(ErrorCode::ProtocolError)), + ))) + } + } + } + + let poll_result = Pin::new(&mut text_io.handle.stream_info) + .poll(cx) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + // TODO Added the frame type. + match poll_result { + Poll::Ready(frame) => match frame.payload() { + Payload::Headers(_) => { + text_io.remain = Some(frame); + text_io.offset = 0; + break; + } + Payload::Data(data) => { + let data = data.data(); + let unfilled_len = buf.remaining(); + let data_len = data.len(); + let fill_len = min(data_len, unfilled_len); + if unfilled_len < data_len { + buf.put_slice(&data[..fill_len]); + text_io.offset += fill_len; + text_io.remain = Some(frame); + break; + } else { + buf.put_slice(&data[..fill_len]); + if frame.flags().is_end_stream() { + text_io.is_closed = true; + break; + } + } + } + Payload::RstStream(_) => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::Other, + HttpError::from(H2Error::ConnectionError(ErrorCode::ProtocolError)), + ))) + } + _ => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::Other, + HttpError::from(H2Error::ConnectionError(ErrorCode::ProtocolError)), + ))) + } + }, + Poll::Pending => { + return Poll::Pending; + } + } + } + Poll::Ready(Ok(())) + } +} + +#[cfg(feature = "http2")] +#[cfg(test)] +mod ut_http2 { + use crate::async_impl::conn::http2::{build_data_frame, build_headers_frame}; + use ylong_http::body::TextBody; + use ylong_http::h2::{ErrorCode, H2Error, Payload}; + use ylong_http::request::RequestBuilder; + + macro_rules! build_request { + ( + Request: { + Method: $method: expr, + Uri: $uri:expr, + Version: $version: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + } + ) => { + RequestBuilder::new() + .method($method) + .url($uri) + .version($version) + $(.header($req_n, $req_v))* + .body(TextBody::from_bytes($req_body.as_bytes())) + .expect("Request build failed") + } + } + + #[test] + fn ut_http2_build_headers_frame() { + let request = build_request!( + Request: { + Method: "GET", + Uri: "http://127.0.0.1:0/data", + Version: "HTTP/2.0", + Header: "te", "trailers", + Header: "host", "127.0.0.1:0", + Body: "Hi", + } + ); + let frame = build_headers_frame(1, request.part().clone(), false) + .expect("headers frame build failed"); + assert_eq!(frame.flags().bits(), 0x4); + let frame = build_headers_frame(1, request.part().clone(), true) + .expect("headers frame build failed"); + assert_eq!(frame.stream_id(), 1); + assert_eq!(frame.flags().bits(), 0x5); + if let Payload::Headers(headers) = frame.payload() { + let (pseudo, _headers) = headers.parts(); + assert_eq!(pseudo.status(), None); + assert_eq!(pseudo.scheme().unwrap(), "http"); + assert_eq!(pseudo.method().unwrap(), "GET"); + assert_eq!(pseudo.authority().unwrap(), "127.0.0.1:0"); + assert_eq!(pseudo.path().unwrap(), "/data") + } else { + panic!("Unexpected frame type") + } + let request = build_request!( + Request: { + Method: "GET", + Uri: "http://127.0.0.1:0/data", + Version: "HTTP/2.0", + Header: "upgrade", "h2", + Header: "host", "127.0.0.1:0", + Body: "Hi", + } + ); + let frame = build_headers_frame(1, request.part().clone(), true); + assert_eq!( + frame.err(), + Some(H2Error::StreamError(1, ErrorCode::ProtocolError).into()) + ); + } + + #[test] + fn ut_http2_build_data_frame() { + let mut request = build_request!( + Request: { + Method: "GET", + Uri: "http://127.0.0.1:0/data", + Version: "HTTP/2.0", + Header: "te", "trailers", + Header: "host", "127.0.0.1:0", + Body: "Hi", + } + ); + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .expect("Build runtime failed."); + + rt.block_on(async move { + if let Some(frame) = build_data_frame(1, request.body_mut()) + .await + .expect("build data frame failed") + { + assert_eq!(frame.flags().bits(), 0x1); + assert_eq!(frame.stream_id(), 1); + if let Payload::Data(data) = frame.payload() { + assert_eq!(data.data().as_slice(), "Hi".as_bytes()) + } else { + panic!("err frame type"); + } + } else { + panic!("unexpected data frame build result"); + } + }); + } +} diff --git a/ylong_http_client/src/async_impl/conn/mod.rs b/ylong_http_client/src/async_impl/conn/mod.rs new file mode 100644 index 0000000..33f094a --- /dev/null +++ b/ylong_http_client/src/async_impl/conn/mod.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::HttpBody; +use crate::error::HttpClientError; +use crate::util::dispatcher::Conn; +use tokio::io::{AsyncRead, AsyncWrite}; +use ylong_http::body::async_impl::Body; + +use crate::async_impl::client::Retryable; +use ylong_http::request::Request; +use ylong_http::response::Response; + +#[cfg(feature = "http1_1")] +mod http1; + +#[cfg(feature = "http2")] +mod http2; + +pub(crate) trait StreamData: AsyncRead { + fn shutdown(&self); +} + +pub(crate) async fn request( + conn: Conn, + request: &mut Request, + _retryable: &mut Retryable, +) -> Result, HttpClientError> +where + T: Body, + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +{ + match conn { + #[cfg(feature = "http1_1")] + Conn::Http1(http1) => http1::request(http1, request).await, + + #[cfg(feature = "http2")] + Conn::Http2(http2) => http2::request(http2, request, _retryable).await, + } +} diff --git a/ylong_http_client/src/async_impl/connector.rs b/ylong_http_client/src/async_impl/connector.rs new file mode 100644 index 0000000..d61fd5c --- /dev/null +++ b/ylong_http_client/src/async_impl/connector.rs @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::util::ConnectorConfig; +use core::future::Future; +use tokio::io::{AsyncRead, AsyncWrite}; +use ylong_http::request::uri::Uri; + +/// `Connector` trait used by `async_impl::Client`. `Connector` provides +/// asynchronous connection establishment interfaces. +#[rustfmt::skip] +pub trait Connector { + /// The type of stream that this connector produces. This must be an async stream that implements AsyncRead, AsyncWrite, and is also Send + Sync. + type Stream: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static; + /// The type of errors that this connector can produce when trying to create a stream. + type Error: Into>; + /// The future type returned by this connector when attempting to create a stream. + type Future: Future> + Unpin + Sync + Send + 'static; + + /// Attempts to establish a connection. + fn connect(&self, uri: &Uri) -> Self::Future; +} + +/// Connector for creating HTTP connections asynchronously. +/// +/// `HttpConnector` implements `async_impl::Connector` trait. +pub struct HttpConnector { + config: ConnectorConfig, +} + +impl HttpConnector { + /// Creates a new `HttpConnector`. + pub(crate) fn new(config: ConnectorConfig) -> HttpConnector { + HttpConnector { config } + } +} + +impl Default for HttpConnector { + fn default() -> Self { + Self::new(ConnectorConfig::default()) + } +} + +#[cfg(not(feature = "__tls"))] +pub(crate) mod no_tls { + use crate::async_impl::Connector; + use core::{future::Future, pin::Pin}; + use std::io::Error; + use tokio::net::TcpStream; + use ylong_http::request::uri::Uri; + + impl Connector for super::HttpConnector { + type Stream = TcpStream; + type Error = Error; + type Future = + Pin> + Sync + Send>>; + + fn connect(&self, uri: &Uri) -> Self::Future { + let addr = if let Some(proxy) = self.config.proxies.match_proxy(uri) { + proxy.via_proxy(uri).authority().unwrap().to_string() + } else { + uri.authority().unwrap().to_string() + }; + Box::pin(async move { + TcpStream::connect(addr) + .await + .and_then(|stream| match stream.set_nodelay(true) { + Ok(()) => Ok(stream), + Err(e) => Err(e), + }) + }) + } + } +} + +#[cfg(feature = "__c_openssl")] +pub(crate) mod c_ssl { + use crate::{ + async_impl::{AsyncSslStream, Connector, MixStream}, + ErrorKind, HttpClientError, + }; + use core::{future::Future, pin::Pin}; + use std::io::Write; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpStream; + use ylong_http::request::uri::{Scheme, Uri}; + + impl Connector for super::HttpConnector { + type Stream = MixStream; + type Error = HttpClientError; + type Future = + Pin> + Sync + Send>>; + + fn connect(&self, uri: &Uri) -> Self::Future { + // Make sure all parts of uri is accurate. + let mut addr = uri.authority().unwrap().to_string(); + let host = uri.host().unwrap().as_str().to_string(); + let port = uri.port().unwrap().as_u16().unwrap(); + let mut auth = None; + let mut is_proxy = false; + + if let Some(proxy) = self.config.proxies.match_proxy(uri) { + addr = proxy.via_proxy(uri).authority().unwrap().to_string(); + auth = proxy + .intercept + .proxy_info() + .basic_auth + .as_ref() + .and_then(|v| v.to_str().ok()); + is_proxy = true; + } + + match *uri.scheme().unwrap() { + Scheme::HTTP => Box::pin(async move { + let stream = TcpStream::connect(addr) + .await + .and_then(|stream| match stream.set_nodelay(true) { + Ok(()) => Ok(stream), + Err(e) => Err(e), + }) + .map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + Ok(MixStream::Http(stream)) + }), + Scheme::HTTPS => { + let config = self.config.tls.clone(); + Box::pin(async move { + let tcp_stream = TcpStream::connect(addr) + .await + .and_then(|stream| match stream.set_nodelay(true) { + Ok(()) => Ok(stream), + Err(e) => Err(e), + }) + .map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + + let tcp_stream = if is_proxy { + tunnel(tcp_stream, host, port, auth).await? + } else { + tcp_stream + }; + + let tls_ssl = config.ssl().map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + let mut stream = AsyncSslStream::new(tls_ssl.into_inner(), tcp_stream) + .map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + Pin::new(&mut stream).connect().await.map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + Ok(MixStream::Https(stream)) + }) + } + } + } + } + + async fn tunnel( + mut conn: TcpStream, + host: String, + port: u16, + auth: Option, + ) -> Result { + let mut req = Vec::new(); + + // `unwrap()` never failed here. + write!( + &mut req, + "CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n" + ) + .unwrap(); + + if let Some(value) = auth { + write!(&mut req, "Proxy-Authorization: {value}\r\n").unwrap(); + } + + write!(&mut req, "\r\n").unwrap(); + + conn.write_all(&req) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + + let mut buf = [0; 8192]; + let mut pos = 0; + + loop { + let n = conn + .read(&mut buf[pos..]) + .await + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + + if n == 0 { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "Error receiving from proxy", + )); + } + + pos += n; + let resp = &buf[..pos]; + if resp.starts_with(b"HTTP/1.1 200") { + if resp.ends_with(b"\r\n\r\n") { + return Ok(conn); + } + if pos == buf.len() { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "proxy headers too long for tunnel", + )); + } + } else if resp.starts_with(b"HTTP/1.1 407") { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "proxy authentication required", + )); + } else { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "unsuccessful tunnel", + )); + } + } + } +} diff --git a/ylong_http_client/src/async_impl/downloader/builder.rs b/ylong_http_client/src/async_impl/downloader/builder.rs new file mode 100644 index 0000000..13e7ca8 --- /dev/null +++ b/ylong_http_client/src/async_impl/downloader/builder.rs @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{Console, DownloadConfig, DownloadOperator, Downloader}; +// TODO: Adapter, use Response later. +use crate::async_impl::Response; +use crate::{SpeedLimit, Timeout}; + +/// A builder that can create a `Downloader`. +/// +/// You can use this builder to build a `Downloader` step by step. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; +/// +/// # async fn create_a_downloader(body: Response) { +/// let downloader = DownloaderBuilder::new().body(body).console().build(); +/// # } +/// ``` +pub struct DownloaderBuilder { + state: S, +} + +/// A state indicates that `DownloaderBuilder` wants a body that needs to be downloaded. +pub struct WantsBody; + +impl DownloaderBuilder { + /// Creates a `DownloaderBuilder` in the `WantsBody` state. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::DownloaderBuilder; + /// + /// let builder = DownloaderBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { state: WantsBody } + } + + /// Sets a body part that needs to be downloaded by the downloader. + /// + /// Then the `DownloaderBuilder` will switch to `WantsOperator` state. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; + /// + /// # async fn set_body(body: Response) { + /// let builder = DownloaderBuilder::new().body(body); + /// # } + /// ``` + pub fn body(self, body: Response) -> DownloaderBuilder { + DownloaderBuilder { + state: WantsOperator { body }, + } + } +} + +impl Default for DownloaderBuilder { + fn default() -> Self { + Self::new() + } +} + +/// A state indicates that `DownloaderBuilder` wants an `DownloadOperator`. +pub struct WantsOperator { + body: Response, +} + +impl DownloaderBuilder { + /// Sets a customized `DownloaderBuilder`. + /// + /// Then the `DownloaderBuilder` will switch to `WantsConfig` state. + /// + /// # Examples + /// + /// ``` + /// # use std::pin::Pin; + /// # use std::task::{Context, Poll}; + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, DownloadOperator, HttpBody, Response}; + /// # use ylong_http_client::HttpClientError; + /// + /// # async fn set_downloader_operator(body: Response) { + /// struct MyOperator; + /// + /// impl DownloadOperator for MyOperator { + /// fn poll_download( + /// self: Pin<&mut Self>, + /// cx: &mut Context<'_>, + /// data: &[u8] + /// ) -> Poll> { + /// todo!() + /// } + /// + /// fn poll_progress( + /// self: Pin<&mut Self>, + /// cx: &mut Context<'_>, + /// downloaded: u64, + /// total: Option + /// ) -> Poll> { + /// todo!() + /// } + /// } + /// + /// let builder = DownloaderBuilder::new().body(body).operator(MyOperator); + /// # } + /// ``` + pub fn operator(self, operator: T) -> DownloaderBuilder> { + DownloaderBuilder { + state: WantsConfig { + body: self.state.body, + operator, + config: DownloadConfig::default(), + }, + } + } + + /// Sets a `Console` to this `Downloader`. The download result and progress + /// will be displayed on the console. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; + /// + /// # async fn set_console(body: Response) { + /// let builder = DownloaderBuilder::new().body(body).console(); + /// # } + /// ``` + pub fn console(self) -> DownloaderBuilder> { + DownloaderBuilder { + state: WantsConfig { + body: self.state.body, + operator: Console, + config: DownloadConfig::default(), + }, + } + } +} + +/// A state indicates that `DownloaderBuilder` wants some configurations. +pub struct WantsConfig { + body: Response, + operator: T, + config: DownloadConfig, +} + +impl DownloaderBuilder> { + /// Sets the timeout for downloading body. + /// + /// Default is `Timeout::none()`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; + /// # use ylong_http_client::Timeout; + /// + /// # async fn set_timeout(body: Response) { + /// let builder = DownloaderBuilder::new().body(body).console().timeout(Timeout::none()); + /// # } + /// ``` + pub fn timeout(mut self, timeout: Timeout) -> Self { + self.state.config.timeout = timeout; + self + } + + /// Sets the speed limit for downloading body. + /// + /// Default is `SpeedLimit::none()`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; + /// # use ylong_http_client::SpeedLimit; + /// + /// # async fn set_timeout(body: Response) { + /// let builder = DownloaderBuilder::new().body(body).console().speed_limit(SpeedLimit::none()); + /// # } + /// ``` + pub fn speed_limit(mut self, speed_limit: SpeedLimit) -> Self { + self.state.config.speed_limit = speed_limit; + self + } + + /// Returns a `Downloader` that uses this `DownloaderBuilder` configuration. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{DownloaderBuilder, Downloader, HttpBody, Response}; + /// + /// # async fn build_downloader(body: Response) { + /// let downloader = DownloaderBuilder::new().body(body).console().build(); + /// # } + /// ``` + pub fn build(self) -> Downloader { + Downloader { + body: self.state.body, + operator: self.state.operator, + config: self.state.config, + info: None, + } + } +} diff --git a/ylong_http_client/src/async_impl/downloader/mod.rs b/ylong_http_client/src/async_impl/downloader/mod.rs new file mode 100644 index 0000000..d1d7dba --- /dev/null +++ b/ylong_http_client/src/async_impl/downloader/mod.rs @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod builder; +mod operator; + +use builder::WantsBody; +use operator::Console; + +pub use builder::DownloaderBuilder; +pub use operator::{DownloadFuture, DownloadOperator, ProgressFuture}; + +// TODO: Adapter, use Response later. +use crate::async_impl::Response; +use crate::{ErrorKind, HttpClientError, SpeedLimit, Timeout}; +use std::time::Instant; +use ylong_http::body::async_impl::Body; + +/// A downloader that can help you download the response body. +/// +/// A `Downloader` provides a template method for downloading the body and +/// needs to use a structure that implements [`DownloadOperator`] trait to read +/// the body. +/// +/// The `DownloadOperator` trait provides two kinds of methods - [`download`] +/// and [`progress`], where: +/// +/// - `download` methods are responsible for reading and copying the body to +/// certain places. +/// +/// - `progress` methods are responsible for progress display. +/// +/// You only need to provide a structure that implements the `DownloadOperator` +/// trait to complete the download process. +/// +/// A default structure `Console` which implements `DownloadOperator` is +/// provided to show download message on console. You can use +/// `Downloader::console` to build a `Downloader` which based on it. +/// +/// [`DownloadOperator`]: DownloadOperator +/// [`download`]: DownloadOperator::download +/// [`progress`]: DownloadOperator::progress +/// +/// # Examples +/// +/// `Console`: +/// ```no_run +/// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; +/// +/// # async fn download_and_show_progress_on_console(response: Response) { +/// // Creates a default `Downloader` that show progress on console. +/// let mut downloader = Downloader::console(response); +/// let _ = downloader.download().await; +/// # } +/// ``` +/// +/// `Custom`: +/// ```no_run +/// # use std::pin::Pin; +/// # use std::task::{Context, Poll}; +/// # use ylong_http_client::async_impl::{Downloader, DownloadOperator, HttpBody, Response}; +/// # use ylong_http_client::{HttpClientError, SpeedLimit, Timeout}; +/// +/// # async fn download_and_show_progress(response: Response) { +/// // Customizes your own `DownloadOperator`. +/// struct MyDownloadOperator; +/// +/// impl DownloadOperator for MyDownloadOperator { +/// fn poll_download( +/// self: Pin<&mut Self>, +/// cx: &mut Context<'_>, +/// data: &[u8], +/// ) -> Poll> { +/// todo!() +/// } +/// +/// fn poll_progress( +/// self: Pin<&mut Self>, +/// cx: &mut Context<'_>, +/// downloaded: u64, +/// total: Option +/// ) -> Poll> { +/// // Writes your customize method. +/// todo!() +/// } +/// } +/// +/// // Creates a default `Downloader` based on `MyDownloadOperator`. +/// // Configures your downloader by using `DownloaderBuilder`. +/// let mut downloader = Downloader::builder() +/// .body(response) +/// .operator(MyDownloadOperator) +/// .timeout(Timeout::none()) +/// .speed_limit(SpeedLimit::none()) +/// .build(); +/// let _ = downloader.download().await; +/// # } +/// ``` +pub struct Downloader { + operator: T, + body: Response, + config: DownloadConfig, + info: Option, +} + +impl Downloader<()> { + /// Creates a `Downloader` that based on a default `DownloadOperator` which + /// show progress on console. + /// + /// # Examples + /// + /// ```no_run + /// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; + /// + /// # async fn download_and_show_progress_on_console(response: Response) { + /// // Creates a default `Downloader` that show progress on console. + /// let mut downloader = Downloader::console(response); + /// let _ = downloader.download().await; + /// # } + /// ``` + pub fn console(response: Response) -> Downloader { + Self::builder().body(response).console().build() + } + + /// Creates a `DownloaderBuilder` and configures downloader step by step. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::Downloader; + /// + /// let builder = Downloader::builder(); + /// ``` + pub fn builder() -> DownloaderBuilder { + DownloaderBuilder::new() + } +} + +impl Downloader { + /// Starts downloading that uses this `Downloader`'s configurations. + /// + /// The download and progress methods of the `DownloadOperator` will be + /// called multiple times until the download is complete. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; + /// + /// # async fn download_response_body(response: Response) { + /// let mut downloader = Downloader::console(response); + /// let _result = downloader.download().await; + /// # } + /// ``` + pub async fn download(&mut self) -> Result<(), HttpClientError> { + // Construct new download info, or reuse previous info. + if self.info.is_none() { + let content_length = self + .body + .headers() + .get("Content") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.parse::().ok()); + self.info = Some(DownloadInfo::new(content_length)); + } + self.limited_download().await + } + + // Downloads response body with speed limitation. + // TODO: Speed Limit. + async fn limited_download(&mut self) -> Result<(), HttpClientError> { + self.show_progress().await?; + self.check_timeout()?; + + let mut buf = [0; 16 * 1024]; + + loop { + let data_size = match self.body.body_mut().data(&mut buf).await { + Ok(0) => { + self.show_progress().await?; + return Ok(()); + } + Ok(size) => size, + Err(e) => { + return Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + )) + } + }; + + let data = &buf[..data_size]; + let mut size = 0; + while size != data.len() { + self.check_timeout()?; + size += self.operator.download(&data[size..]).await?; + self.info.as_mut().unwrap().downloaded_bytes += data.len() as u64; + self.show_progress().await?; + } + } + } + + fn check_timeout(&mut self) -> Result<(), HttpClientError> { + if let Some(timeout) = self.config.timeout.inner() { + let now = Instant::now(); + if now.duration_since(self.info.as_mut().unwrap().start_time) >= timeout { + return Err(HttpClientError::new_with_message( + ErrorKind::Timeout, + "Download timeout", + )); + } + } + Ok(()) + } + + async fn show_progress(&mut self) -> Result<(), HttpClientError> { + let info = self.info.as_mut().unwrap(); + self.operator + .progress(info.downloaded_bytes, info.total_bytes) + .await + } +} + +struct DownloadInfo { + pub(crate) start_time: Instant, + pub(crate) downloaded_bytes: u64, + pub(crate) total_bytes: Option, +} + +impl DownloadInfo { + fn new(total_bytes: Option) -> Self { + Self { + start_time: Instant::now(), + downloaded_bytes: 0, + total_bytes, + } + } +} + +struct DownloadConfig { + pub(crate) timeout: Timeout, + pub(crate) speed_limit: SpeedLimit, +} + +impl Default for DownloadConfig { + fn default() -> Self { + Self { + timeout: Timeout::none(), + speed_limit: SpeedLimit::none(), + } + } +} diff --git a/ylong_http_client/src/async_impl/downloader/operator.rs b/ylong_http_client/src/async_impl/downloader/operator.rs new file mode 100644 index 0000000..9767d0c --- /dev/null +++ b/ylong_http_client/src/async_impl/downloader/operator.rs @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::HttpClientError; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// The trait defines the functionality required for processing bodies of HTTP messages. +pub trait DownloadOperator { + /// Attempts to write the body data read each time to the specified location. + /// + /// This method will be called every time a part of the body data is read. + fn poll_download( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + data: &[u8], + ) -> Poll>; + + /// Attempts to inform you how many bytes have been written to + /// the specified location at this time. + /// + /// You can display the progress according to the number of bytes written. + fn poll_progress( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _downloaded: u64, + _total: Option, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + /// Returns future that writes the body data read each time to the + /// specified location. + /// + /// This method will be called every time a part of the body data is read. + fn download<'a, 'b>(&'a mut self, data: &'b [u8]) -> DownloadFuture<'a, 'b, Self> + where + Self: Unpin + Sized + 'a + 'b, + { + DownloadFuture { + operator: self, + data, + } + } + + /// Returns future that informs you how many bytes have been written to + /// the specified location at this time. + /// + /// You can display the progress according to the number of bytes written. + fn progress<'a>(&'a mut self, downloaded: u64, total: Option) -> ProgressFuture<'a, Self> + where + Self: Unpin + Sized + 'a, + { + ProgressFuture { + processor: self, + downloaded, + total, + } + } +} + +impl DownloadOperator for &mut T +where + T: DownloadOperator + Unpin, +{ + fn poll_download( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + data: &[u8], + ) -> Poll> { + Pin::new(&mut **self).poll_download(cx, data) + } + + fn poll_progress( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + downloaded: u64, + total: Option, + ) -> Poll> { + Pin::new(&mut **self).poll_progress(cx, downloaded, total) + } +} + +pub struct DownloadFuture<'a, 'b, T> { + operator: &'a mut T, + data: &'b [u8], +} + +impl<'a, 'b, T> Future for DownloadFuture<'a, 'b, T> +where + T: DownloadOperator + Unpin + 'a + 'b, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let fut = self.get_mut(); + Pin::new(&mut fut.operator).poll_download(cx, fut.data) + } +} + +pub struct ProgressFuture<'a, T> { + processor: &'a mut T, + downloaded: u64, + total: Option, +} + +impl<'a, T> Future for ProgressFuture<'a, T> +where + T: DownloadOperator + Unpin + 'a, +{ + type Output = Result<(), HttpClientError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let fut = self.get_mut(); + Pin::new(&mut fut.processor).poll_progress(cx, fut.downloaded, fut.total) + } +} + +/// A default body processor that write data to console directly. +pub struct Console; + +impl DownloadOperator for Console { + fn poll_download( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + data: &[u8], + ) -> Poll> { + println!("{data:?}"); + Poll::Ready(Ok(data.len())) + } + + fn poll_progress( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + downloaded: u64, + _total: Option, + ) -> Poll> { + println!("progress: download-{downloaded} bytes"); + Poll::Ready(Ok(())) + } +} diff --git a/ylong_http_client/src/async_impl/http_body.rs b/ylong_http_client/src/async_impl/http_body.rs new file mode 100644 index 0000000..856ef1c --- /dev/null +++ b/ylong_http_client/src/async_impl/http_body.rs @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{Body, StreamData}; + +use crate::error::{ErrorKind, HttpClientError}; +use core::pin::Pin; +use core::task::{Context, Poll}; +use std::future::Future; +use std::io::{Cursor, Read}; +use tokio::io::{AsyncRead, ReadBuf}; +use tokio::time::Sleep; +use ylong_http::body::TextBodyDecoder; +use ylong_http::headers::Headers; + +#[cfg(feature = "http1_1")] +use ylong_http::headers::{HeaderName, HeaderValue}; + +#[cfg(feature = "http1_1")] +use ylong_http::body::{ChunkBodyDecoder, ChunkState}; + +/// `HttpBody` is the body part of the `Response` returned by `Client::request`. +/// `HttpBody` implements `Body` trait, so users can call related methods to get +/// body data. +/// +/// # Examples +/// +/// ```no_run +/// use ylong_http_client::async_impl::{Client, HttpBody, Body}; +/// use ylong_http_client::{Request, EmptyBody}; +/// +/// async fn read_body() { +/// let client = Client::new(); +/// +/// // `HttpBody` is the body part of `response`. +/// let mut response = client.request(Request::new(EmptyBody)).await.unwrap(); +/// +/// // Users can use `Body::data` to get body data. +/// let mut buf = [0u8; 1024]; +/// loop { +/// let size = response.body_mut().data(&mut buf).await.unwrap(); +/// if size == 0 { +/// break; +/// } +/// let _data = &buf[..size]; +/// // Deals with the data. +/// } +/// } +/// ``` +pub struct HttpBody { + kind: Kind, + sleep: Option>>, +} + +type BoxStreamData = Box; + +impl HttpBody { + pub(crate) fn empty() -> Self { + Self { + kind: Kind::Empty, + sleep: None, + } + } + + pub(crate) fn text(len: usize, pre: &[u8], io: BoxStreamData) -> Self { + Self { + kind: Kind::Text(Text::new(len, pre, io)), + sleep: None, + } + } + + #[cfg(feature = "http1_1")] + pub(crate) fn chunk(pre: &[u8], io: BoxStreamData, is_trailer: bool) -> Self { + Self { + kind: Kind::Chunk(Chunk::new(pre, io, is_trailer)), + sleep: None, + } + } + + pub(crate) fn set_sleep(&mut self, sleep: Option>>) { + self.sleep = sleep; + } +} + +// TODO: `TextBodyDecoder` implementation and `ChunkBodyDecoder` implementation. +enum Kind { + Empty, + Text(Text), + #[cfg(feature = "http1_1")] + Chunk(Chunk), +} + +struct Text { + decoder: TextBodyDecoder, + pre: Option>>, + io: Option, +} + +impl Text { + pub(crate) fn new(len: usize, pre: &[u8], io: BoxStreamData) -> Self { + Self { + decoder: TextBodyDecoder::new(len), + pre: (!pre.is_empty()).then_some(Cursor::new(pre.to_vec())), + io: Some(io), + } + } +} + +impl Text { + fn data( + &mut self, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut read = 0; + + if let Some(pre) = self.pre.as_mut() { + // Here cursor read never failed. + let this_read = Read::read(pre, buf).unwrap(); + if this_read == 0 { + self.pre = None; + } else { + read += this_read; + let (text, rem) = self.decoder.decode(&buf[..read]); + + // Contains redundant `rem`, return error. + match (text.is_complete(), rem.is_empty()) { + (true, false) => { + if let Some(io) = self.io.take() { + io.shutdown(); + }; + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Not Eof", + ))); + } + (true, true) => { + self.io = None; + return Poll::Ready(Ok(read)); + } + // TextBodyDecoder decodes as much as possible here. + _ => {} + } + } + } + + if !buf[read..].is_empty() { + if let Some(mut io) = self.io.take() { + let mut read_buf = ReadBuf::new(&mut buf[read..]); + match Pin::new(&mut io).poll_read(cx, &mut read_buf) { + // Disconnected. + Poll::Ready(Ok(())) => { + let filled = read_buf.filled().len(); + if filled == 0 { + io.shutdown(); + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Response Body Incomplete", + ))); + } + let (text, rem) = self.decoder.decode(read_buf.filled()); + read += filled; + // Contains redundant `rem`, return error. + match (text.is_complete(), rem.is_empty()) { + (true, false) => { + io.shutdown(); + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Not Eof", + ))); + } + (true, true) => return Poll::Ready(Ok(read)), + _ => {} + } + self.io = Some(io); + } + Poll::Pending => { + self.io = Some(io); + if read != 0 { + return Poll::Ready(Ok(read)); + } + return Poll::Pending; + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + ))) + } + } + } + } + Poll::Ready(Ok(read)) + } +} + +impl Body for HttpBody { + type Error = HttpClientError; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + if let Some(delay) = self.sleep.as_mut() { + if let Poll::Ready(()) = Pin::new(delay).poll(cx) { + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::Timeout, + "Request timeout", + ))); + } + } + match self.kind { + Kind::Empty => Poll::Ready(Ok(0)), + Kind::Text(ref mut text) => text.data(cx, buf), + #[cfg(feature = "http1_1")] + Kind::Chunk(ref mut chunk) => chunk.data(cx, buf), + } + } + + fn trailer(&mut self) -> Result, Self::Error> { + match self.kind { + #[cfg(feature = "http1_1")] + Kind::Chunk(ref mut chunk) => chunk.get_trailer(), + _ => Ok(None), + } + } +} + +#[cfg(feature = "http1_1")] +struct Chunk { + decoder: ChunkBodyDecoder, + pre: Option>>, + io: Option, + trailer: Vec, +} + +#[cfg(feature = "http1_1")] +impl Chunk { + pub(crate) fn new(pre: &[u8], io: BoxStreamData, is_trailer: bool) -> Self { + Self { + decoder: ChunkBodyDecoder::new().contains_trailer(is_trailer), + pre: (!pre.is_empty()).then_some(Cursor::new(pre.to_vec())), + io: Some(io), + trailer: vec![], + } + } +} + +#[cfg(feature = "http1_1")] +impl Chunk { + fn data( + &mut self, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut read = 0; + + while let Some(pre) = self.pre.as_mut() { + // Here cursor read never failed. + let size = Read::read(pre, &mut buf[read..]).unwrap(); + if size == 0 { + self.pre = None; + } + + let (size, flag) = self.merge_chunks(&mut buf[read..read + size])?; + read += size; + + if flag { + // Return if we find a 0-sized chunk. + self.io = None; + return Poll::Ready(Ok(read)); + } else if read != 0 { + // Return if we get some data. + return Poll::Ready(Ok(read)); + } + } + + // Here `read` must be 0. + while let Some(mut io) = self.io.take() { + let mut read_buf = ReadBuf::new(&mut buf[read..]); + match Pin::new(&mut io).poll_read(cx, &mut read_buf) { + Poll::Ready(Ok(())) => { + let filled = read_buf.filled().len(); + if filled == 0 { + io.shutdown(); + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::BodyTransfer, + "Response Body Incomplete", + ))); + } + let (size, flag) = self.merge_chunks(read_buf.filled_mut())?; + read += size; + if flag { + // Return if we find a 0-sized chunk. + // Return if we get some data. + return Poll::Ready(Ok(read)); + } + self.io = Some(io); + if read != 0 { + return Poll::Ready(Ok(read)); + } + } + Poll::Pending => { + self.io = Some(io); + return Poll::Pending; + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + ))) + } + } + } + + Poll::Ready(Ok(read)) + } + + fn merge_chunks(&mut self, buf: &mut [u8]) -> Result<(usize, bool), HttpClientError> { + // Here we need to merge the chunks into one data block and return. + // The data arrangement in buf is as follows: + // + // data in buf: + // +------+------+------+------+------+------+------+ + // | data | len | data | len | ... | data | len | + // +------+------+------+------+------+------+------+ + // + // We need to merge these data blocks into one block: + // + // after merge: + // +---------------------------+ + // | data | + // +---------------------------+ + + let (chunks, junk) = self + .decoder + .decode(buf) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + + let mut finished = false; + let mut ptrs = Vec::new(); + for chunk in chunks.into_iter() { + if chunk.trailer().is_some() { + if chunk.state() == &ChunkState::Finish { + finished = true; + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + self.trailer.extend_from_slice(b"\r\n"); + break; + } else if chunk.state() == &ChunkState::DataCrlf { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + self.trailer.extend_from_slice(b"\r\n"); + } else { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + } + } else { + if chunk.size() == 0 && chunk.state() != &ChunkState::MetaSize { + finished = true; + break; + } + let data = chunk.data(); + ptrs.push((data.as_ptr(), data.len())) + } + } + + if finished && !junk.is_empty() { + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Invalid Chunk Body", + )); + } + + let start = buf.as_ptr(); + + let mut idx = 0; + for (ptr, len) in ptrs.into_iter() { + let st = ptr as usize - start as usize; + let ed = st + len; + buf.copy_within(st..ed, idx); + idx += len; + } + Ok((idx, finished)) + } + + fn get_trailer(&self) -> Result, HttpClientError> { + if self.trailer.is_empty() { + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "No trailer received", + )); + } + + let mut colon = 0; + let mut lf = 0; + let mut trailer_header_name = HeaderName::from_bytes(b"") + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + let mut trailer_headers = Headers::new(); + for (i, b) in self.trailer.iter().enumerate() { + if *b == b' ' { + continue; + } + if *b == b':' { + colon = i; + if lf == 0 { + let trailer_name = &self.trailer[..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) + })?; + } else { + let trailer_name = &self.trailer[lf + 1..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) + })?; + } + continue; + } + + if *b == b'\n' { + lf = i; + let trailer_value = &self.trailer[colon + 1..lf - 1]; + let trailer_header_value = HeaderValue::from_bytes(trailer_value) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + let _ = trailer_headers + .insert::( + trailer_header_name.clone(), + trailer_header_value.clone(), + ) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + } + } + Ok(Some(trailer_headers)) + } +} + +#[cfg(test)] +mod ut_async_http_body { + use crate::async_impl::http_body::Chunk; + use ylong_http::body::ChunkBodyDecoder; + + /// UT test cases for `Chunk::get_trailers`. + /// + /// # Brief + /// 1. Creates a `Chunk` and set `Trailer`. + /// 2. Calls `get_trailer` method. + /// 3. Checks if the result is correct. + #[test] + fn ut_http_body_chunk() { + let mut chunk = Chunk { + decoder: ChunkBodyDecoder::new().contains_trailer(true), + pre: None, + io: None, + trailer: vec![], + }; + let trailer_info = "Trailer1:value1\r\nTrailer2:value2\r\n"; + chunk.trailer.extend_from_slice(trailer_info.as_bytes()); + let data = chunk.get_trailer().unwrap().unwrap(); + let value1 = data.get("Trailer1"); + assert_eq!(value1.unwrap().to_str().unwrap(), "value1"); + let value2 = data.get("Trailer2"); + assert_eq!(value2.unwrap().to_str().unwrap(), "value2"); + } +} diff --git a/ylong_http_client/src/async_impl/mod.rs b/ylong_http_client/src/async_impl/mod.rs new file mode 100644 index 0000000..3ef6089 --- /dev/null +++ b/ylong_http_client/src/async_impl/mod.rs @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP asynchronous client module. +//! +//! This module provides asynchronous client components. +//! +//! - [`Client`]: The main part of client, which provides the request sending +//! interface and configuration interface. `Client` sends requests in an +//! asynchronous manner. +//! +//! - [`Connector`]: `Connector`s are used to create new connections +//! asynchronously. This module provides `Connector` trait and a `HttpConnector` +//! which implements the trait. + +mod client; +mod conn; +mod connector; +mod downloader; +mod http_body; +mod pool; +mod timeout; +mod uploader; + +pub use client::ClientBuilder; + +pub use connector::Connector; +pub use downloader::{DownloadOperator, Downloader, DownloaderBuilder}; +pub use http_body::HttpBody; +pub use uploader::{UploadOperator, Uploader, UploaderBuilder}; +pub use ylong_http::body::{async_impl::Body, MultiPart, Part}; + +pub(crate) use conn::StreamData; +pub(crate) use connector::HttpConnector; +pub(crate) use pool::ConnPool; + +#[cfg(feature = "__tls")] +mod ssl_stream; +#[cfg(feature = "__tls")] +pub use ssl_stream::{AsyncSslStream, MixStream}; + +// TODO: Remove these later. +/// Client Adapter. +pub type Client = client::Client; + +// TODO: Remove these later. +mod adapter; +pub use adapter::{RequestBuilder, Response}; diff --git a/ylong_http_client/src/async_impl/pool.rs b/ylong_http_client/src/async_impl/pool.rs new file mode 100644 index 0000000..9cf520a --- /dev/null +++ b/ylong_http_client/src/async_impl/pool.rs @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::Connector; +use crate::error::HttpClientError; +use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; +use crate::util::pool::{Pool, PoolKey}; +use crate::{ErrorKind, HttpConfig, HttpVersion, Uri}; +use std::error::Error; +use std::future::Future; +use std::mem::take; +use std::sync::{Arc, Mutex}; +use tokio::io::{AsyncRead, AsyncWrite}; + +pub(crate) struct ConnPool { + pool: Pool>, + connector: Arc, + config: HttpConfig, +} + +impl ConnPool { + pub(crate) fn new(config: HttpConfig, connector: C) -> Self { + Self { + pool: Pool::new(), + connector: Arc::new(connector), + config, + } + } + + pub(crate) async fn connect_to(&self, uri: Uri) -> Result, HttpClientError> { + let key = PoolKey::new( + uri.scheme().unwrap().clone(), + uri.authority().unwrap().clone(), + ); + + self.pool + .get(key, Conns::new) + .conn(self.config.clone(), self.connector.clone().connect(&uri)) + .await + } +} + +pub(crate) struct Conns { + list: Arc>>>, + + #[cfg(feature = "http2")] + h2_occupation: Arc>, +} + +impl Conns { + fn new() -> Self { + Self { + list: Arc::new(Mutex::new(Vec::new())), + + #[cfg(feature = "http2")] + h2_occupation: Arc::new(tokio::sync::Mutex::new(())), + } + } +} + +impl Clone for Conns { + fn clone(&self) -> Self { + Self { + list: self.list.clone(), + + #[cfg(feature = "http2")] + h2_occupation: self.h2_occupation.clone(), + } + } +} + +impl Conns { + async fn conn( + &self, + config: HttpConfig, + connect_fut: F, + ) -> Result, HttpClientError> + where + F: Future>, + E: Into>, + { + match config.version { + #[cfg(feature = "http2")] + HttpVersion::Http2PriorKnowledge => { + { + // The lock `h2_occupation` is used to prevent multiple coroutines from sending Requests at the same time under concurrent conditions, + // resulting in the creation of multiple tcp connections + let _lock = self.h2_occupation.lock().await; + if let Some(conn) = self.get_exist_conn() { + return Ok(conn); + } + // create tcp connection. + let dispatcher = ConnDispatcher::http2( + config.http2_config, + connect_fut.await.map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?, + ); + Ok(self.dispatch_conn(dispatcher)) + } + } + #[cfg(feature = "http1_1")] + HttpVersion::Http11 => { + if let Some(conn) = self.get_exist_conn() { + return Ok(conn); + } + let dispatcher = + ConnDispatcher::http1(connect_fut.await.map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?); + Ok(self.dispatch_conn(dispatcher)) + } + #[cfg(not(feature = "http1_1"))] + HttpVersion::Http11 => Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "Invalid HTTP VERSION", + )), + } + } + + fn dispatch_conn(&self, dispatcher: ConnDispatcher) -> Conn { + // We must be able to get the `Conn` here. + let conn = dispatcher.dispatch().unwrap(); + let mut list = self.list.lock().unwrap(); + list.push(dispatcher); + conn + } + + fn get_exist_conn(&self) -> Option> { + { + let mut list = self.list.lock().unwrap(); + let mut conn = None; + let curr = take(&mut *list); + for dispatcher in curr.into_iter() { + // Discard invalid dispatchers. + if dispatcher.is_shutdown() { + continue; + } + if conn.is_none() { + conn = dispatcher.dispatch(); + } + list.push(dispatcher); + } + conn + } + } +} diff --git a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs new file mode 100644 index 0000000..10a94bc --- /dev/null +++ b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::ssl_stream::{check_io_to_poll, Wrapper}; +use crate::util::c_openssl::{ + error::ErrorStack, + ssl::{self, ShutdownResult, Ssl, SslErrorCode}, +}; +use core::{ + future, + pin::Pin, + slice, + task::{Context, Poll}, +}; +use std::io::{self, Read, Write}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +/// An asynchronous version of [`openssl::ssl::SslStream`]. +#[derive(Debug)] +pub struct AsyncSslStream(ssl::SslStream>); + +impl AsyncSslStream { + fn with_context(self: Pin<&mut Self>, ctx: &mut Context<'_>, f: F) -> R + where + F: FnOnce(&mut ssl::SslStream>) -> R, + { + // SAFETY: must guarantee that you will never move the data out of the + // mutable reference you receive. + let this = unsafe { self.get_unchecked_mut() }; + + // sets context, SslStream to R, reset 0. + this.0.get_mut().context = ctx as *mut _ as usize; + let r = f(&mut this.0); + this.0.get_mut().context = 0; + r + } + + /// Returns a pinned mutable reference to the underlying stream. + fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut S> { + // SAFETY: + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0.get_mut().stream) } + } +} + +impl AsyncSslStream +where + S: AsyncRead + AsyncWrite, +{ + /// Like [`SslStream::new`](ssl::SslStream::new). + pub(crate) fn new(ssl: Ssl, stream: S) -> Result { + // This corresponds to `SSL_set_bio`. + ssl::SslStream::new_base(ssl, Wrapper { stream, context: 0 }).map(AsyncSslStream) + } + + /// Like [`SslStream::connect`](ssl::SslStream::connect). + fn poll_connect(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.with_context(cx, |s| check_result_to_poll(s.connect())) + } + + /// A convenience method wrapping [`poll_connect`](Self::poll_connect). + pub(crate) async fn connect(mut self: Pin<&mut Self>) -> Result<(), ssl::SslError> { + future::poll_fn(|cx| self.as_mut().poll_connect(cx)).await + } +} + +impl AsyncRead for AsyncSslStream +where + S: AsyncRead + AsyncWrite, +{ + // wrap read. + fn poll_read( + self: Pin<&mut Self>, + ctx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + // set async func + self.with_context(ctx, |s| { + let slice = unsafe { + let buf = buf.unfilled_mut(); + slice::from_raw_parts_mut(buf.as_mut_ptr().cast::(), buf.len()) + }; + match check_io_to_poll(s.read(slice))? { + Poll::Ready(len) => { + unsafe { + buf.assume_init(len); + } + buf.advance(len); + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + }) + } +} + +impl AsyncWrite for AsyncSslStream +where + S: AsyncRead + AsyncWrite, +{ + fn poll_write(self: Pin<&mut Self>, ctx: &mut Context, buf: &[u8]) -> Poll> { + self.with_context(ctx, |s| check_io_to_poll(s.write(buf))) + } + + fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { + self.with_context(ctx, |s| check_io_to_poll(s.flush())) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { + // Shuts down the session. + match self.as_mut().with_context(ctx, |s| s.shutdown()) { + // Sends a close notify message to the peer, after which `ShutdownResult::Sent` is returned. + // Awaits the receipt of a close notify message from the peer, after which `ShutdownResult::Received` is returned. + Ok(ShutdownResult::Sent) | Ok(ShutdownResult::Received) => {} + // The SSL session has been closed. + Err(ref e) if e.code() == SslErrorCode::ZERO_RETURN => {} + // When the underlying BIO could not satisfy the needs of SSL_shutdown() to continue the handshake + Err(ref e) + if e.code() == SslErrorCode::WANT_READ || e.code() == SslErrorCode::WANT_WRITE => + { + return Poll::Pending; + } + // Really error. + Err(e) => { + return Poll::Ready(Err(e + .into_io_error() + .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e)))); + } + } + // Returns success when the I/O connection has completely shut down. + self.get_pin_mut().poll_shutdown(ctx) + } +} + +/// Checks `ssl::Error`. +fn check_result_to_poll(r: Result) -> Poll> { + match r { + Ok(t) => Poll::Ready(Ok(t)), + Err(e) => match e.code() { + SslErrorCode::WANT_READ | SslErrorCode::WANT_WRITE => Poll::Pending, + _ => Poll::Ready(Err(e)), + }, + } +} diff --git a/ylong_http_client/src/async_impl/ssl_stream/mix.rs b/ylong_http_client/src/async_impl/ssl_stream/mix.rs new file mode 100644 index 0000000..b6d0e36 --- /dev/null +++ b/ylong_http_client/src/async_impl/ssl_stream/mix.rs @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::AsyncSslStream; +use core::{ + pin::Pin, + task::{Context, Poll}, +}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +/// A stream which may be wrapped with TLS. +pub enum MixStream { + /// A raw HTTP stream. + Http(T), + /// An SSL-wrapped HTTP stream. + Https(AsyncSslStream), +} + +impl AsyncRead for MixStream +where + T: AsyncRead + AsyncWrite + Unpin, +{ + // poll_read separately. + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match &mut *self { + MixStream::Http(s) => Pin::new(s).poll_read(cx, buf), + MixStream::Https(s) => Pin::new(s).poll_read(cx, buf), + } + } +} + +impl AsyncWrite for MixStream +where + T: AsyncRead + AsyncWrite + Unpin, +{ + // poll_write separately. + fn poll_write( + mut self: Pin<&mut Self>, + ctx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match &mut *self { + MixStream::Http(s) => Pin::new(s).poll_write(ctx, buf), + MixStream::Https(s) => Pin::new(s).poll_write(ctx, buf), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + match &mut *self { + MixStream::Http(s) => Pin::new(s).poll_flush(ctx), + MixStream::Https(s) => Pin::new(s).poll_flush(ctx), + } + } + + fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + match &mut *self { + MixStream::Http(s) => Pin::new(s).poll_shutdown(ctx), + MixStream::Https(s) => Pin::new(s).poll_shutdown(ctx), + } + } +} diff --git a/ylong_http_client/src/async_impl/ssl_stream/mod.rs b/ylong_http_client/src/async_impl/ssl_stream/mod.rs new file mode 100644 index 0000000..b9f3b91 --- /dev/null +++ b/ylong_http_client/src/async_impl/ssl_stream/mod.rs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod mix; +mod wrapper; + +#[cfg(feature = "__c_openssl")] +mod c_ssl_stream; +#[cfg(feature = "__c_openssl")] +pub use c_ssl_stream::AsyncSslStream; + +pub use mix::MixStream; + +pub(crate) use wrapper::{check_io_to_poll, Wrapper}; diff --git a/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs b/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs new file mode 100644 index 0000000..bd4c6e6 --- /dev/null +++ b/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use core::{ + fmt::Debug, + pin::Pin, + task::{Context, Poll}, +}; +use std::io::{self, Read, Write}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +#[derive(Debug)] +pub(crate) struct Wrapper { + pub(crate) stream: S, + pub(crate) context: usize, // +} + +impl Wrapper { + /// Gets inner `Stream` and `Context` of `Stream`. + /// + /// # SAFETY + /// Must be called with `context` set to a valid pointer to a live `Context` object, + /// and the wrapper must be pinned in memory. + unsafe fn inner(&mut self) -> (Pin<&mut S>, &mut Context<'_>) { + debug_assert_ne!(self.context, 0); + let stream = Pin::new_unchecked(&mut self.stream); + let context = &mut *(self.context as *mut _); + (stream, context) + } +} + +impl Read for Wrapper +where + S: AsyncRead, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let (stream, cx) = unsafe { self.inner() }; + let mut buf = ReadBuf::new(buf); + match stream.poll_read(cx, &mut buf)? { + Poll::Ready(()) => Ok(buf.filled().len()), + Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)), + } + } +} + +impl Write for Wrapper +where + S: AsyncWrite, +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + let (stream, cx) = unsafe { self.inner() }; + match stream.poll_write(cx, buf) { + Poll::Ready(r) => r, + Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)), + } + } + + fn flush(&mut self) -> io::Result<()> { + let (stream, cx) = unsafe { self.inner() }; + match stream.poll_flush(cx) { + Poll::Ready(r) => r, + Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)), + } + } +} + +/// Checks `io::Result`. +pub(crate) fn check_io_to_poll(r: io::Result) -> Poll> { + match r { + Ok(t) => Poll::Ready(Ok(t)), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending, + Err(e) => Poll::Ready(Err(e)), + } +} diff --git a/ylong_http_client/src/async_impl/timeout.rs b/ylong_http_client/src/async_impl/timeout.rs new file mode 100644 index 0000000..6487a18 --- /dev/null +++ b/ylong_http_client/src/async_impl/timeout.rs @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::async_impl::HttpBody; +use crate::{ErrorKind, HttpClientError}; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::time::Sleep; +use ylong_http::response::Response; + +pub(crate) struct TimeoutFuture { + pub(crate) timeout: Option>>, + pub(crate) future: T, +} + +impl Future for TimeoutFuture +where + T: Future, HttpClientError>> + Unpin, +{ + type Output = Result, HttpClientError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + if let Some(delay) = this.timeout.as_mut() { + if let Poll::Ready(()) = delay.as_mut().poll(cx) { + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::Timeout, + "Request timeout", + ))); + } + } + match Pin::new(&mut this.future).poll(cx) { + Poll::Ready(Ok(mut response)) => { + response.body_mut().set_sleep(this.timeout.take()); + Poll::Ready(Ok(response)) + } + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } +} diff --git a/ylong_http_client/src/async_impl/uploader/builder.rs b/ylong_http_client/src/async_impl/uploader/builder.rs new file mode 100644 index 0000000..a778b9d --- /dev/null +++ b/ylong_http_client/src/async_impl/uploader/builder.rs @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{Console, UploadConfig, UploadOperator, Uploader}; +use crate::async_impl::MultiPart; +use tokio::io::AsyncRead; + +/// A builder that can create a `Uploader`. +/// +/// You can use this builder to build a `Uploader` step by step. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; +/// +/// let uploader = UploaderBuilder::new().reader("HelloWorld".as_bytes()).console().build(); +/// ``` +pub struct UploaderBuilder { + state: S, +} + +/// A state indicates that `UploaderBuilder` wants a `Reader`. +pub struct WantsReader; + +impl UploaderBuilder { + /// Creates a `UploaderBuilder` in the `WantsReader` state. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::UploaderBuilder; + /// + /// let builder = UploaderBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { state: WantsReader } + } + + /// Sets a reader that needs to be read. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::UploaderBuilder; + /// + /// let builder = UploaderBuilder::new().reader("HelloWorld".as_bytes()); + /// ``` + pub fn reader(self, reader: R) -> UploaderBuilder> { + UploaderBuilder { + state: WantsOperator { + reader, + config: UploadConfig::default(), + }, + } + } + + /// Sets a `multipart` that needs to be read. The size of the multipart will + /// be set automatically if it contains. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::UploaderBuilder; + /// + /// let builder = UploaderBuilder::new().reader("HelloWorld".as_bytes()); + /// ``` + pub fn multipart(self, reader: MultiPart) -> UploaderBuilder> { + let total_bytes = reader.total_bytes(); + UploaderBuilder { + state: WantsOperator { + reader, + config: UploadConfig { total_bytes }, + }, + } + } +} + +impl Default for UploaderBuilder { + fn default() -> Self { + Self::new() + } +} + +/// A state indicates that `UploaderBuilder` wants an `UploadOperator`. +pub struct WantsOperator { + reader: R, + config: UploadConfig, +} + +impl UploaderBuilder> { + /// Sets a customized `UploaderOperator`. + /// + /// Then the `UploaderBuilder` will switch to `WantsConfig` state. + /// + /// # Examples + /// + /// ``` + /// # use std::pin::Pin; + /// # use std::task::{Context, Poll}; + /// use tokio::io::ReadBuf; + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader, UploadOperator}; + /// # use ylong_http_client::{HttpClientError, Response}; + /// + /// struct MyOperator; + /// + /// impl UploadOperator for MyOperator { + /// fn poll_progress( + /// self: Pin<&mut Self>, + /// cx: &mut Context<'_>, + /// uploaded: u64, + /// total: Option + /// ) -> Poll> { + /// todo!() + /// } + /// } + /// + /// let builder = UploaderBuilder::new().reader("HelloWorld".as_bytes()).operator(MyOperator); + /// ``` + pub fn operator(self, operator: T) -> UploaderBuilder> { + UploaderBuilder { + state: WantsConfig { + reader: self.state.reader, + operator, + config: self.state.config, + }, + } + } + + /// Sets a `Console` to this `Uploader`. The download result and progress + /// will be displayed on the console. + /// + /// The `Console` needs a `Reader` to display. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; + /// # use ylong_http_client::Response; + /// + /// let builder = UploaderBuilder::new().reader("HelloWorld".as_bytes()).console(); + /// ``` + pub fn console(self) -> UploaderBuilder> { + UploaderBuilder { + state: WantsConfig { + reader: self.state.reader, + operator: Console, + config: self.state.config, + }, + } + } +} + +/// A state indicates that `UploaderBuilder` wants some configurations. +pub struct WantsConfig { + reader: R, + operator: T, + config: UploadConfig, +} + +impl UploaderBuilder> { + /// Sets the total bytes of the uploaded content. + /// + /// Default is `None` which means that you don't know the size of the content. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; + /// + /// let builder = UploaderBuilder::new() + /// .reader("HelloWorld".as_bytes()) + /// .console() + /// .total_bytes(Some(10)); + /// ``` + pub fn total_bytes(mut self, total_bytes: Option) -> Self { + self.state.config.total_bytes = total_bytes; + self + } + + /// Returns a `Uploader` that uses this `UploaderBuilder` configuration. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; + /// # use ylong_http_client::Response; + /// + /// let uploader = UploaderBuilder::new() + /// .reader("HelloWorld".as_bytes()) + /// .console() + /// .build(); + /// ``` + pub fn build(self) -> Uploader { + Uploader { + reader: self.state.reader, + operator: self.state.operator, + config: self.state.config, + info: None, + } + } +} diff --git a/ylong_http_client/src/async_impl/uploader/mod.rs b/ylong_http_client/src/async_impl/uploader/mod.rs new file mode 100644 index 0000000..410a7c1 --- /dev/null +++ b/ylong_http_client/src/async_impl/uploader/mod.rs @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod builder; +mod operator; + +pub use builder::{UploaderBuilder, WantsReader}; +pub use operator::{Console, UploadOperator}; + +use crate::{ErrorKind, HttpClientError}; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, ReadBuf}; +use ylong_http::body::async_impl::Body; + +/// An uploader that can help you upload the request body. +/// +/// An `Uploader` provides a template method for uploading a file or a slice and +/// needs to use a structure that implements [`UploadOperator`] trait to read +/// the file or the slice and convert it into request body. +/// +/// The `UploadOperator` trait provides a [`progress`] method which is +/// responsible for progress display. +/// +/// You only need to provide a structure that implements the `UploadOperator` +/// trait to complete the upload process. +/// +/// A default structure `Console` which implements `UploadOperator` is +/// provided to show download message on console. You can use +/// `Uploader::console` to build a `Uploader` which based on it. +/// +/// [`UploadOperator`]: UploadOperator +/// [`progress`]: UploadOperator::progress +/// +/// # Examples +/// +/// `Console`: +/// ```no_run +/// # use ylong_http_client::async_impl::Uploader; +/// # use ylong_http_client::Response; +/// +/// // Creates a default `Uploader` that show progress on console. +/// let mut uploader = Uploader::console("HelloWorld".as_bytes()); +/// ``` +/// +/// `Custom`: +/// ```no_run +/// # use std::pin::Pin; +/// # use std::task::{Context, Poll}; +/// # use tokio::io::ReadBuf; +/// # use ylong_http_client::async_impl::{Uploader, UploadOperator}; +/// # use ylong_http_client::{Response, SpeedLimit, Timeout}; +/// # use ylong_http_client::HttpClientError; +/// +/// # async fn upload_and_show_progress() { +/// // Customizes your own `UploadOperator`. +/// struct MyUploadOperator; +/// +/// impl UploadOperator for MyUploadOperator { +/// fn poll_progress( +/// self: Pin<&mut Self>, +/// cx: &mut Context<'_>, +/// uploaded: u64, +/// total: Option +/// ) -> Poll> { +/// todo!() +/// } +/// } +/// +/// // Creates a default `Uploader` based on `MyUploadOperator`. +/// // Configures your uploader by using `UploaderBuilder`. +/// let uploader = Uploader::builder().reader("HelloWorld".as_bytes()).operator(MyUploadOperator).build(); +/// # } +/// ``` +pub struct Uploader { + reader: R, + operator: T, + config: UploadConfig, + info: Option, +} + +impl Uploader { + /// Creates an `Uploader` with a `Console` operator which displays process on console. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::Uploader; + /// + /// let uploader = Uploader::console("HelloWorld".as_bytes()); + /// ``` + pub fn console(reader: R) -> Uploader { + UploaderBuilder::new().reader(reader).console().build() + } +} + +impl Uploader<(), ()> { + /// Creates an `UploaderBuilder` and configures uploader step by step. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::async_impl::Uploader; + /// + /// let builder = Uploader::builder(); + /// ``` + pub fn builder() -> UploaderBuilder { + UploaderBuilder::new() + } +} + +impl Body for Uploader +where + R: AsyncRead + Unpin, + T: UploadOperator + Unpin, +{ + type Error = HttpClientError; + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let this = self.get_mut(); + + if this.info.is_none() { + this.info = Some(UploadInfo::new()); + } + + let info = this.info.as_mut().unwrap(); + + match Pin::new(&mut this.operator).poll_progress( + cx, + info.uploaded_bytes, + this.config.total_bytes, + ) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(e)) if e.error_kind() == ErrorKind::UserAborted => { + return Poll::Ready(Ok(0)); + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => return Poll::Pending, + } + + let mut read_buf = ReadBuf::new(buf); + let filled = read_buf.filled().len(); + match Pin::new(&mut this.reader).poll_read(cx, &mut read_buf) { + Poll::Ready(Ok(_)) => {} + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + ))) + } + Poll::Pending => return Poll::Pending, + } + + let new_filled = read_buf.filled().len(); + let read_bytes = new_filled - filled; + info.uploaded_bytes += read_bytes as u64; + Poll::Ready(Ok(read_bytes)) + } +} + +impl AsRef for Uploader { + fn as_ref(&self) -> &R { + &self.reader + } +} + +#[derive(Default)] +struct UploadConfig { + total_bytes: Option, +} + +struct UploadInfo { + uploaded_bytes: u64, +} + +impl UploadInfo { + fn new() -> Self { + Self { uploaded_bytes: 0 } + } +} diff --git a/ylong_http_client/src/async_impl/uploader/operator.rs b/ylong_http_client/src/async_impl/uploader/operator.rs new file mode 100644 index 0000000..6fd22ea --- /dev/null +++ b/ylong_http_client/src/async_impl/uploader/operator.rs @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::HttpClientError; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// A `UploadOperator` represents structures that can read local data to socket. +/// +/// You can implement `UploadOperator` for your structures and pass it to a +/// `Uploader`. Then the `Uploader` can use the `progress` methods to help you +/// upload the local data. +/// +/// # Examples +/// +/// ``` +/// # use std::pin::Pin; +/// # use std::task::{Context, Poll}; +/// # use tokio::io::ReadBuf; +/// # use ylong_http_client::async_impl::UploadOperator; +/// # use ylong_http_client::HttpClientError; +/// +/// // Creates your own operator. +/// struct MyUploadOperator; +/// +/// // Implements `DownloaderOperator` for your structures. +/// impl UploadOperator for MyUploadOperator { +/// fn poll_progress( +/// self: Pin<&mut Self>, +/// cx: &mut Context<'_>, +/// uploaded: u64, +/// total: Option +/// ) -> Poll> { +/// todo!() +/// } +/// } +/// ``` +pub trait UploadOperator { + /// The progress method that you need to implement. You need to perform some + /// operations in this method based on the number of bytes uploaded and + /// the total size. + fn poll_progress( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _uploaded: u64, + _total: Option, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + /// Creates a `Progress` Future. + fn progress<'a>(&'a mut self, uploaded: u64, total: Option) -> ProgressFuture<'a, Self> + where + Self: Unpin + Sized + 'a, + { + ProgressFuture { + operator: self, + uploaded, + total, + } + } +} + +impl UploadOperator for &mut T +where + T: UploadOperator + Unpin, +{ + fn poll_progress( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + uploaded: u64, + total: Option, + ) -> Poll> { + Pin::new(&mut **self).poll_progress(cx, uploaded, total) + } +} + +/// A future that execute `poll_progress` method. +pub struct ProgressFuture<'a, T> { + operator: &'a mut T, + uploaded: u64, + total: Option, +} + +impl<'a, T> Future for ProgressFuture<'a, T> +where + T: UploadOperator + Unpin + 'a, +{ + type Output = Result<(), HttpClientError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let fut = self.get_mut(); + Pin::new(&mut fut.operator).poll_progress(cx, fut.uploaded, fut.total) + } +} + +/// A default download operator that display messages on console. +pub struct Console; + +impl UploadOperator for Console { + fn poll_progress( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + uploaded: u64, + _total: Option, + ) -> Poll> { + println!("progress: upload-{uploaded} bytes"); + Poll::Ready(Ok(())) + } +} diff --git a/ylong_http_client/src/error.rs b/ylong_http_client/src/error.rs new file mode 100644 index 0000000..084cca8 --- /dev/null +++ b/ylong_http_client/src/error.rs @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Definition of `HttpClientErrors` which includes errors that may occur in +//! this crate. + +use core::fmt::{Debug, Display, Formatter}; +use std::error::Error; + +/// The structure encapsulates errors that can be encountered when working with the HTTP client. +pub struct HttpClientError { + kind: ErrorKind, + cause: Option>, +} + +impl HttpClientError { + /// Creates a `UserAborted` error. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::HttpClientError; + /// + /// let user_aborted = HttpClientError::user_aborted(); + /// ``` + pub fn user_aborted() -> Self { + Self { + kind: ErrorKind::UserAborted, + cause: None, + } + } + + /// Creates an `Other` error. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::HttpClientError; + /// + /// # fn error(error: std::io::Error) { + /// let other = HttpClientError::other(Some(error)); + /// # } + /// ``` + pub fn other>>(cause: Option) -> Self { + Self { + kind: ErrorKind::Other, + cause: cause.map(|e| e.into()), + } + } + + /// Gets the `ErrorKind` of this `HttpClientError`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::{ErrorKind, HttpClientError}; + /// + /// let user_aborted = HttpClientError::user_aborted(); + /// assert_eq!(user_aborted.error_kind(), ErrorKind::UserAborted); + /// ``` + pub fn error_kind(&self) -> ErrorKind { + self.kind + } + + pub(crate) fn new_with_cause(kind: ErrorKind, cause: Option) -> Self + where + T: Into>, + { + Self { + kind, + cause: cause.map(|e| e.into()), + } + } + + pub(crate) fn new_with_message(kind: ErrorKind, message: &str) -> Self { + Self { + kind, + cause: Some(CauseMessage::new(message).into()), + } + } +} + +impl Debug for HttpClientError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut builder = f.debug_struct("HttpClientError"); + builder.field("ErrorKind", &self.kind); + if let Some(ref cause) = self.cause { + builder.field("Cause", cause); + } + builder.finish() + } +} + +impl Display for HttpClientError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(self.kind.as_str())?; + + if let Some(ref cause) = self.cause { + write!(f, ": {cause}")?; + } + Ok(()) + } +} + +impl Error for HttpClientError {} + +/// Error kinds which can indicate the type of a `HttpClientError`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorKind { + /// Errors for decoding response body. + BodyDecode, + + /// Errors for transferring request body or response body. + BodyTransfer, + + /// Errors for using various builder. + Build, + + /// Errors for connecting to a server. + Connect, + + /// Errors for upgrading a connection. + ConnectionUpgrade, + + /// Other error kinds. + Other, + + /// Errors for following redirect. + Redirect, + + /// Errors for sending a request. + Request, + + /// Errors for reaching a timeout. + Timeout, + + /// User raised errors. + UserAborted, +} + +impl ErrorKind { + /// Gets the string info of this `ErrorKind`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::ErrorKind; + /// + /// assert_eq!(ErrorKind::UserAborted.as_str(), "User Aborted Error"); + /// ``` + pub fn as_str(&self) -> &'static str { + match self { + Self::BodyDecode => "Body Decode Error", + Self::BodyTransfer => "Body Transfer Error", + Self::Build => "Build Error", + Self::Connect => "Connect Error", + Self::ConnectionUpgrade => "Connection Upgrade Error", + Self::Other => "Other Error", + Self::Redirect => "Redirect Error", + Self::Request => "Request Error", + Self::Timeout => "Timeout Error", + Self::UserAborted => "User Aborted Error", + } + } +} + +/// Messages for summarizing the cause of the error +pub(crate) struct CauseMessage(String); + +impl CauseMessage { + pub(crate) fn new(message: &str) -> Self { + Self(message.to_string()) + } +} + +impl Debug for CauseMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(self.0.as_str()) + } +} + +impl Display for CauseMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(self.0.as_str()) + } +} + +impl Error for CauseMessage {} diff --git a/ylong_http_client/src/lib.rs b/ylong_http_client/src/lib.rs new file mode 100644 index 0000000..56faf34 --- /dev/null +++ b/ylong_http_client/src/lib.rs @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! `ylong_http_client` provides a HTTP client that based on `ylong_http` crate. +//! You can use the client to send request to a server, and then get the response. +//! +//! # Supported HTTP Version +//! - HTTP1.1 +//! +// TODO: Need doc. + +// ylong_http crate re-export. +pub use ylong_http::body::{EmptyBody, TextBody}; +pub use ylong_http::request::uri::{Scheme, Uri}; +pub use ylong_http::request::{method::Method, Request, RequestPart}; +pub use ylong_http::response::{status::StatusCode, ResponsePart}; +pub use ylong_http::version::Version; + +#[cfg(all(feature = "async", any(feature = "http1_1", feature = "http2")))] +pub mod async_impl; + +#[cfg(all(feature = "async", any(feature = "http1_1", feature = "http2")))] +pub use async_impl::{Body, RequestBuilder, Response}; + +#[cfg(all(feature = "sync", any(feature = "http1_1", feature = "http2")))] +pub mod sync_impl; + +#[cfg(all( + any(feature = "async", feature = "sync"), + any(feature = "http1_1", feature = "http2"), +))] +mod error; + +#[cfg(all( + any(feature = "async", feature = "sync"), + any(feature = "http1_1", feature = "http2"), +))] +pub use error::{ErrorKind, HttpClientError}; + +#[cfg(all( + any(feature = "async", feature = "sync"), + any(feature = "http1_1", feature = "http2"), +))] +pub mod util; + +#[cfg(all( + any(feature = "async", feature = "sync"), + any(feature = "http1_1", feature = "http2"), +))] +pub use util::*; diff --git a/ylong_http_client/src/sync_impl/client.rs b/ylong_http_client/src/sync_impl/client.rs new file mode 100644 index 0000000..4570f43 --- /dev/null +++ b/ylong_http_client/src/sync_impl/client.rs @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{Body, Connector, HttpBody, HttpConnector}; +use crate::error::HttpClientError; +use crate::sync_impl::conn; +use crate::sync_impl::pool::ConnPool; +use crate::util::normalizer::RequestFormatter; +use crate::util::proxy::Proxies; +use crate::util::redirect::TriggerKind; +use crate::util::{ClientConfig, HttpConfig, HttpVersion, Proxy, Timeout}; +use crate::util::{ConnectorConfig, Redirect}; +use crate::Request; +use ylong_http::request::uri::Uri; + +// TODO: Adapter, remove this later. +use ylong_http::response::Response; + +/// HTTP synchronous client implementation. Users can use `Client` to +/// send `Request` synchronously. `Client` depends on a `Connector` that +/// can be customized by the user. +/// +/// # Examples +/// +/// ```no_run +/// use ylong_http_client::sync_impl::Client; +/// use ylong_http_client::{Request, Response, EmptyBody}; +/// +/// // Creates a new `Client`. +/// let client = Client::new(); +/// +/// // Creates a new `Request`. +/// let request = Request::new(EmptyBody); +/// +/// // Sends `Request` and block waiting for `Response` to return. +/// let response = client.request(request).unwrap(); +/// +/// // Gets the content of `Response`. +/// let status = response.status(); +/// ``` +pub struct Client { + inner: ConnPool, + client_config: ClientConfig, +} + +impl Client { + /// Creates a new, default `Client`, which uses [`sync_impl::HttpConnector`]. + /// + /// [`sync_impl::HttpConnector`]: HttpConnector + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::Client; + /// + /// let client = Client::new(); + /// ``` + pub fn new() -> Self { + Self::with_connector(HttpConnector::default()) + } + + /// Creates a new, default [`sync_impl::ClientBuilder`]. + /// + /// [`sync_impl::ClientBuilder`]: ClientBuilder + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::Client; + /// + /// let builder = Client::builder(); + /// ``` + pub fn builder() -> ClientBuilder { + ClientBuilder::new() + } +} + +impl Client { + /// Creates a new, default `Client` with a given connector. + pub fn with_connector(connector: C) -> Self { + Self { + inner: ConnPool::new(connector), + client_config: ClientConfig::new(), + } + } + + /// Sends HTTP Request synchronously. This method will block the current + /// thread until a `Response` is obtained or an error occurs. + /// + /// # Examples + /// + /// ```no_run + /// use ylong_http_client::sync_impl::Client; + /// use ylong_http_client::{Request, EmptyBody}; + /// + /// let client = Client::new(); + /// let response = client.request(Request::new(EmptyBody)); + /// ``` + pub fn request( + &self, + mut request: Request, + ) -> Result, HttpClientError> { + RequestFormatter::new(&mut request).normalize()?; + self.retry_send_request(request) + } + + fn retry_send_request( + &self, + mut request: Request, + ) -> Result, HttpClientError> { + let mut retries = self.client_config.retry.times().unwrap_or(0); + loop { + let response = self.send_request_retryable(&mut request); + if response.is_ok() || retries == 0 { + return response; + } + retries -= 1; + } + } + + fn send_request_retryable( + &self, + request: &mut Request, + ) -> Result, HttpClientError> { + let response = self.send_request_with_uri(request.uri().clone(), request)?; + self.redirect_request(response, request) + } + + fn redirect_request( + &self, + mut response: Response, + request: &mut Request, + ) -> Result, HttpClientError> { + let mut redirected_list = vec![]; + let mut dst_uri = Uri::default(); + loop { + if Redirect::is_redirect(response.status().clone(), request) { + redirected_list.push(request.uri().clone()); + let trigger = Redirect::get_redirect( + &mut dst_uri, + &self.client_config.redirect, + &redirected_list, + &response, + request, + )?; + + match trigger { + TriggerKind::NextLink => { + response = conn::request(self.inner.connect_to(dst_uri.clone())?, request)?; + continue; + } + TriggerKind::Stop => { + return Ok(response); + } + } + } else { + return Ok(response); + } + } + } + + fn send_request_with_uri( + &self, + uri: Uri, + request: &mut Request, + ) -> Result, HttpClientError> { + conn::request(self.inner.connect_to(uri)?, request) + } +} + +impl Default for Client { + fn default() -> Self { + Self::new() + } +} + +/// A builder which is used to construct `sync_impl::Client`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::sync_impl::ClientBuilder; +/// +/// let client = ClientBuilder::new().build(); +/// ``` +pub struct ClientBuilder { + /// Options and flags that is related to `HTTP`. + http: HttpConfig, + + /// Options and flags that is related to `Client`. + client: ClientConfig, + + /// Options and flags that is related to `Proxy`. + proxies: Proxies, + + /// Options and flags that is related to `TLS`. + #[cfg(feature = "__tls")] + tls: crate::util::TlsConfigBuilder, +} + +impl ClientBuilder { + /// Creates a new, default `ClientBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { + http: HttpConfig::default(), + client: ClientConfig::default(), + proxies: Proxies::default(), + + #[cfg(feature = "__tls")] + tls: crate::util::TlsConfig::builder(), + } + } + + /// Only use HTTP/1. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new().http1_only(); + /// ``` + pub fn http1_only(mut self) -> Self { + self.http.version = HttpVersion::Http11; + self + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request starts connection util the + /// response body has finished. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::Timeout; + /// + /// let builder = ClientBuilder::new().request_timeout(Timeout::none()); + /// ``` + pub fn request_timeout(mut self, timeout: Timeout) -> Self { + self.client.request_timeout = timeout; + self + } + + /// Sets a timeout for only the connect phase of `Client`. + /// + /// Default is `Timeout::none()`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::Timeout; + /// + /// let builder = ClientBuilder::new().connect_timeout(Timeout::none()); + /// ``` + pub fn connect_timeout(mut self, timeout: Timeout) -> Self { + self.client.connect_timeout = timeout; + self + } + + /// Sets a `Redirect` for this client. + /// + /// Default will follow redirects up to a maximum of 10. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::Redirect; + /// + /// let builder = ClientBuilder::new().redirect(Redirect::none()); + /// ``` + pub fn redirect(mut self, redirect: Redirect) -> Self { + self.client.redirect = redirect; + self + } + + /// Adds a `Proxy` to the list of proxies the `Client` will use. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::sync_impl::ClientBuilder; + /// # use ylong_http_client::{HttpClientError, Proxy}; + /// + /// # fn add_proxy() -> Result<(), HttpClientError> { + /// let builder = ClientBuilder::new().proxy(Proxy::http("http://www.example.com").build()?); + /// # Ok(()) + /// # } + pub fn proxy(mut self, proxy: Proxy) -> Self { + self.proxies.add_proxy(proxy.inner()); + self + } + + /// Sets the maximum allowed TLS version for connections. + /// + /// By default there's no maximum. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::TlsVersion; + /// + /// let builder = ClientBuilder::new().max_tls_version(TlsVersion::TLS_1_2); + /// ``` + #[cfg(feature = "__tls")] + pub fn max_tls_version(mut self, version: crate::util::TlsVersion) -> Self { + self.tls = self.tls.set_max_proto_version(version); + self + } + + /// Sets the minimum required TLS version for connections. + /// + /// By default the TLS backend's own default is used. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::TlsVersion; + /// + /// let builder = ClientBuilder::new().min_tls_version(TlsVersion::TLS_1_2); + /// ``` + #[cfg(feature = "__tls")] + pub fn min_tls_version(mut self, version: crate::util::TlsVersion) -> Self { + self.tls = self.tls.set_min_proto_version(version); + self + } + + /// Adds a custom root certificate. + /// + /// This can be used to connect to a server that has a self-signed. + /// certificate for example. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// use ylong_http_client::Certificate; + /// + /// # fn set_cert(cert: Certificate) { + /// let builder = ClientBuilder::new().add_root_certificate(cert); + /// # } + /// ``` + #[cfg(feature = "__tls")] + pub fn add_root_certificate(mut self, certs: crate::util::Certificate) -> Self { + self.tls = self.tls.add_root_certificates(certs); + self + } + + /// Loads trusted root certificates from a file. The file should contain a + /// sequence of PEM-formatted CA certificates. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new().set_ca_file("ca.crt"); + /// ``` + #[cfg(feature = "__tls")] + pub fn set_ca_file(mut self, path: &str) -> Self { + self.tls = self.tls.set_ca_file(path); + self + } + + /// Sets the list of supported ciphers for protocols before `TLSv1.3`. + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new() + /// .set_cipher_list( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + #[cfg(feature = "__tls")] + pub fn set_cipher_list(mut self, list: &str) -> Self { + self.tls = self.tls.set_cipher_list(list); + self + } + + /// Sets the list of supported ciphers for the `TLSv1.3` protocol. + /// + /// The format consists of TLSv1.3 cipher suite names separated by `:` + /// characters in order of preference. + /// + /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new() + /// .set_cipher_suites( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + #[cfg(feature = "__tls")] + pub fn set_cipher_suites(mut self, list: &str) -> Self { + self.tls = self.tls.set_cipher_suites(list); + self + } + + /// Constructs a `Client` based on the given settings. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::ClientBuilder; + /// + /// let client = ClientBuilder::new().build(); + /// ``` + pub fn build(self) -> Result, HttpClientError> { + let config = ConnectorConfig { + proxies: self.proxies, + #[cfg(feature = "__tls")] + tls: self.tls.build()?, + }; + + let connector = HttpConnector::new(config); + + Ok(Client { + inner: ConnPool::new(connector), + client_config: self.client, + }) + } +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod ut_syn_client { + use crate::sync_impl::Client; + use ylong_http::body::TextBody; + use ylong_http::request::uri::Uri; + use ylong_http::request::Request; + + /// UT test cases for `Client::request`. + /// + /// # Brief + /// 1. Creates a `Client` by calling `Client::new`. + /// 2. Calls `request`. + /// 3. Checks if the result is error. + #[test] + fn ut_request_client_err() { + let client = Client::new(); + let reader = "Hello World"; + let body = TextBody::from_bytes(reader.as_bytes()); + let mut req = Request::new(body); + let request_uri = req.uri_mut(); + *request_uri = Uri::from_bytes(b"http://_:80").unwrap(); + let response = client.request(req); + assert!(response.is_err()) + } + + /// UT test cases for `Client::new`. + /// + /// # Brief + /// 1. Creates a `Client` by calling `Client::new`. + /// 2. Calls `request`. + /// 3. Checks if the result is correct. + #[test] + fn ut_client_new() { + let _ = Client::default(); + let _ = Client::new(); + } + + /// UT test cases for `Client::builder`. + /// + /// # Brief + /// 1. Creates a `Client` by calling `Client::builder`. + /// 2. Calls `http_config`, `client_config`, `tls_config` and `build` respectively. + /// 3. Checks if the result is correct. + #[cfg(feature = "__tls")] + #[test] + fn ut_client_builder() { + let builder = Client::builder().build(); + assert!(builder.is_ok()); + } +} diff --git a/ylong_http_client/src/sync_impl/conn/http1.rs b/ylong_http_client/src/sync_impl/conn/http1.rs new file mode 100644 index 0000000..35ddebf --- /dev/null +++ b/ylong_http_client/src/sync_impl/conn/http1.rs @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{ErrorKind, HttpClientError}; +use crate::sync_impl::conn::StreamData; +use crate::sync_impl::HttpBody; +use crate::util::dispatcher::http1::Http1Conn; +use crate::Request; +use std::io::{Read, Write}; +use ylong_http::body::sync_impl::Body; +use ylong_http::h1::{RequestEncoder, ResponseDecoder}; + +// TODO: Adapter, remove this later. +use ylong_http::response::Response; + +const TEMP_BUF_SIZE: usize = 16 * 1024; + +pub(crate) fn request( + mut conn: Http1Conn, + request: &mut Request, +) -> Result, HttpClientError> +where + T: Body, + S: Read + Write + 'static, +{ + let mut buf = vec![0u8; TEMP_BUF_SIZE]; + + // Encodes request. + let mut encode_part = Some(RequestEncoder::new(request.part().clone())); + let mut encode_body = Some(request.body_mut()); + let mut write = 0; + while encode_part.is_some() || encode_body.is_some() { + if write < buf.len() { + if let Some(part) = encode_part.as_mut() { + let size = part + .encode(&mut buf[write..]) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + write += size; + if size == 0 { + encode_part = None; + } + } + } + + if write < buf.len() { + if let Some(body) = encode_body.as_mut() { + let size = body.data(&mut buf[write..]).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::BodyTransfer, Some(e)) + })?; + write += size; + if size == 0 { + encode_body = None; + } + } + } + + if write == buf.len() { + conn.raw_mut() + .write_all(&buf[..write]) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + write = 0; + } + } + + if write != 0 { + conn.raw_mut() + .write_all(&buf[..write]) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + } + + // Decodes response part. + let (part, pre) = { + let mut decoder = ResponseDecoder::new(); + loop { + let size = conn + .raw_mut() + .read(buf.as_mut_slice()) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + match decoder.decode(&buf[..size]) { + Ok(None) => {} + Ok(Some((part, rem))) => break (part, rem), + Err(e) => return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))), + } + } + }; + + // Generates response body. + let body = { + let chunked = part + .headers + .get("Transfer-Encoding") + .map(|v| v.to_str().unwrap_or(String::new())) + .and_then(|s| s.find("chunked")) + .is_some(); + let content_length = part + .headers + .get("Content-Length") + .map(|v| v.to_str().unwrap_or(String::new())) + .and_then(|s| s.parse::().ok()); + + let is_trailer = part.headers.get("Trailer").is_some(); + + match (chunked, content_length, pre.is_empty()) { + (true, None, _) => HttpBody::chunk(pre, Box::new(conn), is_trailer), + (false, Some(len), _) => HttpBody::text(len, pre, Box::new(conn)), + (false, None, true) => HttpBody::empty(), + _ => { + return Err(HttpClientError::new_with_message( + ErrorKind::Request, + "Invalid Response Format", + )) + } + } + }; + Ok(Response::from_raw_parts(part, body)) +} + +impl Read for Http1Conn { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.raw_mut().read(buf) + } +} + +impl StreamData for Http1Conn { + fn shutdown(&self) { + Self::shutdown(self) + } +} diff --git a/ylong_http_client/src/sync_impl/conn/mod.rs b/ylong_http_client/src/sync_impl/conn/mod.rs new file mode 100644 index 0000000..2714fdb --- /dev/null +++ b/ylong_http_client/src/sync_impl/conn/mod.rs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::HttpClientError; +use crate::sync_impl::HttpBody; +use crate::util::dispatcher::Conn; +use std::io::{Read, Write}; +use ylong_http::body::sync_impl::Body; +use ylong_http::request::Request; +use ylong_http::response::Response; + +#[cfg(feature = "http1_1")] +mod http1; + +pub(crate) trait StreamData: Read { + fn shutdown(&self); +} + +pub(crate) fn request( + conn: Conn, + request: &mut Request, +) -> Result, HttpClientError> +where + T: Body, + S: Read + Write + 'static, +{ + match conn { + #[cfg(feature = "http1_1")] + Conn::Http1(http1) => http1::request(http1, request), + + #[cfg(feature = "http2")] + Conn::Http2(_) => todo!(), + } +} diff --git a/ylong_http_client/src/sync_impl/connector.rs b/ylong_http_client/src/sync_impl/connector.rs new file mode 100644 index 0000000..1ad6f93 --- /dev/null +++ b/ylong_http_client/src/sync_impl/connector.rs @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::util::ConnectorConfig; +use std::io::{Read, Write}; +use ylong_http::request::uri::Uri; + +/// `Connector` trait used by `Client`. `Connector` provides synchronous +/// connection establishment interfaces. +pub trait Connector { + /// The connection object established by `Connector::connect`. + type Stream: Read + Write + 'static; + /// Possible errors during connection establishment. + type Error: Into>; + + /// Attempts to establish a synchronous connection. + fn connect(&self, uri: &Uri) -> Result; +} + +/// Connector for creating HTTP connections synchronously. +/// +/// `HttpConnector` implements `sync_impl::Connector` trait. +pub struct HttpConnector { + config: ConnectorConfig, +} + +impl HttpConnector { + /// Creates a new `HttpConnector`. + pub(crate) fn new(config: ConnectorConfig) -> HttpConnector { + HttpConnector { config } + } +} + +impl Default for HttpConnector { + fn default() -> Self { + Self::new(ConnectorConfig::default()) + } +} + +#[cfg(not(feature = "__tls"))] +pub mod no_tls { + use crate::sync_impl::Connector; + use std::io::Error; + use std::net::TcpStream; + use ylong_http::request::uri::Uri; + + impl Connector for super::HttpConnector { + type Stream = TcpStream; + type Error = Error; + + fn connect(&self, uri: &Uri) -> Result { + let addr = if let Some(proxy) = self.config.proxies.match_proxy(uri) { + proxy.via_proxy(uri).authority().unwrap().to_string() + } else { + uri.authority().unwrap().to_string() + }; + TcpStream::connect(addr) + } + } +} + +#[cfg(feature = "__c_openssl")] +pub mod c_ssl { + use crate::{ + sync_impl::{Connector, MixStream}, + ErrorKind, HttpClientError, + }; + use std::io::{Read, Write}; + use std::net::TcpStream; + use ylong_http::request::uri::{Scheme, Uri}; + + impl Connector for super::HttpConnector { + type Stream = MixStream; + type Error = HttpClientError; + + fn connect(&self, uri: &Uri) -> Result { + // Make sure all parts of uri is accurate. + let mut addr = uri.authority().unwrap().to_string(); + let host = uri.host().unwrap().as_str().to_string(); + let port = uri.port().unwrap().as_u16().unwrap(); + let mut auth = None; + let mut is_proxy = false; + + if let Some(proxy) = self.config.proxies.match_proxy(uri) { + addr = proxy.via_proxy(uri).authority().unwrap().to_string(); + auth = proxy + .intercept + .proxy_info() + .basic_auth + .as_ref() + .and_then(|v| v.to_str().ok()); + is_proxy = true; + } + match *uri.scheme().unwrap() { + Scheme::HTTP => Ok(MixStream::Http(TcpStream::connect(addr).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?)), + Scheme::HTTPS => { + let tcp_stream = TcpStream::connect(addr).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + + let tcp_stream = if is_proxy { + tunnel(tcp_stream, host, port, auth)? + } else { + tcp_stream + }; + + let stream = self + .config + .tls + .ssl() + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))? + .into_inner() + .connect(tcp_stream) + .map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + })?; + Ok(MixStream::Https(stream)) + } + } + } + } + + fn tunnel( + mut conn: TcpStream, + host: String, + port: u16, + auth: Option, + ) -> Result { + let mut req = Vec::new(); + + // `unwrap()` never failed here. + write!( + &mut req, + "CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n" + ) + .unwrap(); + + if let Some(value) = auth { + write!(&mut req, "Proxy-Authorization: {value}\r\n").unwrap(); + } + + write!(&mut req, "\r\n").unwrap(); + + conn.write_all(&req) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + + let mut buf = [0; 8192]; + let mut pos = 0; + + loop { + let n = conn + .read(&mut buf[pos..]) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + + if n == 0 { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "Error receiving from proxy", + )); + } + + pos += n; + let resp = &buf[..pos]; + if resp.starts_with(b"HTTP/1.1 200") { + if resp.ends_with(b"\r\n\r\n") { + return Ok(conn); + } + if pos == buf.len() { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "proxy headers too long for tunnel", + )); + } + } else if resp.starts_with(b"HTTP/1.1 407") { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "proxy authentication required", + )); + } else { + return Err(HttpClientError::new_with_message( + ErrorKind::Connect, + "unsuccessful tunnel", + )); + } + } + } +} diff --git a/ylong_http_client/src/sync_impl/http_body.rs b/ylong_http_client/src/sync_impl/http_body.rs new file mode 100644 index 0000000..15167d5 --- /dev/null +++ b/ylong_http_client/src/sync_impl/http_body.rs @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::Body; +use crate::error::{ErrorKind, HttpClientError}; +use crate::sync_impl::conn::StreamData; +use std::io::{Cursor, Read}; +use ylong_http::body::{ChunkBodyDecoder, ChunkState, TextBodyDecoder}; +use ylong_http::headers::{HeaderName, HeaderValue, Headers}; + +/// `HttpBody` is the body part of the `Response` returned by `Client::request`. +/// `HttpBody` implements `Body` trait, so users can call related methods to get +/// body data. +/// +/// # Examples +/// +/// ```no_run +/// use ylong_http_client::sync_impl::{Client, HttpBody, Body}; +/// use ylong_http_client::{Request, EmptyBody}; +/// +/// let mut client = Client::new(); +/// +/// // `HttpBody` is the body part of `response`. +/// let mut response = client.request(Request::new(EmptyBody)).unwrap(); +/// +/// // Users can use `Body::data` to get body data. +/// let mut buf = [0u8; 1024]; +/// loop { +/// let size = response.body_mut().data(&mut buf).unwrap(); +/// if size == 0 { +/// break; +/// } +/// let _data = &buf[..size]; +/// // Deals with the data. +/// } +/// ``` +pub struct HttpBody { + kind: Kind, +} + +type BoxStreamData = Box; + +impl HttpBody { + pub(crate) fn empty() -> Self { + Self { kind: Kind::Empty } + } + + pub(crate) fn text(len: usize, pre: &[u8], io: BoxStreamData) -> Self { + Self { + kind: Kind::Text(Text::new(len, pre, io)), + } + } + + pub(crate) fn chunk(pre: &[u8], io: BoxStreamData, is_trailer: bool) -> Self { + Self { + kind: Kind::Chunk(Chunk::new(pre, io, is_trailer)), + } + } +} + +// TODO: `TextBodyDecoder` implementation and `ChunkBodyDecoder` implementation. +enum Kind { + Empty, + Text(Text), + Chunk(Chunk), +} + +struct Text { + decoder: TextBodyDecoder, + pre: Option>>, + io: Option, +} + +impl Text { + pub(crate) fn new(len: usize, pre: &[u8], io: BoxStreamData) -> Self { + Self { + decoder: TextBodyDecoder::new(len), + pre: (!pre.is_empty()).then_some(Cursor::new(pre.to_vec())), + io: Some(io), + } + } +} + +impl Body for HttpBody { + type Error = HttpClientError; + + fn data(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + match self.kind { + Kind::Empty => Ok(0), + Kind::Text(ref mut text) => text.data(buf), + Kind::Chunk(ref mut chunk) => chunk.data(buf), + } + } + + fn trailer(&mut self) -> Result, Self::Error> { + match self.kind { + Kind::Chunk(ref mut chunk) => chunk.get_trailer(), + _ => Ok(None), + } + } +} + +impl Text { + fn data(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut read = 0; + + if let Some(pre) = self.pre.as_mut() { + // Here cursor read never failed. + let this_read = pre.read(buf).unwrap(); + if this_read == 0 { + self.pre = None; + } else { + read += this_read; + let (text, rem) = self.decoder.decode(&buf[..read]); + + match (text.is_complete(), rem.is_empty()) { + (true, false) => { + if let Some(io) = self.io.take() { + io.shutdown(); + }; + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Not Eof", + )); + } + (true, true) => { + self.io = None; + return Ok(read); + } + _ => {} + } + } + } + + if !buf[read..].is_empty() { + if let Some(mut io) = self.io.take() { + match io.read(&mut buf[read..]) { + // Disconnected. + Ok(0) => { + io.shutdown(); + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Response Body Incomplete", + )); + } + Ok(filled) => { + let (text, rem) = self.decoder.decode(&buf[read..read + filled]); + read += filled; + // Contains redundant `rem`, return error. + match (text.is_complete(), rem.is_empty()) { + (true, false) => { + io.shutdown(); + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Not Eof", + )); + } + (true, true) => return Ok(read), + _ => {} + } + self.io = Some(io); + } + Err(e) => { + return Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + )) + } + } + } + } + Ok(read) + } +} + +struct Chunk { + decoder: ChunkBodyDecoder, + pre: Option>>, + io: Option, + trailer: Vec, +} + +impl Chunk { + pub(crate) fn new(pre: &[u8], io: BoxStreamData, is_trailer: bool) -> Self { + Self { + decoder: ChunkBodyDecoder::new().contains_trailer(is_trailer), + pre: (!pre.is_empty()).then_some(Cursor::new(pre.to_vec())), + io: Some(io), + trailer: vec![], + } + } +} + +impl Chunk { + fn data(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut read = 0; + + while let Some(pre) = self.pre.as_mut() { + // Here cursor read never failed. + let size = pre.read(&mut buf[read..]).unwrap(); + + if size == 0 { + self.pre = None; + } + + let (size, flag) = self.merge_chunks(&mut buf[read..read + size])?; + read += size; + + if flag { + // Return if we find a 0-sized chunk. + self.io = None; + return Ok(read); + } else if read != 0 { + // Return if we get some data. + return Ok(read); + } + } + + // Here `read` must be 0. + while let Some(mut io) = self.io.take() { + match io.read(&mut buf[read..]) { + Ok(filled) => { + if filled == 0 { + io.shutdown(); + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Response Body Incomplete", + )); + } + let (size, flag) = self.merge_chunks(&mut buf[read..read + filled])?; + read += size; + if flag { + // Return if we find a 0-sized chunk. + // Return if we get some data. + return Ok(read); + } + self.io = Some(io); + if read != 0 { + return Ok(read); + } + } + Err(e) => { + return Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + )) + } + } + } + Ok(read) + } + + fn merge_chunks(&mut self, buf: &mut [u8]) -> Result<(usize, bool), HttpClientError> { + // Here we need to merge the chunks into one data block and return. + // The data arrangement in buf is as follows: + // + // data in buf: + // +------+------+------+------+------+------+------+ + // | data | len | data | len | ... | data | len | + // +------+------+------+------+------+------+------+ + // + // We need to merge these data blocks into one block: + // + // after merge: + // +---------------------------+ + // | data | + // +---------------------------+ + + let (chunks, junk) = self + .decoder + .decode(buf) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + + let mut finished = false; + let mut ptrs = Vec::new(); + for chunk in chunks.into_iter() { + if chunk.trailer().is_some() { + if chunk.state() == &ChunkState::Finish { + finished = true; + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + self.trailer.extend_from_slice(b"\r\n"); + break; + } else if chunk.state() == &ChunkState::DataCrlf { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + self.trailer.extend_from_slice(b"\r\n"); + } else { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + } + } else { + if chunk.size() == 0 && chunk.state() != &ChunkState::MetaSize { + finished = true; + break; + } + let data = chunk.data(); + ptrs.push((data.as_ptr(), data.len())) + } + } + + if finished && !junk.is_empty() { + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "Invalid Chunk Body", + )); + } + + let start = buf.as_ptr(); + + let mut idx = 0; + for (ptr, len) in ptrs.into_iter() { + let st = ptr as usize - start as usize; + let ed = st + len; + buf.copy_within(st..ed, idx); + idx += len; + } + Ok((idx, finished)) + } + + fn get_trailer(&self) -> Result, HttpClientError> { + if self.trailer.is_empty() { + return Err(HttpClientError::new_with_message( + ErrorKind::BodyDecode, + "No trailer received", + )); + } + + let mut colon = 0; + let mut lf = 0; + let mut trailer_header_name = HeaderName::from_bytes(b"") + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + let mut trailer_headers = Headers::new(); + for (i, b) in self.trailer.iter().enumerate() { + if *b == b' ' { + continue; + } + if *b == b':' { + colon = i; + if lf == 0 { + let trailer_name = &self.trailer[..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) + })?; + } else { + let trailer_name = &self.trailer[lf + 1..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) + })?; + } + continue; + } + + if *b == b'\n' { + lf = i; + let trailer_value = &self.trailer[colon + 1..lf - 1]; + let trailer_header_value = HeaderValue::from_bytes(trailer_value) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + let _ = trailer_headers + .insert::( + trailer_header_name.clone(), + trailer_header_value.clone(), + ) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + } + } + Ok(Some(trailer_headers)) + } +} + +#[cfg(test)] +mod ut_syn_http_body { + use crate::sync_impl::http_body::Chunk; + use crate::sync_impl::{Body, HttpBody}; + use ylong_http::body::ChunkBodyDecoder; + + /// UT test cases for `HttpBody::empty`. + /// + /// # Brief + /// 1. Creates a `HttpBody` by calling `HttpBody::empty`. + /// 2. Calls `data` method. + /// 3. Checks if the result is correct. + #[test] + fn ut_http_body_empty() { + let mut body = HttpBody::empty(); + let mut buf = []; + let data = body.data(&mut buf); + assert!(data.is_ok()); + assert_eq!(data.unwrap(), 0); + } + + /// UT test cases for `Chunk::get_trailers`. + /// + /// # Brief + /// 1. Creates a `Chunk` and set `Trailer`. + /// 2. Calls `get_trailer` method. + /// 3. Checks if the result is correct. + #[test] + fn ut_http_body_chunk() { + let mut chunk = Chunk { + decoder: ChunkBodyDecoder::new().contains_trailer(true), + pre: None, + io: None, + trailer: vec![], + }; + let trailer_info = "Trailer1:value1\r\nTrailer2:value2\r\n"; + chunk.trailer.extend_from_slice(trailer_info.as_bytes()); + let data = chunk.get_trailer().unwrap().unwrap(); + let value1 = data.get("Trailer1"); + assert_eq!(value1.unwrap().to_str().unwrap(), "value1"); + let value2 = data.get("Trailer2"); + assert_eq!(value2.unwrap().to_str().unwrap(), "value2"); + } +} diff --git a/ylong_http_client/src/sync_impl/mod.rs b/ylong_http_client/src/sync_impl/mod.rs new file mode 100644 index 0000000..a3b9f25 --- /dev/null +++ b/ylong_http_client/src/sync_impl/mod.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP synchronous client module. +//! +//! This module provides synchronous client components. +//! +//! - [`Client`]: The main part of client, which provides the request sending +//! interface and configuration interface. `Client` sends requests in a +//! synchronous blocking manner. +//! +//! - [`Connector`]: `Connector`s are used to create new connections +//! synchronously. This module provides `Connector` trait and a `HttpConnector` +//! which implements the trait. + +// TODO: Reconstruct `sync_impl`. + +mod client; +mod conn; +mod connector; +mod http_body; +mod pool; +mod reader; + +pub use client::{Client, ClientBuilder}; +pub use connector::Connector; +pub use http_body::HttpBody; +pub use reader::{BodyProcessError, BodyProcessor, BodyReader, DefaultBodyProcessor}; +pub use ylong_http::body::sync_impl::Body; + +// TODO: Adapter, remove this later. +pub use ylong_http::response::Response; + +pub(crate) use connector::HttpConnector; + +#[cfg(feature = "__tls")] +mod ssl_stream; +#[cfg(feature = "__tls")] +pub use ssl_stream::MixStream; diff --git a/ylong_http_client/src/sync_impl/pool.rs b/ylong_http_client/src/sync_impl/pool.rs new file mode 100644 index 0000000..282c8a6 --- /dev/null +++ b/ylong_http_client/src/sync_impl/pool.rs @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{ErrorKind, HttpClientError}; +use crate::sync_impl::Connector; +use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; +use crate::util::pool::{Pool, PoolKey}; +use crate::Uri; +use std::error::Error; +use std::io::{Read, Write}; +use std::mem::take; +use std::sync::{Arc, Mutex}; + +pub(crate) struct ConnPool { + pool: Pool>, + connector: Arc, +} + +impl ConnPool { + pub(crate) fn new(connector: C) -> Self { + Self { + pool: Pool::new(), + connector: Arc::new(connector), + } + } + + pub(crate) fn connect_to(&self, uri: Uri) -> Result, HttpClientError> { + let key = PoolKey::new( + uri.scheme().unwrap().clone(), + uri.authority().unwrap().clone(), + ); + + self.pool + .get(key, Conns::new) + .conn(|| self.connector.clone().connect(&uri)) + } +} + +pub(crate) struct Conns { + list: Arc>>>, +} + +impl Conns { + fn new() -> Self { + Self { + list: Arc::new(Mutex::new(Vec::new())), + } + } +} + +impl Clone for Conns { + fn clone(&self) -> Self { + Self { + list: self.list.clone(), + } + } +} + +impl Conns { + fn conn(&self, connect_fn: F) -> Result, HttpClientError> + where + F: FnOnce() -> Result, + E: Into>, + { + let mut list = self.list.lock().unwrap(); + let mut conn = None; + let curr = take(&mut *list); + for dispatcher in curr.into_iter() { + // Discard invalid dispatchers. + if dispatcher.is_shutdown() { + continue; + } + if conn.is_none() { + conn = dispatcher.dispatch(); + } + list.push(dispatcher); + } + + if let Some(conn) = conn { + Ok(conn) + } else { + let dispatcher = ConnDispatcher::http1( + connect_fn() + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?, + ); + // We must be able to get the `Conn` here. + let conn = dispatcher.dispatch().unwrap(); + list.push(dispatcher); + Ok(conn) + } + } +} diff --git a/ylong_http_client/src/sync_impl/reader.rs b/ylong_http_client/src/sync_impl/reader.rs new file mode 100644 index 0000000..b5fe763 --- /dev/null +++ b/ylong_http_client/src/sync_impl/reader.rs @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::Body; +use crate::error::HttpClientError; +use crate::util::Timeout; +use crate::ErrorKind; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::time::{Duration, Instant}; + +/// A reader used to read all the body data to a specified location and provide +/// echo function. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::sync_impl::{BodyReader, BodyProcessor, BodyProcessError}; +/// use ylong_http_client::TextBody; +/// +/// // Defines a processor, which provides read and echo ability. +/// struct Processor { +/// vec: Vec, +/// echo: usize, +/// } +/// +/// // Implements `BodyProcessor` trait for `&mut Processor` instead of `Processor` +/// // if users want to get the result in struct after reading. +/// impl BodyProcessor for &mut Processor { +/// fn write(&mut self, data: &[u8]) -> Result<(), BodyProcessError> { +/// self.vec.extend_from_slice(data); +/// Ok(()) +/// } +/// +/// fn progress(&mut self, filled: usize) -> Result<(), BodyProcessError> { +/// self.echo += 1; +/// Ok(()) +/// } +/// } +/// +/// let mut body = TextBody::from_bytes(b"HelloWorld"); +/// let mut processor = Processor { vec: Vec::new(), echo: 0 }; +/// let _ = BodyReader::new(&mut processor).read_all(&mut body); +/// +/// // All data is read. +/// assert_eq!(processor.vec, b"HelloWorld"); +/// // It will be echoed multiple times during the reading process. +/// assert_ne!(processor.echo, 0); +/// ``` +pub struct BodyReader { + pub(crate) read_timeout: Timeout, + pub(crate) processor: T, +} + +impl BodyReader { + /// Creates a new `BodyReader` with the given `Processor`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::{BodyReader, DefaultBodyProcessor}; + /// + /// let reader = BodyReader::new(DefaultBodyProcessor::new()); + /// ``` + pub fn new(processor: T) -> Self { + Self { + read_timeout: Timeout::none(), + processor, + } + } + + /// Sets body read timeout. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::{BodyReader, DefaultBodyProcessor}; + /// use ylong_http_client::util::Timeout; + /// + /// let reader = BodyReader::new(DefaultBodyProcessor::new()) + /// .read_timeout(Timeout::none()); + /// ``` + pub fn read_timeout(mut self, timeout: Timeout) -> Self { + self.read_timeout = timeout; + self + } + + /// Reads all the body data. During the read process, [`BodyProcessor::write`] and + /// [`BodyProcessor::progress`] will be called multiple times. + /// + /// [`BodyProcessor::write`]: BodyProcessor::write + /// [`BodyProcessor::progress`]: BodyProcessor::progress + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::{BodyReader, BodyProcessor}; + /// use ylong_http_client::TextBody; + /// + /// let mut body = TextBody::from_bytes(b"HelloWorld"); + /// let _ = BodyReader::default().read_all(&mut body); + /// ``` + pub fn read_all(&mut self, body: &mut B) -> Result<(), HttpClientError> { + // Use buffers up to 16K in size to read body. + const TEMP_BUF_SIZE: usize = 16 * 1024; + + let mut last = Instant::now(); + let mut buf = [0u8; TEMP_BUF_SIZE]; + let mut written = 0usize; + + loop { + let read_len = body + .data(&mut buf) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + + if read_len == 0 { + self.processor + .progress(written) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + break; + } + + self.processor + .write(&buf[..read_len]) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + + written += read_len; + + let now = Instant::now(); + if now.duration_since(last) >= Duration::from_secs(1) { + self.processor + .progress(written) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + } + last = now; + } + Ok(()) + } +} + +impl Default for BodyReader { + fn default() -> Self { + Self::new(DefaultBodyProcessor::new()) + } +} + +/// The trait defines methods for processing bodies of HTTP messages. Unlike the async version, this is for synchronous usage. +pub trait BodyProcessor { + /// Writes the body data read each time to the specified location. + /// + /// This method will be called every time a part of the body data is read. + fn write(&mut self, data: &[u8]) -> Result<(), BodyProcessError>; + + /// Informs users how many bytes have been written to the specified location + /// at this time. Users can display the progress according to the number of + /// bytes written. + fn progress(&mut self, filled: usize) -> Result<(), BodyProcessError>; +} + +/// Error occurs when processing body data. +#[derive(Debug)] +pub struct BodyProcessError; + +impl Display for BodyProcessError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl Error for BodyProcessError {} + +/// A default body processor that write data to console directly. +pub struct DefaultBodyProcessor; + +impl DefaultBodyProcessor { + /// Creates a new `DefaultBodyProcessor`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::sync_impl::DefaultBodyProcessor; + /// + /// let processor = DefaultBodyProcessor::new(); + /// ``` + pub fn new() -> Self { + Self + } +} + +impl BodyProcessor for DefaultBodyProcessor { + fn write(&mut self, data: &[u8]) -> Result<(), BodyProcessError> { + println!("{data:?}"); + Ok(()) + } + + fn progress(&mut self, filled: usize) -> Result<(), BodyProcessError> { + println!("filled: {filled}"); + Ok(()) + } +} + +impl Default for DefaultBodyProcessor { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod ut_syn_reader { + use crate::sync_impl::{BodyReader, DefaultBodyProcessor}; + use crate::util::Timeout; + use ylong_http::body::TextBody; + + /// UT test cases for `BodyReader::read_timeout`. + /// + /// # Brief + /// 1. Creates a `BodyReader` with `DefaultBodyProcessor::default` by calling `BodyReader::new`. + /// 2. Calls `read_timeout`. + /// 3. Checks if the result is correct. + #[test] + fn ut_body_reader_read_timeout() { + let reader = BodyReader::new(DefaultBodyProcessor::default()).read_timeout(Timeout::none()); + assert_eq!(reader.read_timeout, Timeout::none()); + } + + /// UT test cases for `BodyReader::read_all`. + /// + /// # Brief + /// 1. Creates a `BodyReader` by calling `BodyReader::default`. + /// 2. Creates a `TextBody`. + /// 3. Calls `read_all` method. + /// 4. Checks if the result is corrent. + #[test] + fn ut_body_reader_read_all() { + let mut body = TextBody::from_bytes(b"HelloWorld"); + let res = BodyReader::default().read_all(&mut body); + assert!(res.is_ok()); + } +} diff --git a/ylong_http_client/src/sync_impl/ssl_stream.rs b/ylong_http_client/src/sync_impl/ssl_stream.rs new file mode 100644 index 0000000..cf001b8 --- /dev/null +++ b/ylong_http_client/src/sync_impl/ssl_stream.rs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#[cfg(feature = "__c_openssl")] +use crate::util::c_openssl::ssl::SslStream; +use std::io::{Read, Write}; + +/// A stream which may be wrapped with TLS. +pub enum MixStream { + /// A raw HTTP stream. + Http(T), + /// An SSL-wrapped HTTP stream. + Https(SslStream), +} + +impl Read for MixStream +where + T: Read + Write, +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match &mut *self { + MixStream::Http(s) => s.read(buf), + MixStream::Https(s) => s.read(buf), + } + } +} +impl Write for MixStream +where + T: Read + Write, +{ + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match &mut *self { + MixStream::Http(s) => s.write(buf), + MixStream::Https(s) => s.write(buf), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match &mut *self { + MixStream::Http(s) => s.flush(), + MixStream::Https(s) => s.flush(), + } + } +} diff --git a/ylong_http_client/src/util/base64.rs b/ylong_http_client/src/util/base64.rs new file mode 100644 index 0000000..b3f26bd --- /dev/null +++ b/ylong_http_client/src/util/base64.rs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Base64 simple implementation. + +pub(crate) fn encode(input: &[u8]) -> Vec { + let mut output = Vec::new(); + + for chunk in input.chunks(3) { + output.push(BASE64_TABLE[((chunk[0] >> 2) & 0x3f) as usize]); + if chunk.len() == 3 { + output.push(BASE64_TABLE[(((chunk[0] & 0x3) << 4) | ((chunk[1] >> 4) & 0xf)) as usize]); + output.push(BASE64_TABLE[(((chunk[1] & 0xf) << 2) | ((chunk[2] >> 6) & 0x3)) as usize]); + output.push(BASE64_TABLE[(chunk[2] & 0x3f) as usize]); + } else if chunk.len() == 2 { + output.push(BASE64_TABLE[(((chunk[0] & 0x3) << 4) | ((chunk[1] >> 4) & 0xf)) as usize]); + output.push(BASE64_TABLE[((chunk[1] & 0xf) << 2) as usize]); + output.push(b'='); + } else if chunk.len() == 1 { + output.push(BASE64_TABLE[((chunk[0] & 0x3) << 4) as usize]); + output.push(b'='); + output.push(b'='); + } + } + output +} + +static BASE64_TABLE: [u8; 64] = [ + //0 1 2 3 4 5 6 7 + b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', // 0 + b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', // 1 + b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', // 2 + b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', // 3 + b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', // 4 + b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', // 5 + b'w', b'x', b'y', b'z', b'0', b'1', b'2', b'3', // 6 + b'4', b'5', b'6', b'7', b'8', b'9', b'+', b'/', // 7 +]; + +#[cfg(test)] +mod ut_util_base64 { + use crate::util::base64::encode; + + /// UT test cases for `base64::encode`. + /// + /// # Brief + /// 1. Calls `encode` to parse the string and convert it into `base64` format. + /// 2. Checks if the results are correct. + #[test] + fn ut_util_base64_encode() { + assert_eq!(encode(b"this is an example"), b"dGhpcyBpcyBhbiBleGFtcGxl"); + assert_eq!(encode(b"hello"), b"aGVsbG8="); + assert_eq!(encode(b""), b""); + assert_eq!(encode(b"a"), b"YQ=="); + assert_eq!(encode(b"ab"), b"YWI="); + assert_eq!(encode(b"abc"), b"YWJj"); + } +} diff --git a/ylong_http_client/src/util/c_openssl/adapter.rs b/ylong_http_client/src/util/c_openssl/adapter.rs new file mode 100644 index 0000000..cf293c9 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/adapter.rs @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{error::HttpClientError, util::AlpnProtocolList}; +use crate::{ + util::c_openssl::{ + error::ErrorStack, + ssl::{Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslVersion}, + x509::{X509Ref, X509Store, X509}, + }, + ErrorKind, +}; +use std::path::Path; + +/// `TlsContextBuilder` implementation based on `SSL_CTX`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; +/// +/// let context = TlsConfigBuilder::new() +/// .set_ca_file("ca.crt") +/// .set_max_proto_version(TlsVersion::TLS_1_2) +/// .set_min_proto_version(TlsVersion::TLS_1_2) +/// .set_cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK") +/// .build(); +/// ``` +pub struct TlsConfigBuilder { + inner: SslContextBuilder, +} + +impl TlsConfigBuilder { + /// Creates a new, default `SslContextBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfigBuilder; + /// + /// let builder = TlsConfigBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { + inner: SslContext::builder(SslMethod::tls_client()), + } + } + + /// Loads trusted root certificates from a file. The file should contain a + /// sequence of PEM-formatted CA certificates. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfigBuilder; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_ca_file("ca.crt"); + /// ``` + pub fn set_ca_file>(mut self, path: T) -> Self { + self.inner = self.inner.set_ca_file(path); + self + } + + /// Sets the maximum supported protocol version. A value of `None` will + /// enable protocol versions down the the highest version supported by `OpenSSL`. + /// + /// Requires `OpenSSL 1.1.0` or or `LibreSSL 2.6.1` or newer. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_max_proto_version(TlsVersion::TLS_1_2); + /// ``` + pub fn set_max_proto_version(mut self, version: TlsVersion) -> Self { + self.inner = self.inner.set_max_proto_version(version.into_inner()); + self + } + + /// Sets the minimum supported protocol version. A value of `None` will + /// enable protocol versions down the the lowest version supported by `OpenSSL`. + /// + /// Requires `OpenSSL 1.1.0` or `LibreSSL 2.6.1` or newer. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_min_proto_version(TlsVersion::TLS_1_2); + /// ``` + pub fn set_min_proto_version(mut self, version: TlsVersion) -> Self { + self.inner = self.inner.set_min_proto_version(version.into_inner()); + self + } + + /// Sets the list of supported ciphers for protocols before `TLSv1.3`. + /// + /// The `set_ciphersuites` method controls the cipher suites for `TLSv1.3`. + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfigBuilder; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_cipher_list( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + pub fn set_cipher_list(mut self, list: &str) -> Self { + self.inner = self.inner.set_cipher_list(list); + self + } + + /// Sets the list of supported ciphers for the `TLSv1.3` protocol. + /// + /// The `set_cipher_list` method controls the cipher suites for protocols + /// before `TLSv1.3`. + /// + /// The format consists of TLSv1.3 cipher suite names separated by `:` + /// characters in order of preference. + /// + /// Requires `OpenSSL 1.1.1` or `LibreSSL 3.4.0` or newer. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfigBuilder; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_cipher_suites( + /// "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK" + /// ); + /// ``` + pub fn set_cipher_suites(mut self, list: &str) -> Self { + self.inner = self.inner.set_cipher_suites(list); + self + } + + /// Loads a leaf certificate from a file. + /// + /// Only a single certificate will be loaded - use `add_extra_chain_cert` to + /// add the remainder of the certificate chain, or `set_certificate_chain_file` + /// to load the entire chain from a single file. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::{TlsConfigBuilder, TlsFileType}; + /// + /// let builder = TlsConfigBuilder::new() + /// .set_certificate_file("cert.pem", TlsFileType::PEM); + /// ``` + pub fn set_certificate_file>(mut self, path: T, file_type: TlsFileType) -> Self { + self.inner = self + .inner + .set_certificate_file(path, file_type.into_inner()); + self + } + + /// Loads a certificate chain from a file. + /// + /// The file should contain a sequence of PEM-formatted certificates, + /// the first being the leaf certificate, and the remainder forming the + /// chain of certificates up to and including the trusted root certificate. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfigBuilder; + /// + /// let builder = TlsConfigBuilder::new().set_certificate_chain_file("cert.pem"); + /// ``` + pub fn set_certificate_chain_file>(mut self, path: T) -> Self { + self.inner = self.inner.set_certificate_chain_file(path); + self + } + + /// Sets the leaf certificate. + /// + /// Use `add_extra_chain_cert` to add the remainder of the certificate chain. + /// + /// # Examples + /// + /// ```no_run + /// use ylong_http_client::util::{TlsConfigBuilder, Cert}; + /// + /// let x509 = Cert::from_pem(b"pem-content").unwrap(); + /// let builder = TlsConfigBuilder::new().set_certificate(&x509); + /// ``` + pub fn set_certificate(mut self, cert: &Cert) -> Self { + self.inner = self.inner.set_certificate(cert.as_ref()); + self + } + + /// Appends a certificate to the certificate chain. + /// + /// This chain should contain all certificates necessary to go from the + /// certificate specified by `set_certificate` to a trusted root. + /// + /// This method is based on `openssl::SslContextBuilder::add_extra_chain_cert`. + /// + /// # Examples + /// + /// ```no_run + /// use ylong_http_client::util::{TlsConfigBuilder, Cert}; + /// + /// let root = Cert::from_pem(b"pem-content").unwrap(); + /// let chain = Cert::from_pem(b"pem-content").unwrap(); + /// let builder = TlsConfigBuilder::new() + /// .set_certificate(&root) + /// .add_extra_chain_cert(chain); + /// ``` + pub fn add_extra_chain_cert(mut self, cert: Cert) -> Self { + self.inner = self.inner.add_extra_chain_cert(cert.into_inner()); + self + } + + /// Adds custom root certificate. + pub fn add_root_certificates(mut self, certs: Certificate) -> Self { + let v = certs.into_inner(); + for (i, cert) in v.into_iter().enumerate() { + if i == 0 { + self.inner = self.inner.set_certificate(cert.0.as_ref()); + } else { + self.inner = self.inner.add_extra_chain_cert(cert.0); + } + } + self + } + + /// Sets the protocols to sent to the server for Application Layer Protocol Negotiation (ALPN). + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + /// + /// # Examples + /// ``` + /// use ylong_http_client::util::{TlsConfigBuilder}; + /// + /// let protocols = b"\x06spdy/1\x08http/1.1"; + /// let builder = TlsConfigBuilder::new().set_alpn_protos(protocols); + /// ``` + pub fn set_alpn_protos(mut self, protocols: &[u8]) -> Self { + self.inner = self.inner.set_alpn_protos(protocols); + self + } + + /// Sets the protocols to sent to the server for Application Layer Protocol Negotiation (ALPN). + /// + /// This method is based on `openssl::SslContextBuilder::set_alpn_protos`. + /// + /// Requires OpenSSL 1.0.2 or LibreSSL 2.6.1 or newer. + /// + /// # Examples + /// ``` + /// use ylong_http_client::util::{AlpnProtocol, AlpnProtocolList, TlsConfigBuilder}; + /// + /// let protocols = AlpnProtocolList::new() + /// .extend(AlpnProtocol::SPDY1) + /// .extend(AlpnProtocol::HTTP11); + /// let builder = TlsConfigBuilder::new().set_alpn_protos(protocols.as_slice()); + /// ``` + pub fn set_alpn_proto_list(mut self, list: AlpnProtocolList) -> Self { + self.inner = self.inner.set_alpn_protos(list.as_slice()); + self + } + + /// Controls the use of built-in system certificates during certificate validation. + /// Default to `true` -- uses built-in system certs. + pub fn build_in_root_certs(mut self, is_use: bool) -> Self { + if !is_use { + let cert_store = X509Store::new(); + match cert_store { + Ok(store) => self.inner = self.inner.set_cert_store(store), + Err(e) => self.inner = SslContextBuilder::from_error(e), + } + } + self + } + + /// Builds a `TlsContext`. Returns `Err` if an error occurred during configuration. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// + /// let context = TlsConfigBuilder::new() + /// .set_ca_file("ca.crt") + /// .set_max_proto_version(TlsVersion::TLS_1_2) + /// .set_min_proto_version(TlsVersion::TLS_1_2) + /// .set_cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK") + /// .build(); + /// ``` + pub fn build(self) -> Result { + Ok(TlsConfig(self.inner.build().map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Build, Some(e)) + })?)) + } +} + +impl Default for TlsConfigBuilder { + fn default() -> Self { + Self::new() + } +} + +/// `TlsContext` is based on `SSL_CTX`, which provides context +/// object of `TLS` streams. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::util::TlsConfig; +/// +/// let builder = TlsConfig::builder(); +/// ``` +#[derive(Debug, Clone)] +pub struct TlsConfig(SslContext); + +impl TlsConfig { + /// Creates a new, default `TlsContextBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::TlsConfig; + /// + /// let builder = TlsConfig::builder(); + /// ``` + pub fn builder() -> TlsConfigBuilder { + TlsConfigBuilder::new() + } + + /// Creates a new, default `TlsSsl`. + pub(crate) fn ssl(&self) -> Result { + let ctx = &self.0; + let ssl = Ssl::new(ctx)?; + Ok(TlsSsl(ssl)) + } +} + +impl Default for TlsConfig { + fn default() -> Self { + TlsConfig::builder().build().unwrap() + } +} + +/// /// `TlsSsl` is based on `Ssl` +pub(crate) struct TlsSsl(Ssl); + +impl TlsSsl { + pub(crate) fn into_inner(self) -> Ssl { + self.0 + } +} + +/// `TlsVersion` is based on `openssl::SslVersion`, which provides `SSL/TLS` +/// protocol version. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::util::TlsVersion; +/// +/// let version = TlsVersion::TLS_1_2; +/// ``` +pub struct TlsVersion(SslVersion); + +impl TlsVersion { + /// Constant for TLS version 1. + pub const TLS_1_0: Self = Self(SslVersion::TLS_1_0); + /// Constant for TLS version 1.1. + pub const TLS_1_1: Self = Self(SslVersion::TLS_1_1); + /// Constant for TLS version 1.2. + pub const TLS_1_2: Self = Self(SslVersion::TLS_1_2); + /// Constant for TLS version 1.3. + pub const TLS_1_3: Self = Self(SslVersion::TLS_1_3); + + /// Consumes `TlsVersion` and then takes `SslVersion`. + pub(crate) fn into_inner(self) -> SslVersion { + self.0 + } +} + +/// `TlsFileType` is based on `openssl::SslFileType`, which provides an +/// identifier of the format of a certificate or key file. +/// +/// ``` +/// use ylong_http_client::util::TlsFileType; +/// +/// let file_type = TlsFileType::PEM; +/// ``` +pub struct TlsFileType(SslFiletype); + +impl TlsFileType { + /// Constant for PEM file type. + pub const PEM: Self = Self(SslFiletype::PEM); + /// Constant for ASN1 file type. + pub const ASN1: Self = Self(SslFiletype::ASN1); + + /// Consumes `TlsFileType` and then takes `SslFiletype`. + pub(crate) fn into_inner(self) -> SslFiletype { + self.0 + } +} + +/// `Cert` is based on `X509`, which indicates `X509` public +/// key certificate. +/// +/// ``` +/// # use ylong_http_client::Cert; +/// +/// # fn read_from_pem(pem: &[u8]) { +/// let cert = Cert::from_pem(pem); +/// # } +/// +/// # fn read_from_der(der: &[u8]) { +/// let cert = Cert::from_der(der); +/// # } +/// ``` +pub struct Cert(X509); + +impl Cert { + /// Deserializes a PEM-encoded `Cert` structure. + /// + /// The input should have a header like below: + /// + /// ```text + /// -----BEGIN CERTIFICATE----- + /// ``` + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::Cert; + /// + /// # fn read_from_pem(pem: &[u8]) { + /// let cert = Cert::from_pem(pem); + /// # } + /// ``` + pub fn from_pem(pem: &[u8]) -> Result { + Ok(Self(X509::from_pem(pem).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Build, Some(e)) + })?)) + } + + /// Deserializes a DER-encoded `Cert` structure. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Cert; + /// + /// # fn read_from_der(der: &[u8]) { + /// let cert = Cert::from_der(der); + /// # } + /// ``` + pub fn from_der(der: &[u8]) -> Result { + Ok(Self(X509::from_der(der).map_err(|e| { + HttpClientError::new_with_cause(ErrorKind::Build, Some(e)) + })?)) + } + + /// Deserializes a list of PEM-formatted certificates. + pub fn stack_from_pem(pem: &[u8]) -> Result, HttpClientError> { + Ok(X509::stack_from_pem(pem) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))? + .into_iter() + .map(Self) + .collect()) + } + + /// Gets a reference to `X509Ref`. + pub(crate) fn as_ref(&self) -> &X509Ref { + self.0.as_ref() + } + + /// Consumes `X509` and then takes `X509`. + pub(crate) fn into_inner(self) -> X509 { + self.0 + } +} + +/// Represents a server X509 certificates. +/// +/// You can use `from_pem` to parse a `&[u8]` into a list of certificates. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::Certificate; +/// +/// fn from_pem(pem: &[u8]) { +/// let certs = Certificate::from_pem(pem); +/// } +/// ``` +pub struct Certificate { + inner: Vec, +} + +impl Certificate { + /// Deserializes a list of PEM-formatted certificates. + pub fn from_pem(pem: &[u8]) -> Result { + let inner = X509::stack_from_pem(pem) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))? + .into_iter() + .map(Cert) + .collect(); + Ok(Certificate { inner }) + } + + pub(crate) fn into_inner(self) -> Vec { + self.inner + } +} + +#[cfg(test)] +mod ut_openssl_adapter { + use crate::util::{Cert, TlsConfigBuilder, TlsFileType, TlsVersion}; + + /// UT test cases for `TlsConfigBuilder::new`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new` + /// 2. Checks if the result is as expected. + #[test] + fn ut_tls_config_builder_new() { + let _ = TlsConfigBuilder::default(); + let builder = TlsConfigBuilder::new(); + assert!(builder.set_ca_file("folder/ca.crt").build().is_err()); + } + + /// UT test cases for `TlsConfigBuilder::new`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_cipher_suites`. + /// 3. Provides an invalid path as argument. + /// 4. Checks if the result is as expected. + #[test] + fn ut_set_cipher_suites() { + let builder = TlsConfigBuilder::new().set_cipher_suites("INVALID STRING"); + assert!(builder.build().is_err()); + } + + /// UT test cases for `TlsConfigBuilder::set_max_proto_version`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_max_proto_version`. + /// 3. Checks if the result is as expected. + #[test] + fn ut_set_max_proto_version() { + let builder = TlsConfigBuilder::new() + .set_max_proto_version(TlsVersion::TLS_1_2) + .build(); + assert!(builder.is_ok()); + } + + /// UT test cases for `TlsConfigBuilder::set_min_proto_version`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_min_proto_version`. + /// 3. Checks if the result is as expected. + #[test] + fn ut_set_min_proto_version() { + let builder = TlsConfigBuilder::new() + .set_min_proto_version(TlsVersion::TLS_1_2) + .build(); + assert!(builder.is_ok()); + } + + /// UT test cases for `TlsConfigBuilder::set_cipher_list`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_cipher_list`. + /// 3. Checks if the result is as expected. + #[test] + fn ut_set_cipher_list() { + let builder = TlsConfigBuilder::new() + .set_cipher_list( + "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", + ) + .build(); + assert!(builder.is_ok()); + } + + /// UT test cases for `TlsConfigBuilder::set_certificate_file`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_certificate_file`. + /// 3. Provides an invalid path as argument. + /// 4. Checks if the result is as expected. + #[test] + fn ut_set_certificate_file() { + let builder = TlsConfigBuilder::new() + .set_certificate_file("cert.pem", TlsFileType::PEM) + .build(); + assert!(builder.is_err()); + } + + /// UT test cases for `TlsConfigBuilder::set_certificate_chain_file`. + /// + /// # Brief + /// 1. Creates a `TlsConfigBuilder` by calling `TlsConfigBuilder::new`. + /// 2. Calls `set_certificate_chain_file`. + /// 3. Provides an invalid path as argument. + /// 4. Checks if the result is as expected. + #[test] + fn ut_set_certificate_chain_file() { + let builder = TlsConfigBuilder::new() + .set_certificate_chain_file("cert.pem") + .build(); + assert!(builder.is_err()); + } + + /// UT test cases for `X509::from_pem`. + /// + /// # Brief + /// 1. Creates a `X509` by calling `X509::from_pem`. + /// 2. Provides an invalid pem as argument. + /// 3. Checks if the result is as expected. + #[test] + fn ut_x509_from_pem() { + let pem = "(pem-content)"; + let x509 = Cert::from_pem(pem.as_bytes()); + // println!("{:?}", x509); + assert!(x509.is_err()); + + let cert = include_bytes!("../../../tests/file/root-ca.pem"); + println!("{:?}", std::str::from_utf8(cert).unwrap()); + // let debugged = format!("{:#?}", cert); + // println!("{debugged}"); + let x509 = Cert::from_pem(cert); + // println!("{:?}", x509); + assert!(x509.is_ok()); + } + + /// UT test cases for `X509::from_der`. + /// + /// # Brief + /// 1. Creates a `X509` by calling `X509::from_der`. + /// 2. Provides an invalid der as argument. + /// 3. Checks if the result is as expected. + #[test] + fn ut_x509_from_der() { + let der = "(dar-content)"; + let x509 = Cert::from_der(der.as_bytes()); + assert!(x509.is_err()); + } +} diff --git a/ylong_http_client/src/util/c_openssl/bio.rs b/ylong_http_client/src/util/c_openssl/bio.rs new file mode 100644 index 0000000..48e37b6 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/bio.rs @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{ + check_ptr, + error::ErrorStack, + ffi::bio::{ + BIO_clear_flags, BIO_free_all, BIO_get_data, BIO_meth_free, BIO_meth_new, + BIO_meth_set_create, BIO_meth_set_ctrl, BIO_meth_set_destroy, BIO_meth_set_puts, + BIO_meth_set_read, BIO_meth_set_write, BIO_new, BIO_new_mem_buf, BIO_set_data, + BIO_set_flags, BIO_set_init, BIO, BIO_METHOD, + }, + ssl_init, +}; +use core::{any::Any, marker::PhantomData, panic::AssertUnwindSafe, ptr, slice}; +use libc::{c_char, c_int, c_long, c_void, strlen}; +use std::{ + io::{self, Read, Write}, + panic::catch_unwind, +}; + +#[derive(Debug)] +pub struct Bio(*mut BIO); + +impl Drop for Bio { + fn drop(&mut self) { + unsafe { + BIO_free_all(self.0); + } + } +} + +#[derive(Debug)] +pub struct BioSlice<'a>(*mut BIO, PhantomData<&'a [u8]>); + +impl<'a> BioSlice<'a> { + pub(crate) fn from_byte(buf: &'a [u8]) -> Result, ErrorStack> { + unsafe { + ssl_init(); + let bio = check_ptr(BIO_new_mem_buf( + buf.as_ptr() as *const _, + buf.len() as c_int, + ))?; + Ok(BioSlice(bio, PhantomData)) + } + } + + pub(crate) fn as_ptr(&self) -> *mut BIO { + self.0 + } +} + +const BIO_TYPE_NONE: c_int = 0; + +const BIO_CTRL_FLUSH: c_int = 11; +const BIO_CTRL_DGRAM_QUERY: c_int = 40; + +const BIO_FLAGS_READ: c_int = 0x01; +const BIO_FLAGS_WRITE: c_int = 0x02; +const BIO_FLAGS_IO_SPECIAL: c_int = 0x04; +const BIO_FLAGS_SHOULD_RETRY: c_int = 0x08; +const BIO_FLAGS_RWS: c_int = BIO_FLAGS_READ | BIO_FLAGS_WRITE | BIO_FLAGS_IO_SPECIAL; + +#[derive(Debug)] +pub struct BioMethodInner(*mut BIO_METHOD); +impl BioMethodInner { + fn new() -> Result { + unsafe { + let ptr = check_ptr(BIO_meth_new(BIO_TYPE_NONE, b"rust\0".as_ptr() as *const _))?; + let bio_method = BioMethodInner(ptr); + + BIO_meth_set_write(ptr, bwrite::); + BIO_meth_set_read(ptr, bread::); + BIO_meth_set_puts(ptr, bputs::); + BIO_meth_set_ctrl(ptr, ctrl::); + BIO_meth_set_create(ptr, create); + BIO_meth_set_destroy(ptr, destroy::); + + Ok(bio_method) + } + } + + fn get(&self) -> *mut BIO_METHOD { + self.0 + } +} + +unsafe impl Sync for BioMethod {} +unsafe impl Send for BioMethod {} + +impl Drop for BioMethodInner { + fn drop(&mut self) { + unsafe { BIO_meth_free(self.0) } + } +} + +#[derive(Debug)] +pub struct BioMethod(BioMethodInner); +impl BioMethod { + fn new() -> Result { + let method = BioMethodInner::new::()?; + Ok(BioMethod(method)) + } + + fn get(&self) -> *mut BIO_METHOD { + self.0.get() + } +} + +pub(crate) struct StreamState { + pub(crate) stream: S, + pub(crate) error: Option, + pub(crate) panic: Option>, + pub(crate) dtls_mtu_size: c_long, +} +unsafe fn get_state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { + &mut *(BIO_get_data(bio) as *mut _) +} + +pub(crate) unsafe fn get_error(bio: *mut BIO) -> Option { + let state = get_state::(bio); + state.error.take() +} + +pub(crate) unsafe fn get_panic(bio: *mut BIO) -> Option> { + let state = get_state::(bio); + state.panic.take() +} + +pub(crate) unsafe fn get_stream_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S { + let state: &'a StreamState = &*(BIO_get_data(bio) as *const StreamState); + &state.stream +} + +pub(crate) unsafe fn get_stream_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S { + &mut get_state(bio).stream +} + +pub(crate) fn new(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack> { + let bio_method = BioMethod::new::()?; + + let stream_state = Box::new(StreamState { + stream, + error: None, + panic: None, + dtls_mtu_size: 0, + }); + + unsafe { + let bio = check_ptr(BIO_new(bio_method.get()))?; + BIO_set_data(bio, Box::into_raw(stream_state) as *mut _); + BIO_set_init(bio, 1); + + Ok((bio, bio_method)) + } +} + +fn retry_error(err: &io::Error) -> bool { + matches!( + err.kind(), + io::ErrorKind::WouldBlock | io::ErrorKind::NotConnected + ) +} + +unsafe extern "C" fn ctrl( + bio: *mut BIO, + ctrl_cmd: c_int, + _num: c_long, + _ptr: *mut c_void, +) -> c_long { + let state = get_state::(bio); + + if ctrl_cmd == BIO_CTRL_FLUSH { + match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) { + Ok(Err(err)) => { + state.error = Some(err); + 0 + } + Ok(Ok(())) => 1, + Err(err) => { + state.panic = Some(err); + 0 + } + } + } else if ctrl_cmd == BIO_CTRL_DGRAM_QUERY { + state.dtls_mtu_size + } else { + 0 + } +} + +#[allow(non_snake_case)] +unsafe fn BIO_set_num(_bio: *mut BIO, _num: c_int) {} + +unsafe extern "C" fn create(bio: *mut BIO) -> c_int { + BIO_set_init(bio, 0); + BIO_set_flags(bio, 0); + BIO_set_num(bio, 0); + BIO_set_data(bio, ptr::null_mut()); + 1 +} + +unsafe extern "C" fn destroy(bio: *mut BIO) -> c_int { + if bio.is_null() { + return 0; + } + let data = BIO_get_data(bio); + drop(Box::>::from_raw(data as *mut _)); + BIO_set_init(bio, 0); + BIO_set_data(bio, ptr::null_mut()); + 1 +} + +unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS); + + let state = get_state::(bio); + if len < 0 { + state.error = Some(io::Error::from(io::ErrorKind::InvalidInput)); + return -1; + } + + let buf = slice::from_raw_parts(buf as *const _, len as usize); + + match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) { + Ok(Err(err)) => { + if retry_error(&err) { + BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_WRITE) + } + state.error = Some(err); + -1 + } + Ok(Ok(len)) => len as c_int, + Err(err) => { + state.panic = Some(err); + -1 + } + } +} + +unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS); + + let state = get_state::(bio); + let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize); + + match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) { + Ok(Err(err)) => { + if retry_error(&err) { + BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ) + } + state.error = Some(err); + -1 + } + Ok(Ok(len)) => len as c_int, + Err(err) => { + state.panic = Some(err); + -1 + } + } +} + +unsafe extern "C" fn bputs(bio: *mut BIO, buf: *const c_char) -> c_int { + bwrite::(bio, buf, strlen(buf) as c_int) +} diff --git a/ylong_http_client/src/util/c_openssl/error.rs b/ylong_http_client/src/util/c_openssl/error.rs new file mode 100644 index 0000000..70c66c8 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/error.rs @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::ssl_init; +#[cfg(feature = "c_openssl_3_0")] +use crate::util::c_openssl::ffi::err::ERR_get_error_all; +#[cfg(feature = "c_openssl_1_1")] +use crate::util::c_openssl::ffi::err::{ERR_func_error_string, ERR_get_error_line_data}; + +use crate::util::c_openssl::ffi::err::{ERR_lib_error_string, ERR_reason_error_string}; +use core::{ffi::CStr, ptr, str}; + +#[cfg(feature = "c_openssl_1_1")] +use libc::c_char; +#[cfg(feature = "c_openssl_3_0")] +use std::ffi::CString; + +use libc::{c_int, c_ulong}; + +use std::{borrow::Cow, error::Error, fmt}; + +const ERR_TXT_MALLOCED: c_int = 0x01; +const ERR_TXT_STRING: c_int = 0x02; + +/// An error reported from OpenSSL. +#[derive(Debug)] +pub(crate) struct StackError { + code: c_ulong, + #[cfg(feature = "c_openssl_1_1")] + file: *const c_char, + #[cfg(feature = "c_openssl_3_0")] + file: CString, + line: c_int, + #[cfg(feature = "c_openssl_3_0")] + func: Option, + data: Option>, +} + +impl Clone for StackError { + fn clone(&self) -> Self { + Self { + code: self.code, + #[cfg(feature = "c_openssl_1_1")] + file: self.file, + #[cfg(feature = "c_openssl_3_0")] + file: self.file.clone(), + line: self.line, + #[cfg(feature = "c_openssl_3_0")] + func: self.func.clone(), + data: self.data.clone(), + } + } +} + +impl StackError { + /// Returns the first error on the OpenSSL error stack. + fn get() -> Option { + unsafe { + ssl_init(); + + let mut file = ptr::null(); + let mut line = 0; + #[cfg(feature = "c_openssl_3_0")] + let mut func = ptr::null(); + let mut data = ptr::null(); + let mut flags = 0; + + #[cfg(feature = "c_openssl_1_1")] + match ERR_get_error_line_data(&mut file, &mut line, &mut data, &mut flags) { + 0 => None, + code => { + let data = if flags & ERR_TXT_STRING != 0 { + let bytes = CStr::from_ptr(data as *const _).to_bytes(); + let data = str::from_utf8(bytes).unwrap_or(""); + let data = if flags & ERR_TXT_MALLOCED != 0 { + Cow::Owned(data.to_string()) + } else { + Cow::Borrowed(data) + }; + Some(data) + } else { + None + }; + Some(StackError { + code, + file, + line, + data, + }) + } + } + + #[cfg(feature = "c_openssl_3_0")] + match ERR_get_error_all(&mut file, &mut line, &mut func, &mut data, &mut flags) { + 0 => None, + code => { + let data = if flags & ERR_TXT_STRING != 0 { + let bytes = CStr::from_ptr(data as *const _).to_bytes(); + let data = str::from_utf8(bytes).unwrap(); + let data = if flags & ERR_TXT_MALLOCED != 0 { + Cow::Owned(data.to_string()) + } else { + Cow::Borrowed(data) + }; + Some(data) + } else { + None + }; + + let file = CStr::from_ptr(file).to_owned(); + let func = if func.is_null() { + None + } else { + Some(CStr::from_ptr(func).to_owned()) + }; + Some(StackError { + code, + file, + line, + func, + data, + }) + } + } + } + } + + /// Returns the raw OpenSSL error code for this error. + fn code(&self) -> c_ulong { + self.code + } +} + +impl fmt::Display for StackError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error:{:08X}", self.code())?; + unsafe { + let lib_error = ERR_lib_error_string(self.code); + if !lib_error.is_null() { + let bytes = CStr::from_ptr(lib_error as *const _).to_bytes(); + write!(f, "lib: ({}), ", str::from_utf8(bytes).unwrap_or_default())?; + } else { + write!(f, "lib: ({}), ", error_get_lib(self.code))?; + } + } + + #[cfg(feature = "c_openssl_1_1")] + { + let func_error = unsafe { ERR_func_error_string(self.code) }; + if !func_error.is_null() { + let bytes = unsafe { core::ffi::CStr::from_ptr(func_error as *const _).to_bytes() }; + write!(f, "func: ({}), ", str::from_utf8(bytes).unwrap_or_default())?; + } else { + write!(f, "func: ({}), ", error_get_func(self.code))?; + } + } + + #[cfg(feature = "c_openssl_3_0")] + { + let func_error = self.func.as_ref().map(|s| s.to_str().unwrap_or_default()); + match func_error { + Some(s) => write!(f, ":{s}")?, + None => write!(f, ":func({})", error_get_func(self.code))?, + } + } + + unsafe { + let reason_error = ERR_reason_error_string(self.code); + if !reason_error.is_null() { + let bytes = CStr::from_ptr(reason_error as *const _).to_bytes(); + write!( + f, + "reason: ({}), ", + str::from_utf8(bytes).unwrap_or_default() + )?; + } else { + write!(f, "reason: ({}), ", error_get_reason(self.code))?; + } + } + write!( + f, + ":{:?}:{}:{}", + self.file, + self.line, + self.data.as_deref().unwrap_or("") + ) + } +} + +unsafe impl Sync for StackError {} +unsafe impl Send for StackError {} + +#[derive(Clone, Debug)] +pub struct ErrorStack(Vec); + +impl fmt::Display for ErrorStack { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if self.0.is_empty() { + return fmt.write_str("Error happened in OpenSSL"); + } + + for err in &self.0 { + write!(fmt, "{err} ")?; + } + Ok(()) + } +} + +impl Error for ErrorStack {} + +impl ErrorStack { + pub(crate) fn get() -> ErrorStack { + let mut vec = vec![]; + while let Some(err) = StackError::get() { + vec.push(err); + } + ErrorStack(vec) + } + + pub(crate) fn errors(&self) -> &[StackError] { + &self.0 + } +} + +#[cfg(feature = "c_openssl_3_0")] +const ERR_SYSTEM_FLAG: c_ulong = c_int::max_value() as c_ulong + 1; +#[cfg(feature = "c_openssl_3_0")] +const fn error_system_error(code: c_ulong) -> bool { + code & ERR_SYSTEM_FLAG != 0 +} + +pub(crate) const fn error_get_lib(code: c_ulong) -> c_int { + #[cfg(feature = "c_openssl_1_1")] + return ((code >> 24) & 0x0FF) as c_int; + + #[cfg(feature = "c_openssl_3_0")] + return ((2 as c_ulong * (error_system_error(code) as c_ulong)) + | (((code >> 23) & 0xFF) * (!error_system_error(code) as c_ulong))) as c_int; +} + +#[allow(unused_variables)] +const fn error_get_func(code: c_ulong) -> c_int { + #[cfg(feature = "c_openssl_1_1")] + return ((code >> 12) & 0xFFF) as c_int; + + #[cfg(feature = "c_openssl_3_0")] + 0 +} + +pub(crate) const fn error_get_reason(code: c_ulong) -> c_int { + #[cfg(feature = "c_openssl_1_1")] + return (code & 0xFFF) as c_int; + + #[cfg(feature = "c_openssl_3_0")] + return ((2 as c_ulong * (error_system_error(code) as c_ulong)) + | ((code & 0x7FFFFF) * (!error_system_error(code) as c_ulong))) as c_int; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/bio.rs b/ylong_http_client/src/util/c_openssl/ffi/bio.rs new file mode 100644 index 0000000..7f54a53 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/bio.rs @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use libc::{c_char, c_int, c_long, c_void}; + +pub(crate) enum BIO {} + +// for `BIO` +extern "C" { + /// Creates a memory BIO using len bytes of data at buf, if len is -1 then the + /// buf is assumed to be nul terminated and its length is determined by strlen. + pub(crate) fn BIO_new_mem_buf(buf: *const c_void, len: c_int) -> *mut BIO; + + pub(crate) fn BIO_set_data(a: *mut BIO, data: *mut c_void); + + pub(crate) fn BIO_get_data(a: *mut BIO) -> *mut c_void; + + pub(crate) fn BIO_free_all(bio: *mut BIO); + + pub(crate) fn BIO_new(b_type: *const BIO_METHOD) -> *mut BIO; + + pub(crate) fn BIO_set_init(a: *mut BIO, init: c_int); + + pub(crate) fn BIO_set_flags(b: *mut BIO, flags: c_int); + + pub(crate) fn BIO_clear_flags(b: *mut BIO, flags: c_int); +} + +#[derive(Debug)] +pub(crate) enum BIO_METHOD {} + +// for `BIO_METHOD` +extern "C" { + pub(crate) fn BIO_meth_new(type_: c_int, name: *const c_char) -> *mut BIO_METHOD; + + pub(crate) fn BIO_meth_free(biom: *mut BIO_METHOD); + + pub(crate) fn BIO_meth_set_write( + biom: *mut BIO_METHOD, + write: unsafe extern "C" fn(*mut BIO, *const c_char, c_int) -> c_int, + ) -> c_int; + + pub(crate) fn BIO_meth_set_read( + biom: *mut BIO_METHOD, + read: unsafe extern "C" fn(*mut BIO, *mut c_char, c_int) -> c_int, + ) -> c_int; + + pub(crate) fn BIO_meth_set_puts( + biom: *mut BIO_METHOD, + read: unsafe extern "C" fn(*mut BIO, *const c_char) -> c_int, + ) -> c_int; + + pub(crate) fn BIO_meth_set_ctrl( + biom: *mut BIO_METHOD, + read: unsafe extern "C" fn(*mut BIO, c_int, c_long, *mut c_void) -> c_long, + ) -> c_int; + + pub(crate) fn BIO_meth_set_create( + biom: *mut BIO_METHOD, + create: unsafe extern "C" fn(*mut BIO) -> c_int, + ) -> c_int; + + pub(crate) fn BIO_meth_set_destroy( + biom: *mut BIO_METHOD, + destroy: unsafe extern "C" fn(*mut BIO) -> c_int, + ) -> c_int; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/err.rs b/ylong_http_client/src/util/c_openssl/ffi/err.rs new file mode 100644 index 0000000..31b9592 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/err.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use libc::{c_char, c_int, c_ulong}; + +extern "C" { + /// ERR_lib_error_string() and ERR_reason_error_string() return the library + /// name and reason string respectively. \ + /// If there is no text string registered for the given error code, + /// the error string will contain the numeric code. + pub(crate) fn ERR_lib_error_string(err: c_ulong) -> *const c_char; + pub(crate) fn ERR_reason_error_string(err: c_ulong) -> *const c_char; + + pub(crate) fn ERR_peek_last_error() -> c_ulong; + + pub(crate) fn ERR_clear_error(); + + /// Returns the earliest error code from the thread's error queue and removes + /// the entry. This function can be called repeatedly until there are no more + /// error codes to return. + #[cfg(feature = "c_openssl_3_0")] + pub(crate) fn ERR_get_error_all( + file: *mut *const c_char, + line: *mut c_int, + function: *mut *const c_char, + data: *mut *const c_char, + flags: *mut c_int, + ) -> c_ulong; + + #[cfg(feature = "c_openssl_1_1")] + pub(crate) fn ERR_get_error_line_data( + file: *mut *const c_char, + line: *mut c_int, + data: *mut *const c_char, + flags: *mut c_int, + ) -> c_ulong; + + #[cfg(feature = "c_openssl_1_1")] + pub(crate) fn ERR_func_error_string(err: c_ulong) -> *const c_char; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/mod.rs b/ylong_http_client/src/util/c_openssl/ffi/mod.rs new file mode 100644 index 0000000..c981bf1 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/mod.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![allow(clippy::upper_case_acronyms)] +#![allow(non_camel_case_types)] + +pub(crate) mod bio; +pub(crate) mod err; +pub(crate) mod pem; +pub(crate) mod ssl; +pub(crate) mod x509; + +use libc::c_int; +pub(crate) enum OPENSSL_INIT_SETTINGS {} + +extern "C" { + /// Calls this function will explicitly initialise BOTH libcrypto and libssl. + pub(crate) fn OPENSSL_init_ssl(opts: u64, settings: *const OPENSSL_INIT_SETTINGS) -> c_int; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/pem.rs b/ylong_http_client/src/util/c_openssl/ffi/pem.rs new file mode 100644 index 0000000..d7d028a --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/pem.rs @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{bio::BIO, x509::C_X509}; +use libc::{c_char, c_int, c_void}; + +// callback func +pub(crate) type PemPasswordCb = Option< + unsafe extern "C" fn( + buf: *mut c_char, + size: c_int, + rwflag: c_int, + user_data: *mut c_void, + ) -> c_int, +>; + +extern "C" { + /// The PEM functions read structures in PEM format. + pub(crate) fn PEM_read_bio_X509( + bio: *mut BIO, + out: *mut *mut C_X509, + callback: PemPasswordCb, + user_data: *mut c_void, + ) -> *mut C_X509; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs new file mode 100644 index 0000000..1610e64 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{ + bio::BIO, + x509::{C_X509, X509_STORE}, +}; +use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; + +/// This is the global context structure which is created by a server or client +/// once per program life-time and which holds mainly default values for the +/// `SSL` structures which are later created for the connections. +pub(crate) enum SSL_CTX {} + +// for `SSL_CTX` +extern "C" { + /// Creates a new `SSL_CTX` object, which holds various configuration and + /// data relevant to SSL/TLS or DTLS session establishment. \ + /// Returns `Null` if failed. + pub(crate) fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX; + + /// Frees an allocated `SSL_CTX` object + pub(crate) fn SSL_CTX_free(ctx: *mut SSL_CTX); + + /// Increments the reference count for an existing `SSL_CTX` structure. + /// Returns 1 for success and 0 for failure + pub(crate) fn SSL_CTX_up_ref(x: *mut SSL_CTX) -> c_int; + + /// Internal handling functions for SSL_CTX objects. + pub(crate) fn SSL_CTX_ctrl( + ctx: *mut SSL_CTX, + cmd: c_int, + larg: c_long, + parg: *mut c_void, + ) -> c_long; + + /// Set default locations for trusted CA certificates. + pub(crate) fn SSL_CTX_load_verify_locations( + ctx: *mut SSL_CTX, + CAfile: *const c_char, + CApath: *const c_char, + ) -> c_int; + + /// Sets the list of available ciphers (TLSv1.2 and below) for ctx using the + /// control string str.\ + /// This function does not impact TLSv1.3 ciphersuites. + pub(crate) fn SSL_CTX_set_cipher_list(ssl: *mut SSL_CTX, s: *const c_char) -> c_int; + + /// Uses to configure the available TLSv1.3 ciphersuites for ctx. + pub(crate) fn SSL_CTX_set_ciphersuites(ctx: *mut SSL_CTX, str: *const c_char) -> c_int; + + /// Loads the first certificate stored in file into ctx. + /// The formatting type of the certificate must be specified from the known + /// types SSL_FILETYPE_PEM, SSL_FILETYPE_ASN1. + pub(crate) fn SSL_CTX_use_certificate_file( + ctx: *mut SSL_CTX, + cert_file: *const c_char, + file_type: c_int, + ) -> c_int; + + /// Loads a certificate chain from file into ctx. The certificates must be in + /// PEM format and must be sorted starting with the subject's certificate (actual + /// client or server certificate), followed by intermediate CA certificates + /// if applicable, and ending at the highest level (root) CA. + pub(crate) fn SSL_CTX_use_certificate_chain_file( + ctx: *mut SSL_CTX, + cert_chain_file: *const c_char, + ) -> c_int; + + /// Loads the certificate `cert` into ctx.\ + /// The rest of the certificates needed to form the complete certificate chain + /// can be specified using the `SSL_CTX_add_extra_chain_cert` function + pub(crate) fn SSL_CTX_use_certificate(ctx: *mut SSL_CTX, cert: *mut C_X509) -> c_int; + + /// Client sets the list of protocols available to be negotiated. + pub(crate) fn SSL_CTX_set_alpn_protos( + s: *mut SSL_CTX, + data: *const c_uchar, + len: c_uint, + ) -> c_int; + + /// Sets/replaces the certificate verification storage of ctx to/with store. + /// If another X509_STORE object is currently set in ctx, it will be X509_STORE_free()ed. + pub(crate) fn SSL_CTX_set_cert_store(ctx: *mut SSL_CTX, store: *mut X509_STORE); +} + +/// This is the main SSL/TLS structure which is created by a server or client per +/// established connection. This actually is the core structure in the SSL API. +/// At run-time the application usually deals with this structure which has links +/// to mostly all other structures. +pub(crate) enum SSL {} + +// for `SSL` +extern "C" { + /// Creates a new `SSL` structure which is needed to hold the data for a + /// TLS/SSL connection. \ + /// Returns `Null` if failed. + pub(crate) fn SSL_new(ctx: *mut SSL_CTX) -> *mut SSL; + + pub(crate) fn SSL_free(ssl: *mut SSL); + + /// Obtains result code for TLS/SSL I/O operation.\ + /// SSL_get_error() must be used in the same thread that performed the TLS/SSL + /// I/O operation, and no other OpenSSL function calls should appear in between. + pub(crate) fn SSL_get_error(ssl: *const SSL, ret: c_int) -> c_int; + + /// Returns an abbreviated string indicating the current state of the SSL object ssl. + pub(crate) fn SSL_state_string_long(ssl: *const SSL) -> *const c_char; + + /// Returns the result of the verification of the X509 certificate presented by the peer, if any. + pub(crate) fn SSL_get_verify_result(ssl: *const SSL) -> c_long; + + pub(crate) fn SSL_set_bio(ssl: *mut SSL, rbio: *mut BIO, wbio: *mut BIO); + + pub(crate) fn SSL_get_rbio(ssl: *const SSL) -> *mut BIO; + + pub(crate) fn SSL_read(ssl: *mut SSL, buf: *mut c_void, num: c_int) -> c_int; + + pub(crate) fn SSL_write(ssl: *mut SSL, buf: *const c_void, num: c_int) -> c_int; + + pub(crate) fn SSL_connect(ssl: *mut SSL) -> c_int; + + pub(crate) fn SSL_shutdown(ssl: *mut SSL) -> c_int; +} + +/// This is a dispatch structure describing the internal ssl library methods/functions +/// which implement the various protocol versions (SSLv3 TLSv1, ...). +/// It's needed to create an `SSL_CTX`. +pub(crate) enum SSL_METHOD {} + +// for `SSL_METHOD` +extern "C" { + /// Is the general-purpose version-flexible SSL/TLS methods. The actual protocol + /// version used will be negotiated to the highest version mutually supported + /// by the client and the server. + pub(crate) fn TLS_client_method() -> *const SSL_METHOD; +} diff --git a/ylong_http_client/src/util/c_openssl/ffi/x509.rs b/ylong_http_client/src/util/c_openssl/ffi/x509.rs new file mode 100644 index 0000000..b835746 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ffi/x509.rs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use libc::{c_char, c_long, c_uchar}; + +pub(crate) enum C_X509 {} + +// for `C_X509` +extern "C" { + pub(crate) fn X509_free(a: *mut C_X509); + + /// Returns a human readable error string for verification error n. + pub(crate) fn X509_verify_cert_error_string(n: c_long) -> *const c_char; + + /// Attempts to decode len bytes at *ppin.\ + /// If successful a pointer to the TYPE structure is returned and *ppin is + /// incremented to the byte following the parsed data. + pub(crate) fn d2i_X509( + a: *mut *mut C_X509, + pp: *mut *const c_uchar, + length: c_long, + ) -> *mut C_X509; +} + +pub(crate) enum X509_STORE {} + +// for `X509_STORE` +extern "C" { + /// Returns a new `X509_STORE`. + pub(crate) fn X509_STORE_new() -> *mut X509_STORE; + + /// Frees up a single `X509_STORE` object. + pub(crate) fn X509_STORE_free(store: *mut X509_STORE); +} diff --git a/ylong_http_client/src/util/c_openssl/foreign.rs b/ylong_http_client/src/util/c_openssl/foreign.rs new file mode 100644 index 0000000..e23d7a6 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/foreign.rs @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use core::cell::UnsafeCell; + +pub struct ForeignRefWrapper(UnsafeCell<()>); + +pub trait Foreign: Sized { + /// The raw C struct. + type CStruct; + /// A reference to the rust type. + type Ref: ForeignRef; + + /// The raw C struct pointer to rust type. + fn from_ptr(ptr: *mut Self::CStruct) -> Self; + /// Returns a raw pointer to the C struct. + fn as_ptr(&self) -> *mut Self::CStruct; +} + +pub trait ForeignRef: Sized { + /// The raw C struct. + type CStruct; + + /// # Safety + /// Dereference of raw pointer. + #[inline] + unsafe fn from_ptr<'a>(ptr: *mut Self::CStruct) -> &'a Self { + &*(ptr as *mut _) + } + + /// # Safety + /// Dereference of raw pointer. + #[inline] + unsafe fn from_ptr_mut<'a>(ptr: *mut Self::CStruct) -> &'a mut Self { + &mut *(ptr as *mut _) + } + + /// Returns a raw pointer to the C struct. + #[inline] + fn as_ptr(&self) -> *mut Self::CStruct { + self as *const _ as *mut _ + } +} + +macro_rules! foreign_type { + ( + type CStruct = $ctype:ty; + fn drop = $drop:expr; + + $(#[$own_attr:meta])* + pub(crate) struct $owned:ident; + + $(#[$borrow_attr:meta])* + pub(crate) struct $borrowed:ident; + ) => { + // Wraps * mut C struct. + $(#[$own_attr])* + pub(crate) struct $owned(*mut $ctype); + + impl crate::util::c_openssl::foreign::Foreign for $owned { + type CStruct = $ctype; + type Ref = $borrowed; + + #[inline] + fn from_ptr(ptr: *mut $ctype) -> $owned { + $owned(ptr) + } + + #[inline] + fn as_ptr(&self) -> *mut $ctype { + self.0 + } + } + + impl Drop for $owned { + #[inline] + fn drop(&mut self) { + unsafe { $drop(self.0) } + } + } + + // * owned -> * Deref::deref(&owned) -> * &borrowed -> borrowed + impl core::ops::Deref for $owned { + type Target = $borrowed; + + #[inline] + fn deref(&self) -> &$borrowed { + unsafe{ crate::util::c_openssl::foreign::ForeignRef::from_ptr(self.0) } + } + } + + // * owned -> * DerefMut::deref_mut(&mut owned) -> * &mut borrowed -> mut borrowed + impl core::ops::DerefMut for $owned { + #[inline] + fn deref_mut(&mut self) -> &mut $borrowed { + unsafe{ crate::util::c_openssl::foreign::ForeignRef::from_ptr_mut(self.0) } + } + } + + // owned.borrow -> & borrowed + impl std::borrow::Borrow<$borrowed> for $owned { + #[inline] + fn borrow(&self) -> &$borrowed { + &**self + } + } + + // owned.as_ref -> & borrowed + impl AsRef<$borrowed> for $owned { + #[inline] + fn as_ref(&self) -> &$borrowed { + &**self + } + } + + // A type implementing `ForeignRef` should simply be a newtype wrapper around `ForeignRefWrapper`. + $(#[$borrow_attr:meta])* + pub(crate) struct $borrowed(crate::util::c_openssl::foreign::ForeignRefWrapper); + + impl crate::util::c_openssl::foreign::ForeignRef for $borrowed { + type CStruct = $ctype; + } + + // Unsate Send and Sync mark. + unsafe impl Send for $owned {} + unsafe impl Send for $borrowed {} + unsafe impl Sync for $owned {} + unsafe impl Sync for $borrowed {} + }; +} diff --git a/ylong_http_client/src/util/c_openssl/mod.rs b/ylong_http_client/src/util/c_openssl/mod.rs new file mode 100644 index 0000000..3b55fe4 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/mod.rs @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! TLS implementation based on [`Openssl`] +//! +//! [`Openssl`]: https://www.openssl.org/ + +#[macro_use] +mod foreign; +mod bio; +mod ffi; + +pub(crate) mod error; +pub(crate) mod ssl; +pub(crate) mod x509; + +pub mod adapter; +pub use adapter::{Cert, Certificate, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; + +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::util::c_openssl::ffi::OPENSSL_init_ssl; +use core::ptr; +use error::ErrorStack; +use libc::c_int; +use std::sync::Once; + +/// Automatic loading of the libssl error strings. This option is a default option. +pub(crate) const OPENSSL_INIT_LOAD_SSL_STRINGS: u64 = 0x00200000; + +/// Checks null-pointer. +pub(crate) fn check_ptr(ptr: *mut T) -> Result<*mut T, ErrorStack> { + if ptr.is_null() { + Err(ErrorStack::get()) + } else { + Ok(ptr) + } +} + +/// Gets errors if the return value <= 0. +pub(crate) fn check_ret(r: c_int) -> Result { + if r <= 0 { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} + +/// Calls this function will explicitly initialise BOTH libcrypto and libssl. +pub(crate) fn ssl_init() { + static SSL_INIT: Once = Once::new(); + let init_options = OPENSSL_INIT_LOAD_SSL_STRINGS; + + SSL_INIT.call_once(|| unsafe { + OPENSSL_init_ssl(init_options, ptr::null_mut()); + }) +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/ctx.rs b/ylong_http_client/src/util/c_openssl/ssl/ctx.rs new file mode 100644 index 0000000..e58f50d --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/ctx.rs @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{filetype::SslFiletype, method::SslMethod, version::SslVersion}; +use crate::{ + c_openssl::x509::X509Store, + util::c_openssl::{ + check_ptr, check_ret, + error::ErrorStack, + ffi::ssl::{ + SSL_CTX_ctrl, SSL_CTX_free, SSL_CTX_load_verify_locations, SSL_CTX_new, + SSL_CTX_set_alpn_protos, SSL_CTX_set_cert_store, SSL_CTX_set_cipher_list, + SSL_CTX_set_ciphersuites, SSL_CTX_up_ref, SSL_CTX_use_certificate, + SSL_CTX_use_certificate_chain_file, SSL_CTX_use_certificate_file, SSL_CTX, + }, + foreign::{Foreign, ForeignRef}, + ssl_init, + x509::{X509Ref, X509}, + }, +}; +use core::{fmt, mem, ptr}; +use libc::{c_int, c_long, c_uint, c_void}; +use std::{ffi::CString, path::Path}; + +const SSL_CTRL_EXTRA_CHAIN_CERT: c_int = 14; + +const SSL_CTRL_SET_MIN_PROTO_VERSION: c_int = 123; +const SSL_CTRL_SET_MAX_PROTO_VERSION: c_int = 124; + +foreign_type!( + type CStruct = SSL_CTX; + fn drop = SSL_CTX_free; + pub(crate) struct SslContext; + pub(crate) struct SslContextRef; +); + +impl SslContext { + pub(crate) fn builder(method: SslMethod) -> SslContextBuilder { + SslContextBuilder::new(method) + } +} + +// TODO: add useful info here. +impl fmt::Debug for SslContext { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "SslContext") + } +} + +impl Clone for SslContext { + fn clone(&self) -> Self { + (**self).to_owned() + } +} + +impl ToOwned for SslContextRef { + type Owned = SslContext; + + fn to_owned(&self) -> Self::Owned { + unsafe { + SSL_CTX_up_ref(self.as_ptr()); + SslContext::from_ptr(self.as_ptr()) + } + } +} + +/// A builder for `SslContext`. +pub(crate) struct SslContextBuilder(Result); + +impl SslContextBuilder { + pub(crate) fn new(method: SslMethod) -> Self { + ssl_init(); + match check_ptr(unsafe { SSL_CTX_new(method.as_ptr()) }) { + Ok(ptr) => SslContextBuilder::from_ptr(ptr), + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Creates a `SslContextBuilder` from a `SSL_CTX`. + pub(crate) fn from_ptr(ptr: *mut SSL_CTX) -> Self { + SslContextBuilder(Ok(SslContext(ptr))) + } + + /// Creates a `SslContextBuilder` from a `SSL_CTX`. + pub(crate) fn as_ptr(&self) -> Result<*mut SSL_CTX, ErrorStack> { + match &self.0 { + Ok(ctx) => Ok(ctx.0), + Err(e) => Err(e.to_owned()), + } + } + + /// Builds a `SslContext`. + pub(crate) fn build(self) -> Result { + self.0 + } + + pub(crate) fn from_error(e: ErrorStack) -> Self { + SslContextBuilder(Err(e)) + } + + pub(crate) fn set_min_proto_version(self, version: SslVersion) -> Self { + let ptr = match self.as_ptr() { + Ok(p) => p, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_ctrl( + ptr, + SSL_CTRL_SET_MIN_PROTO_VERSION, + version.0 as c_long, + ptr::null_mut(), + ) + } as c_int) + { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + pub(crate) fn set_max_proto_version(self, version: SslVersion) -> Self { + let ptr = match self.as_ptr() { + Ok(p) => p, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_ctrl( + ptr, + SSL_CTRL_SET_MAX_PROTO_VERSION, + version.0 as c_long, + ptr::null_mut(), + ) + } as c_int) + { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Loads trusted root certificates from a file.\ + /// Uses to Set default locations for trusted CA certificates. + /// + /// The file should contain a sequence of PEM-formatted CA certificates. + pub(crate) fn set_ca_file

(self, file: P) -> Self + where + P: AsRef, + { + let path = match file.as_ref().as_os_str().to_str() { + Some(path) => path, + None => return SslContextBuilder(Err(ErrorStack::get())), + }; + let file = match CString::new(path) { + Ok(path) => path, + Err(_) => return SslContextBuilder(Err(ErrorStack::get())), + }; + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_load_verify_locations(ptr, file.as_ptr() as *const _, ptr::null()) + }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Sets the list of supported ciphers for protocols before `TLSv1.3`. + pub(crate) fn set_cipher_list(self, list: &str) -> Self { + let list = match CString::new(list) { + Ok(cstr) => cstr, + Err(_) => return SslContextBuilder(Err(ErrorStack::get())), + }; + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { SSL_CTX_set_cipher_list(ptr, list.as_ptr() as *const _) }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Sets the list of supported ciphers for the `TLSv1.3` protocol. + pub(crate) fn set_cipher_suites(self, list: &str) -> Self { + let list = match CString::new(list) { + Ok(cstr) => cstr, + Err(_) => return SslContextBuilder(Err(ErrorStack::get())), + }; + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { SSL_CTX_set_ciphersuites(ptr, list.as_ptr() as *const _) }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Loads a leaf certificate from a file. + /// + /// Only a single certificate will be loaded - use `add_extra_chain_cert` to add the remainder + /// of the certificate chain, or `set_certificate_chain_file` to load the entire chain from a + /// single file. + pub(crate) fn set_certificate_file

(self, file: P, file_type: SslFiletype) -> Self + where + P: AsRef, + { + let path = match file.as_ref().as_os_str().to_str() { + Some(path) => path, + None => return SslContextBuilder(Err(ErrorStack::get())), + }; + let file = match CString::new(path) { + Ok(path) => path, + Err(_) => return SslContextBuilder(Err(ErrorStack::get())), + }; + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_use_certificate_file(ptr, file.as_ptr() as *const _, file_type.as_raw()) + }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Loads a certificate chain from file into ctx. + /// The certificates must be in PEM format and must be sorted starting with + /// the subject's certificate (actual client or server certificate), followed + /// by intermediate CA certificates if applicable, and ending at the highest + /// level (root) CA. + pub(crate) fn set_certificate_chain_file

(self, file: P) -> Self + where + P: AsRef, + { + let path = match file.as_ref().as_os_str().to_str() { + Some(path) => path, + None => return SslContextBuilder(Err(ErrorStack::get())), + }; + let file = match CString::new(path) { + Ok(path) => path, + Err(_) => return SslContextBuilder(Err(ErrorStack::get())), + }; + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_use_certificate_chain_file(ptr, file.as_ptr() as *const _) + }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Sets the leaf certificate. + /// + /// Use `add_extra_chain_cert` to add the remainder of the certificate chain. + pub(crate) fn set_certificate(self, key: &X509Ref) -> Self { + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { SSL_CTX_use_certificate(ptr, key.as_ptr()) }) { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Appends a certificate to the certificate chain. + /// + /// This chain should contain all certificates necessary to go from the certificate specified by + /// `set_certificate` to a trusted root. + pub(crate) fn add_extra_chain_cert(self, cert: X509) -> Self { + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match check_ret(unsafe { + SSL_CTX_ctrl( + ptr, + SSL_CTRL_EXTRA_CHAIN_CERT, + 0, + cert.as_ptr() as *mut c_void, + ) + } as c_int) + { + Ok(_num) => self, + Err(e) => SslContextBuilder(Err(e)), + } + } + + /// Sets the protocols to sent to the server for Application Layer Protocol Negotiation (ALPN). + pub(crate) fn set_alpn_protos(self, protocols: &[u8]) -> Self { + assert!(protocols.len() <= c_uint::max_value() as usize); + + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + + match unsafe { SSL_CTX_set_alpn_protos(ptr, protocols.as_ptr(), protocols.len() as c_uint) } + { + 0 => self, + _ => SslContextBuilder(Err(ErrorStack::get())), + } + } + + pub(crate) fn set_cert_store(self, cert_store: X509Store) -> Self { + let ptr = match self.as_ptr() { + Ok(ptr) => ptr, + Err(e) => return SslContextBuilder(Err(e)), + }; + unsafe { + SSL_CTX_set_cert_store(ptr, cert_store.as_ptr()); + mem::forget(cert_store); + } + self + } +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/error.rs b/ylong_http_client/src/util/c_openssl/ssl/error.rs new file mode 100644 index 0000000..6a48a8b --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/error.rs @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::c_openssl::error::ErrorStack; +use core::fmt; +use libc::c_int; +use std::{error::Error, io}; + +use super::MidHandshakeSslStream; + +#[derive(Debug)] +pub(crate) struct SslError { + pub(crate) code: SslErrorCode, + pub(crate) internal: Option, +} + +#[derive(Debug)] +pub(crate) enum InternalError { + Io(io::Error), + Ssl(ErrorStack), +} + +impl SslError { + pub(crate) fn code(&self) -> SslErrorCode { + self.code + } + + pub(crate) fn into_io_error(self) -> Result { + match self.internal { + Some(InternalError::Io(e)) => Ok(e), + _ => Err(self), + } + } + + pub(crate) fn get_io_error(&self) -> Option<&io::Error> { + match self.internal { + Some(InternalError::Io(ref e)) => Some(e), + _ => None, + } + } +} + +impl Error for SslError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self.internal { + Some(InternalError::Io(ref e)) => Some(e), + Some(InternalError::Ssl(ref e)) => Some(e), + None => None, + } + } +} + +impl fmt::Display for SslError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.code { + SslErrorCode::ZERO_RETURN => write!(f, "SSL session has been closed"), + SslErrorCode::SYSCALL => { + if let Some(InternalError::Io(e)) = &self.internal { + write!(f, "SslCode[{}], IO Error: {}", self.code, e) + } else { + write!(f, "SslCode[{}], Unexpected EOF", self.code) + } + } + SslErrorCode::SSL => { + if let Some(InternalError::Ssl(e)) = &self.internal { + write!(f, "ErrorStack: {e}") + } else { + write!(f, "SslCode: [{}]", self.code) + } + } + SslErrorCode::WANT_READ => { + if let Some(InternalError::Io(e)) = &self.internal { + write!(f, "SslCode[{}], IO Error: {}", self.code, e) + } else { + write!( + f, + "SslCode[{}], Read operation should be retried", + self.code + ) + } + } + SslErrorCode::WANT_WRITE => { + if let Some(InternalError::Io(e)) = &self.internal { + write!(f, "SslCode[{}], IO Error: {}", self.code, e) + } else { + write!( + f, + "SslCode[{}], Write operation should be retried", + self.code + ) + } + } + _ => { + write!(f, "Unknown SslCode[{}]", self.code) + } + } + } +} + +const SSL_ERROR_SSL: c_int = 1; +const SSL_ERROR_SYSCALL: c_int = 5; +const SSL_ERROR_WANT_READ: c_int = 2; +const SSL_ERROR_WANT_WRITE: c_int = 3; +const SSL_ERROR_ZERO_RETURN: c_int = 6; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub(crate) struct SslErrorCode(c_int); + +impl SslErrorCode { + pub(crate) const ZERO_RETURN: SslErrorCode = SslErrorCode(SSL_ERROR_ZERO_RETURN); + pub(crate) const WANT_READ: SslErrorCode = SslErrorCode(SSL_ERROR_WANT_READ); + pub(crate) const WANT_WRITE: SslErrorCode = SslErrorCode(SSL_ERROR_WANT_WRITE); + pub(crate) const SYSCALL: SslErrorCode = SslErrorCode(SSL_ERROR_SYSCALL); + pub(crate) const SSL: SslErrorCode = SslErrorCode(SSL_ERROR_SSL); + + pub(crate) fn from_int(err: c_int) -> SslErrorCode { + SslErrorCode(err) + } +} + +impl fmt::Display for SslErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug)] +pub(crate) enum HandshakeError { + SetupFailure(ErrorStack), + Failure(MidHandshakeSslStream), + WouldBlock(MidHandshakeSslStream), +} + +impl From for HandshakeError { + fn from(e: ErrorStack) -> HandshakeError { + HandshakeError::SetupFailure(e) + } +} + +impl Error for HandshakeError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + HandshakeError::SetupFailure(ref e) => Some(e), + HandshakeError::Failure(ref s) | HandshakeError::WouldBlock(ref s) => Some(s.error()), + } + } +} + +impl fmt::Display for HandshakeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + HandshakeError::SetupFailure(ref e) => write!(f, "Stream setup failed: {e}")?, + HandshakeError::Failure(ref s) => { + write!(f, "Handshake failed: {}", s.error())?; + } + HandshakeError::WouldBlock(ref s) => { + write!(f, "Handshake was interrupted: {}", s.error())?; + } + } + Ok(()) + } +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/filetype.rs b/ylong_http_client/src/util/c_openssl/ssl/filetype.rs new file mode 100644 index 0000000..f32aec6 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/filetype.rs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use libc::c_int; + +const X509_FILETYPE_PEM: c_int = 1; +const X509_FILETYPE_ASN1: c_int = 2; + +pub(crate) struct SslFiletype(c_int); +impl SslFiletype { + pub(crate) const PEM: SslFiletype = SslFiletype(X509_FILETYPE_PEM); + pub(crate) const ASN1: SslFiletype = SslFiletype(X509_FILETYPE_ASN1); + + pub(crate) fn as_raw(&self) -> c_int { + self.0 + } +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/method.rs b/ylong_http_client/src/util/c_openssl/ssl/method.rs new file mode 100644 index 0000000..d62b5e2 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/method.rs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::util::c_openssl::ffi::ssl::{TLS_client_method, SSL_METHOD}; + +pub(crate) struct SslMethod(*const SSL_METHOD); +impl SslMethod { + pub(crate) fn as_ptr(&self) -> *const SSL_METHOD { + self.0 + } + + pub(crate) fn tls_client() -> SslMethod { + unsafe { SslMethod(TLS_client_method()) } + } +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/mod.rs b/ylong_http_client/src/util/c_openssl/ssl/mod.rs new file mode 100644 index 0000000..75351e7 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/mod.rs @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod ctx; +mod error; +mod filetype; +mod method; +mod ssl_base; +mod stream; +mod version; + +pub(crate) use ctx::{SslContext, SslContextBuilder}; +pub(crate) use error::{InternalError, SslError, SslErrorCode}; +pub(crate) use filetype::SslFiletype; +pub(crate) use method::SslMethod; +pub(crate) use ssl_base::{Ssl, SslRef}; +pub(crate) use stream::{MidHandshakeSslStream, ShutdownResult, SslStream}; +pub(crate) use version::SslVersion; diff --git a/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs new file mode 100644 index 0000000..4d8a0f3 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{error::HandshakeError, MidHandshakeSslStream, SslContext, SslErrorCode, SslStream}; +use crate::{ + c_openssl::{ + ffi::{ + bio::BIO, + ssl::{ + SSL_connect, SSL_get_error, SSL_get_rbio, SSL_get_verify_result, SSL_read, + SSL_state_string_long, SSL_write, + }, + }, + foreign::ForeignRef, + x509::X509VerifyResult, + }, + util::c_openssl::{ + check_ptr, + error::ErrorStack, + ffi::ssl::{SSL_free, SSL_new, SSL}, + foreign::Foreign, + }, +}; +use core::{cmp, ffi, fmt, str}; +use libc::{c_int, c_void}; +use std::io::{Read, Write}; + +foreign_type!( + type CStruct = SSL; + fn drop = SSL_free; + /// The main SSL/TLS structure. + pub(crate) struct Ssl; + pub(crate) struct SslRef; +); + +impl Ssl { + pub(crate) fn new(ctx: &SslContext) -> Result { + unsafe { + let ptr = check_ptr(SSL_new(ctx.as_ptr()))?; + Ok(Ssl::from_ptr(ptr)) + } + } + + /// Client connect to Server. + /// only `sync` use. + #[allow(dead_code)] + pub(crate) fn connect(self, stream: S) -> Result, HandshakeError> + where + S: Read + Write, + { + let mut stream = SslStream::new_base(self, stream)?; + let ret = unsafe { SSL_connect(stream.ssl.as_ptr()) }; + if ret > 0 { + Ok(stream) + } else { + let error = stream.get_error(ret); + match error.code { + SslErrorCode::WANT_READ | SslErrorCode::WANT_WRITE => { + Err(HandshakeError::WouldBlock(MidHandshakeSslStream { + _stream: stream, + error, + })) + } + _ => Err(HandshakeError::Failure(MidHandshakeSslStream { + _stream: stream, + error, + })), + } + } + } +} + +impl SslRef { + pub(crate) fn get_error(&self, err: c_int) -> SslErrorCode { + unsafe { SslErrorCode::from_int(SSL_get_error(self.as_ptr(), err)) } + } + + fn ssl_status(&self) -> &'static str { + let status = unsafe { + let ptr = SSL_state_string_long(self.as_ptr()); + ffi::CStr::from_ptr(ptr as *const _) + }; + str::from_utf8(status.to_bytes()).unwrap_or_default() + } + + pub(crate) fn verify_result(&self) -> X509VerifyResult { + unsafe { X509VerifyResult::from_raw(SSL_get_verify_result(self.as_ptr()) as c_int) } + } + + pub(crate) fn get_raw_bio(&self) -> *mut BIO { + unsafe { SSL_get_rbio(self.as_ptr()) } + } + + pub(crate) fn read(&mut self, buf: &mut [u8]) -> c_int { + let len = cmp::min(c_int::MAX as usize, buf.len()) as c_int; + unsafe { SSL_read(self.as_ptr(), buf.as_ptr() as *mut c_void, len) } + } + + pub(crate) fn write(&mut self, buf: &[u8]) -> c_int { + let len = cmp::min(c_int::MAX as usize, buf.len()) as c_int; + unsafe { SSL_write(self.as_ptr(), buf.as_ptr() as *const c_void, len) } + } +} + +impl fmt::Debug for SslRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Ssl[state: {}, verify result: {}]", + &self.ssl_status(), + &self.verify_result() + ) + } +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/stream.rs b/ylong_http_client/src/util/c_openssl/ssl/stream.rs new file mode 100644 index 0000000..deb0d49 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/stream.rs @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{InternalError, Ssl, SslError, SslErrorCode, SslRef}; +use crate::{ + c_openssl::{ + bio::{self, get_error, get_panic, get_stream_mut, get_stream_ref}, + error::ErrorStack, + ffi::ssl::{SSL_connect, SSL_set_bio, SSL_shutdown}, + foreign::Foreign, + }, + util::c_openssl::bio::BioMethod, +}; +use core::{fmt, marker::PhantomData, mem::ManuallyDrop}; +use libc::c_int; +use std::{ + io::{self, Read, Write}, + panic::resume_unwind, +}; + +/// A TLS session over a stream. +pub struct SslStream { + pub(crate) ssl: ManuallyDrop, + method: ManuallyDrop, + p: PhantomData, +} + +impl SslStream { + pub(crate) fn get_error(&mut self, err: c_int) -> SslError { + self.check_panic(); + let code = self.ssl.get_error(err); + let internal = match code { + SslErrorCode::SSL => { + let e = ErrorStack::get(); + Some(InternalError::Ssl(e)) + } + SslErrorCode::SYSCALL => { + let error = ErrorStack::get(); + if error.errors().is_empty() { + self.get_bio_error().map(InternalError::Io) + } else { + Some(InternalError::Ssl(error)) + } + } + SslErrorCode::WANT_WRITE | SslErrorCode::WANT_READ => { + self.get_bio_error().map(InternalError::Io) + } + _ => None, + }; + SslError { code, internal } + } + + fn check_panic(&mut self) { + if let Some(err) = unsafe { get_panic::(self.ssl.get_raw_bio()) } { + resume_unwind(err) + } + } + + fn get_bio_error(&mut self) -> Option { + unsafe { get_error::(self.ssl.get_raw_bio()) } + } + + pub(crate) fn get_ref(&self) -> &S { + unsafe { + let bio = self.ssl.get_raw_bio(); + get_stream_ref(bio) + } + } + + pub(crate) fn get_mut(&mut self) -> &mut S { + unsafe { + let bio = self.ssl.get_raw_bio(); + get_stream_mut(bio) + } + } + + pub(crate) fn ssl(&self) -> &SslRef { + &self.ssl + } +} + +impl fmt::Debug for SslStream +where + S: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "stream[{:?}], {:?}", &self.get_ref(), &self.ssl()) + } +} + +impl Drop for SslStream { + fn drop(&mut self) { + unsafe { + ManuallyDrop::drop(&mut self.ssl); + ManuallyDrop::drop(&mut self.method); + } + } +} + +impl SslStream { + pub(crate) fn ssl_read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let ret = self.ssl.read(buf); + if ret > 0 { + Ok(ret as usize) + } else { + Err(self.get_error(ret)) + } + } + + pub(crate) fn ssl_write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let ret = self.ssl.write(buf); + if ret > 0 { + Ok(ret as usize) + } else { + Err(self.get_error(ret)) + } + } + + pub(crate) fn new_base(ssl: Ssl, stream: S) -> Result { + unsafe { + let (bio, method) = bio::new(stream)?; + SSL_set_bio(ssl.as_ptr(), bio, bio); + + Ok(SslStream { + ssl: ManuallyDrop::new(ssl), + method: ManuallyDrop::new(method), + p: PhantomData, + }) + } + } + + pub(crate) fn connect(&mut self) -> Result<(), SslError> { + let ret = unsafe { SSL_connect(self.ssl.as_ptr()) }; + if ret > 0 { + Ok(()) + } else { + Err(self.get_error(ret)) + } + } + + pub(crate) fn shutdown(&mut self) -> Result { + unsafe { + match SSL_shutdown(self.ssl.as_ptr()) { + 0 => Ok(ShutdownResult::Sent), + 1 => Ok(ShutdownResult::Received), + n => Err(self.get_error(n)), + } + } + } +} + +impl Read for SslStream { + // ssl_read + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + match self.ssl_read(buf) { + Ok(n) => return Ok(n), + // The TLS/SSL peer has closed the connection for writing by sending + // the close_notify alert. No more data can be read. + // Does not necessarily indicate that the underlying transport has been closed. + Err(ref e) if e.code == SslErrorCode::ZERO_RETURN => return Ok(0), + // A non-recoverable, fatal error in the SSL library occurred, usually a protocol error. + Err(ref e) if e.code == SslErrorCode::SYSCALL && e.get_io_error().is_none() => { + return Ok(0) + } + // When the last operation was a read operation from a nonblocking BIO. + Err(ref e) if e.code == SslErrorCode::WANT_READ && e.get_io_error().is_none() => {} + // Other error. + Err(err) => { + return Err(err + .into_io_error() + .unwrap_or_else(|err| io::Error::new(io::ErrorKind::Other, err))) + } + }; + } + } +} + +impl Write for SslStream { + // ssl_write + fn write(&mut self, buf: &[u8]) -> io::Result { + loop { + match self.ssl_write(buf) { + Ok(n) => return Ok(n), + // When the last operation was a read operation from a nonblocking BIO. + Err(ref e) if e.code == SslErrorCode::WANT_READ && e.get_io_error().is_none() => {} + Err(err) => { + return Err(err + .into_io_error() + .unwrap_or_else(|err| io::Error::new(io::ErrorKind::Other, err))); + } + } + } + } + + // S.flush() + fn flush(&mut self) -> io::Result<()> { + self.get_mut().flush() + } +} + +/// An SSL stream midway through the handshake process. +#[derive(Debug)] +pub(crate) struct MidHandshakeSslStream { + pub(crate) _stream: SslStream, + pub(crate) error: SslError, +} + +impl MidHandshakeSslStream { + pub(crate) fn error(&self) -> &SslError { + &self.error + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum ShutdownResult { + Sent, + Received, +} diff --git a/ylong_http_client/src/util/c_openssl/ssl/version.rs b/ylong_http_client/src/util/c_openssl/ssl/version.rs new file mode 100644 index 0000000..c924550 --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/ssl/version.rs @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use libc::c_int; + +const TLS1_VERSION: c_int = 0x301; +const TLS1_1_VERSION: c_int = 0x302; +const TLS1_2_VERSION: c_int = 0x303; +const TLS1_3_VERSION: c_int = 0x304; + +pub(crate) struct SslVersion(pub c_int); + +impl SslVersion { + /// TLSv1.0 + pub(crate) const TLS_1_0: SslVersion = SslVersion(TLS1_VERSION); + + /// TLSv1.1 + pub(crate) const TLS_1_1: SslVersion = SslVersion(TLS1_1_VERSION); + + /// TLSv1.2 + pub(crate) const TLS_1_2: SslVersion = SslVersion(TLS1_2_VERSION); + + /// TLSv1.3 + pub(crate) const TLS_1_3: SslVersion = SslVersion(TLS1_3_VERSION); +} diff --git a/ylong_http_client/src/util/c_openssl/x509.rs b/ylong_http_client/src/util/c_openssl/x509.rs new file mode 100644 index 0000000..7686a1b --- /dev/null +++ b/ylong_http_client/src/util/c_openssl/x509.rs @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{ + bio::BioSlice, + check_ptr, + error::{error_get_lib, error_get_reason, ErrorStack}, + ffi::{ + err::{ERR_clear_error, ERR_peek_last_error}, + pem::PEM_read_bio_X509, + x509::{ + d2i_X509, X509_STORE_free, X509_STORE_new, X509_verify_cert_error_string, X509_STORE, + }, + }, + foreign::Foreign, + ssl_init, +}; +use crate::util::c_openssl::ffi::x509::{X509_free, C_X509}; +use core::{ffi, fmt, ptr, str}; +use libc::{c_int, c_long}; + +foreign_type!( + type CStruct = C_X509; + fn drop = X509_free; + pub(crate) struct X509; + pub(crate) struct X509Ref; +); + +const ERR_LIB_PEM: c_int = 9; +const PEM_R_NO_START_LINE: c_int = 108; + +impl X509 { + pub(crate) fn from_pem(pem: &[u8]) -> Result { + ssl_init(); + let bio = BioSlice::from_byte(pem)?; + let ptr = check_ptr(unsafe { + PEM_read_bio_X509(bio.as_ptr(), ptr::null_mut(), None, ptr::null_mut()) + })?; + Ok(X509::from_ptr(ptr)) + } + + pub(crate) fn from_der(der: &[u8]) -> Result { + ssl_init(); + let len = + ::std::cmp::min(der.len(), ::libc::c_long::max_value() as usize) as ::libc::c_long; + let ptr = check_ptr(unsafe { d2i_X509(ptr::null_mut(), &mut der.as_ptr(), len) })?; + Ok(X509::from_ptr(ptr)) + } + + /// Deserializes a list of PEM-formatted certificates. + pub(crate) fn stack_from_pem(pem: &[u8]) -> Result, ErrorStack> { + unsafe { + ssl_init(); + let bio = BioSlice::from_byte(pem)?; + + let mut certs = vec![]; + loop { + let r = PEM_read_bio_X509(bio.as_ptr(), ptr::null_mut(), None, ptr::null_mut()); + if r.is_null() { + let err = ERR_peek_last_error(); + if error_get_lib(err) == ERR_LIB_PEM + && error_get_reason(err) == PEM_R_NO_START_LINE + { + ERR_clear_error(); + break; + } + + return Err(ErrorStack::get()); + } else { + certs.push(X509(r)); + } + } + Ok(certs) + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) struct X509VerifyResult(c_int); + +impl X509VerifyResult { + fn error_string(&self) -> &'static str { + ssl_init(); + unsafe { + let s = X509_verify_cert_error_string(self.0 as c_long); + str::from_utf8(ffi::CStr::from_ptr(s).to_bytes()).unwrap_or("") + } + } + + pub(crate) fn from_raw(err: c_int) -> X509VerifyResult { + X509VerifyResult(err) + } +} + +impl fmt::Debug for X509VerifyResult { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("X509VerifyResult") + .field("code", &self.0) + .field("error", &self.error_string()) + .finish() + } +} + +impl fmt::Display for X509VerifyResult { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(self.error_string()) + } +} + +foreign_type!( + type CStruct = X509_STORE; + fn drop = X509_STORE_free; + pub(crate) struct X509Store; + pub(crate) struct X509StoreRef; +); + +impl X509Store { + pub(crate) fn new() -> Result { + Ok(X509Store(check_ptr(unsafe { X509_STORE_new() })?)) + } +} diff --git a/ylong_http_client/src/util/config/client.rs b/ylong_http_client/src/util/config/client.rs new file mode 100644 index 0000000..a38eff1 --- /dev/null +++ b/ylong_http_client/src/util/config/client.rs @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Client configure module. + +use crate::util::{Redirect, Retry, Timeout}; + +/// Options and flags which can be used to configure a client. +pub(crate) struct ClientConfig { + pub(crate) redirect: Redirect, + pub(crate) retry: Retry, + pub(crate) connect_timeout: Timeout, + pub(crate) request_timeout: Timeout, +} + +impl ClientConfig { + /// Creates a new and default `ClientConfig`. + pub(crate) fn new() -> Self { + Self { + redirect: Redirect::no_limit(), + retry: Retry::none(), + connect_timeout: Timeout::none(), + request_timeout: Timeout::none(), + } + } +} + +impl Default for ClientConfig { + fn default() -> Self { + Self::new() + } +} diff --git a/ylong_http_client/src/util/config/connector.rs b/ylong_http_client/src/util/config/connector.rs new file mode 100644 index 0000000..b9408c4 --- /dev/null +++ b/ylong_http_client/src/util/config/connector.rs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Connector configure module. + +use crate::util::proxy::Proxies; + +#[derive(Default)] +pub(crate) struct ConnectorConfig { + pub(crate) proxies: Proxies, + + #[cfg(feature = "__tls")] + pub(crate) tls: crate::util::TlsConfig, +} diff --git a/ylong_http_client/src/util/config/http.rs b/ylong_http_client/src/util/config/http.rs new file mode 100644 index 0000000..29a666a --- /dev/null +++ b/ylong_http_client/src/util/config/http.rs @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! HTTP configure module. + +/// Options and flags which can be used to configure `HTTP` related logic. +#[derive(Clone)] +pub(crate) struct HttpConfig { + pub(crate) version: HttpVersion, + + #[cfg(feature = "http2")] + pub(crate) http2_config: http2::H2Config, +} + +impl HttpConfig { + /// Creates a new, default `HttpConfig`. + pub(crate) fn new() -> Self { + Self { + version: HttpVersion::Http11, + + #[cfg(feature = "http2")] + http2_config: http2::H2Config::default(), + } + } +} + +impl Default for HttpConfig { + fn default() -> Self { + Self::new() + } +} + +/// `HTTP` version to use. +#[derive(PartialEq, Eq, Clone)] +pub(crate) enum HttpVersion { + /// Enforces `HTTP/1.1` requests. + Http11, + + #[cfg(feature = "http2")] + /// Enforce `HTTP/2.0` requests without `HTTP/1.1` Upgrade. + Http2PriorKnowledge, +} + +#[cfg(feature = "http2")] +pub(crate) mod http2 { + const DEFAULT_MAX_FRAME_SIZE: u32 = 2 << 13; + const DEFAULT_HEADER_TABLE_SIZE: u32 = 4096; + const DEFAULT_MAX_HEADER_LIST_SIZE: u32 = 16 << 20; + + /// Settings which can be used to configure a http2 connection. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_header_table_size(4096) + /// .set_max_header_list_size(16 << 20) + /// .set_max_frame_size(2 << 13); + /// ``` + #[derive(Clone)] + pub struct H2Config { + max_frame_size: u32, + max_header_list_size: u32, + header_table_size: u32, + } + + impl H2Config { + /// `H2Config` constructor. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Sets the SETTINGS_MAX_FRAME_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_max_frame_size(2 << 13); + /// ``` + pub fn set_max_frame_size(mut self, size: u32) -> Self { + self.max_frame_size = size; + self + } + + /// Sets the SETTINGS_MAX_HEADER_LIST_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_max_header_list_size(16 << 20); + /// ``` + pub fn set_max_header_list_size(mut self, size: u32) -> Self { + self.max_header_list_size = size; + self + } + + /// Sets the SETTINGS_HEADER_TABLE_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_max_header_list_size(4096); + /// ``` + pub fn set_header_table_size(mut self, size: u32) -> Self { + self.header_table_size = size; + self + } + + /// Gets the SETTINGS_MAX_FRAME_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_max_frame_size(2 << 13); + /// assert_eq!(config.max_frame_size(), 2 << 13); + /// ``` + pub fn max_frame_size(&self) -> u32 { + self.max_frame_size + } + + /// Gets the SETTINGS_MAX_HEADER_LIST_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_max_header_list_size(16 << 20); + /// assert_eq!(config.max_header_list_size(), 16 << 20); + /// ``` + pub fn max_header_list_size(&self) -> u32 { + self.max_header_list_size + } + + /// Gets the SETTINGS_MAX_FRAME_SIZE. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::H2Config; + /// + /// let config = H2Config::new() + /// .set_header_table_size(4096); + /// assert_eq!(config.header_table_size(), 4096); + /// ``` + pub fn header_table_size(&self) -> u32 { + self.header_table_size + } + } + + impl Default for H2Config { + fn default() -> Self { + Self { + max_frame_size: DEFAULT_MAX_FRAME_SIZE, + max_header_list_size: DEFAULT_MAX_HEADER_LIST_SIZE, + header_table_size: DEFAULT_HEADER_TABLE_SIZE, + } + } + } +} diff --git a/ylong_http_client/src/util/config/mod.rs b/ylong_http_client/src/util/config/mod.rs new file mode 100644 index 0000000..3d480ef --- /dev/null +++ b/ylong_http_client/src/util/config/mod.rs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod client; +mod connector; +mod http; +mod settings; + +pub(crate) use client::ClientConfig; +pub(crate) use connector::ConnectorConfig; +pub(crate) use http::{HttpConfig, HttpVersion}; + +pub use settings::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; + +#[cfg(feature = "__tls")] +mod tls; +#[cfg(feature = "__tls")] +pub use tls::{AlpnProtocol, AlpnProtocolList}; + +#[cfg(feature = "tls_rust_ssl")] +pub use tls::{Certificate, PrivateKey, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; + +#[cfg(feature = "http2")] +pub use http::http2::H2Config; diff --git a/ylong_http_client/src/util/config/settings.rs b/ylong_http_client/src/util/config/settings.rs new file mode 100644 index 0000000..eea5a04 --- /dev/null +++ b/ylong_http_client/src/util/config/settings.rs @@ -0,0 +1,638 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::{ErrorKind, HttpClientError}; +use crate::util::proxy; +use crate::util::redirect as redirect_util; +use crate::util::redirect::{RedirectStrategy, TriggerKind}; +use core::cmp; +use core::time::Duration; +use ylong_http::request::uri::Uri; +use ylong_http::request::Request; +use ylong_http::response::status::StatusCode; +use ylong_http::response::Response; + +/// Redirects settings of requests. +/// +/// # Example +/// +/// ``` +/// use ylong_http_client::Redirect; +/// +/// // The default maximum number of redirects is 10. +/// let redirect = Redirect::default(); +/// +/// // No redirect +/// let no_redirect = Redirect::none(); +/// +/// // Custom the number of redirects. +/// let max = Redirect::limited(10); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Redirect(Option); + +impl Redirect { + /// Gets the strategy of redirects. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::util::Redirect; + /// + /// # let redirect = Redirect::limited(10); + /// let strategy = redirect.redirect_strategy(); + /// + /// # assert!(strategy.is_some()); + /// ``` + pub fn redirect_strategy(&self) -> Option<&redirect_util::RedirectStrategy> { + self.0.as_ref() + } + + /// Sets max number of redirects. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::util::Redirect; + /// + /// let redirect = Redirect::limited(10); + /// ``` + pub fn limited(max: usize) -> Self { + Self(Some(RedirectStrategy::limited(max))) + } + + /// Sets unlimited number of redirects. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::util::Redirect; + /// + /// let redirect = Redirect::no_limit(); + /// ``` + pub fn no_limit() -> Self { + Self(Some(RedirectStrategy::limited(usize::MAX))) + } + + /// Stops redirects. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http_client::Redirect; + /// + /// let redirect = Redirect::none(); + /// ``` + pub fn none() -> Self { + Self(Some(RedirectStrategy::none())) + } + + pub(crate) fn get_redirect( + dst_uri: &mut Uri, + redirect: &Redirect, + redirect_list: &[Uri], + response: &Response, + request: &mut Request, + ) -> Result { + redirect_util::Redirect::get_trigger_kind( + dst_uri, + redirect, + redirect_list, + response, + request, + ) + } + + pub(crate) fn is_redirect(status_code: StatusCode, request: &mut Request) -> bool { + redirect_util::Redirect::check_redirect(status_code, request) + } +} + +impl Default for Redirect { + // redirect default limit 10 times + fn default() -> Self { + Self(Some(RedirectStrategy::default())) + } +} + +/// Retries settings of requests. The default value is `Retry::NEVER`. +/// +/// # Example +/// +/// ``` +/// use ylong_http_client::Retry; +/// +/// // Never retry. +/// let never = Retry::none(); +/// +/// // The maximum number of redirects is 3. +/// let max = Retry::max(); +/// +/// // Custom the number of retries. +/// let custom = Retry::new(2).unwrap(); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Retry(Option); + +impl Retry { + const MAX_RETRIES: usize = 3; + + /// Customizes the number of retries. Returns `Err` if `times` is greater than 3. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::Retry; + /// + /// assert!(Retry::new(1).is_ok()); + /// assert!(Retry::new(10).is_err()); + /// ``` + pub fn new(times: usize) -> Result { + if times >= Self::MAX_RETRIES { + return Err(HttpClientError::new_with_message( + ErrorKind::Build, + "Invalid Retry Times", + )); + } + Ok(Self(Some(times))) + } + + /// Creates a `Retry` that indicates never retry. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Retry; + /// + /// let retry = Retry::none(); + /// ``` + pub fn none() -> Self { + Self(None) + } + + /// Creates a `Retry` with a max retry times. + /// + /// The maximum number of redirects is 3. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Retry; + /// + /// let retry = Retry::max(); + /// ``` + pub fn max() -> Self { + Self(Some(Self::MAX_RETRIES)) + } + + /// Get the retry times, returns None if not set. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::util::Retry; + /// + /// assert!(Retry::default().times().is_none()); + /// ``` + pub fn times(&self) -> Option { + self.0 + } +} + +impl Default for Retry { + fn default() -> Self { + Self::none() + } +} + +/// Timeout settings. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::Timeout; +/// +/// let timeout = Timeout::none(); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Timeout(Option); + +impl Timeout { + /// Creates a `Timeout` without limiting the timeout. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Timeout; + /// + /// let timeout = Timeout::none(); + /// ``` + pub fn none() -> Self { + Self(None) + } + + /// Creates a new `Timeout` from the specified number of whole seconds. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Timeout; + /// + /// let timeout = Timeout::from_secs(9); + /// ``` + pub fn from_secs(secs: u64) -> Self { + Self(Some(Duration::from_secs(secs))) + } + + pub(crate) fn inner(&self) -> Option { + self.0 + } +} + +impl Default for Timeout { + fn default() -> Self { + Self::none() + } +} + +/// Speed limit settings. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::SpeedLimit; +/// +/// let limit = SpeedLimit::new(); +/// ``` +pub struct SpeedLimit { + min: (u64, Duration), + max: u64, +} + +impl SpeedLimit { + /// Creates a new `SpeedLimit`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::SpeedLimit; + /// + /// let limit = SpeedLimit::new(); + /// ``` + pub fn new() -> Self { + Self::none() + } + + /// Sets the minimum speed and the seconds for which the current speed is + /// allowed to be less than this minimum speed. + /// + /// The unit of speed is bytes per second, and the unit of duration is seconds. + /// + /// The minimum speed cannot exceed the maximum speed that has been set. If + /// the set value exceeds the currently set maximum speed, the minimum speed + /// will be set to the current maximum speed. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::SpeedLimit; + /// + /// // Sets minimum speed is 1024B/s, the duration is 10s. + /// let limit = SpeedLimit::new().min_speed(1024, 10); + /// ``` + pub fn min_speed(mut self, min: u64, secs: u64) -> Self { + self.min = (cmp::min(self.max, min), Duration::from_secs(secs)); + self + } + + /// Sets the maximum speed. + /// + /// The unit of speed is bytes per second. + /// + /// The maximum speed cannot be lower than the minimum speed that has been + /// set. If the set value is lower than the currently set minimum speed, the + /// maximum speed will be set to the current minimum speed. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::SpeedLimit; + /// + /// let limit = SpeedLimit::new().max_speed(1024); + /// ``` + pub fn max_speed(mut self, max: u64) -> Self { + self.max = cmp::max(self.min.0, max); + self + } + + /// Creates a `SpeedLimit` without limiting the speed. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::SpeedLimit; + /// + /// let limit = SpeedLimit::none(); + /// ``` + pub fn none() -> Self { + Self { + min: (0, Duration::MAX), + max: u64::MAX, + } + } +} + +impl Default for SpeedLimit { + fn default() -> Self { + Self::new() + } +} + +/// Proxy settings. +/// +/// `Proxy` has functions which is below: +/// +/// - replace origin uri by proxy uri to link proxy server. +/// - set username and password to login proxy server. +/// - set no proxy which can keep origin uri not to be replaced by proxy uri. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http_client::Proxy; +/// +/// // All http request will be intercepted by `https://www.example.com`, +/// // but https request will link to server directly. +/// let proxy = Proxy::http("http://www.example.com").build(); +/// +/// // All https request will be intercepted by `http://www.example.com`, +/// // but http request will link to server directly. +/// let proxy = Proxy::https("http://www.example.com").build(); +/// +/// // All https and http request will be intercepted by "http://www.example.com". +/// let proxy = Proxy::all("http://www.example.com").build(); +/// ``` +#[derive(Clone)] +pub struct Proxy(proxy::Proxy); + +impl Proxy { + /// Passes all HTTP and HTTPS to the proxy URL. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// // All https and http request will be intercepted by `http://example.com`. + /// let builder = Proxy::all("http://example.com"); + /// ``` + pub fn all(addr: &str) -> ProxyBuilder { + ProxyBuilder { + inner: proxy::Proxy::all(addr), + } + } + + /// Passes HTTP to the proxy URL. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// // All http request will be intercepted by https://example.com, + /// // but https request will link to server directly. + /// let proxy = Proxy::http("https://example.com"); + /// ``` + pub fn http(addr: &str) -> ProxyBuilder { + ProxyBuilder { + inner: proxy::Proxy::http(addr), + } + } + + /// Passes HTTPS to the proxy URL. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// // All https request will be intercepted by http://example.com, + /// // but http request will link to server directly. + /// let proxy = Proxy::https("http://example.com"); + /// ``` + pub fn https(addr: &str) -> ProxyBuilder { + ProxyBuilder { + inner: proxy::Proxy::https(addr), + } + } + + pub(crate) fn inner(self) -> proxy::Proxy { + self.0 + } +} + +/// A builder that constructs a `Proxy`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::Proxy; +/// +/// let proxy = Proxy::all("http://www.example.com") +/// .basic_auth("Aladdin", "open sesame") +/// .build(); +/// ``` +pub struct ProxyBuilder { + inner: Result, +} + +impl ProxyBuilder { + /// Pass HTTPS to the proxy URL, but the https uri which is in the no proxy list, will not pass the proxy URL. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// let builder = Proxy::https("http://example.com").no_proxy("https://example2.com"); + /// ``` + pub fn no_proxy(mut self, no_proxy: &str) -> Self { + self.inner = self.inner.map(|mut proxy| { + proxy.no_proxy(no_proxy); + proxy + }); + self + } + + /// Pass HTTPS to the proxy URL, and set username and password which is required by the proxy server. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// let builder = Proxy::https("http://example.com").basic_auth("username", "password"); + /// ``` + pub fn basic_auth(mut self, username: &str, password: &str) -> Self { + self.inner = self.inner.map(|mut proxy| { + proxy.basic_auth(username, password); + proxy + }); + self + } + + /// Constructs a `Proxy`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::Proxy; + /// + /// let proxy = Proxy::all("http://proxy.example.com").build(); + /// ``` + pub fn build(self) -> Result { + Ok(Proxy(self.inner?)) + } +} + +#[cfg(test)] +mod ut_settings { + use crate::error::HttpClientError; + use crate::util::redirect as redirect_util; + use crate::util::redirect::TriggerKind; + use crate::{Redirect, Retry}; + use ylong_http::h1::ResponseDecoder; + use ylong_http::request::uri::Uri; + use ylong_http::request::Request; + use ylong_http::response::status::StatusCode; + use ylong_http::response::Response; + + fn create_trigger( + redirect: &Redirect, + previous: &[Uri], + ) -> Result { + let redirect_status = redirect_util::RedirectStatus::new(previous); + redirect.0.as_ref().unwrap().get_trigger(redirect_status) + } + /// UT test cases for `Redirect::is_redirect`. + /// + /// # Brief + /// 1. Creates a `request` by calling `request::new`. + /// 2. Uses `redirect::is_redirect` to check whether is redirected. + /// 3. Checks if the result is true. + #[test] + fn ut_setting_is_redirect() { + let mut request = Request::new("this is a body"); + let code = StatusCode::MOVED_PERMANENTLY; + let res = Redirect::is_redirect(code, &mut request); + assert!(res); + } + /// UT test cases for `Redirect::get_redirect` error branch. + /// + /// # Brief + /// 1. Creates a `redirect` by calling `Redirect::default`. + /// 2. Uses `Redirect::get_redirect` to get redirected trigger kind. + /// 3. Checks if the results are error. + #[test] + fn ut_setting_get_redirect_kind_err() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response::from_raw_parts(result.0, result.1); + let mut request = Request::new("this is a body"); + let mut uri = Uri::default(); + let redirect = Redirect::default(); + let redirect_list: Vec = vec![]; + let res = + Redirect::get_redirect(&mut uri, &redirect, &redirect_list, &response, &mut request); + assert!(res.is_err()); + } + + /// UT test cases for `Redirect::default`. + /// + /// # Brief + /// 1. Creates a `Redirect` by calling `Redirect::default`. + /// 2. Uses `Redirect::create_trigger` to get redirected uri. + /// 3. Checks if the results are correct. + #[test] + fn ut_setting_redirect_default() { + let redirect = Redirect::default(); + let next = Uri::from_bytes(b"http://example.com").unwrap(); + let previous = (0..9) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + let redirect_uri = match create_trigger(&redirect, &previous).unwrap() { + TriggerKind::NextLink => next.to_string(), + TriggerKind::Stop => previous.get(9).unwrap().to_string(), + }; + assert_eq!(redirect_uri, "http://example.com".to_string()); + } + + /// UT test cases for `Redirect::max_limit`. + /// + /// # Brief + /// 1. Creates a `Redirect` by calling `Redirect::max_limit`. + /// 2. Sets redirect times which is over max limitation times. + /// 2. Uses `Redirect::create_trigger` to get redirected uri. + /// 3. Checks if the results are err. + #[test] + fn ut_setting_redirect_over_redirect_max() { + let redirect = Redirect::limited(10); + let previous = (0..10) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + if let Ok(other) = create_trigger(&redirect, &previous) { + panic!("unexpected {:?}", other); + }; + } + + /// UT test cases for `Redirect::no_redirect`. + /// + /// # Brief + /// 1. Creates a `Redirect` by calling `Redirect::no_redirect`. + /// 2. Uses `Redirect::create_trigger` but get origin uri. + /// 3. Checks if the results are correct. + #[test] + fn ut_setting_no_redirect() { + let redirect = Redirect::none(); + let next = Uri::from_bytes(b"http://example.com").unwrap(); + let previous = (0..1) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + let redirect_uri = match create_trigger(&redirect, &previous).unwrap() { + TriggerKind::NextLink => next.to_string(), + TriggerKind::Stop => previous.get(0).unwrap().to_string(), + }; + assert_eq!(redirect_uri, "http://example0.com".to_string()); + } + + /// UT test cases for `Retry::new`. + /// + /// # Brief + /// 1. Creates a `Retry` by calling `Retry::new`. + /// 2. Checks if the results are correct. + #[test] + fn ut_retry_new() { + let retry = Retry::new(1); + assert!(retry.is_ok()); + let retry = Retry::new(3); + assert!(retry.is_err()); + let retry = Retry::new(10); + assert!(retry.is_err()); + } +} diff --git a/ylong_http_client/src/util/config/tls/alpn/mod.rs b/ylong_http_client/src/util/config/tls/alpn/mod.rs new file mode 100644 index 0000000..56be16a --- /dev/null +++ b/ylong_http_client/src/util/config/tls/alpn/mod.rs @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// TLS Application-Layer Protocol Negotiation (ALPN) Protocol is defined in [`RFC7301`]. +/// `AlpnProtocol` contains some protocols used in HTTP, which registered in [`IANA`]. +/// +/// [`RFC7301`]: https://www.rfc-editor.org/rfc/rfc7301.html#section-3 +/// [`IANA`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids +/// +/// # Examples +/// ``` +/// use ylong_http_client::util::AlpnProtocol; +/// +/// let alpn = AlpnProtocol::HTTP11; +/// assert_eq!(alpn.as_use_bytes(), b"\x08http/1.1"); +/// assert_eq!(alpn.id_sequence(), b"http/1.1"); +/// ``` +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct AlpnProtocol(Inner); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Inner { + HTTP09, + HTTP10, + HTTP11, + SPDY1, + SPDY2, + SPDY3, + H2, + H2C, + H3, +} + +impl AlpnProtocol { + /// `HTTP/0.9` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const HTTP09: Self = Self(Inner::HTTP09); + + /// `HTTP/1.0` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const HTTP10: Self = Self(Inner::HTTP10); + + /// `HTTP/1.1` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const HTTP11: Self = Self(Inner::HTTP11); + + /// `SPDY/1` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const SPDY1: Self = Self(Inner::SPDY1); + + /// `SPDY/2` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const SPDY2: Self = Self(Inner::SPDY2); + + /// `SPDY/3` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const SPDY3: Self = Self(Inner::SPDY3); + + /// `HTTP/2 over TLS` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const H2: Self = Self(Inner::H2); + + /// `HTTP/2 over TCP` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const H2C: Self = Self(Inner::H2C); + + /// `HTTP/3` in [`IANA Registration`]. + /// + /// [`IANA Registration`]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids + pub const H3: Self = Self(Inner::H3); + + /// Gets ALPN “wire format”, which consists protocol name prefixed by its byte length. + pub fn as_use_bytes(&self) -> &[u8] { + match *self { + AlpnProtocol::HTTP09 => b"\x08http/0.9", + AlpnProtocol::HTTP10 => b"\x08http/1.0", + AlpnProtocol::HTTP11 => b"\x08http/1.1", + AlpnProtocol::SPDY1 => b"\x06spdy/1", + AlpnProtocol::SPDY2 => b"\x06spdy/2", + AlpnProtocol::SPDY3 => b"\x06spdy/3", + AlpnProtocol::H2 => b"\x02h2", + AlpnProtocol::H2C => b"\x03h2c", + AlpnProtocol::H3 => b"\x02h3", + } + } + + /// Gets ALPN protocol name, which also called identification sequence. + pub fn id_sequence(&self) -> &[u8] { + &self.as_use_bytes()[1..] + } +} + +/// `AlpnProtocolList` consists of a sequence of supported protocol names +/// prefixed by their byte length. +/// +/// # Examples +/// ``` +/// use ylong_http_client::util::{AlpnProtocol, AlpnProtocolList}; +/// +/// let list = AlpnProtocolList::new() +/// .extend(AlpnProtocol::SPDY1) +/// .extend(AlpnProtocol::HTTP11); +/// assert_eq!(list.as_slice(), b"\x06spdy/1\x08http/1.1"); +/// ``` +#[derive(Debug, Default)] +pub struct AlpnProtocolList(Vec); + +impl AlpnProtocolList { + /// Creates a new `AlpnProtocolList`. + pub fn new() -> Self { + AlpnProtocolList(vec![]) + } + + fn extend_from_slice(&mut self, other: &[u8]) { + self.0.extend_from_slice(other); + } + + /// Adds an `AlpnProtocol`. + pub fn extend(mut self, protocol: AlpnProtocol) -> Self { + self.extend_from_slice(protocol.as_use_bytes()); + self + } + + /// Gets `Vec` of ALPN “wire format”, which consists of a sequence of + /// supported protocol names prefixed by their byte length. + pub fn into_bytes(self) -> Vec { + self.0 + } + + /// Gets `&[u8]` of ALPN “wire format”, which consists of a sequence of + /// supported protocol names prefixed by their byte length. + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +#[cfg(test)] +mod ut_alpn { + use crate::util::{AlpnProtocol, AlpnProtocolList}; + + /// UT test cases for `AlpnProtocol::as_use_bytes`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocol`. + /// 2. Gets `&[u8]` by AlpnProtocol::as_use_bytes. + /// 3. Checks whether the result is correct. + #[test] + fn ut_alpn_as_use_bytes() { + assert_eq!(AlpnProtocol::HTTP09.as_use_bytes(), b"\x08http/0.9"); + } + + /// UT test cases for `AlpnProtocol::id_sequence`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocol`. + /// 2. Gets `&[u8]` by AlpnProtocol::id_sequence. + /// 3. Checks whether the result is correct. + #[test] + fn ut_alpn_id_sequence() { + assert_eq!(AlpnProtocol::HTTP09.id_sequence(), b"http/0.9"); + } + + /// UT test cases for `AlpnProtocolList::new`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocolList` by `AlpnProtocolList::new`. + /// 2. Checks whether the result is correct. + #[test] + fn ut_alpn_list_new() { + assert_eq!(AlpnProtocolList::new().as_slice(), b""); + } + + /// UT test cases for `AlpnProtocolList::add`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocolList` by `AlpnProtocolList::new`. + /// 2. Adds several `AlpnProtocol`s. + /// 3. Checks whether the result is correct. + #[test] + fn ut_alpn_list_add() { + assert_eq!( + AlpnProtocolList::new() + .extend(AlpnProtocol::SPDY1) + .extend(AlpnProtocol::HTTP11) + .as_slice(), + b"\x06spdy/1\x08http/1.1" + ); + } + + /// UT test cases for `AlpnProtocolList::as_slice`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocolList` and adds several `AlpnProtocol`s. + /// 2. Gets slice by `AlpnProtocolList::as_slice`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_alpn_list_as_slice() { + assert_eq!( + AlpnProtocolList::new() + .extend(AlpnProtocol::HTTP09) + .as_slice(), + b"\x08http/0.9" + ); + } + + /// UT test cases for `AlpnProtocolList::to_bytes`. + /// + /// # Brief + /// 1. Creates a `AlpnProtocolList` and adds several `AlpnProtocol`s. + /// 2. Gets bytes by `AlpnProtocolList::to_bytes`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_alpn_list_to_bytes() { + assert_eq!( + AlpnProtocolList::new() + .extend(AlpnProtocol::SPDY1) + .extend(AlpnProtocol::HTTP11) + .into_bytes(), + b"\x06spdy/1\x08http/1.1".to_vec() + ); + } +} diff --git a/ylong_http_client/src/util/config/tls/mod.rs b/ylong_http_client/src/util/config/tls/mod.rs new file mode 100644 index 0000000..07801f6 --- /dev/null +++ b/ylong_http_client/src/util/config/tls/mod.rs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod alpn; +pub use alpn::{AlpnProtocol, AlpnProtocolList}; diff --git a/ylong_http_client/src/util/dispatcher.rs b/ylong_http_client/src/util/dispatcher.rs new file mode 100644 index 0000000..59018a2 --- /dev/null +++ b/ylong_http_client/src/util/dispatcher.rs @@ -0,0 +1,1427 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pub(crate) trait Dispatcher { + type Handle; + + fn dispatch(&self) -> Option; + + fn is_shutdown(&self) -> bool; +} + +pub(crate) enum ConnDispatcher { + #[cfg(feature = "http1_1")] + Http1(http1::Http1Dispatcher), + + #[cfg(feature = "http2")] + Http2(http2::Http2Dispatcher), +} + +impl Dispatcher for ConnDispatcher { + type Handle = Conn; + + fn dispatch(&self) -> Option { + match self { + #[cfg(feature = "http1_1")] + Self::Http1(h1) => h1.dispatch().map(Conn::Http1), + + #[cfg(feature = "http2")] + Self::Http2(h2) => h2.dispatch().map(Conn::Http2), + } + } + + fn is_shutdown(&self) -> bool { + match self { + #[cfg(feature = "http1_1")] + Self::Http1(h1) => h1.is_shutdown(), + + #[cfg(feature = "http2")] + Self::Http2(h2) => h2.is_shutdown(), + } + } +} + +pub(crate) enum Conn { + #[cfg(feature = "http1_1")] + Http1(http1::Http1Conn), + + #[cfg(feature = "http2")] + Http2(http2::Http2Conn), +} + +#[cfg(feature = "http1_1")] +pub(crate) mod http1 { + use super::{ConnDispatcher, Dispatcher}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + + impl ConnDispatcher { + pub(crate) fn http1(io: S) -> Self { + Self::Http1(Http1Dispatcher::new(io)) + } + } + + /// HTTP1-based connection manager, which can dispatch connections to other + /// threads according to HTTP1 syntax. + pub(crate) struct Http1Dispatcher { + inner: Arc>, + } + + pub(crate) struct Inner { + pub(crate) io: S, + // `occupied` indicates that the connection is occupied. Only one coroutine + // can get the handle at the same time. Once the handle is fetched, the flag + // position is true. + pub(crate) occupied: AtomicBool, + // `shutdown` indicates that the connection need to be shut down. + pub(crate) shutdown: AtomicBool, + } + + impl Http1Dispatcher { + pub(crate) fn new(io: S) -> Self { + Self { + inner: Arc::new(Inner { + io, + occupied: AtomicBool::new(false), + shutdown: AtomicBool::new(false), + }), + } + } + } + + impl Dispatcher for Http1Dispatcher { + type Handle = Http1Conn; + + fn dispatch(&self) -> Option { + self.inner + .occupied + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .ok() + .map(|_| Http1Conn { + inner: self.inner.clone(), + }) + } + + fn is_shutdown(&self) -> bool { + self.inner.shutdown.load(Ordering::Relaxed) + } + } + + /// Handle returned to other threads for I/O operations. + pub(crate) struct Http1Conn { + pub(crate) inner: Arc>, + } + + impl Http1Conn { + // TODO: Use `UnsafeCell` instead when `Arc::get_mut_unchecked` become stable. + pub(crate) fn raw_mut(&mut self) -> &mut S { + // SAFETY: In the case of `HTTP1`, only one coroutine gets the handle + // at the same time. + &mut unsafe { &mut *(Arc::as_ptr(&self.inner) as *mut Inner) }.io + } + + pub(crate) fn shutdown(&self) { + self.inner.shutdown.store(true, Ordering::Release); + } + } + + impl Drop for Http1Conn { + fn drop(&mut self) { + self.inner.occupied.store(false, Ordering::Release) + } + } +} + +#[cfg(feature = "http2")] +pub(crate) mod http2 { + use super::{ConnDispatcher, Dispatcher}; + use crate::dispatcher::http2::StreamState::Closed; + use crate::error::HttpClientError; + use crate::util::H2Config; + use crate::ErrorKind; + use std::collections::{HashMap, VecDeque}; + use std::future::Future; + use std::mem::take; + use std::pin::Pin; + use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + use std::sync::{Arc, Mutex}; + use std::task::{Context, Poll, Waker}; + use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + use tokio::sync::mpsc::error::TryRecvError; + use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; + use tokio::sync::MutexGuard; + use ylong_http::error::HttpError; + use ylong_http::h2; + use ylong_http::h2::Payload::Settings; + use ylong_http::h2::{ + ErrorCode, Frame, FrameDecoder, FrameEncoder, FrameFlags, FrameKind, FramesIntoIter, + Goaway, H2Error, Payload, RstStream, Setting, SettingsBuilder, + }; + + impl ConnDispatcher { + pub(crate) fn http2(config: H2Config, io: S) -> Self { + Self::Http2(Http2Dispatcher::new(config, io)) + } + } + + // The data type of the first Frame sent to the `StreamController`. + type Send2Ctrl = (Option<(u32, UnboundedSender)>, Frame); + + const DEFAULT_MAX_STREAM_ID: u32 = u32::MAX >> 1; + const DEFAULT_MAX_FRAME_SIZE: usize = 2 << 13; + const DEFAULT_MAX_HEADER_LIST_SIZE: usize = 16 << 20; + + // HTTP2-based connection manager, which can dispatch connections to other + // threads according to HTTP2 syntax. + pub(crate) struct Http2Dispatcher { + pub(crate) controller: Arc>, + pub(crate) next_stream_id: Arc, + pub(crate) sender: UnboundedSender, + } + + pub(crate) struct Http2Conn { + // Handle id + pub(crate) id: u32, + // Sends frame to StreamController + pub(crate) sender: UnboundedSender, + pub(crate) stream_info: StreamInfo, + } + + pub(crate) struct StreamInfo { + // Stream id + pub(crate) id: u32, + pub(crate) next_stream_id: Arc, + // Receive the Response frame transmitted from the StreamController + pub(crate) receiver: FrameReceiver, + // Used to handle TCP Stream + pub(crate) controller: Arc>, + } + + pub(crate) struct StreamController { + // I/O unavailability flag, which prevents the upper layer from using this I/O to create new streams. + pub(crate) io_shutdown: AtomicBool, + // Indicates that the dispatcher is occupied. At this time, a user coroutine is already acting as the dispatcher. + pub(crate) occupied: AtomicU32, + pub(crate) dispatcher_invalid: AtomicBool, + pub(crate) manager: tokio::sync::Mutex>, + pub(crate) stream_waker: Mutex, + } + + pub(crate) struct StreamWaker { + waker: HashMap, + } + + pub(crate) struct IoManager { + inner: Inner, + senders: HashMap>, + frame_receiver: UnboundedReceiver, + streams: Streams, + frame_iter: FrameIter, + connection_frame: ConnectionFrames, + } + + #[derive(Default)] + pub(crate) struct FrameIter { + iter: Option, + } + + pub(crate) struct Streams { + stream_to_send: VecDeque, + buffer: HashMap, + } + + pub(crate) struct StreamBuffer { + state: StreamState, + frames: VecDeque, + } + + pub(crate) struct Inner { + pub(crate) io: S, + pub(crate) encoder: FrameEncoder, + pub(crate) decoder: FrameDecoder, + } + + pub(crate) enum ReadState { + EmptyIo, + CurrentStream, + } + + enum DispatchState { + Partial, + Finish, + } + + #[derive(Clone)] + pub(crate) enum ResetReason { + Local, + Remote, + Goaway(u32), + } + + #[derive(Clone)] + pub(crate) enum SettingsSync { + Send(h2::Settings), + Acknowledging(h2::Settings), + Synced, + } + + pub(crate) struct StreamId { + // TODO Determine the maximum value of id. + next_id: AtomicU32, + } + + // TODO Add "open", "half-closed", "reserved" state + #[derive(Clone)] + pub(crate) enum StreamState { + Idle, + Closed(ResetReason), + } + + #[derive(Default)] + pub(crate) struct FrameReceiver { + receiver: Option>, + } + + impl StreamController { + pub(crate) fn new( + inner: Inner, + frame_receiver: UnboundedReceiver, + connection_frame: ConnectionFrames, + ) -> Self { + let manager = IoManager::new(inner, frame_receiver, connection_frame); + Self { + io_shutdown: AtomicBool::new(false), + // 0 means io is not occupied + occupied: AtomicU32::new(0), + dispatcher_invalid: AtomicBool::new(false), + manager: tokio::sync::Mutex::new(manager), + stream_waker: Mutex::new(StreamWaker::new()), + } + } + + pub(crate) fn shutdown(&self) { + self.io_shutdown.store(true, Ordering::Release); + } + + pub(crate) fn invalid(&self) { + self.dispatcher_invalid.store(true, Ordering::Release); + } + } + + impl Streams { + pub(crate) fn new() -> Self { + Self { + stream_to_send: VecDeque::new(), + buffer: HashMap::new(), + } + } + + pub(crate) fn size(&self) -> usize { + self.stream_to_send.len() + } + + pub(crate) fn insert(&mut self, frame: Frame) { + let id = frame.stream_id() as u32; + self.stream_to_send.push_back(id); + match self.buffer.get_mut(&id) { + Some(sender) => { + sender.push_back(frame); + } + None => { + let mut sender = StreamBuffer::new(); + sender.push_back(frame); + self.buffer.insert(id, sender); + } + } + } + + pub(crate) fn get_goaway_streams( + &mut self, + last_stream_id: u32, + ) -> Result, H2Error> { + let mut ids = vec![]; + for (id, sender) in self.buffer.iter_mut() { + if *id >= last_stream_id { + ids.push(*id); + sender.go_away(*id)?; + } + } + Ok(ids) + } + + pub(crate) fn recv_local_reset(&mut self, id: u32) -> Result<(), H2Error> { + match self.buffer.get_mut(&id) { + None => Err(H2Error::ConnectionError(ErrorCode::ProtocolError)), + Some(sender) => { + match sender.state { + Closed(ResetReason::Remote | ResetReason::Local) => {} + _ => { + sender.state = Closed(ResetReason::Local); + } + } + Ok(()) + } + } + } + + pub(crate) fn recv_remote_reset(&mut self, id: u32) -> Result<(), H2Error> { + match self.buffer.get_mut(&id) { + None => Err(H2Error::ConnectionError(ErrorCode::ProtocolError)), + Some(sender) => { + match sender.state { + Closed(ResetReason::Remote) => {} + _ => { + sender.state = Closed(ResetReason::Remote); + } + } + Ok(()) + } + } + } + + // TODO At present, only the state is changed to closed, and other states are not involved, and it needs to be added later + pub(crate) fn recv_headers(&mut self, id: u32) -> Result { + match self.buffer.get_mut(&id) { + None => Err(H2Error::ConnectionError(ErrorCode::ProtocolError)), + Some(sender) => match sender.state { + Closed(ResetReason::Goaway(last_id)) => { + if id > last_id { + return Err(H2Error::ConnectionError(ErrorCode::StreamClosed)); + } + Ok(sender.state.clone()) + } + Closed(ResetReason::Remote) => { + Err(H2Error::ConnectionError(ErrorCode::StreamClosed)) + } + _ => Ok(sender.state.clone()), + }, + } + } + + pub(crate) fn recv_data(&mut self, id: u32) -> Result { + self.recv_headers(id) + } + + pub(crate) fn pop_front(&mut self) -> Result, H2Error> { + match self.stream_to_send.pop_front() { + None => Ok(None), + Some(id) => { + // TODO Subsequent consideration is to delete the corresponding elements in the map after the status becomes Closed + match self.buffer.get_mut(&id) { + None => Err(H2Error::ConnectionError(ErrorCode::IntervalError)), + Some(sender) => { + // TODO For the time being, match state is used here, and the complete logic should be judged based on the frame type and state + match sender.state { + Closed(ResetReason::Remote | ResetReason::Local) => Ok(None), + _ => Ok(sender.pop_front()), + } + } + } + } + } + } + } + + impl StreamBuffer { + pub(crate) fn push_back(&mut self, frame: Frame) { + self.frames.push_back(frame); + } + + pub(crate) fn pop_front(&mut self) -> Option { + self.frames.pop_front() + } + + pub(crate) fn new() -> Self { + Self { + state: StreamState::Idle, + frames: VecDeque::new(), + } + } + + pub(crate) fn go_away(&mut self, last_stream_id: u32) -> Result<(), H2Error> { + match self.state { + Closed(ResetReason::Local | ResetReason::Remote) => {} + Closed(ResetReason::Goaway(id)) => { + if last_stream_id > id { + return Err(H2Error::ConnectionError(ErrorCode::ProtocolError)); + } + self.state = Closed(ResetReason::Goaway(last_stream_id)); + } + _ => { + self.state = Closed(ResetReason::Goaway(last_stream_id)); + } + } + Ok(()) + } + } + + impl SettingsSync { + pub(crate) fn ack_settings() -> Frame { + Frame::new(0, FrameFlags::new(0x1), Settings(h2::Settings::new(vec![]))) + } + } + + pub(crate) struct ConnectionFrames { + preface: bool, + settings: SettingsSync, + } + + impl ConnectionFrames { + pub(crate) fn new(settings: h2::Settings) -> Self { + Self { + preface: true, + settings: SettingsSync::Send(settings), + } + } + } + + impl StreamWaker { + pub(crate) fn new() -> Self { + Self { + waker: HashMap::new(), + } + } + } + + impl IoManager { + pub(crate) fn new( + inner: Inner, + frame_receiver: UnboundedReceiver, + connection_frame: ConnectionFrames, + ) -> Self { + Self { + inner, + senders: HashMap::new(), + frame_receiver, + streams: Streams::new(), + frame_iter: FrameIter::default(), + connection_frame, + } + } + + fn close_frame_receiver(&mut self) { + self.frame_receiver.close() + } + } + + impl FrameIter { + pub(crate) fn is_empty(&self) -> bool { + self.iter.is_none() + } + } + + impl StreamId { + fn stream_id_generate(&self) -> u32 { + self.next_id.fetch_add(2, Ordering::Relaxed) + } + + fn get_next_id(&self) -> u32 { + self.next_id.load(Ordering::Relaxed) + } + } + + impl Http2Dispatcher { + pub(crate) fn new(config: H2Config, io: S) -> Self { + // send_preface(&mut io).await?; + + let connection_frames = build_connection_frames(config); + let inner = Inner { + io, + encoder: FrameEncoder::new(DEFAULT_MAX_FRAME_SIZE, DEFAULT_MAX_HEADER_LIST_SIZE), + decoder: FrameDecoder::new(), + }; + + // For each stream to send the frame to the controller + let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); + + let stream_controller = Arc::new(StreamController::new(inner, rx, connection_frames)); + + // The id of the client stream, starting from 1 + let next_stream_id = StreamId { + next_id: AtomicU32::new(1), + }; + Self { + controller: stream_controller, + sender: tx, + next_stream_id: Arc::new(next_stream_id), + } + } + } + + impl Dispatcher for Http2Dispatcher { + type Handle = Http2Conn; + + // Call this method to get a stream + fn dispatch(&self) -> Option { + let id = self.next_stream_id.stream_id_generate(); + // TODO Consider how to create a new connection and transfer state + if id > DEFAULT_MAX_STREAM_ID { + return None; + } + let controller = self.controller.clone(); + let sender = self.sender.clone(); + let handle = Http2Conn::new(id, self.next_stream_id.clone(), sender, controller); + Some(handle) + } + + // TODO When the stream id reaches the maximum value, shutdown the current connection + fn is_shutdown(&self) -> bool { + self.controller.io_shutdown.load(Ordering::Relaxed) + } + } + + impl Http2Conn { + pub(crate) fn new( + id: u32, + next_stream_id: Arc, + sender: UnboundedSender, + controller: Arc>, + ) -> Self { + let stream_info = StreamInfo { + id, + next_stream_id, + receiver: FrameReceiver::default(), + controller, + }; + Self { + id, + sender, + stream_info, + } + } + + pub(crate) fn send_frame_to_controller( + &mut self, + frame: Frame, + ) -> Result<(), HttpClientError> { + if self.stream_info.receiver.is_none() { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); + self.stream_info.receiver.set_receiver(rx); + self.sender.send((Some((self.id, tx)), frame)).map_err(|_| { + HttpClientError::new_with_cause( + ErrorKind::Request, + Some(String::from("resend")), + ) + }) + } else { + self.sender.send((None, frame)).map_err(|_| { + HttpClientError::new_with_cause( + ErrorKind::Request, + Some(String::from("resend")), + ) + }) + } + } + } + + impl Future for StreamInfo { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let stream_info = self.get_mut(); + + // First, check whether the frame of the current stream is in the Channel. + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + if let Some(frame) = stream_info.receiver.recv_frame(stream_info.id)? { + { + let mut stream_waker = stream_info + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + // + wakeup_next_stream(&mut stream_waker.waker); + } + return Poll::Ready(Ok(frame)); + } + + // If the dispatcher sends a goaway frame, all streams on the current connection are unavailable. + if stream_info + .controller + .dispatcher_invalid + .load(Ordering::Relaxed) + { + return Poll::Ready(Err(H2Error::ConnectionError(ErrorCode::ConnectError).into())); + } + + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + if is_io_available(&stream_info.controller.occupied, stream_info.id)? { + { + // Second, try to get io and read the frame of the current stream from io. + if let Ok(mut io_manager) = stream_info.controller.manager.try_lock() { + if stream_info + .poll_match_result(cx, &mut io_manager)? + .is_pending() + { + return Poll::Pending; + } + } + } + { + let mut stream_waker = stream_info + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + wakeup_next_stream(&mut stream_waker.waker); + } + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + let frame_opt = get_frame(stream_info.receiver.recv_frame(stream_info.id)?); + return Poll::Ready(frame_opt); + } + + { + let mut io_manager = { + // Third, wait to acquire the lock of waker, which is used to insert the current waker, and wait to be awakened by the io stream. + let mut stream_waker = stream_info + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + + // Fourth, after obtaining the waker lock, + // you need to check the Receiver again to prevent the Receiver from receiving a frame while waiting for the waker. + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + if let Some(frame) = stream_info.receiver.recv_frame(stream_info.id)? { + wakeup_next_stream(&mut stream_waker.waker); + return Poll::Ready(Ok(frame)); + } + + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + if is_io_available(&stream_info.controller.occupied, stream_info.id)? { + // Fifth, get io again to prevent no other streams from controlling io while waiting for the waker, + // leaving only the current stream. + match stream_info.controller.manager.try_lock() { + Ok(guard) => guard, + _ => { + stream_waker + .waker + .insert(stream_info.id, cx.waker().clone()); + return Poll::Pending; + } + } + } else { + stream_waker + .waker + .insert(stream_info.id, cx.waker().clone()); + return Poll::Pending; + } + }; + if stream_info + .poll_match_result(cx, &mut io_manager)? + .is_pending() + { + return Poll::Pending; + } + } + { + { + let mut stream_waker = stream_info + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + wakeup_next_stream(&mut stream_waker.waker); + } + // The error cannot occur. Therefore, the error is thrown directly without connection-level processing. + let frame_opt = get_frame(stream_info.receiver.recv_frame(stream_info.id)?); + Poll::Ready(frame_opt) + } + } + } + + impl StreamInfo { + fn poll_match_result( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + loop { + match self.poll_io(cx, io_manager) { + Poll::Ready(Ok(_)) => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Err(h2_error)) => { + match h2_error { + H2Error::StreamError(id, code) => { + let rest_payload = RstStream::new(code.clone().into_code()); + let frame = Frame::new( + id as usize, + FrameFlags::empty(), + Payload::RstStream(rest_payload), + ); + io_manager.streams.recv_local_reset(id)?; + if self + .poll_send_reset(cx, frame.clone(), io_manager)? + .is_pending() + { + compare_exchange_occupation( + &self.controller.occupied, + 0, + self.id, + )?; + return Poll::Pending; + } + if self.id == id { + return Poll::Ready(Err(H2Error::StreamError(id, code).into())); + } else { + self.controller_send_frame_to_stream(id, frame, io_manager); + { + let mut stream_waker = self + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + // TODO Is there a situation where the result has been returned, but the waker has not been inserted into the map? how to deal with. + if let Some(waker) = stream_waker.waker.remove(&id) { + waker.wake(); + } + } + } + } + H2Error::ConnectionError(code) => { + io_manager.close_frame_receiver(); + self.controller.shutdown(); + // Since ConnectError may be caused by an io error, so when the client + // actively sends a goaway frame, all streams are shut down and no streams are allowed to complete. + // TODO Then consider separating io errors from frame errors to allow streams whose stream id is less than last_stream_id to continue + self.controller.invalid(); + // last_stream_id is set to 0 to ensure that all streams are shutdown. + let goaway_payload = + Goaway::new(code.clone().into_code(), 0, vec![]); + let frame = Frame::new( + 0, + FrameFlags::empty(), + Payload::Goaway(goaway_payload), + ); + // io_manager.connection_frame.going_away(frame); + if self + .poll_send_go_away(cx, frame.clone(), io_manager)? + .is_pending() + { + compare_exchange_occupation( + &self.controller.occupied, + 0, + self.id, + )?; + return Poll::Pending; + } + + self.goaway_unsent_stream(io_manager, 0, frame)?; + self.goaway_and_shutdown(); + return Poll::Ready(Err(H2Error::ConnectionError(code).into())); + } + } + } + Poll::Pending => { + compare_exchange_occupation(&self.controller.occupied, 0, self.id)?; + return Poll::Pending; + } + } + } + } + + fn poll_io( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + if self.poll_send_preface(cx, io_manager)?.is_pending() { + return Poll::Pending; + } + if self.poll_send_settings(cx, io_manager)?.is_pending() { + return Poll::Pending; + } + match self.poll_dispatch_frame(cx, io_manager)? { + Poll::Ready(state) => { + if let DispatchState::Partial = state { + return Poll::Ready(Ok(())); + } + } + Poll::Pending => { + return Poll::Pending; + } + } + // Write and read frames to io in a loop until the frame of the current stream is read and exit the loop. + loop { + if self.poll_write_frame(cx, io_manager)?.is_pending() { + return Poll::Pending; + } + match self.poll_read_frame(cx, io_manager)? { + Poll::Ready(ReadState::EmptyIo) => {} + Poll::Ready(ReadState::CurrentStream) => { + return Poll::Ready(Ok(())); + } + Poll::Pending => { + return Poll::Pending; + } + } + } + } + + fn poll_dispatch_frame( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + if io_manager.frame_iter.is_empty() { + return Poll::Ready(Ok(DispatchState::Finish)); + } + let iter_option = take(&mut io_manager.frame_iter.iter); + match iter_option { + None => Poll::Ready(Err(H2Error::ConnectionError(ErrorCode::IntervalError))), + Some(iter) => self.dispatch_read_frames(cx, io_manager, iter), + } + } + + fn poll_send_preface( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + const PREFACE_MSG: &str = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + if io_manager.connection_frame.preface { + let mut buf = [0u8; PREFACE_MSG.len()]; + buf.copy_from_slice(PREFACE_MSG.as_bytes()); + + let mut start_index = 0; + loop { + if start_index == PREFACE_MSG.len() { + io_manager.connection_frame.preface = false; + break; + } + match Pin::new(&mut io_manager.inner.io) + .poll_write(cx, &buf[start_index..]) + .map_err(|_| H2Error::ConnectionError(ErrorCode::IntervalError))? + { + Poll::Ready(written) => { + start_index += written; + } + Poll::Pending => { + return Poll::Pending; + } + } + } + return poll_flush_io(cx, &mut io_manager.inner); + } + Poll::Ready(Ok(())) + } + + fn poll_send_go_away( + &self, + cx: &mut Context<'_>, + goaway: Frame, + io_manager: &mut MutexGuard>, + ) -> Poll> { + let mut buf = [0u8; 1024]; + if write_frame_to_io(cx, &mut buf, goaway, &mut io_manager.inner)?.is_pending() { + Poll::Pending + } else { + poll_flush_io(cx, &mut io_manager.inner) + } + } + + fn poll_send_reset( + &self, + cx: &mut Context<'_>, + reset: Frame, + io_manager: &mut MutexGuard>, + ) -> Poll> { + let mut buf = [0u8; 1024]; + if write_frame_to_io(cx, &mut buf, reset, &mut io_manager.inner)?.is_pending() { + Poll::Pending + } else { + poll_flush_io(cx, &mut io_manager.inner) + } + } + + fn poll_send_settings( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + if let SettingsSync::Send(settings) = io_manager.connection_frame.settings.clone() { + let mut buf = [0u8; 1024]; + let frame = Frame::new(0, FrameFlags::empty(), Settings(settings.clone())); + if write_frame_to_io(cx, &mut buf, frame, &mut io_manager.inner)?.is_pending() { + Poll::Pending + } else { + io_manager.connection_frame.settings = SettingsSync::Acknowledging(settings); + poll_flush_io(cx, &mut io_manager.inner) + } + } else { + Poll::Ready(Ok(())) + } + } + + fn poll_write_frame( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + const FRAME_WRITE_NUM: usize = 10; + + // Send 10 frames each time, if there is not enough in the queue, read enough from mpsc::Receiver + while io_manager.streams.size() < FRAME_WRITE_NUM { + match io_manager.frame_receiver.try_recv() { + // The Frame sent by the Handle for the first time will carry a Sender at the same time, + // which is used to send the Response Frame back to the Handle + Ok((Some((id, sender)), frame)) => { + if io_manager.senders.insert(id, sender).is_some() { + return Poll::Ready(Err(H2Error::ConnectionError( + ErrorCode::IntervalError, + ))); + } + io_manager.streams.insert(frame); + } + Ok((None, frame)) => { + io_manager.streams.insert(frame); + } + Err(TryRecvError::Empty) => { + break; + } + Err(TryRecvError::Disconnected) => { + return Poll::Ready(Err(H2Error::ConnectionError(ErrorCode::ConnectError))) + } + } + } + let mut buf = [0u8; 1024]; + for _i in 0..FRAME_WRITE_NUM { + match io_manager.streams.pop_front()? { + Some(frame) => { + if write_frame_to_io(cx, &mut buf, frame, &mut io_manager.inner)? + .is_pending() + { + return Poll::Pending; + } + } + None => { + break; + } + } + } + poll_flush_io(cx, &mut io_manager.inner) + } + + fn poll_read_frame( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + ) -> Poll> { + // Read all the frames in io until the frame of the current stream is read and stop. + let mut buf = [0u8; 1024]; + loop { + let mut read_buf = ReadBuf::new(&mut buf); + match Pin::new(&mut io_manager.inner.io).poll_read(cx, &mut read_buf) { + Poll::Ready(Err(_)) => { + return Poll::Ready(Err(H2Error::ConnectionError(ErrorCode::ConnectError))) + } + Poll::Pending => { + return Poll::Pending; + } + _ => {} + } + let read = read_buf.filled().len(); + if read == 0 { + break; + } + let frames = io_manager.inner.decoder.decode(&buf[..read])?; + let frame_iterator = frames.into_iter(); + + match self.dispatch_read_frames(cx, io_manager, frame_iterator)? { + Poll::Ready(state) => { + if let DispatchState::Partial = state { + return Poll::Ready(Ok(ReadState::CurrentStream)); + } + } + Poll::Pending => { + return Poll::Pending; + } + } + } + Poll::Ready(Ok(ReadState::EmptyIo)) + } + + fn dispatch_read_frames( + &self, + cx: &mut Context<'_>, + io_manager: &mut MutexGuard>, + mut frame_iterator: FramesIntoIter, + ) -> Poll> { + let mut meet_this = false; + loop { + match frame_iterator.next() { + None => break, + Some(frame_kind) => { + if let FrameKind::Complete(frame) = frame_kind { + match frame.payload() { + Settings(settings) => { + if self + .recv_settings_frame( + cx, + io_manager, + frame.flags().is_ack(), + settings, + )? + .is_pending() + { + return Poll::Pending; + } + continue; + } + Payload::Ping(ping) => { + if self + .recv_ping_frame( + cx, + io_manager, + frame.flags().is_ack(), + ping, + )? + .is_pending() + { + return Poll::Pending; + } + continue; + } + Payload::PushPromise(_) => { + // TODO The current settings_enable_push is fixed to false + return Poll::Ready(Err(H2Error::ConnectionError( + ErrorCode::ProtocolError, + ))); + } + Payload::Goaway(goaway) => { + // shutdown io,prevent the creation of new stream + self.controller.shutdown(); + io_manager.close_frame_receiver(); + let last_stream_id = goaway.get_last_stream_id(); + if self.next_stream_id.get_next_id() as usize <= last_stream_id + { + return Poll::Ready(Err(H2Error::ConnectionError( + ErrorCode::ProtocolError, + ))); + } + self.goaway_unsent_stream( + io_manager, + last_stream_id as u32, + frame.clone(), + )?; + continue; + } + Payload::RstStream(_reset) => { + io_manager + .streams + .recv_remote_reset(frame.stream_id() as u32)?; + } + Payload::Headers(_headers) => { + if let Closed(ResetReason::Local) = + io_manager.streams.recv_headers(frame.stream_id() as u32)? + { + continue; + } + } + Payload::Data(_data) => { + if let Closed(ResetReason::Local) = + io_manager.streams.recv_data(frame.stream_id() as u32)? + { + continue; + } + } + // TODO Windows that processes streams and connections separately. + Payload::WindowUpdate(_windows) => { + continue; + } + Payload::Priority(_priority) => continue, + } + + let stream_id = frame.stream_id() as u32; + if stream_id == self.id { + meet_this = true; + self.controller_send_frame_to_stream(stream_id, frame, io_manager); + break; + } else { + self.controller_send_frame_to_stream(stream_id, frame, io_manager); + // TODO After adding frames such as Reset/Priority, there may be problems with the following logic, because the lack of waker cannot wake up + let mut stream_waker = self + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + // TODO Is there a situation where the result has been returned, but the waker has not been inserted into the map? how to deal with. + if let Some(waker) = stream_waker.waker.remove(&stream_id) { + waker.wake(); + } + } + } + } + } + } + + if meet_this { + io_manager.frame_iter.iter = Some(frame_iterator); + Poll::Ready(Ok(DispatchState::Partial)) + } else { + Poll::Ready(Ok(DispatchState::Finish)) + } + } + + fn goaway_unsent_stream( + &self, + io_manager: &mut MutexGuard>, + last_stream_id: u32, + goaway: Frame, + ) -> Result<(), H2Error> { + let goaway_streams = io_manager.streams.get_goaway_streams(last_stream_id)?; + { + let mut stream_waker = self + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + for goaway_stream in goaway_streams { + self.controller_send_frame_to_stream(goaway_stream, goaway.clone(), io_manager); + if let Some(waker) = stream_waker.waker.remove(&goaway_stream) { + waker.wake(); + } + } + } + Ok(()) + } + + fn goaway_and_shutdown(&self) { + { + let mut waker_guard = self + .controller + .stream_waker + .lock() + .expect("Blocking get waker lock failed! "); + let waker_map = take(&mut waker_guard.waker); + for (_id, waker) in waker_map.into_iter() { + waker.wake() + } + } + } + + fn recv_settings_frame( + &self, + cx: &mut Context<'_>, + guard: &mut MutexGuard>, + is_ack: bool, + settings: &h2::Settings, + ) -> Poll> { + if is_ack { + match guard.connection_frame.settings.clone() { + SettingsSync::Acknowledging(local_settings) => { + for setting in local_settings.get_settings() { + if let Setting::MaxHeaderListSize(size) = setting { + guard.inner.decoder.set_max_header_list_size(*size as usize); + } + if let Setting::MaxFrameSize(size) = setting { + guard.inner.decoder.set_max_frame_size(*size)?; + } + } + guard.connection_frame.settings = SettingsSync::Synced; + Poll::Ready(Ok(())) + } + _ => Poll::Ready(Err(H2Error::ConnectionError(ErrorCode::ProtocolError))), + } + } else { + for setting in settings.get_settings() { + if let Setting::HeaderTableSize(size) = setting { + guard.inner.encoder.update_header_table_size(*size as usize); + } + if let Setting::MaxFrameSize(size) = setting { + guard.inner.encoder.update_max_frame_size(*size as usize); + } + } + // reply ack Settings + let mut buf = [0u8; 1024]; + if write_frame_to_io(cx, &mut buf, SettingsSync::ack_settings(), &mut guard.inner)? + .is_pending() + { + Poll::Pending + } else { + poll_flush_io(cx, &mut guard.inner) + } + } + } + + fn recv_ping_frame( + &self, + cx: &mut Context<'_>, + guard: &mut MutexGuard>, + is_ack: bool, + ping: &h2::Ping, + ) -> Poll> { + if is_ack { + // TODO The sending logic of ping has not been implemented yet, so there is no processing for ack + Poll::Ready(Ok(())) + } else { + // reply ack Settings + let ack = Frame::new(0, FrameFlags::new(0x1), Payload::Ping(ping.clone())); + let mut buf = [0u8; 1024]; + if write_frame_to_io(cx, &mut buf, ack, &mut guard.inner)?.is_pending() { + Poll::Pending + } else { + poll_flush_io(cx, &mut guard.inner) + } + } + } + + fn controller_send_frame_to_stream( + &self, + stream_id: u32, + frame: Frame, + guard: &mut MutexGuard>, + ) { + // TODO Need to consider when to delete useless Sender after support reset stream + if let Some(sender) = guard.senders.get(&stream_id) { + // If the client coroutine has exited, this frame is skipped. + let _ = sender.send(frame); + } + } + } + + impl FrameReceiver { + fn set_receiver(&mut self, receiver: UnboundedReceiver) { + self.receiver = Some(receiver); + } + + fn recv_frame(&mut self, id: u32) -> Result, HttpError> { + if let Some(ref mut receiver) = self.receiver { + match receiver.try_recv() { + Ok(frame) => Ok(Some(frame)), + Err(TryRecvError::Disconnected) => { + Err(H2Error::StreamError(id, ErrorCode::StreamClosed).into()) + } + Err(TryRecvError::Empty) => Ok(None), + } + } else { + Err(H2Error::StreamError(id, ErrorCode::IntervalError).into()) + } + } + + fn is_none(&self) -> bool { + self.receiver.is_none() + } + } + + // TODO Temporarily only deal with the Settings frame + pub(crate) fn build_connection_frames(config: H2Config) -> ConnectionFrames { + const DEFAULT_ENABLE_PUSH: bool = false; + let settings = SettingsBuilder::new() + .max_header_list_size(config.max_header_list_size()) + .max_frame_size(config.max_frame_size()) + .header_table_size(config.header_table_size()) + .enable_push(DEFAULT_ENABLE_PUSH) + .build(); + + ConnectionFrames::new(settings) + } + + // io write interface + fn write_frame_to_io( + cx: &mut Context<'_>, + buf: &mut [u8], + frame: Frame, + inner: &mut Inner, + ) -> Poll> + where + S: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static, + { + let mut remain_size = 0; + inner.encoder.set_frame(frame); + loop { + let size = inner + .encoder + .encode(&mut buf[remain_size..]) + .map_err(|_| H2Error::ConnectionError(ErrorCode::IntervalError))?; + + let total = size + remain_size; + + // All the bytes of the frame are written + if total == 0 { + break; + } + match Pin::new(&mut inner.io) + .poll_write(cx, &buf[..total]) + .map_err(|_| H2Error::ConnectionError(ErrorCode::IntervalError))? + { + Poll::Ready(written) => { + remain_size = total - written; + // written is not necessarily equal to total + if remain_size > 0 { + for i in 0..remain_size { + buf[i] = buf[written + i]; + } + } + } + Poll::Pending => { + return Poll::Pending; + } + } + } + Poll::Ready(Ok(())) + } + + fn poll_flush_io(cx: &mut Context<'_>, inner: &mut Inner) -> Poll> + where + S: AsyncRead + AsyncWrite + Unpin + Sync + Send + 'static, + { + Pin::new(&mut inner.io) + .poll_flush(cx) + .map_err(|_| H2Error::ConnectionError(ErrorCode::ConnectError)) + } + + fn get_frame(frame: Option) -> Result { + frame.ok_or(H2Error::ConnectionError(ErrorCode::IntervalError).into()) + } + + fn wakeup_next_stream(waker_map: &mut HashMap) { + { + if !waker_map.is_empty() { + let mut id = 0; + if let Some((index, _)) = waker_map.iter().next() { + id = *index; + } + if let Some(waker) = waker_map.remove(&id) { + waker.wake(); + } + } + } + } + + fn is_io_available(occupied: &AtomicU32, id: u32) -> Result { + let is_occupied = occupied.load(Ordering::Relaxed); + if is_occupied == 0 { + return Ok(true); + } + if is_occupied == id { + compare_exchange_occupation(occupied, id, 0)?; + return Ok(true); + } + Ok(false) + } + + fn compare_exchange_occupation( + occupied: &AtomicU32, + current: u32, + new: u32, + ) -> Result<(), HttpError> { + occupied + .compare_exchange(current, new, Ordering::Acquire, Ordering::Relaxed) + .map_err(|_| H2Error::ConnectionError(ErrorCode::IntervalError))?; + Ok(()) + } +} diff --git a/ylong_http_client/src/util/mod.rs b/ylong_http_client/src/util/mod.rs new file mode 100644 index 0000000..0f2ced2 --- /dev/null +++ b/ylong_http_client/src/util/mod.rs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Http client Util module. +//! +//! A tool module that supports various functions of the http client. +//! +//! -[`ClientConfig`] is used to configure a client with options and flags. +//! -[`HttpConfig`] is used to configure `HTTP` related logic. +//! -[`HttpVersion`] is used to provide Http Version. + +#![allow(dead_code)] +#![allow(unused_imports)] + +mod config; +pub(crate) use config::{ClientConfig, ConnectorConfig, HttpConfig, HttpVersion}; +pub use config::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; + +#[cfg(feature = "__tls")] +pub use config::{AlpnProtocol, AlpnProtocolList}; + +#[cfg(feature = "__c_openssl")] +pub(crate) mod c_openssl; +#[cfg(feature = "__c_openssl")] +pub use c_openssl::{Cert, Certificate, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; + +#[cfg(feature = "http2")] +pub use config::H2Config; + +#[cfg(any(feature = "http1_1", feature = "http2"))] +pub(crate) mod dispatcher; + +pub(crate) mod normalizer; +pub(crate) mod pool; + +pub(crate) mod base64; +pub(crate) mod proxy; +pub(crate) mod redirect; diff --git a/ylong_http_client/src/util/normalizer.rs b/ylong_http_client/src/util/normalizer.rs new file mode 100644 index 0000000..345b004 --- /dev/null +++ b/ylong_http_client/src/util/normalizer.rs @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove this file later. + +use crate::{ErrorKind, HttpClientError, Uri}; +use ylong_http::request::uri::Scheme; +use ylong_http::request::Request; + +pub(crate) struct RequestFormatter<'a, T> { + part: &'a mut Request, +} + +impl<'a, T> RequestFormatter<'a, T> { + pub(crate) fn new(part: &'a mut Request) -> Self { + Self { part } + } + + pub(crate) fn normalize(&mut self) -> Result<(), HttpClientError> { + let uri_formatter = UriFormatter::new(); + uri_formatter.format(self.part.uri_mut())?; + + let host_value = self.part.uri().authority().unwrap().to_str(); + + if self.part.headers_mut().get("Accept").is_none() { + let _ = self.part.headers_mut().insert("Accept", "*/*"); + } + + if self.part.headers_mut().get("Host").is_none() { + let _ = self + .part + .headers_mut() + .insert("Host", host_value.as_bytes()); + } + + Ok(()) + } +} + +pub(crate) struct UriFormatter; + +impl UriFormatter { + pub(crate) fn new() -> Self { + Self + } + + pub(crate) fn format(&self, uri: &mut Uri) -> Result<(), HttpClientError> { + let host = match uri.host() { + Some(host) => host.clone(), + None => { + return Err(HttpClientError::new_with_message( + ErrorKind::Request, + "No host in url", + )) + } + }; + + #[cfg(feature = "__tls")] + let mut scheme = Scheme::HTTPS; + + #[cfg(not(feature = "__tls"))] + let mut scheme = Scheme::HTTP; + + if let Some(req_scheme) = uri.scheme() { + scheme = req_scheme.clone() + }; + + let port; + + if let Some(req_port) = uri.port().and_then(|port| port.as_u16().ok()) { + port = req_port; + } else { + match scheme { + Scheme::HTTPS => port = 443, + Scheme::HTTP => port = 80, + } + } + + let mut new_uri = Uri::builder(); + new_uri = new_uri.scheme(scheme); + new_uri = new_uri.authority(format!("{}:{}", host.as_str(), port).as_bytes()); + + if let Some(path) = uri.path() { + new_uri = new_uri.path(path.clone()); + } + + if let Some(query) = uri.query() { + new_uri = new_uri.query(query.clone()); + } + + *uri = new_uri.build().map_err(|_| { + HttpClientError::new_with_message(ErrorKind::Request, "Normalize url failed") + })?; + + Ok(()) + } +} diff --git a/ylong_http_client/src/util/pool.rs b/ylong_http_client/src/util/pool.rs new file mode 100644 index 0000000..7109a3a --- /dev/null +++ b/ylong_http_client/src/util/pool.rs @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Connection pool implementation. + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::hash::Hash; +use std::sync::{Arc, Mutex}; +use ylong_http::request::uri::{Authority, Scheme}; + +pub(crate) struct Pool { + pool: Arc>>, +} + +impl Pool { + pub(crate) fn new() -> Self { + Self { + pool: Arc::new(Mutex::new(HashMap::new())), + } + } +} + +impl Pool { + pub(crate) fn get(&self, key: K, create_fn: F) -> V + where + F: FnOnce() -> V, + { + let mut inner = self.pool.lock().unwrap(); + match (*inner).entry(key) { + Entry::Occupied(conns) => conns.get().clone(), + Entry::Vacant(e) => e.insert(create_fn()).clone(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub(crate) struct PoolKey(Scheme, Authority); + +impl PoolKey { + pub(crate) fn new(scheme: Scheme, authority: Authority) -> Self { + Self(scheme, authority) + } +} +#[cfg(test)] +mod ut_pool { + use crate::pool::{Pool, PoolKey}; + use std::sync::{Arc, Mutex}; + use ylong_http::request::uri::Uri; + /// UT test cases for `Pool::get`. + /// + /// # Brief + /// 1. Creates a `pool` by calling `Pool::new()`. + /// 2. Uses `pool::get` to get connection. + /// 3. Checks if the results are correct. + #[test] + fn ut_pool_get() { + let uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap(); + let key = PoolKey::new( + uri.scheme().unwrap().clone(), + uri.authority().unwrap().clone(), + ); + let data = String::from("Data info"); + let consume_and_return_data = move || data; + let pool = Pool::new(); + let res = pool.get(key, consume_and_return_data); + assert_eq!(res, "Data info".to_string()); + } +} diff --git a/ylong_http_client/src/util/proxy.rs b/ylong_http_client/src/util/proxy.rs new file mode 100644 index 0000000..f63d779 --- /dev/null +++ b/ylong_http_client/src/util/proxy.rs @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Proxy implementation. + +use crate::error::HttpClientError; +use crate::util::base64::encode; +use crate::util::normalizer::UriFormatter; +use crate::ErrorKind; +use core::convert::TryFrom; +use std::net::IpAddr; +use ylong_http::headers::HeaderValue; +use ylong_http::request::uri::{Authority, Scheme, Uri}; + +/// `Proxies` is responsible for managing a list of proxies. +#[derive(Clone, Default)] +pub(crate) struct Proxies { + list: Vec, +} + +impl Proxies { + pub(crate) fn add_proxy(&mut self, proxy: Proxy) { + self.list.push(proxy) + } + + pub(crate) fn match_proxy(&self, uri: &Uri) -> Option<&Proxy> { + self.list.iter().find(|proxy| proxy.is_intercepted(uri)) + } +} + +/// Proxy is a configuration of client which should manage the destination address of request. +/// +/// A `Proxy` has below rules: +/// +/// - Manage the uri of destination address. +/// - Manage the request content such as headers. +/// - Provide no proxy function which the request will not affected by proxy. +#[derive(Clone)] +pub(crate) struct Proxy { + pub(crate) intercept: Intercept, + pub(crate) no_proxy: Option, +} + +impl Proxy { + pub(crate) fn new(intercept: Intercept) -> Self { + Self { + intercept, + no_proxy: None, + } + } + + pub(crate) fn http(uri: &str) -> Result { + Ok(Proxy::new(Intercept::Http(ProxyInfo::new(uri)?))) + } + + pub(crate) fn https(uri: &str) -> Result { + Ok(Proxy::new(Intercept::Https(ProxyInfo::new(uri)?))) + } + + pub(crate) fn all(uri: &str) -> Result { + Ok(Proxy::new(Intercept::All(ProxyInfo::new(uri)?))) + } + + pub(crate) fn basic_auth(&mut self, username: &str, password: &str) { + let auth = encode(format!("{username}:{password}").as_bytes()); + + // All characters in base64 format are valid characters, so we ignore the error. + let mut auth = HeaderValue::from_bytes(auth.as_slice()).unwrap(); + auth.set_sensitive(true); + + match &mut self.intercept { + Intercept::All(info) => info.basic_auth = Some(auth), + Intercept::Http(info) => info.basic_auth = Some(auth), + Intercept::Https(info) => info.basic_auth = Some(auth), + } + } + + pub(crate) fn no_proxy(&mut self, no_proxy: &str) { + self.no_proxy = NoProxy::from_str(no_proxy); + } + + pub(crate) fn via_proxy(&self, uri: &Uri) -> Uri { + let info = self.intercept.proxy_info(); + + let mut builder = Uri::builder(); + builder = builder + .scheme(info.scheme().clone()) + .authority(info.authority().clone()); + + if let Some(path) = uri.path() { + builder = builder.path(path.clone()); + } + + if let Some(query) = uri.query() { + builder = builder.query(query.clone()); + } + + // Here all parts of builder is accurate. + builder.build().unwrap() + } + + pub(crate) fn is_intercepted(&self, uri: &Uri) -> bool { + let no_proxy = self + .no_proxy + .as_ref() + .map(|no_proxy| no_proxy.contain(uri.to_string().as_str())) + .unwrap_or(false); + + match self.intercept { + Intercept::All(_) => !no_proxy, + Intercept::Http(_) => !no_proxy && *uri.scheme().unwrap() == Scheme::HTTP, + Intercept::Https(_) => !no_proxy && *uri.scheme().unwrap() == Scheme::HTTPS, + } + } +} + +#[derive(Clone)] +pub(crate) enum Intercept { + All(ProxyInfo), + Http(ProxyInfo), + Https(ProxyInfo), +} + +impl Intercept { + pub(crate) fn proxy_info(&self) -> &ProxyInfo { + match self { + Self::All(info) => info, + Self::Http(info) => info, + Self::Https(info) => info, + } + } +} + +/// ProxyInfo which contains authentication, scheme and host. +#[derive(Clone)] +pub(crate) struct ProxyInfo { + pub(crate) scheme: Scheme, + pub(crate) authority: Authority, + pub(crate) basic_auth: Option, +} + +impl ProxyInfo { + pub(crate) fn new(uri: &str) -> Result { + let mut uri = match Uri::try_from(uri) { + Ok(u) => u, + Err(e) => { + return Err(HttpClientError::new_with_cause(ErrorKind::Build, Some(e))); + } + }; + + // Makes sure that all parts of uri exist. + UriFormatter::new().format(&mut uri)?; + let (scheme, authority, _, _) = uri.into_parts(); + + // `scheme` and `authority` must have values after formatting. + Ok(Self { + basic_auth: None, + scheme: scheme.unwrap(), + authority: authority.unwrap(), + }) + } + + pub(crate) fn authority(&self) -> &Authority { + &self.authority + } + + pub(crate) fn scheme(&self) -> &Scheme { + &self.scheme + } +} + +#[derive(Clone)] +enum Ip { + Address(IpAddr), +} + +#[derive(Clone, Default)] +pub(crate) struct NoProxy { + ips: Vec, + domains: Vec, +} + +impl NoProxy { + pub(crate) fn from_str(no_proxy: &str) -> Option { + if no_proxy.is_empty() { + return None; + } + + let no_proxy_vec = no_proxy.split(',').map(|c| c.trim()).collect::>(); + let mut ip_list = Vec::new(); + let mut domains_list = Vec::new(); + + for host in no_proxy_vec { + match host.parse::() { + Ok(ip) => ip_list.push(Ip::Address(ip)), + Err(_) => domains_list.push(host.to_string()), + } + } + Some(NoProxy { + ips: ip_list, + domains: domains_list, + }) + } + + pub(crate) fn contain(&self, proxy_host: &str) -> bool { + match proxy_host.parse::() { + Ok(ip) => self.contains_ip(ip), + Err(_) => self.contains_domain(proxy_host), + } + } + + fn contains_ip(&self, ip: IpAddr) -> bool { + for block_ip in self.ips.iter() { + match block_ip { + Ip::Address(i) => { + if &ip == i { + return true; + } + } + } + } + false + } + + fn contains_domain(&self, domain: &str) -> bool { + for block_domain in self.domains.iter() { + if block_domain == "*" + || block_domain.ends_with(domain) + || block_domain == domain + || block_domain.trim_matches('.') == domain + { + return true; + } else if domain.ends_with(block_domain) { + // .example.com and www. + if block_domain.starts_with('.') + || domain.as_bytes().get(domain.len() - block_domain.len() - 1) == Some(&b'.') + { + return true; + } + } + } + false + } +} + +#[cfg(test)] +mod ut_proxy { + use crate::util::proxy::{Proxies, Proxy}; + use ylong_http::request::uri::{Scheme, Uri}; + + /// UT test cases for `Proxies`. + /// + /// # Brief + /// 1. Creates a `Proxies`. + /// 2. Adds some `Proxy` to `Proxies` + /// 3. Calls `Proxies::match_proxy` with some `Uri`s and get the results. + /// 4. Checks if the test result is correct. + #[test] + fn ut_proxies() { + let mut proxies = Proxies::default(); + proxies.add_proxy(Proxy::http("http://www.aaa.com").unwrap()); + proxies.add_proxy(Proxy::https("http://www.bbb.com").unwrap()); + + let uri = Uri::from_bytes(b"http://www.example.com").unwrap(); + let proxy = proxies.match_proxy(&uri).unwrap(); + assert!(proxy.no_proxy.is_none()); + let info = proxy.intercept.proxy_info(); + assert_eq!(info.scheme, Scheme::HTTP); + assert_eq!(info.authority.to_string(), "www.aaa.com:80"); + + let uri = Uri::from_bytes(b"https://www.example.com").unwrap(); + let matched = proxies.match_proxy(&uri).unwrap(); + assert!(matched.no_proxy.is_none()); + let info = matched.intercept.proxy_info(); + assert_eq!(info.scheme, Scheme::HTTP); + assert_eq!(info.authority.to_string(), "www.bbb.com:80"); + + // with no_proxy + let mut proxies = Proxies::default(); + let mut proxy = Proxy::http("http://www.aaa.com").unwrap(); + proxy.no_proxy("http://no_proxy.aaa.com"); + proxies.add_proxy(proxy); + + let uri = Uri::from_bytes(b"http://www.bbb.com").unwrap(); + let matched = proxies.match_proxy(&uri).unwrap(); + let info = matched.intercept.proxy_info(); + assert_eq!(info.scheme, Scheme::HTTP); + assert_eq!(info.authority.to_string(), "www.aaa.com:80"); + + let uri = Uri::from_bytes(b"http://no_proxy.aaa.com").unwrap(); + assert!(proxies.match_proxy(&uri).is_none()); + } +} diff --git a/ylong_http_client/src/util/redirect.rs b/ylong_http_client/src/util/redirect.rs new file mode 100644 index 0000000..4b8f64b --- /dev/null +++ b/ylong_http_client/src/util/redirect.rs @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::error::ErrorKind; +use crate::error::HttpClientError; +use crate::util; +use ylong_http::headers::Headers; +use ylong_http::request::method::Method; +use ylong_http::request::uri::Uri; +use ylong_http::request::Request; +use ylong_http::response::status::StatusCode; +use ylong_http::response::Response; + +/// Redirect strategy supports limited times of redirection and no redirect +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RedirectStrategy { + inner: StrategyKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum StrategyKind { + LimitTimes(usize), + NoRedirect, +} + +/// Redirect status supports to check response status and next +/// redirected uri +#[derive(Clone)] +pub struct RedirectStatus<'a> { + previous_uri: &'a [Uri], +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Trigger { + inner: TriggerKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum TriggerKind { + NextLink, + Stop, +} + +impl RedirectStrategy { + pub(crate) fn limited(max: usize) -> Self { + Self { + inner: StrategyKind::LimitTimes(max), + } + } + + pub(crate) fn none() -> Self { + Self { + inner: StrategyKind::NoRedirect, + } + } + + pub(crate) fn redirect(&self, status: RedirectStatus) -> Result { + match self.inner { + StrategyKind::LimitTimes(max) => { + if status.previous_uri.len() >= max { + Err(HttpClientError::new_with_message( + ErrorKind::Build, + "Over redirect max limit", + )) + } else { + Ok(status.transfer()) + } + } + StrategyKind::NoRedirect => Ok(status.stop()), + } + } + + pub(crate) fn get_trigger( + &self, + redirect_status: RedirectStatus, + ) -> Result { + let trigger = self.redirect(redirect_status)?; + Ok(trigger.inner) + } +} + +impl Default for RedirectStrategy { + fn default() -> RedirectStrategy { + RedirectStrategy::limited(10) + } +} + +impl<'a> RedirectStatus<'a> { + pub(crate) fn new(previous_uri: &'a [Uri]) -> Self { + Self { previous_uri } + } + + fn transfer(self) -> Trigger { + Trigger { + inner: TriggerKind::NextLink, + } + } + + fn stop(self) -> Trigger { + Trigger { + inner: TriggerKind::Stop, + } + } +} + +pub(crate) struct Redirect; + +impl Redirect { + pub(crate) fn get_trigger_kind( + dst_uri: &mut Uri, + redirect: &util::Redirect, + redirect_list: &[Uri], + response: &Response, + request: &mut Request, + ) -> Result { + let location = match response.headers().get("location") { + Some(value) => value, + None => { + return Err(HttpClientError::new_with_message( + ErrorKind::Redirect, + "No location in response's headers", + )); + } + }; + + let loc_str = location.to_str().unwrap(); + let loc_bytes = loc_str.as_str().trim_start_matches('/').as_bytes(); + + let mut loc_uri = Uri::from_bytes(loc_bytes) + .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Redirect, Some(e)))?; + if loc_uri.scheme().is_none() || loc_uri.authority().is_none() { + // request uri is existed, so can use unwrap directly + let origin_scheme = request + .uri() + .scheme() + .ok_or(HttpClientError::new_with_message( + ErrorKind::Connect, + "No uri scheme in request", + ))? + .as_str(); + let auth = request + .uri() + .authority() + .ok_or(HttpClientError::new_with_message( + ErrorKind::Connect, + "No uri authority in request", + ))? + .to_str(); + let origin_auth = auth.as_str(); + loc_uri = Uri::builder() + .scheme(origin_scheme) + .authority(origin_auth) + // loc_uri is existed, so can use unwrap directly + .path( + loc_uri + .path() + .ok_or(HttpClientError::new_with_message( + ErrorKind::Connect, + "No loc_uri path in location", + ))? + .as_str(), + ) + .query( + loc_uri + .query() + .ok_or(HttpClientError::new_with_message( + ErrorKind::Connect, + "No loc_uri query in location", + ))? + .as_str(), + ) + .build() + .unwrap(); + } + + let redirect_status = RedirectStatus::new(redirect_list); + let trigger = redirect + .redirect_strategy() + .unwrap() + .get_trigger(redirect_status)?; + + match trigger { + TriggerKind::NextLink => { + Self::remove_sensitive_headers(request.headers_mut(), &loc_uri, redirect_list); + *dst_uri = loc_uri.clone(); + *request.uri_mut() = loc_uri; + Ok(TriggerKind::NextLink) + } + TriggerKind::Stop => Ok(TriggerKind::Stop), + } + } + + fn remove_sensitive_headers(headers: &mut Headers, next: &Uri, previous: &[Uri]) { + if let Some(previous) = previous.last() { + // TODO: Check this logic. + let cross_host = next.authority().unwrap() != previous.authority().unwrap(); + if cross_host { + let _ = headers.remove("authorization"); + let _ = headers.remove("cookie"); + let _ = headers.remove("cookie2"); + let _ = headers.remove("proxy_authorization"); + let _ = headers.remove("www_authenticate"); + } + } + } + + pub(crate) fn check_redirect(status_code: StatusCode, request: &mut Request) -> bool { + match status_code { + StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { + Self::update_header_and_method(request); + true + } + StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => true, + _ => false, + } + } + + fn update_header_and_method(request: &mut Request) { + for header_name in [ + "transfer_encoding", + "content_encoding", + "content_type", + "content_length", + "content_language", + "content_location", + "digest", + "last_modified", + ] { + let _ = request.headers_mut().remove(header_name); + } + let method = request.method_mut(); + match *method { + Method::GET | Method::HEAD => {} + _ => { + *method = Method::GET; + } + } + } +} + +#[cfg(test)] +mod ut_redirect { + use crate::redirect::Redirect; + use crate::util::config::Redirect as setting_redirect; + use crate::util::redirect::{RedirectStatus, RedirectStrategy, TriggerKind}; + use ylong_http::h1::ResponseDecoder; + use ylong_http::request::uri::Uri; + use ylong_http::request::Request; + use ylong_http::response::status::StatusCode; + use ylong_http::response::Response; + /// UT test cases for `Redirect::check_redirect`. + /// + /// # Brief + /// 1. Creates a `request` by calling `request::new`. + /// 2. Uses `redirect::check_redirect` to check whether is redirected. + /// 3. Checks if the result is true. + #[test] + fn ut_check_redirect() { + let mut request = Request::new("this is a body"); + let code = StatusCode::MOVED_PERMANENTLY; + let res = Redirect::check_redirect(code, &mut request); + assert!(res); + } + /// UT test cases for `Redirect::get_trigger_kind`. + /// + /// # Brief + /// 1. Creates a `redirect` by calling `setting_redirect::default`. + /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind. + /// 3. Checks if the results are correct. + #[test] + fn ut_get_trigger_kind() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t http://example3.com:80/foo?a=1 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response::from_raw_parts(result.0, result.1); + let mut request = Request::new("this is a body"); + let request_uri = request.uri_mut(); + *request_uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap(); + let mut uri = Uri::default(); + let redirect = setting_redirect::default(); + let redirect_list: Vec = vec![]; + let res = Redirect::get_trigger_kind( + &mut uri, + &redirect, + &redirect_list, + &response, + &mut request, + ); + assert!(res.is_ok()); + } + /// UT test cases for `Redirect::get_trigger_kind` err branch. + /// + /// # Brief + /// 1. Creates a `redirect` by calling `setting_redirect::default`. + /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind. + /// 3. Checks if the results are error. + #[test] + fn ut_get_trigger_kind_err() { + let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t example3.com:80 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); + let mut decoder = ResponseDecoder::new(); + let result = decoder.decode(response_str).unwrap().unwrap(); + let response = Response::from_raw_parts(result.0, result.1); + let mut request = Request::new("this is a body"); + let request_uri = request.uri_mut(); + *request_uri = Uri::from_bytes(b"http://example1.com:80").unwrap(); + let mut uri = Uri::default(); + let redirect = setting_redirect::default(); + let redirect_list: Vec = vec![]; + let res = Redirect::get_trigger_kind( + &mut uri, + &redirect, + &redirect_list, + &response, + &mut request, + ); + assert!(res.is_err()); + } + + /// UT test cases for `RedirectStrategy::default`. + /// + /// # Brief + /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::default`. + /// 2. Uses `RedirectStrategy::get_trigger` to get redirected uri. + /// 3. Checks if the results are correct. + #[test] + fn ut_redirect_default() { + let strategy = RedirectStrategy::default(); + let next = Uri::from_bytes(b"http://example.com").unwrap(); + let previous = (0..9) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + let redirect_uri = match strategy + .get_trigger(RedirectStatus::new(&previous)) + .unwrap() + { + TriggerKind::NextLink => next.to_string(), + TriggerKind::Stop => previous.get(9).unwrap().to_string(), + }; + assert_eq!(redirect_uri, "http://example.com".to_string()); + } + + /// UT test cases for `RedirectStrategy::limited`. + /// + /// # Brief + /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::limited`. + /// 2. Sets redirect times which is over max limitation times. + /// 3. Uses `RedirectStrategy::get_trigger` to get redirected uri. + /// 4. Checks if the results are err. + #[test] + fn ut_redirect_over_redirect_max() { + let strategy = RedirectStrategy::limited(10); + let previous = (0..10) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + if let Ok(other) = strategy.get_trigger(RedirectStatus::new(&previous)) { + panic!("unexpected {:?}", other); + } + } + + /// UT test cases for `RedirectStrategy::none`. + /// + /// # Brief + /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::none`. + /// 2. Uses `RedirectStrategy::get_trigger` but get origin uri. + /// 3. Checks if the results are correct. + #[test] + fn ut_no_redirect() { + let strategy = RedirectStrategy::none(); + let next = Uri::from_bytes(b"http://example.com").unwrap(); + let previous = (0..1) + .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) + .collect::>(); + + let redirect_uri = match strategy + .get_trigger(RedirectStatus::new(&previous)) + .unwrap() + { + TriggerKind::NextLink => next.to_string(), + TriggerKind::Stop => previous.get(0).unwrap().to_string(), + }; + assert_eq!(redirect_uri, "http://example0.com".to_string()); + } +} diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs new file mode 100644 index 0000000..69adb0e --- /dev/null +++ b/ylong_http_client/tests/common/mod.rs @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// Server handle. +pub struct Handle { + pub port: u16, +} + +/// Creates a `Request`. +macro_rules! ylong_request { + ( + Request: { + Method: $method: expr, + Host: $host: expr, + Port: $port: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + ) => { + ylong_http_client::RequestBuilder::new() + .method($method) + .url(format!("{}:{}", $host, $port).as_str()) + $(.header($req_n, $req_v))* + .body(ylong_http::body::TextBody::from_bytes($req_body.as_bytes())) + .expect("Request build failed") + }; +} + +/// Sets server async function. +macro_rules! set_server_fn { + ( + $server_fn_name: ident, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => { + async fn $server_fn_name(request: hyper::Request) -> Result, std::convert::Infallible> { + match request.method().as_str() { + $( + $method => { + assert_eq!($method, request.method().as_str(), "Assert request method failed"); + assert_eq!( + $host, + request.uri().host().expect("Uri in request do not have a host."), + "Assert request host failed", + ); + assert_eq!( + $version, + format!("{:?}", request.version()), + "Assert request version failed", + ); + $(assert_eq!( + $req_v, + request + .headers() + .get($req_n) + .expect(format!("Get request header \"{}\" failed", $req_n).as_str()) + .to_str() + .expect(format!("Convert request header \"{}\" into string failed", $req_n).as_str()), + "Assert request header {} failed", $req_n, + );)* + let body = hyper::body::to_bytes(request.into_body()).await + .expect("Get request body failed"); + assert_eq!($req_body.as_bytes(), body, "Assert request body failed"); + Ok( + hyper::Response::builder() + .version(hyper::Version::HTTP_11) + .status($status) + $(.header($resp_n, $resp_v))* + .body($resp_body.into()) + .expect("Build response failed") + ) + }, + )* + _ => {panic!("Unrecognized METHOD !");}, + } + } + + }; +} + +macro_rules! get_handle { + ($service_fn: ident) => {{ + let mut port = 10000; + let listener = loop { + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); + match tokio::net::TcpListener::bind(addr).await { + Ok(listener) => break listener, + Err(_) => { + port += 1; + if port == u16::MAX { + port = 10000; + } + continue; + } + } + }; + let port = listener.local_addr().unwrap().port(); + + tokio::spawn(async move { + let mut acceptor = openssl::ssl::SslAcceptor::mozilla_intermediate(openssl::ssl::SslMethod::tls()) + .expect("SslAcceptorBuilder error"); + acceptor + .set_session_id_context(b"test") + .expect("Set session id error"); + acceptor + .set_private_key_file("tests/file/key.pem", openssl::ssl::SslFiletype::PEM) + .expect("Set private key error"); + acceptor + .set_certificate_chain_file("tests/file/cert.pem") + .expect("Set cert error"); + let acceptor = acceptor.build(); + + // start_tx + // .send(()) + // .await + // .expect("Start channel (Client-Half) be closed unexpectedly"); + + let (stream, _) = listener.accept().await.expect("TCP listener accpet error"); + let ssl = openssl::ssl::Ssl::new(acceptor.context()).expect("Ssl Error"); + let mut stream = tokio_openssl::SslStream::new(ssl, stream).expect("SslStream Error"); + core::pin::Pin::new(&mut stream).accept().await.unwrap(); // SSL negotiation finished successfully + + hyper::server::conn::Http::new() + .http1_only(true) + .http1_keep_alive(true) + .serve_connection(stream, hyper::service::service_fn($service_fn)) + .await + }); + + Handle { + port, + } + }}; +} + +macro_rules! ylong_client_test_case { + ( + Tls: $tls_config: expr, + RuntimeThreads: $thread_num: expr, + $(ClientNum: $client_num: expr,)? + IsAsync: $is_async: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + set_server_fn!( + ylong_server_fn, + $(Request: { + Method: $method, + Host: $host, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads($thread_num) + .enable_all() + .build() + .expect("Build runtime failed."); + + // The number of servers may be variable based on the number of servers set by the user. + // However, cliipy checks that the variable does not need to be variable. + #[allow(unused_mut, unused_assignments)] + let mut server_num = 1; + $(server_num = $client_num;)? + + let mut handles_vec = vec![]; + for _i in 0.. server_num { + let (tx, rx) = std::sync::mpsc::channel(); + let server_handle = runtime.spawn(async move { + let handle = get_handle!(ylong_server_fn); + // handle + // .server_start + // .recv() + // .await + // .expect("Start channel (Server-Half) be closed unexpectedly"); + tx.send(handle) + .expect("Failed to send the handle to the test thread."); + }); + runtime + .block_on(server_handle) + .expect("Runtime start server coroutine failed"); + let handle = rx + .recv() + .expect("Handle send channel (Server-Half) be closed unexpectedly"); + handles_vec.push(handle); + } + + let mut shut_downs = vec![]; + if $is_async { + let client = ylong_http_client::async_impl::Client::builder() + .set_ca_file($tls_config) + .build() + .unwrap(); + let client = std::sync::Arc::new(client); + for _i in 0..server_num { + let handle = handles_vec.pop().expect("No more handles !"); + + ylong_client_test_case!( + Runtime: runtime, + AsyncClient: client, + ServerHandle: handle, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Host: $host, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ) + } + } else { + let client = ylong_http_client::sync_impl::Client::builder() + .set_ca_file($tls_config) + .build() + .unwrap(); + let client = std::sync::Arc::new(client); + for _i in 0..server_num { + let handle = handles_vec.pop().expect("No more handles !"); + ylong_client_test_case!( + Runtime: runtime, + SyncClient: client, + ServerHandle: handle, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Host: $host, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ) + } + } + for shutdown_handle in shut_downs { + runtime.block_on(shutdown_handle).expect("Runtime wait for server shutdown failed"); + } + }}; + ( + Runtime: $runtime: expr, + AsyncClient: $async_client: expr, + ServerHandle: $handle:expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + use ylong_http_client::async_impl::Body; + let client = std::sync::Arc::clone(&$async_client); + let shutdown_handle = $runtime.spawn(async move { + $( + let request = ylong_request!( + Request: { + Method: $method, + Host: $host, + Port: $handle.port, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + ); + + let mut response = client + .request(request) + .await + .expect("Request send failed"); + + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed"); + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]).await + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + )* + // $handle + // .client_shutdown + // .send(()) + // .await + // .expect("Client channel (Server-Half) be closed unexpectedly"); + // $handle + // .server_shutdown + // .recv() + // .await + // .expect("Server channel (Server-Half) be closed unexpectedly"); + }); + $shut_downs.push(shutdown_handle); + }}; + + ( + Runtime: $runtime: expr, + SyncClient: $sync_client: expr, + ServerHandle: $handle:expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + use ylong_http_client::sync_impl::Body; + let client = std::sync::Arc::clone(&$sync_client); + $( + let request = ylong_request!( + Request: { + Method: $method, + Host: $host, + Port: $handle.port, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + ); + let mut response = client + .request(request) + .expect("Request send failed"); + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed"); + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]) + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + )* + let shutdown_handle = $runtime.spawn(async move { + // $handle + // .client_shutdown + // .send(()) + // .await + // .expect("Client channel (Server-Half) be closed unexpectedly"); + // $handle + // .server_shutdown + // .recv() + // .await + // .expect("Server channel (Server-Half) be closed unexpectedly"); + }); + $shut_downs.push(shutdown_handle); + }}; +} diff --git a/ylong_http_client/tests/file/cert.pem b/ylong_http_client/tests/file/cert.pem new file mode 100644 index 0000000..3ff058b --- /dev/null +++ b/ylong_http_client/tests/file/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG +A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub +3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ +mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6 +TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI +ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y +euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq +hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM +6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE +wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY +oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9 +dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp +HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/ylong_http_client/tests/file/key.pem b/ylong_http_client/tests/file/key.pem new file mode 100644 index 0000000..d381795 --- /dev/null +++ b/ylong_http_client/tests/file/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo9CWMRLMXo1CF +/iORh9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0 +kU6o5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNS +wiHA1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAM +jmaloZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iT +Hk7F14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCt +OzboKgs1AgMBAAECggEBAKLj6IOJBKXolczpzb8UkyAjAkGBektcseV07gelJ/fk +3z0LuWPv5p12E/HlXB24vU2x/ikUbbP3eMsawRzDEahQqmNmPEkYAYUAy/Qpi9GN +DYvn3LqDec4jVgeQKS+p9H2DzUpTogp8zR2//yzbuWBg2+F//xh7vU0S0RQCziPM +x7RSBgbhxSfChfEJbS2sDnzfh0jRQmoY95iFv7puet1FJtzdZ4fgCd1RqmC2lFM5 +H0eZtN/Cz19lieVs0b996DErdEBqClVZO00eYbRozCDaBzRU3ybB/dMrGJxhkkXm +wb3kWMtziH9qOYsostuHIFu8eKFLloKxFnq2R4DGxOECgYEA2KUIZISOeGJSBcLJ +JAUK2gvgXPNo4HHWIwOA9xeN3ZJlsnPlffXQNnm6t1st1V2gfMm9I2n0m/F0y2B/ +n/XGSa8bghfPA9l0c2h58lkL3JQJR/paa8ycTz+YZPrznEyN7Qa0RrJXUvZv9lQL +Hc3+FHcSHgMqDV2f2bHAEu9YGi0CgYEAx6VEIPNvrHFgjo/jk1RTuk+m0xEWQsZL +Cs+izQMr2TaeJn8LG+93AvFuYn0J0nT3WuStLPrUg8i4IhSS6lf1tId5ivIZPm4r +YwMyblBJXhnHbk7Uqodjfw/3s6V2HAu++B7hTdyVr9DFuST9uv4m8bkPV8rfX1jE +I2rAPVWvgikCgYB+wNAQP547wQrMZBLbCDg5KwmyWJfb+b6X7czexOEz6humNTjo +YZHYzY/5B1fhpk3ntQD8X1nGg5caBvOk21+QbOtjShrM3cXMYCw5JvBRtitX+Zo9 +yBEMLOE0877ki8XeEDYZxu5gk98d+D4oygUGZEQtWxyXhVepPt5qNa8OYQKBgQDH +RVgZI6KFlqzv3wMh3PutbS9wYQ+9GrtwUQuIYe/0YSW9+vSVr5E0qNKrD28sV39F +hBauXLady0yvB6YUrjMbPFW+sCMuQzyfGWPO4+g3OrfqjFiM1ZIkE0YEU9Tt7XNx +qTDtTI1D7bhNMnTnniI1B6ge0und+3XafAThs5L48QKBgQCTTpfqMt8kU3tcI9sf +0MK03y7kA76d5uw0pZbWFy7KI4qnzWutCzb+FMPWWsoFtLJLPZy//u/ZCUVFVa4d +0Y/ASNQIESVPXFLAltlLo4MSmsg1vCBsbviEEaPeEjvMrgki93pYtd/aOSgkYC1T +mEq154s5rmqh+h+XRIf7Au0SLw== +-----END PRIVATE KEY----- diff --git a/ylong_http_client/tests/file/root-ca.pem b/ylong_http_client/tests/file/root-ca.pem new file mode 100644 index 0000000..4ec2f53 --- /dev/null +++ b/ylong_http_client/tests/file/root-ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G +ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV +eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr +7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92 +aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc +klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN +XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn +BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv +Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3 +AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy +OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3 +mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9 +GA== +-----END CERTIFICATE----- diff --git a/ylong_http_client/tests/helper.rs b/ylong_http_client/tests/helper.rs new file mode 100644 index 0000000..87fc5b4 --- /dev/null +++ b/ylong_http_client/tests/helper.rs @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use tokio::sync::mpsc::{Receiver, Sender}; + +pub struct Handle { + pub port: u16, + + // This channel allows the server to notify the client when it is up and running. + pub server_start: Receiver<()>, + + // This channel allows the client to notify the server when it is ready to shut down. + pub client_shutdown: Sender<()>, + + // This channel allows the server to notify the client when it has shut down. + pub server_shutdown: Receiver<()>, +} + +#[macro_export] +macro_rules! start_local_test_server { + ($server_fn: ident) => {{ + use hyper::service::{make_service_fn, service_fn}; + use hyper::Server; + use std::convert::Infallible; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use tokio::sync::mpsc::channel; + + let (start_tx, start_rx) = channel::<()>(1); + let (client_tx, mut client_rx) = channel::<()>(1); + let (server_tx, server_rx) = channel::<()>(1); + let mut port = 10000; + + let server = loop { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); + match Server::try_bind(&addr) { + Ok(server) => break server, + Err(_) => { + port += 1; + if port == u16::MAX { + port = 10000; + } + continue; + } + } + }; + + tokio::spawn(async move { + let make_svc = + make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn($server_fn)) }); + server + .serve(make_svc) + .with_graceful_shutdown(async { + start_tx + .send(()) + .await + .expect("Start channel (Client-Half) be closed unexpectedly"); + client_rx + .recv() + .await + .expect("Client channel (Client-Half) be closed unexpectedly"); + }) + .await + .expect("Start server failed"); + server_tx + .send(()) + .await + .expect("Server channel (Client-Half) be closed unexpectedly"); + }); + + Handle { + port, + server_start: start_rx, + client_shutdown: client_tx, + server_shutdown: server_rx, + } + }}; +} + +#[macro_export] +macro_rules! ylong_client_test_case { + ( + RuntimeThreads: $thread_num: expr, + $(ClientNum: $client_num: expr,)? + IsAsync: $is_async: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + async fn server_fn(request: Request) -> Result, Infallible> { + + match request.method().as_str() { + $( + $method => { + assert_eq!($method, request.method().as_str(), "Assert request method failed"); + assert_eq!( + $host, + request.uri().host().expect("Uri in request do not have a host."), + "Assert request host failed", + ); + assert_eq!( + $version, + format!("{:?}", request.version()), + "Assert request version failed", + ); + $(assert_eq!( + $req_v, + request + .headers() + .get($req_n) + .expect(format!("Get request header \"{}\" failed", $req_n).as_str()) + .to_str() + .expect(format!("Convert request header \"{}\" into string failed", $req_n).as_str()), + "Assert request header {} failed", $req_n, + );)* + let body = hyper::body::to_bytes(request.into_body()).await + .expect("Get request body failed"); + assert_eq!($req_body.as_bytes(), body, "Assert request body failed"); + + Ok( + Response::builder() + .version(hyper::Version::HTTP_11) + .status($status) + $(.header($resp_n, $resp_v))* + .body($resp_body.into()) + .expect("Build response failed") + ) + }, + )* + _ => {panic!("Unrecognized METHOD!");}, + } + } + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads($thread_num) + .enable_all() + .build() + .expect("Build runtime failed."); + + // The number of servers may vary based on the user's configuration. + // However, Clippy checks to ensure that this variable doesn't need to be mutable. + #[allow(unused_mut, unused_assignments)] + let mut server_num = 1; + $(server_num = $client_num;)? + + let mut handles_vec = vec![]; + for _i in 0.. server_num { + let (tx, rx) = std::sync::mpsc::channel(); + let server_handle = runtime.spawn(async move { + let mut handle = start_local_test_server!(server_fn); + handle.server_start.recv().await.expect("Start channel (Server-Half) be closed unexpectedly"); + tx.send(handle).expect("Failed to send the handle to the test thread."); + }); + runtime.block_on(server_handle).expect("Runtime start server coroutine failed"); + let handle = rx.recv().expect("Handle send channel (Server-Half) be closed unexpectedly"); + handles_vec.push(handle); + } + + let mut shut_downs = vec![]; + if $is_async { + let client = ylong_http_client::async_impl::Client::new(); + let client = Arc::new(client); + for _i in 0..server_num { + let mut handle = handles_vec.pop().expect("No more handles !"); + + ylong_client_test_case!( + Runtime: runtime, + AsyncClient: client, + ServerHandle: handle, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Host: $host, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ) + } + } else { + let client = ylong_http_client::sync_impl::Client::new(); + let client = Arc::new(client); + for _i in 0..server_num { + let mut handle = handles_vec.pop().expect("No more handles !"); + ylong_client_test_case!( + Runtime: runtime, + SyncClient: client, + ServerHandle: handle, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Host: $host, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ) + } + } + for shutdown_handle in shut_downs { + runtime.block_on(shutdown_handle).expect("Runtime wait for server shutdown failed"); + } + }}; + + ( + Runtime: $runtime: expr, + AsyncClient: $async_client: expr, + ServerHandle: $handle:expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + let client = Arc::clone(&$async_client); + let shutdown_handle = $runtime.spawn(async move { + $( + let request = RequestBuilder::new() + .method($method) + .url(format!("{}:{}", $host, $handle.port).as_str()) + $(.header($req_n, $req_v))* + .body(TextBody::from_bytes($req_body.as_bytes())) + .expect("Request build failed"); + let mut response = client + .request(request) + .await + .expect("Request send failed"); + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed"); + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]).await + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + )* + $handle + .client_shutdown + .send(()) + .await + .expect("Client channel (Server-Half) be closed unexpectedly"); + $handle + .server_shutdown + .recv() + .await + .expect("Server channel (Server-Half) be closed unexpectedly"); + }); + $shut_downs.push(shutdown_handle); + }}; + + ( + Runtime: $runtime: expr, + SyncClient: $sync_client: expr, + ServerHandle: $handle:expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Host: $host: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + let client = Arc::clone(&$sync_client); + $( + let request = RequestBuilder::new() + .method($method) + .url(format!("{}:{}", $host, $handle.port).as_str()) + $(.header($req_n, $req_v))* + .body(TextBody::from_bytes($req_body.as_bytes())) + .expect("Request build failed"); + let mut response = client + .request(request) + .expect("Request send failed"); + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed"); + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]) + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + )* + let shutdown_handle = $runtime.spawn(async move { + $handle.client_shutdown.send(()).await.expect("Client channel (Server-Half) be closed unexpectedly"); + $handle.server_shutdown.recv().await.expect("Server channel (Server-Half) be closed unexpectedly"); + }); + $shut_downs.push(shutdown_handle); + }} + +} diff --git a/ylong_http_client/tests/sdv_client.rs b/ylong_http_client/tests/sdv_client.rs new file mode 100644 index 0000000..960d86a --- /dev/null +++ b/ylong_http_client/tests/sdv_client.rs @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![cfg(not(feature = "__tls"))] + +use hyper::{Body, Request, Response}; +use std::convert::Infallible; +use std::sync::Arc; +use ylong_http::body::async_impl::Body as AsyncBody; +use ylong_http_client::{RequestBuilder, TextBody}; + +mod helper; +use helper::Handle; +use ylong_http::body::sync_impl::Body as SyncBody; + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Starts a hyper server with the tokio coroutine. +/// 2. Creates an async::Client. +/// 3. The client sends a request message. +/// 4. Verifies the received request on the server. +/// 5. The server sends a response message. +/// 6. Verifies the received response on the client. +/// 7. Shuts down the server. +/// 8. Repeats the preceding operations to start the next test case. +#[test] +fn sdv_async_client_send_request() { + // `GET` request + ylong_client_test_case!( + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request. + ylong_client_test_case!( + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "HEAD", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `Post` Request. + ylong_client_test_case!( + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request without body. + ylong_client_test_case!( + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "HEAD", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Body: "", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `PUT` request. + ylong_client_test_case!( + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "PUT", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Creates a hyper server with the tokio coroutine. +/// 2. Creates an async::Client. +/// 3. The client repeatedly sends requests to the the server. +/// 4. Verifies each response returned by the server. +/// 5. Shuts down the server. +#[test] +fn sdv_client_send_request_repeatedly() { + ylong_client_test_case!( + RuntimeThreads: 2, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Creates an async::Client. +/// 2. Creates five servers and five coroutine sequentially. +/// 3. The client sends requests to the created servers in five coroutines. +/// 4. Verifies the responses returned by each server. +/// 5. Shuts down the servers. +#[test] +fn sdv_client_making_multiple_connections() { + ylong_client_test_case!( + RuntimeThreads: 2, + ClientNum: 5, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + ); +} + +/// SDV test cases for `sync::Client`. +/// +/// # Brief +/// 1. Creates a runtime to host the server. +/// 2. Creates a server within the runtime coroutine. +/// 3. Creates a sync::Client. +/// 4. The client sends a request to the the server. +/// 5. Verifies the response returned by the server. +/// 6. Shuts down the server. +#[test] +fn sdv_synchronized_client_send_request() { + // `PUT` request. + ylong_client_test_case!( + RuntimeThreads: 2, + IsAsync: false, + Request: { + Method: "PUT", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +/// SDV test cases for `sync::Client`. +/// +/// # Brief +/// 1. Creates a runtime to host the server. +/// 2. Creates a server within the runtime coroutine. +/// 3. Creates a sync::Client. +/// 4. The client sends requests to the the server repeatedly. +/// 5. Verifies each response returned by the server. +/// 6. Shuts down the server. +#[test] +fn sdv_synchronized_client_send_request_repeatedly() { + ylong_client_test_case!( + RuntimeThreads: 2, + IsAsync: false, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} diff --git a/ylong_http_client/tests/sdv_https_c_ssl.rs b/ylong_http_client/tests/sdv_https_c_ssl.rs new file mode 100644 index 0000000..590503f --- /dev/null +++ b/ylong_http_client/tests/sdv_https_c_ssl.rs @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#[macro_use] +mod common; + +use common::Handle; +use std::path::PathBuf; + +// TODO: Add doc for sdv tests. +#[test] +fn sdv_async_client_send_request() { + let dir = env!("CARGO_MANIFEST_DIR"); + let mut path = PathBuf::from(dir); + path.push("tests/file/root-ca.pem"); + + // `GET` request + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request. + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "HEAD", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `Post` Request. + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request without body. + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "HEAD", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Body: "", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `PUT` request. + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 1, + IsAsync: true, + Request: { + Method: "PUT", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +#[test] +fn sdv_client_send_request_repeatedly() { + let dir = env!("CARGO_MANIFEST_DIR"); + let mut path = PathBuf::from(dir); + path.push("tests/file/root-ca.pem"); + + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 2, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} + +#[test] +fn sdv_client_making_multiple_connections() { + let dir = env!("CARGO_MANIFEST_DIR"); + let mut path = PathBuf::from(dir); + path.push("tests/file/root-ca.pem"); + + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 2, + ClientNum: 5, + IsAsync: true, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + ); +} + +#[test] +fn sdv_synchronized_client_send_request() { + let dir = env!("CARGO_MANIFEST_DIR"); + let mut path = PathBuf::from(dir); + path.push("tests/file/root-ca.pem"); + + // `PUT` request. + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 2, + IsAsync: false, + Request: { + Method: "PUT", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +#[test] +fn sdv_synchronized_client_send_request_repeatedly() { + let dir = env!("CARGO_MANIFEST_DIR"); + let mut path = PathBuf::from(dir); + path.push("tests/file/root-ca.pem"); + + ylong_client_test_case!( + Tls: path.to_str().unwrap(), + RuntimeThreads: 2, + IsAsync: false, + Request: { + Method: "GET", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Host: "127.0.0.1", + Header: "Host", "127.0.0.1", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} -- Gitee