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
-
+
-
+
-
+
-
+
-
+
+
+
+
+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);