From 8c34bf7ab917390db78f3a149c07ce5de41462cc Mon Sep 17 00:00:00 2001 From: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com> Date: Tue, 29 Aug 2023 20:04:28 +0400 Subject: [PATCH 01/12] Docs: Prepare main page for REST --- README.md | 111 +++++++++++++++++++++++------------------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index f745aea..f8ab395 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,38 @@

UCall

-JSON Remote Procedure Calls Library
-Up to 100x Faster than FastAPI
+Remote Procedure Calls Library
+Up to 100x Faster than FastAPI and gRPC


-Discord +Discord     -LinkedIn +LinkedIn     -Twitter +Twitter     -Blog +Blog     -GitHub +GitHub +

+ +

+RESTful API • JSON-RPC / HTTPS • HTTP • TCP +
+C 99 • +Python 3 • +JavaScript 🔜 +
+Linux • MacOS • Windows • WebAssembly

--- -Most modern networking is built either on slow and ambiguous REST APIs or unnecessarily complex gRPC. -FastAPI, for example, looks very approachable. -We aim to be equally or even simpler to use. +Tired of slow REST backends or overly complex gRPC setups? +UCall aims to be as approachable as FastAPI but significantly faster and more broadly applicable. + +## 📊 FastAPI UX Comparison @@ -48,7 +59,6 @@ pip install ucall ```python from fastapi import FastAPI -import uvicorn server = FastAPI() @@ -56,6 +66,7 @@ server = FastAPI() def sum(a: int, b: int): return a + b +import uvicorn uvicorn.run(...) ``` @@ -63,12 +74,11 @@ uvicorn.run(...)
```python -from ucall.posix import Server -# from ucall.uring import Server on 5.19+ +from ucall.server import Server server = Server() -@server +@server.get('/sum') def sum(a: int, b: int): return a + b @@ -79,19 +89,21 @@ server.run()
+## 📈 Performance Metrics + It takes over a millisecond to handle a trivial FastAPI call on a recent 8-core CPU. In that time, light could have traveled 300 km through optics to the neighboring city or country, in my case. How does UCall compare to FastAPI and gRPC? -| Setup | 🔁 | Server | Latency w 1 client | Throughput w 32 clients | -| :---------------------- | :---: | :----: | -----------------: | ----------------------: | -| Fast API over REST | ❌ | 🐍 | 1'203 μs | 3'184 rps | -| Fast API over WebSocket | ✅ | 🐍 | 86 μs | 11'356 rps ¹ | -| gRPC ² | ✅ | 🐍 | 164 μs | 9'849 rps | -| | | | | | -| UCall with POSIX | ❌ | C | 62 μs | 79'000 rps | -| UCall with io_uring | ✅ | 🐍 | 40 μs | 210'000 rps | -| UCall with io_uring | ✅ | C | 22 μs | 231'000 rps | +| Setup | 🔁 | Server | Protocol | Latency w 1 client | Throughput w 32 clients | +| :---------------------- | :---: | :----: | :--------------: | -----------------: | ----------------------: | +| Fast API over REST | ❌ | 🐍 | REST | 1'203 μs | 3'184 rps | +| Fast API over WebSocket | ✅ | 🐍 | REST | 86 μs | 11'356 rps ¹ | +| gRPC ² | ✅ | 🐍 | gRPC | 164 μs | 9'849 rps | +| | | | | | | +| UCall with POSIX | ✅ | C | REST or JSON-RPC | 62 μs | 79'000 rps | +| UCall with io_uring | ✅ | 🐍 | REST or JSON-RPC | 40 μs | 210'000 rps | +| UCall with io_uring | ✅ | C | REST or JSON-RPC | 22 μs | 231'000 rps |
Table legend @@ -111,7 +123,7 @@ These specific numbers were obtained on `c7g.metal` beefy instances with Gravito
-## How is that possible? +## 🛠 How Does UCall Achieve This? How can a tiny pet-project with just a couple thousand lines of code compete with two of the most established networking libraries? **UCall stands on the shoulders of Giants**: @@ -137,9 +149,9 @@ def echo(data: bytes): return data ``` -## More Functionality than FastAPI +## 🎨 Rich Type Pallete -FastAPI supports native type, while UCall supports `numpy.ndarray`, `PIL.Image` and other custom types. +FastAPI supports native types, while UCall also supports `numpy.ndarray`, `PIL.Image` and other custom types. This comes handy when you build real applications or want to deploy Multi-Modal AI, like we do with [UForm](https://github.com/unum-cloud/uform). ```python @@ -158,7 +170,9 @@ def vectorize(description: str, photo: PIL.Image.Image) -> numpy.ndarray: return joint_embedding.cpu().detach().numpy() ``` -We also have our own optional `Client` class that helps with those custom types. +## 🖥 Client Libraries + +UCall offers a Python `Client` class and a CLI tool for easy interaction with UCall servers. ```python from ucall.client import Client @@ -178,8 +192,6 @@ response = client({ response = client.vectorize(description=description, image=image) ``` -## CLI like [cURL](https://curl.se/docs/manpage.html) - Aside from the Python `Client`, we provide an easy-to-use Command Line Interface, which comes with `pip install ucall`. It allow you to call a remote server, upload files, with direct support for images and NumPy arrays. Translating previous example into a Bash script, to call the server on the same machine: @@ -225,7 +237,7 @@ ucall auth id:int=256 ucall auth id:str=256 ``` -## Free Tier Throughput +## 📊 AWS Free Tier Performance We will leave bandwidth measurements to enthusiasts, but will share some more numbers. The general logic is that you can't squeeze high performance from Free-Tier machines. @@ -238,23 +250,18 @@ This library is so fast, that it doesn't need more than 1 core, so you can run a | Fast API over WebSocket | ✅ | 🐍 | 1 | 1'504 rps | 3'051 rps | | gRPC | ✅ | 🐍 | 1 | 1'169 rps | 1'974 rps | | | | | | | | -| UCall with POSIX | ❌ | C | 1 | 1'082 rps | 2'438 rps | +| UCall with POSIX | ✅ | C | 1 | 1'082 rps | 2'438 rps | | UCall with io_uring | ✅ | C | 1 | - | 5'864 rps | -| UCall with POSIX | ❌ | C | 32 | 3'399 rps | 39'877 rps | +| UCall with POSIX | ✅ | C | 32 | 3'399 rps | 39'877 rps | | UCall with io_uring | ✅ | C | 32 | - | 88'455 rps | In this case, every server was bombarded by requests from 1 or a fleet of 32 other instances in the same availability zone. If you want to reproduce those benchmarks, check out the [`sum` examples on GitHub][sum-examples]. -## Quick Start - -For Python: - -```sh -pip install ucall -``` +## 📝 C API Example -For CMake projects: +UCall provides an ABI-stable C 99 interface. +To use it with the CMake build system: ```cmake include(FetchContent) @@ -300,30 +307,8 @@ int main(int argc, char** argv) { } ``` -## Roadmap - -- [x] Batch Requests -- [x] JSON-RPC over raw TCP sockets -- [x] JSON-RPC over TCP with HTTP -- [x] Concurrent sessions -- [x] NumPy `array` and Pillow serialization -- [ ] HTTP**S** support -- [ ] Batch-capable endpoints for ML -- [ ] Zero-ETL relay calls -- [ ] Integrating with [UKV][ukv] -- [ ] WebSockets for web interfaces -- [ ] AF_XDP and UDP-based analogs on Linux - -> Want to affect the roadmap and request a feature? Join the discussions on Discord. - -## Why JSON-RPC? - -- Transport independent: UDP, TCP, bring what you want. -- Application layer is optional: use HTTP or not. -- Unlike REST APIs, there is just one way to pass arguments. - [simdjson]: https://github.com/simdjson/simdjson [base64]: https://github.com/powturbo/Turbo-Base64 [picohttpparser]: https://github.com/h2o/picohttpparser [sum-examples]: https://github.com/unum-cloud/ucall/tree/dev/examples/sum -[ukv]: https://github.com/unum-cloud/ukv +[ustore]: https://github.com/unum-cloud/ustore From c8870c1e19505bc9a59e91acca4d57963e1ee231 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Thu, 31 Aug 2023 09:18:05 +0200 Subject: [PATCH 02/12] ci: update setup docker action to latest and fix QEMU --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 879adc4..980364b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,9 +44,9 @@ jobs: - name: Setup Docker if: matrix.os != 'windows-2022' - uses: crazy-max/ghaction-setup-docker@v1.0.0 - with: - version: 23.0.1 + uses: crazy-max/ghaction-setup-docker@v1 + env: + SIGN_QEMU_BINARY: 1 - name: Setup QEMU if: matrix.os != 'windows-2022' From b3f3ca10f606d02a1322cca5da2e393644031384 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:21:35 +0000 Subject: [PATCH 03/12] Add: Interface to retrieve header field by name. --- examples/login/ucall_server_rest.cpp | 4 ++ include/ucall/ucall.h | 37 ++++++----- src/headers/backend_core.hpp | 12 ++++ src/headers/parse/protocol/http.hpp | 46 +++++++------- src/headers/parse/protocol/jsonrpc.hpp | 84 ++++++++++++++++--------- src/headers/parse/protocol/protocol.hpp | 18 ++++++ src/headers/parse/protocol/rest.hpp | 82 ++++++++++++++---------- src/headers/parse/protocol/tcp.hpp | 3 + src/headers/shared.hpp | 1 - 9 files changed, 184 insertions(+), 103 deletions(-) diff --git a/examples/login/ucall_server_rest.cpp b/examples/login/ucall_server_rest.cpp index 912b501..4a26bc4 100644 --- a/examples/login/ucall_server_rest.cpp +++ b/examples/login/ucall_server_rest.cpp @@ -25,10 +25,14 @@ static bool get_param_int_from_path(ucall_call_t call, char const* name, int64_t } static void validate_session(ucall_call_t call, ucall_callback_tag_t) { + char const* user_agent = nullptr; + size_t user_agent_len = 0; int64_t a{}, b{}; char c_str[256]{}; + bool got_a = ucall_param_named_i64(call, "user_id", 0, &a); bool got_b = get_param_int_from_path(call, "session_id", &b); + bool got_c = ucall_get_request_header(call, "User-Agent", 0, &user_agent, &user_agent_len); if (!got_a || !got_b) return ucall_call_reply_error_invalid_params(call); diff --git a/include/ucall/ucall.h b/include/ucall/ucall.h index e795b40..4cb0fe2 100644 --- a/include/ucall/ucall.h +++ b/include/ucall/ucall.h @@ -147,28 +147,28 @@ void ucall_take_call(ucall_server_t server, uint16_t thread_idx); */ void ucall_take_calls(ucall_server_t server, uint16_t thread_idx); -bool ucall_param_named_bool( // - ucall_call_t call, // - ucall_str_t json_pointer, // - size_t json_pointer_length, // +bool ucall_param_named_bool( // + ucall_call_t call, // + ucall_str_t param_name, // + size_t param_name_length, // bool* output); -bool ucall_param_named_i64( // - ucall_call_t call, // - ucall_str_t json_pointer, // - size_t json_pointer_length, // +bool ucall_param_named_i64( // + ucall_call_t call, // + ucall_str_t param_name, // + size_t param_name_length, // int64_t* output); -bool ucall_param_named_f64( // - ucall_call_t call, // - ucall_str_t json_pointer, // - size_t json_pointer_length, // +bool ucall_param_named_f64( // + ucall_call_t call, // + ucall_str_t param_name, // + size_t param_name_length, // double* output); -bool ucall_param_named_str( // - ucall_call_t call, // - ucall_str_t json_pointer, // - size_t json_pointer_length, // +bool ucall_param_named_str( // + ucall_call_t call, // + ucall_str_t param_name, // + size_t param_name_length, // ucall_str_t* output, size_t* output_length); bool ucall_param_positional_bool(ucall_call_t, size_t, bool*); @@ -176,6 +176,11 @@ bool ucall_param_positional_i64(ucall_call_t, size_t, int64_t*); bool ucall_param_positional_f64(ucall_call_t, size_t, double*); bool ucall_param_positional_str(ucall_call_t, size_t, ucall_str_t*, size_t*); +bool ucall_get_request_header(ucall_call_t call, // + ucall_str_t header_name, // + size_t header_name_length, // + ucall_str_t* output, size_t* output_length); + /** * @param call Encapsulates the context and the arguments of the current request. * @param json_reply The response to send, which must be a valid JSON string. diff --git a/src/headers/backend_core.hpp b/src/headers/backend_core.hpp index c2b62ba..cf0535c 100644 --- a/src/headers/backend_core.hpp +++ b/src/headers/backend_core.hpp @@ -172,4 +172,16 @@ bool ucall_param_positional_str(ucall_call_t call, size_t position, ucall_str_t* return false; } +bool ucall_get_request_header(ucall_call_t call, ucall_str_t header_name, size_t header_name_length, + ucall_str_t* output, size_t* output_length) { + unum::ucall::automata_t& automata = *reinterpret_cast(call); + size_t name_len = unum::ucall::string_length(header_name, header_name_length); + std::string_view res = automata.get_protocol().get_header({header_name, name_len}); + if (res.size() == 0) + return false; + *output = res.data(); + *output_length = res.size(); + return true; +} + #pragma endregion \ No newline at end of file diff --git a/src/headers/parse/protocol/http.hpp b/src/headers/parse/protocol/http.hpp index 9112235..87a48ce 100644 --- a/src/headers/parse/protocol/http.hpp +++ b/src/headers/parse/protocol/http.hpp @@ -17,18 +17,22 @@ static constexpr char const* http_header_k = static constexpr std::size_t http_header_size_k = 78; static constexpr std::size_t http_header_length_offset_k = 33; static constexpr std::size_t http_header_length_capacity_k = 9; +static constexpr std::size_t http_max_headers_k = 32; struct http_protocol_t { - size_t body_size{}; - /// @brief Expected reception length extracted from HTTP headers. + /// @brief Expected receiving/sending length extracted from HTTP headers. std::optional content_length{}; /// @brief Active parsed request parsed_request_t parsed{}; + phr_header headers[http_max_headers_k]{}; + size_t count_headers = http_max_headers_k; + std::string_view get_content() const noexcept; request_type_t get_request_type() const noexcept; any_param_t get_param(size_t) const noexcept; any_param_t get_param(std::string_view) const noexcept; + std::string_view get_header(std::string_view) const noexcept; inline void prepare_response(exchange_pipes_t& pipes) noexcept; @@ -56,7 +60,7 @@ struct http_protocol_t { inline void http_protocol_t::prepare_response(exchange_pipes_t& pipes) noexcept { pipes.append_reserved(http_header_k, http_header_size_k); - body_size = pipes.output_span().size(); + content_length = pipes.output_span().size(); } inline bool http_protocol_t::append_response(exchange_pipes_t& pipes, std::string_view response) noexcept { @@ -70,9 +74,10 @@ inline bool http_protocol_t::append_error(exchange_pipes_t& pipes, std::string_v inline void http_protocol_t::finalize_response(exchange_pipes_t& pipes) noexcept { auto output = pipes.output_span(); - body_size = output.size() - body_size; - auto res = std::to_chars(output.data() + http_header_length_offset_k, - output.data() + http_header_length_offset_k + http_header_length_capacity_k, body_size); + content_length = output.size() - *content_length; + auto res = + std::to_chars(output.data() + http_header_length_offset_k, + output.data() + http_header_length_offset_k + http_header_length_capacity_k, *content_length); if (res.ec != std::errc()) { // TODO Return error @@ -89,6 +94,16 @@ inline any_param_t http_protocol_t::get_param(size_t) const noexcept { return an inline any_param_t http_protocol_t::get_param(std::string_view) const noexcept { return any_param_t(); } +inline std::string_view http_protocol_t::get_header(std::string_view header_name) const noexcept { + for (std::size_t i = 0; i < count_headers; ++i) { + if (headers[i].name_len == 0) + continue; + if (std::string_view(headers[i].name, headers[i].name_len) == header_name) + return std::string_view(headers[i].value, headers[i].value_len); + } + return std::string_view(); +} + bool http_protocol_t::is_input_complete(span_gt input) noexcept { if (!content_length) { @@ -113,30 +128,21 @@ bool http_protocol_t::is_input_complete(span_gt input) noexcept { * @warning This doesn't check the headers for validity or additional metadata. */ inline std::optional http_protocol_t::parse_headers(std::string_view body) noexcept { - // A typical HTTP-header may look like this - // POST /endpoint HTTP/1.1 - // Host: rpc.example.com - // Content-Type: application/json - // Content-Length: ... - // Accept: application/json - constexpr size_t const max_headers_k = 32; char const* method{}; size_t method_len{}; char const* path{}; size_t path_len{}; int minor_version{}; - phr_header headers[max_headers_k]{}; - size_t count_headers{max_headers_k}; int res = phr_parse_request(body.data(), body.size(), &method, &method_len, &path, &path_len, &minor_version, headers, &count_headers, 0); if (res == -2) - return default_error_t{-206, "Partial HTTP request"}; + return default_error_t{206, "Partial HTTP request"}; if (res < 0) - return default_error_t{-400, "Not a HTTP request"}; + return default_error_t{400, "Not a HTTP request"}; parsed.path = std::string_view(path, path_len); auto type_str = std::string_view(method, method_len); @@ -149,14 +155,12 @@ inline std::optional http_protocol_t::parse_headers(std::string else if (type_str == "DELETE") parsed.type = delete_k; else - return default_error_t{-405, "Unsupported request type"}; + return default_error_t{405, "Unsupported request type"}; for (std::size_t i = 0; i < count_headers; ++i) { if (headers[i].name_len == 0) continue; - if (std::string_view(headers[i].name, headers[i].name_len) == std::string_view("Keep-Alive")) - parsed.keep_alive = std::string_view(headers[i].value, headers[i].value_len); - else if (std::string_view(headers[i].name, headers[i].name_len) == std::string_view("Content-Type")) + if (std::string_view(headers[i].name, headers[i].name_len) == std::string_view("Content-Type")) parsed.content_type = std::string_view(headers[i].value, headers[i].value_len); else if (std::string_view(headers[i].name, headers[i].name_len) == std::string_view("Content-Length")) parsed.content_length = std::string_view(headers[i].value, headers[i].value_len); diff --git a/src/headers/parse/protocol/jsonrpc.hpp b/src/headers/parse/protocol/jsonrpc.hpp index 59b5818..1b5dd0a 100644 --- a/src/headers/parse/protocol/jsonrpc.hpp +++ b/src/headers/parse/protocol/jsonrpc.hpp @@ -26,41 +26,16 @@ template struct jsonrpc_protocol_t { sjd::parser parser{}; std::variant elements{}; - inline any_param_t as_variant(sj::simdjson_result const& elm) const noexcept { - if (elm.is_bool()) - return elm.get_bool().value_unsafe(); - if (elm.is_int64()) - return elm.get_int64().value_unsafe(); - if (elm.is_double()) - return elm.get_double().value_unsafe(); - if (elm.is_string()) - return elm.get_string().value_unsafe(); - return nullptr; - } + inline any_param_t as_variant(sj::simdjson_result const& elm) const noexcept; - inline std::string_view get_content() const noexcept { return base_proto.get_content(); }; + inline std::string_view get_content() const noexcept; request_type_t get_request_type() const noexcept; - inline any_param_t get_param(std::string_view name) const noexcept { - char json_pointer[json_pointer_capacity_k]{}; - bool has_slash = name.size() && name.front() == '/'; - std::size_t final_size = name.size() + 8u - has_slash; - if (final_size > json_pointer_capacity_k) - return nullptr; - std::memcpy((void*)json_pointer, "/params/", 8u); - std::memcpy((void*)(json_pointer + 8u - has_slash), name.data(), name.size()); - return as_variant(active_obj.element.at_pointer({json_pointer, final_size})); - } + inline any_param_t get_param(std::string_view name) const noexcept; - inline any_param_t get_param(std::size_t position) const noexcept { - char json_pointer[json_pointer_capacity_k]{}; - std::memcpy((void*)json_pointer, "/params/", 8u); - std::to_chars_result res = std::to_chars(json_pointer + 8u, json_pointer + json_pointer_capacity_k, position); - if (res.ec != std::errc(0)) - return nullptr; - std::size_t final_size = res.ptr - json_pointer; - return as_variant(active_obj.element.at_pointer({json_pointer, final_size})); - } + inline any_param_t get_param(std::size_t position) const noexcept; + + std::string_view get_header(std::string_view) const noexcept; std::optional set_to(sjd::element const&) noexcept; @@ -183,11 +158,58 @@ inline std::optional jsonrpc_protocol_t::parse return std::nullopt; } +template +inline any_param_t +jsonrpc_protocol_t::as_variant(sj::simdjson_result const& elm) const noexcept { + if (elm.is_bool()) + return elm.get_bool().value_unsafe(); + if (elm.is_int64()) + return elm.get_int64().value_unsafe(); + if (elm.is_double()) + return elm.get_double().value_unsafe(); + if (elm.is_string()) + return elm.get_string().value_unsafe(); + return nullptr; +} + +template +inline std::string_view jsonrpc_protocol_t::get_content() const noexcept { + return base_proto.get_content(); +} + template inline request_type_t jsonrpc_protocol_t::get_request_type() const noexcept { return base_proto.get_request_type(); } +template +inline any_param_t jsonrpc_protocol_t::get_param(std::string_view name) const noexcept { + char json_pointer[json_pointer_capacity_k]{}; + bool has_slash = name.size() && name.front() == '/'; + std::size_t final_size = name.size() + 8u - has_slash; + if (final_size > json_pointer_capacity_k) + return nullptr; + std::memcpy((void*)json_pointer, "/params/", 8u); + std::memcpy((void*)(json_pointer + 8u - has_slash), name.data(), name.size()); + return as_variant(active_obj.element.at_pointer({json_pointer, final_size})); +} + +template +inline any_param_t jsonrpc_protocol_t::get_param(std::size_t position) const noexcept { + char json_pointer[json_pointer_capacity_k]{}; + std::memcpy((void*)json_pointer, "/params/", 8u); + std::to_chars_result res = std::to_chars(json_pointer + 8u, json_pointer + json_pointer_capacity_k, position); + if (res.ec != std::errc(0)) + return nullptr; + std::size_t final_size = res.ptr - json_pointer; + return as_variant(active_obj.element.at_pointer({json_pointer, final_size})); +} + +template +inline std::string_view jsonrpc_protocol_t::get_header(std::string_view header_name) const noexcept { + return base_proto.get_header(header_name); +} + template std::optional jsonrpc_protocol_t::set_to(sjd::element const& doc) noexcept { if (!doc.is_object()) diff --git a/src/headers/parse/protocol/protocol.hpp b/src/headers/parse/protocol/protocol.hpp index a9b180d..47139a5 100644 --- a/src/headers/parse/protocol/protocol.hpp +++ b/src/headers/parse/protocol/protocol.hpp @@ -31,6 +31,7 @@ class protocol_t { request_type_t get_request_type() const noexcept; any_param_t get_param(size_t) const noexcept; any_param_t get_param(std::string_view) const noexcept; + std::string_view get_header(std::string_view) const noexcept; void prepare_response(exchange_pipes_t&) noexcept; bool append_response(exchange_pipes_t&, std::string_view) noexcept; @@ -151,6 +152,23 @@ inline any_param_t protocol_t::get_param(std::string_view param_name) const noex return nullptr; } +inline std::string_view protocol_t::get_header(std::string_view header_name) const noexcept { + switch (type) { + case protocol_type_t::tcp_k: + return std::get(sp_proto).get_header(header_name); + case protocol_type_t::http_k: + return std::get(sp_proto).get_header(header_name); + case protocol_type_t::jsonrpc_tcp_k: + return std::get>(sp_proto).get_header(header_name); + case protocol_type_t::jsonrpc_http_k: + return std::get>(sp_proto).get_header(header_name); + case protocol_type_t::rest_k: + return std::get(sp_proto).get_header(header_name); + } + + return std::string_view(); +} + void protocol_t::prepare_response(exchange_pipes_t& pipes) noexcept { switch (type) { case protocol_type_t::tcp_k: diff --git a/src/headers/parse/protocol/rest.hpp b/src/headers/parse/protocol/rest.hpp index 5eb5efe..0ece17d 100644 --- a/src/headers/parse/protocol/rest.hpp +++ b/src/headers/parse/protocol/rest.hpp @@ -27,44 +27,16 @@ struct rest_protocol_t { sjd::parser parser{}; std::variant elements{}; - inline any_param_t as_variant(sj::simdjson_result const& elm) const noexcept { - if (elm.is_bool()) - return elm.get_bool().value_unsafe(); - if (elm.is_int64()) - return elm.get_int64().value_unsafe(); - if (elm.is_double()) - return elm.get_double().value_unsafe(); - if (elm.is_string()) - return elm.get_string().value_unsafe(); - return nullptr; - } + inline any_param_t as_variant(sj::simdjson_result const& elm) const noexcept; - inline std::string_view get_content() const noexcept { return base_proto.get_content(); }; + inline std::string_view get_content() const noexcept; request_type_t get_request_type() const noexcept; - inline any_param_t get_param(std::string_view name) const noexcept { - char json_pointer[json_pointer_capacity_k]{}; - bool has_slash = name.size() && name.front() == '/'; - std::size_t final_size = name.size() + 1u - has_slash; - if (final_size > json_pointer_capacity_k) - return nullptr; - std::memcpy((void*)(json_pointer), "/", 1); - std::memcpy((void*)(json_pointer + 1u - has_slash), name.data(), name.size()); - auto from_body = as_variant(active_obj.element.at_pointer({json_pointer, final_size})); - if (!std::holds_alternative(from_body)) - return from_body; - - json_pointer[0] = '{'; - json_pointer[final_size++] = '}'; - size_t position_in_path = active_obj.method_name.find({json_pointer, final_size}, 0); - if (position_in_path == std::string_view::npos) - return nullptr; - size_t len = std::count_if(base_proto.parsed.path.begin() + position_in_path, base_proto.parsed.path.end(), - [](char sym) { return sym != '/'; }); - return std::string_view{base_proto.parsed.path.data() + position_in_path, len}; - } + inline any_param_t get_param(std::string_view name) const noexcept; + + inline any_param_t get_param(std::size_t position) const noexcept; - inline any_param_t get_param(std::size_t position) const noexcept { return nullptr; } + std::string_view get_header(std::string_view) const noexcept; std::optional set_to(sjd::element const&) noexcept; @@ -149,8 +121,50 @@ inline std::optional rest_protocol_t::parse_content() noexcept return std::nullopt; } +inline any_param_t rest_protocol_t::as_variant(sj::simdjson_result const& elm) const noexcept { + if (elm.is_bool()) + return elm.get_bool().value_unsafe(); + if (elm.is_int64()) + return elm.get_int64().value_unsafe(); + if (elm.is_double()) + return elm.get_double().value_unsafe(); + if (elm.is_string()) + return elm.get_string().value_unsafe(); + return nullptr; +} + +inline std::string_view rest_protocol_t::get_content() const noexcept { return base_proto.get_content(); } + inline request_type_t rest_protocol_t::get_request_type() const noexcept { return base_proto.get_request_type(); } +inline any_param_t rest_protocol_t::get_param(std::string_view name) const noexcept { + char json_pointer[json_pointer_capacity_k]{}; + bool has_slash = name.size() && name.front() == '/'; + std::size_t final_size = name.size() + 1u - has_slash; + if (final_size > json_pointer_capacity_k) + return nullptr; + std::memcpy((void*)(json_pointer), "/", 1); + std::memcpy((void*)(json_pointer + 1u - has_slash), name.data(), name.size()); + auto from_body = as_variant(active_obj.element.at_pointer({json_pointer, final_size})); + if (!std::holds_alternative(from_body)) + return from_body; + + json_pointer[0] = '{'; + json_pointer[final_size++] = '}'; + size_t position_in_path = active_obj.method_name.find({json_pointer, final_size}, 0); + if (position_in_path == std::string_view::npos) + return nullptr; + size_t len = std::count_if(base_proto.parsed.path.begin() + position_in_path, base_proto.parsed.path.end(), + [](char sym) { return sym != '/'; }); + return std::string_view{base_proto.parsed.path.data() + position_in_path, len}; +} + +inline any_param_t rest_protocol_t::get_param(std::size_t position) const noexcept { return nullptr; } + +inline std::string_view rest_protocol_t::get_header(std::string_view header_name) const noexcept { + return base_proto.get_header(header_name); +} + std::optional rest_protocol_t::set_to(sjd::element const& doc) noexcept { if (!doc.is_object()) return default_error_t{400, "The JSON sent is not a valid request object."}; diff --git a/src/headers/parse/protocol/tcp.hpp b/src/headers/parse/protocol/tcp.hpp index c0b9492..063b317 100644 --- a/src/headers/parse/protocol/tcp.hpp +++ b/src/headers/parse/protocol/tcp.hpp @@ -17,6 +17,7 @@ struct tcp_protocol_t { request_type_t get_request_type() const noexcept; any_param_t get_param(size_t) const noexcept; any_param_t get_param(std::string_view) const noexcept; + std::string_view get_header(std::string_view) const noexcept; inline void prepare_response(exchange_pipes_t& pipes) noexcept; @@ -46,6 +47,8 @@ inline any_param_t tcp_protocol_t::get_param(size_t) const noexcept { return any inline any_param_t tcp_protocol_t::get_param(std::string_view) const noexcept { return any_param_t(); } +inline std::string_view tcp_protocol_t::get_header(std::string_view) const noexcept { return std::string_view(); } + inline void tcp_protocol_t::prepare_response(exchange_pipes_t& pipes) noexcept {} inline bool tcp_protocol_t::append_response(exchange_pipes_t& pipes, std::string_view response) noexcept { diff --git a/src/headers/shared.hpp b/src/headers/shared.hpp index 0d749dc..9e7d440 100644 --- a/src/headers/shared.hpp +++ b/src/headers/shared.hpp @@ -49,7 +49,6 @@ template constexpr std::size_t round_up_to(std::size_t n) struct parsed_request_t { request_type_t type{}; std::string_view path{}; - std::string_view keep_alive{}; std::string_view content_type{}; std::string_view content_length{}; std::string_view body{}; From c44f050273356b284b52f82a55d317583b9cf262 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:59:40 +0000 Subject: [PATCH 04/12] Add: interface for callback to get raw content --- examples/login/ucall_server_rest.cpp | 24 +++++++++++++++++--- include/ucall/ucall.h | 3 +++ src/headers/backend_core.hpp | 7 ++++++ src/headers/parse/protocol/rest.hpp | 34 ++++++++++++---------------- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/examples/login/ucall_server_rest.cpp b/examples/login/ucall_server_rest.cpp index 4a26bc4..78521ec 100644 --- a/examples/login/ucall_server_rest.cpp +++ b/examples/login/ucall_server_rest.cpp @@ -25,14 +25,12 @@ static bool get_param_int_from_path(ucall_call_t call, char const* name, int64_t } static void validate_session(ucall_call_t call, ucall_callback_tag_t) { - char const* user_agent = nullptr; - size_t user_agent_len = 0; + int64_t a{}, b{}; char c_str[256]{}; bool got_a = ucall_param_named_i64(call, "user_id", 0, &a); bool got_b = get_param_int_from_path(call, "session_id", &b); - bool got_c = ucall_get_request_header(call, "User-Agent", 0, &user_agent, &user_agent_len); if (!got_a || !got_b) return ucall_call_reply_error_invalid_params(call); @@ -40,6 +38,24 @@ static void validate_session(ucall_call_t call, ucall_callback_tag_t) { ucall_call_reply_content(call, res, strlen(res)); } +static void upload_binary(ucall_call_t call, ucall_callback_tag_t) { + char const* user_agent = nullptr; + char const* binary = nullptr; + char const* path = nullptr; + size_t user_agent_len = 0; + size_t binary_len = 0; + size_t path_len = 0; + + bool got_path = ucall_param_named_str(call, "path", 0, &path, &path_len); + bool got_bin = ucall_get_request_body(call, &binary, &binary_len); + bool got_head = ucall_get_request_header(call, "User-Agent", 0, &user_agent, &user_agent_len); + if (!got_bin || !got_head) + return ucall_call_reply_error_invalid_params(call); + + std::string res = "{\"uploaded_binary_size\": " + std::to_string(binary_len) + "}"; + ucall_call_reply_content(call, res.data(), res.size()); +} + static void get_books(ucall_call_t call, ucall_callback_tag_t punned_books) noexcept { auto* books = reinterpret_cast*>(punned_books); std::string response = "{ \"books\": ["; @@ -143,6 +159,8 @@ int main(int argc, char** argv) { ucall_add_procedure(server, "/validate_session/{session_id}", &validate_session, request_type_t::get_k, nullptr); + ucall_add_procedure(server, "/upload_binary/{path}", &upload_binary, request_type_t::put_k, nullptr); + if (config.max_threads > 1) { std::vector threads; for (uint16_t i = 0; i != config.max_threads; ++i) diff --git a/include/ucall/ucall.h b/include/ucall/ucall.h index 4cb0fe2..1aff2a4 100644 --- a/include/ucall/ucall.h +++ b/include/ucall/ucall.h @@ -181,6 +181,9 @@ bool ucall_get_request_header(ucall_call_t call, // size_t header_name_length, // ucall_str_t* output, size_t* output_length); +bool ucall_get_request_body(ucall_call_t call, // + ucall_str_t* output, size_t* output_length); + /** * @param call Encapsulates the context and the arguments of the current request. * @param json_reply The response to send, which must be a valid JSON string. diff --git a/src/headers/backend_core.hpp b/src/headers/backend_core.hpp index cf0535c..bcad119 100644 --- a/src/headers/backend_core.hpp +++ b/src/headers/backend_core.hpp @@ -184,4 +184,11 @@ bool ucall_get_request_header(ucall_call_t call, ucall_str_t header_name, size_t return true; } +bool ucall_get_request_body(ucall_call_t call, ucall_str_t* output, size_t* output_length) { + unum::ucall::automata_t& automata = *reinterpret_cast(call); + std::string_view body = automata.get_protocol().get_content(); + *output = body.data(); + *output_length = body.size(); + return true; +} #pragma endregion \ No newline at end of file diff --git a/src/headers/parse/protocol/rest.hpp b/src/headers/parse/protocol/rest.hpp index 0ece17d..9138b6c 100644 --- a/src/headers/parse/protocol/rest.hpp +++ b/src/headers/parse/protocol/rest.hpp @@ -25,7 +25,7 @@ struct rest_protocol_t { http_protocol_t base_proto{}; rest_obj active_obj{}; sjd::parser parser{}; - std::variant elements{}; + std::variant elements{}; inline any_param_t as_variant(sj::simdjson_result const& elm) const noexcept; @@ -38,8 +38,6 @@ struct rest_protocol_t { std::string_view get_header(std::string_view) const noexcept; - std::optional set_to(sjd::element const&) noexcept; - inline void prepare_response(exchange_pipes_t& pipes) noexcept; bool append_response(exchange_pipes_t&, std::string_view) noexcept; @@ -99,9 +97,11 @@ inline std::optional rest_protocol_t::parse_headers(std::string inline std::optional rest_protocol_t::parse_content() noexcept { std::string_view json_doc = base_proto.get_content(); - if (base_proto.parsed.content_type != "application/json") - return default_error_t{415, "Unsupported: Only application/json is currently supported"}; - + if (base_proto.parsed.content_type != "application/json") { + elements.emplace(); + return std::nullopt; // Only json parser is currently implemented + // return default_error_t{415, "Unsupported: Only application/json is currently supported"}; + } if (json_doc.size() > parser.capacity()) { if (parser.allocate(json_doc.size(), json_doc.size() / 2) != sj::SUCCESS) return default_error_t{500, "Out of memory"}; @@ -145,9 +145,11 @@ inline any_param_t rest_protocol_t::get_param(std::string_view name) const noexc return nullptr; std::memcpy((void*)(json_pointer), "/", 1); std::memcpy((void*)(json_pointer + 1u - has_slash), name.data(), name.size()); - auto from_body = as_variant(active_obj.element.at_pointer({json_pointer, final_size})); - if (!std::holds_alternative(from_body)) - return from_body; + if (!std::holds_alternative(elements)) { + auto from_body = as_variant(active_obj.element.at_pointer({json_pointer, final_size})); + if (!std::holds_alternative(from_body)) + return from_body; + } json_pointer[0] = '{'; json_pointer[final_size++] = '}'; @@ -165,15 +167,6 @@ inline std::string_view rest_protocol_t::get_header(std::string_view header_name return base_proto.get_header(header_name); } -std::optional rest_protocol_t::set_to(sjd::element const& doc) noexcept { - if (!doc.is_object()) - return default_error_t{400, "The JSON sent is not a valid request object."}; - - active_obj.method_name = base_proto.parsed.path; - active_obj.element = doc; - return std::nullopt; -} - template inline std::optional rest_protocol_t::populate_response(exchange_pipes_t& pipes, calle_at find_and_call) noexcept { @@ -184,7 +177,10 @@ inline std::optional rest_protocol_t::populate_response(exchang // return default_error_t{-32601, "Method not found"}; // } // } else { - set_to(std::get(elements)); + active_obj.method_name = base_proto.parsed.path; + if (std::holds_alternative(elements)) + active_obj.element = std::get(elements); + if (!find_and_call(active_obj.method_name, get_request_type())) return default_error_t{404, "Method not found"}; // } From 3bcfdb282f10b108bd76415e4b075794ca3e5eaf Mon Sep 17 00:00:00 2001 From: Gurgen Yegoryan <21982202+gurgenyegoryan@users.noreply.github.com> Date: Fri, 1 Sep 2023 15:31:44 +0400 Subject: [PATCH 05/12] Make: Add rebase action --- .github/workflows/release.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 980364b..6f05e86 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,31 @@ jobs: - run: cp .github/workflows/package.json . && npm install && npx semantic-release + rebase: + name: Rebase Dev. Branch + needs: versioning + runs-on: ubuntu-latest + steps: + - name: Checkout the latest code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Perform rebase + run: | + git fetch origin main + git checkout main-dev + git rebase origin/main + + - name: Push changes + uses: CasperWA/push-protected@v2 + with: + token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + branch: main-dev + unprotect_reviews: True + force: True + + build_wheels: name: Build Wheels for ${{ matrix.os }} runs-on: ${{ matrix.os }} From 3d6ea0276d8cc8dd9d77e89fae2a2c554d866b89 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:24:25 +0000 Subject: [PATCH 06/12] Add: TLS test for FastAPI --- examples/login/fastapi_client.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/examples/login/fastapi_client.py b/examples/login/fastapi_client.py index 2b2f46c..b698f51 100644 --- a/examples/login/fastapi_client.py +++ b/examples/login/fastapi_client.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import httpx import os import struct import random @@ -17,7 +18,7 @@ class ClientREST: - def __init__(self, uri: str = '127.0.0.1', port: int = 8000, identity: int = PROCESS_ID) -> None: + def __init__(self, uri: str = '127.0.0.1', port: int = 8545, identity: int = PROCESS_ID) -> None: self.identity = identity self.url = f'http://{uri}:{port}/' @@ -26,11 +27,28 @@ def __call__(self, *, a: Optional[int] = None, b: Optional[int] = None) -> int: b = random.randint(1, 1000) if b is None else b result = requests.get( f'{self.url}validate_session?user_id={a}&session_id={b}').text - c = int(result) + c = False if result == 'false' else True assert ((a ^ b) % 23 == 0) == c, 'Wrong Answer' return c +class ClientTLSREST: + + def __init__(self, uri: str = '127.0.0.1', port: int = 8545, identity: int = PROCESS_ID) -> None: + self.identity = identity + self.url = f'https://{uri}:{port}/' + + def __call__(self, *, a: Optional[int] = None, b: Optional[int] = None) -> int: + with httpx.Client(verify=False) as client: + a = random.randint(1, 1000) if a is None else a + b = random.randint(1, 1000) if b is None else b + result = client.get( + f'{self.url}validate_session?user_id={a}&session_id={b}').text + c = False if result == 'false' else True + assert ((a ^ b) % 23 == 0) == c, 'Wrong Answer' + return c + + class ClientRESTReddit: def __init__(self, uri: str = '127.0.0.1', port: int = 8000, identity: int = PROCESS_ID) -> None: From 76f46f0fd6ce5b3bae1cdab4fa4b9bfcd05c77bd Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:10:02 +0000 Subject: [PATCH 07/12] Fix: http header count reset --- src/headers/parse/protocol/http.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/headers/parse/protocol/http.hpp b/src/headers/parse/protocol/http.hpp index 87a48ce..7b5b231 100644 --- a/src/headers/parse/protocol/http.hpp +++ b/src/headers/parse/protocol/http.hpp @@ -134,6 +134,7 @@ inline std::optional http_protocol_t::parse_headers(std::string char const* path{}; size_t path_len{}; int minor_version{}; + count_headers = http_max_headers_k; int res = phr_parse_request(body.data(), body.size(), &method, &method_len, &path, &path_len, &minor_version, headers, &count_headers, 0); From e62487d5944d8c366be45491df2dc75e7da65b03 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:10:14 +0000 Subject: [PATCH 08/12] Add: TLS non reusing Case --- examples/login/jsonrpc_client.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/examples/login/jsonrpc_client.py b/examples/login/jsonrpc_client.py index d30c193..4ce03f9 100644 --- a/examples/login/jsonrpc_client.py +++ b/examples/login/jsonrpc_client.py @@ -153,6 +153,35 @@ def recv(self): return self.response.json +class CaseTLSNoReuse: + """JSON-RPC Client that operates directly over TCP/IPv4 stack, without HTTP""" + + def __init__(self, uri: str = '127.0.0.1', port: int = 8545, identity: int = PROCESS_ID) -> None: + self.identity = identity + self.expected = -1 + self.client = ClientTLS(uri, port, allow_self_signed=True) + self.response = None + + def __call__(self) -> str: + self.send() + res = self.recv() + self.client.sock.close() + self.client.sock = None + return res + + def send(self): + user_id = random.randint(1, 1000) + session_id = random.randint(1, 1000) + self.expected = ((user_id ^ session_id) % 23) == 0 + + self.response = self.client.validate_session( + user_id=user_id, session_id=session_id) + + def recv(self): + assert self.response.json == self.expected + return self.response.json + + class CaseTCPHTTP: """JSON-RPC Client that operates directly over TCP/IPv4 stack, with HTTP""" From 80cd29232def079e3dcec2b6c4bf58484aae67aa Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:45:39 +0000 Subject: [PATCH 09/12] Fix: Decrypt TLS requests fully Add: Handle `Connection: close` header --- src/headers/automata.hpp | 8 ++++---- src/headers/connection.hpp | 18 +++++++++++++++--- src/headers/containers.hpp | 3 +++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/headers/automata.hpp b/src/headers/automata.hpp index 878b761..988e62e 100644 --- a/src/headers/automata.hpp +++ b/src/headers/automata.hpp @@ -165,10 +165,10 @@ void automata_t::operator()() noexcept { connection.pipes.mark_submitted_outputs(completed_result); if (!connection.pipes.has_remaining_outputs()) { connection.exchanges++; - // if (connection.exchanges >= server.max_lifetime_exchanges) TODO Why? - // return close_gracefully(); - // else - return receive_next(); + if (connection.must_close()) + return close_gracefully(); + else + return receive_next(); } else { connection.pipes.prepare_more_outputs(); return send_next(); diff --git a/src/headers/connection.hpp b/src/headers/connection.hpp index 22f8eb9..2a03a79 100644 --- a/src/headers/connection.hpp +++ b/src/headers/connection.hpp @@ -72,10 +72,15 @@ struct connection_t { bool expired() const noexcept { return std::chrono::high_resolution_clock::now().time_since_epoch().count() - last_active_ns > max_inactive_duration_ns_k; - }; + } bool is_ready() const noexcept { return tls_ctx == nullptr || ptls_handshake_is_complete(tls_ctx); } + bool must_close() const noexcept { + auto conn = protocol.get_header("Connection"); + return conn == "Close" || conn == "close"; + } + bool prepare_step() noexcept { if (is_ready()) return true; @@ -114,10 +119,17 @@ struct connection_t { return; work_buf.off = 0; + int res = 0; size_t in_len = pipes.input_span().size(); - int res = ptls_receive(tls_ctx, &work_buf, pipes.input_span().data(), &in_len); + void const* input = pipes.input_span().data(); + while (in_len != 0 && res != -1) { + size_t consumed = in_len; + res = ptls_receive(tls_ctx, &work_buf, input, &consumed); + in_len -= consumed; + input += consumed; + } if (res != -1) { - pipes.release_inputs(); + pipes.release_current_input(); std::memcpy(pipes.next_input_address(), work_buf.base, work_buf.off); pipes.absorb_input(work_buf.off); } diff --git a/src/headers/containers.hpp b/src/headers/containers.hpp index 6976c68..9818f51 100644 --- a/src/headers/containers.hpp +++ b/src/headers/containers.hpp @@ -218,6 +218,9 @@ class exchange_pipes_t { input_.dynamic.reset(); input_.embedded_used = 0; } + + void release_current_input() noexcept { input_.embedded_used = 0; } + void release_outputs() noexcept { output_.dynamic.reset(); output_.embedded_used = 0; From 92e24ea7d41a27d2e4e4b796323d85dc983b0d43 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 7 Sep 2023 14:52:49 +0000 Subject: [PATCH 10/12] Fix: Parted TLS requests --- src/headers/automata.hpp | 2 +- src/headers/connection.hpp | 10 +++++++--- src/headers/containers.hpp | 7 +++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/headers/automata.hpp b/src/headers/automata.hpp index 988e62e..2a01a55 100644 --- a/src/headers/automata.hpp +++ b/src/headers/automata.hpp @@ -117,7 +117,7 @@ void automata_t::operator()() noexcept { // If we have reached the end of the stream, // it is time to analyze the contents // and send back a response. - connection.decrypt(); + connection.decrypt(completed_result); if (connection.protocol.is_input_complete(connection.pipes.input_span())) { server.engine.raise_request(connection.pipes, connection.protocol, this); diff --git a/src/headers/connection.hpp b/src/headers/connection.hpp index 2a03a79..34b6ef0 100644 --- a/src/headers/connection.hpp +++ b/src/headers/connection.hpp @@ -114,7 +114,7 @@ struct connection_t { } } - void decrypt() noexcept { + void decrypt(size_t received_amount) noexcept { if (tls_ctx == nullptr || !ptls_handshake_is_complete(tls_ctx)) return; @@ -122,14 +122,18 @@ struct connection_t { int res = 0; size_t in_len = pipes.input_span().size(); void const* input = pipes.input_span().data(); + if (received_amount != in_len) { + input += (in_len - received_amount); + in_len = received_amount; + } while (in_len != 0 && res != -1) { size_t consumed = in_len; res = ptls_receive(tls_ctx, &work_buf, input, &consumed); in_len -= consumed; input += consumed; } - if (res != -1) { - pipes.release_current_input(); + if (res != -1 && work_buf.off > 0) { + pipes.drop_last_input(received_amount); std::memcpy(pipes.next_input_address(), work_buf.base, work_buf.off); pipes.absorb_input(work_buf.off); } diff --git a/src/headers/containers.hpp b/src/headers/containers.hpp index 9818f51..8383d7c 100644 --- a/src/headers/containers.hpp +++ b/src/headers/containers.hpp @@ -246,6 +246,13 @@ class exchange_pipes_t { std::memmove(input_.embedded, input_.embedded + cnt, input_.embedded_used); } + void drop_last_input(size_t cnt) noexcept { + if (input_.dynamic.size() >= cnt) + input_.dynamic.pop_back(cnt); + else if (input_.embedded_used >= cnt) + input_.embedded_used -= cnt; + } + bool shift_input_to_dynamic() noexcept { if (!input_.dynamic.append_n(input_.embedded, input_.embedded_used)) return false; From f31a6886c1ec1f306130e3bc939c92e736bf7c4a Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 7 Sep 2023 14:58:06 +0000 Subject: [PATCH 11/12] Add: A benchmark case with python `httpx` for non reuse --- examples/login/jsonrpc_client.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/examples/login/jsonrpc_client.py b/examples/login/jsonrpc_client.py index 4ce03f9..5f1b660 100644 --- a/examples/login/jsonrpc_client.py +++ b/examples/login/jsonrpc_client.py @@ -1,3 +1,4 @@ +import httpx import os import json import errno @@ -182,6 +183,28 @@ def recv(self): return self.response.json +class CaseTLSNoReuseX: + + def __init__(self, uri: str = '127.0.0.1', port: int = 8545, identity: int = PROCESS_ID) -> None: + self.identity = identity + self.url = f'https://{uri}:{port}/' + + def __call__(self, *, a: Optional[int] = None, b: Optional[int] = None) -> int: + + with httpx.Client(verify=False) as client: + a = random.randint(1, 1000) if a is None else a + b = random.randint(1, 1000) if b is None else b + jsonrpc = {"jsonrpc": "2.0", "id": self.identity, + "method": "validate_session", "params": {"user_id": a, "session_id": b}} + result = client.post( + f'{self.url}', json=jsonrpc, + headers={"Connection": "close"}, + ) + result = result.text + c = False if result == 'false' else True + return c + + class CaseTCPHTTP: """JSON-RPC Client that operates directly over TCP/IPv4 stack, with HTTP""" From 07a08cc5a4ec9c68ab53825609b7658c8adf5616 Mon Sep 17 00:00:00 2001 From: Ishkhan Nazaryan <105867377+ishkhan42@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:14:48 +0000 Subject: [PATCH 12/12] Build: Fix mac build --- src/headers/connection.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/headers/connection.hpp b/src/headers/connection.hpp index 34b6ef0..bd202a0 100644 --- a/src/headers/connection.hpp +++ b/src/headers/connection.hpp @@ -123,16 +123,17 @@ struct connection_t { size_t in_len = pipes.input_span().size(); void const* input = pipes.input_span().data(); if (received_amount != in_len) { - input += (in_len - received_amount); + input = static_cast(input) + (in_len - received_amount); in_len = received_amount; } while (in_len != 0 && res != -1) { size_t consumed = in_len; res = ptls_receive(tls_ctx, &work_buf, input, &consumed); in_len -= consumed; - input += consumed; + input = static_cast(input) + consumed; } if (res != -1 && work_buf.off > 0) { + printf("WB: %i\n", work_buf.off); pipes.drop_last_input(received_amount); std::memcpy(pipes.next_input_address(), work_buf.base, work_buf.off); pipes.absorb_input(work_buf.off);