# UCall

> Client-server communication engine built around Noise, io_uring and SIMD

## Introduction

> What would a client-server communication engine look like built around Noise, io_uring, and SIMD?
> UCall keeps application handlers synchronous while its connection state machines overlap network I/O.

UCall is a ¹ performance-oriented, ² portable, and ³ compact client-server communication engine, with a ⁴ twist!
How ¹ fast?
About 291,000 1 KB echoes per second from one server thread natively, about 278,000 through its Rust bindings and about 268,000 through its Python bindings, against about 83,000 for gRPC on tonic, 21,000 for FastAPI and 15,000 for Python gRPC, in the [framework comparison](https://github.com/unum-cloud/UCall/blob/main/bench/README.md#framework-comparison).
How ² portable?
A C ABI fronts a C++20 core with POSIX, Linux epoll with AIO and io_uring, macOS and FreeBSD kqueue, and Windows IOCP backends.
IOCP runs one event loop per interface.
Python and Rust bindings wrap that ABI directly, with the same caller-owned polling threads.
How ³ compact?
HTTP framing, routing, and transport state live in templated headers, with one native library exporting the C interface.
What's the ⁴ twist?
For encrypted native connections, it uses the Noise secure communication protocol instead of implementing TLS.

Moreover, it's easy to use synchronous handlers, even when the underlying backend uses asynchronous IO like Linux `io_uring`.
And it provides server-side batching for high-latency batch-processing endpoints, like GPU inference tasks and OLAP database queries.
The [native examples](https://github.com/unum-cloud/UCall/blob/main/examples/README.md) demonstrate binary echo, conditional key-value writes, atomic ledger transfers, and batched inference through the C ABI.

## FastAPI UX Comparison

Both columns serve the same typed endpoint; UCall's [Python bindings](https://www.unum.cloud/docs/ucall/python.md) bind its parameters in C and answer on the polling thread.

<table width="100%"><tbody>
<tr>
<th width="50%">FastAPI</th><th width="50%">UCall</th>
</tr>
<tr>
<td>

```sh
pip install fastapi uvicorn
```

</td>
<td>

```sh
pip install ucall
```

</td>
</tr>
<tr>
<td>

```python
from fastapi import FastAPI

server = FastAPI()

@server.get('/sum')
def sum(a: int, b: int):
    return a + b

import uvicorn
uvicorn.run(server, host='0.0.0.0', port=8000)
```

</td>
<td>

```python
import ucall

server = ucall.Server(ucall.Interface('0.0.0.0:8000'))

@server.get('/sum')
def sum(a: int, b: int):
    return a + b

server.run()
```

</td>
</tr>
</tbody></table>

## Build From Source

A source build needs CMake 3.28 and a C++20 compiler, and pulls StringZilla, ForkUnion and SmashTable as submodules:

```sh
git clone --recursive https://github.com/unum-cloud/UCall.git && cd UCall
cmake -B build_release -D CMAKE_BUILD_TYPE=Release -D UCALL_BUILD_SHARED=ON
cmake --build build_release --parallel
cmake --install build_release --prefix ~/.local
```

> The install holds the C headers, `libucall_static`, `libucall_shared`, a `ucall.pc`, and a CMake package naming `ucall::static` and `ucall::shared`, which a C program links like any C library.
> It also holds `ucall`, a command-line client for one HTTP or Noise call, as in `ucall -d @body.json http://127.0.0.1:8080/echo`.
> `-H` adds a header line, `-d @-` reads the body from standard input, `-i` prints the status line and every response field before the body, and `--server-key` pins the server for an `http+noise://` URL.
> `ucall keygen FILE` writes a fresh Noise private key and prints its public half, and the exit code is 0 for a 2xx answer and the status's hundreds digit otherwise.
> Leaving `UCALL_BUILD_SHARED` off installs the archive without the shared library and its `ucall.pc`.

`pip install .` builds the Python bindings from the same checkout, and `cargo build` the Rust crate; the [contributing guide](https://github.com/unum-cloud/UCall/blob/main/CONTRIBUTING.md) lists the presets, tests and cross builds.

## C Quickstart

One callback reads a request's body and replies with it, and `main` initializes the server, registers the route, and polls its one loop on the calling thread.
The listing is [`examples/hello.c`](https://github.com/unum-cloud/UCall/blob/main/examples/hello.c), which CI links against both installed libraries:

```c
#include <stdio.h>

#include <ucall/ucall.h>

static void echo(ucall_received_http_request_t request, void* context) {
    ucall_server_t server = *(ucall_server_t*)context;
    ucall_outcome_t outcome = {0};
    ucall_bytes_view_t body = {0};
    ucall_server_request_body_t read = {.server = server, .outcome = &outcome, .request = request, .body = &body};
    ucall_server_request_body(&read);
    ucall_server_reply_content_t reply = {.server = server, .outcome = &outcome, .request = request, .body = body};
    ucall_server_reply_content(&reply);
}

int main(void) {
    ucall_network_interface_t listener = {
        .address = {(ucall_byte_t const*)"127.0.0.1:8080", 14},
        .max_concurrent_connections = 1024,
    };
    ucall_server_options_t options = {.interfaces = &listener, .interfaces_count = 1};
    ucall_server_t server = NULL;
    ucall_outcome_t outcome = {0};
    ucall_server_init_t init = {.server = &server, .outcome = &outcome, .options = &options};
    ucall_server_init(&init);
    if (outcome.status != ucall_status_success_k) {
        fprintf(stderr, "%s\n", outcome.message);
        return 1;
    }

    ucall_server_add_http_endpoint_t route = {.server = server,
                                              .outcome = &outcome,
                                              .uri_template = {(ucall_byte_t const*)"/echo", 5},
                                              .verb = ucall_verb_post_k,
                                              .callback = echo,
                                              .context = &server};
    ucall_server_add_http_endpoint(&route);
    ucall_server_take_calls_t poll = {.server = server, .outcome = &outcome};
    ucall_server_take_calls(&poll);
    ucall_server_free(server);
    return outcome.status == ucall_status_success_k ? 0 : 1;
}
```

```sh
export PKG_CONFIG_PATH=~/.local/lib/pkgconfig LD_LIBRARY_PATH=~/.local/lib
cc examples/hello.c $(pkg-config --cflags --libs ucall) -o hello && ./hello &
curl -X POST --data 'Hello, UCall!' http://127.0.0.1:8080/echo
```

## Rust Quickstart

The crate wraps the same ABI, with closures for handlers and threads the application owns for polling:

```rust
use std::time::Duration;
use ucall::*;

let interface = Interface::new("127.0.0.1:0").pipelined_requests(16);
let mut server = Server::new(&[interface])?;
server.post("/sum", |request: Request| {
    let sum: u64 = request.body().iter().map(|&byte| u64::from(byte)).sum();
    let _ = request.reply(HTTPStatus::OK, &[], sum.to_string().as_bytes());
})?;
let batching = Batching { min_size: 8, max_size: 64, max_latency: Duration::from_micros(500) };
let infer = server.batch("/infer", Verb::Post, batching, |batch: Batch| {
    for request in batch {
        let _ = request.reply(HTTPStatus::OK, &[], b"scored");
    }
})?;
let address = server.address(0).unwrap().to_owned();

let mut client = Client::new(ClientOptions { connections: 4, ..Default::default() })?;
std::thread::scope(|scope| {
    for event_loop in 0..server.event_loops() {
        let server = &server;
        scope.spawn(move || server.take_calls(event_loop));
    }
    scope.spawn(|| infer.take_batches());
    let sum = client.post(format!("http://{address}/sum"), vec![1, 2, 3]);
    let scored = client.post(format!("http://{address}/infer"), b"pixels".as_slice());
    server.stop();
    assert_eq!(sum?.body(), b"6");
    assert_eq!(scored?.body(), b"scored");
    Ok::<(), Error>(())
})?;
```

The [Rust guide](https://www.unum.cloud/docs/ucall/rust.md) covers streaming, files, Noise and metrics.

## Performance

The native [benchmark driver](https://github.com/unum-cloud/UCall/blob/main/bench/README.md) measures complete HTTP exchanges through the C ABI, including connection reuse, response validation, throughput, latency percentiles through p99.99, and client CPU time per request.
It runs closed-loop by default, or open-loop at a fixed arrival rate that charges each call its delay from the intended send time.
Each run records the selected backend, CPU kernel class, application polling threads, connection budget, and calls in flight.
On the server side, `ucall_server_event_loop_statistics()` counts each loop's submissions, completions, kernel enters, and copied bytes, which the echo example prints on exit to price one request.
The [framework comparison](https://github.com/unum-cloud/UCall/blob/main/bench/README.md#framework-comparison) serves a 1 KB loopback echo and a typed sum through UCall natively and through its Rust and Python bindings, axum, actix-web, FastAPI and Python gRPC from the same cores.
The guide also measures pipelining depth, Noise against TLS 1.3 behind nginx, scaling across event loops, and 10,000 connections.
The [C ABI page](https://www.unum.cloud/docs/ucall/c.md) describes the implemented native surface; SDK and authorization plans below are labeled as such.

## Design Principles

### Asynchronous Engine for Synchronous Applications

Talking to remote services over a network can cost much more than the local computation that handles a request.
Moreover, the packet exchange between the application and the network card involves multiple context switches and kernel calls.
To mitigate these latencies, UCall uses several asynchronous IO mechanisms under the hood, like `io_uring`.
Now the question is, how to expose this asynchrony to the user code?

The UCall way is to keep the application code synchronous.
No `async`/`await` keywords or `future`/`promise` handles.
For lightweight operations, a handler stays an ordinary synchronous function.
For heavy operations, there is no secret pill, but there is a common "latency hiding" trick - batching.

Batch endpoints group independent requests by their configured size and latency budgets.
Endpoint callbacks run synchronously on the application polling caller; a deadline cannot preempt a callback.
Registering a batch endpoint returns its `ucall_http_batch_endpoint_t` handle.
Each batch endpoint has one queue fed by every event loop and one application thread polling that handle through `ucall_server_take_batches()`, so its callback never runs on an event loop.
With `max_pipelined_requests` on an interface and `max_pipelined_calls` on a client, one connection carries several requests at once, so a single client can fill a batch; replies still leave in request order.

### Caller-Owned Polling

Select the backend and CPU class in the server or client configuration.
The polling threads must support the selected CPU class; `serial` is a conservative choice when their capabilities differ from the initialization thread's.
UCall creates no threads.

Register server routes before polling.
`ucall_server_take_call()` advances one loop with a bounded wait; a zero timeout performs one nonblocking progress round.
`ucall_server_take_calls()` drives that loop continuously until shutdown.
Each loop has one application owner, and `ucall_server_stop()` can wake it from another thread.
The first stop drains: listeners close, idle connections close, and every request already held gets its reply, announced as its connection's last.
A second stop cuts the replies still in flight; there is no drain timeout.
An interface takes a `listen_descriptor` in place of an address, accepting on a socket systemd or a supervisor passed in, TCP or AF_UNIX, and never closing it.
Once an interface holds its whole connection budget, a new peer evicts the idle keep-alive connection that progressed least recently, and slow peers holding requests are never cut by a deadline.

`ucall_client_call()` submits an exchange and returns its acceptance result.
Each accepted call produces one final record through `ucall_client_take_completed()`, which drives I/O and retrieves completion batches.
Initialize, submit, poll, and free a client on the same owner thread.
Keep borrowed request inputs alive until their completion is retrieved.
After retrieval, the caller owns each response until `ucall_client_response_free()`, even past client teardown.
A response is capped by the client's `max_response_bytes`, which takes the kernel's largest receive buffer by default, as `max_request_bytes` caps a server's requests.
Stop and join server polling threads before freeing the server.

The client resolves host names itself, through a stub resolver its own event loop drives, so a lookup never blocks the polling thread.
It reads `/etc/resolv.conf` with its `search` and `ndots` options and `/etc/hosts` on POSIX, and the adapters' settings on Windows.
Queries for AAAA and A go over UDP and retry over TCP when truncated, follow CNAME chains, and cache answers and refusals for their TTL.
Connections race the answered addresses Happy Eyeballs style, alternating IPv6 and IPv4 from IPv6, waiting 50 ms for an AAAA answer behind an A one, and starting the next address 250 ms after the previous one.
The client's `log` sink reports dials that fail every address, refused handshakes and refused redirects, in the same human or JSON lines the server writes.

### Encryption: From TLS to Noise

There is a good reason to implement TLS in a networking library - it's the most widespread encryption standard on the Internet.
There is also every reason not to - TLS is a massive and complex standard, with multiple versions, dozens of cipher suites, countless configuration options, error-prone details, and poor ergonomics.
Integrating TLS properly even using OpenSSL, BoringSSL, LibreSSL, and mbedTLS in a clean & performant way in line with principles of Unum - failed twice over two years.

Moreover, the certificate management, PKI, and trust chains are an absolute ergonomic nightmare.
There is a better way... that is broadly deployed, but little known.
The Noise Protocol Framework!

UCall uses a `protocol+transport` URL scheme convention (like Docker's `http+unix://`) to explicitly specify the transport: `http://` or `http+noise://`.
Noise uses a Diffie-Hellman __(DH)__ key exchange to establish a shared secret between the client and server, with the handshake pattern determining which keys each side must know.
Several handshake patterns are defined, each suitable for different use cases:

- __XX__: Full mutual authentication where both parties authenticate each other during the handshake.
  Best for peer-to-peer scenarios or when the server doesn't have a fixed identity known in advance.
- __IK__: Client knows the server's public key in advance (Identity Known).
  Provides faster handshakes and is ideal for typical client-server architectures, similar to TLS with certificate pinning.
- __KK__: Both parties know each other's public keys beforehand (Known Known).
  A server interface names the one initiator key it admits through `noise_peer_key`, and a client names the server's key per call.

`ucall_noise_key_generate()` draws static keys from the operating system's entropy, and `ucall_noise_key_derive()` recovers the public key that peers see from a stored private one.

Noise can be implemented in several ways, depending on the choice of three cryptographic primitives: Diffie-Hellman key exchange, symmetric cipher, and hash function.
Examples of primitive combinations are:

| Name                       | Key exchange |            Cipher |    Hash |
| :------------------------- | -----------: | ----------------: | ------: |
| `25519_AESGCM_SHA256`      |   Curve25519 |       AES-256-GCM | SHA-256 |
| `25519_ChaChaPoly_SHA256`  |   Curve25519 | ChaCha20-Poly1305 | SHA-256 |
| `25519_ChaChaPoly_BLAKE2s` |   Curve25519 | ChaCha20-Poly1305 | BLAKE2s |
| `448_ChaChaPoly_SHA512`    |     Curve448 | ChaCha20-Poly1305 | SHA-512 |

UCall implements exclusively the `25519_AESGCM_SHA256`.
First, it uses the [Curve25519](https://en.wikipedia.org/wiki/Curve25519) elliptic curve for the DH asymmetric key exchange to produce a shared secret.
Then, it uses the [AES-256-GCM](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) authenticated symmetric cipher for encrypting and decrypting the data.
Finally, it uses the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hash function for hashing operations during the handshake and key derivation.

Why choose AES-256-GCM and SHA-256?
Both primitives ship in [StringZilla][stringzilla], so the transport shares the engine's capability-selected kernels rather than maintaining a separate cryptographic backend.
The selected CPU class carries matching string searches, AES-GCM, SHA-256, and atomic operations together.
The implemented x86 and Arm kernels use the available cryptographic instruction paths, with serial fallbacks for the baseline class.

Noise itself sits __beneath__ HTTP, independently of the socket backend.
Payloads split into independently authenticated records, invisible to the protocol above.
The record bound comes from its two-byte length prefix and authentication overhead; it does not become an application message-size limit.

### Authentication: Macaroons, Biscuits, and Other Pastries

__Roadmap:__ Native Macaroon and Biscuit parsers are not implemented.
The following describes the authorization model being considered; current applications inspect headers and apply their own policy.

Traditional web authentication often combines OAuth2 flows, JWTs, and session cookies.
Delegating access to part of a service can require additional token issuance, scopes, and consent flows; capability tokens offer another way to express those restrictions.

There is a better way, pioneered by Google in 2014: [Macaroons][macaroons].
A Macaroon is a bearer token built on chained HMACs that supports __attenuation__ - anyone holding a token can add restrictions to it, but nobody can remove them.
No database lookup required for verification, just the server's secret key.

```text
Original token:  user=alice, account=12345
                        ↓
Alice restricts: user=alice, account=12345, time < 2025-01-01, method = GET
                        ↓
Alice gives to Bob - Bob can only read, only until January
                        ↓
Bob restricts:   user=alice, account=12345, time < 2025-01-01, method = GET, path = /api/reports/*
                        ↓
Bob gives to Carol - Carol can only read reports, nothing else
```

The cryptography (HMAC chain) guarantees that restrictions can never be removed - only added.
The server verifies the signature, then evaluates each restriction against the current request.
No round-trip to a database, no OAuth dance for delegation.

[Biscuit][biscuit] is the modern successor, replacing free-form restriction strings with a proper [Datalog][datalog] query language and swapping HMAC (symmetric) for Ed25519 (asymmetric).
This means verification requires only the server's __public__ key - multiple services can verify tokens without sharing secrets.

```datalog
// Biscuit token with Datalog restrictions
user("alice");
role("viewer");
check if time($t), $t < 2025-01-01T00:00:00Z;
check if operation($op), ["read", "list"].contains($op);
check if resource($r), $r.starts_with("/api/");
```

The proposed integration would cover time bounds, path patterns, HTTP methods, IP ranges, and application-defined restrictions.
A possible `Authorization` header format is `Authorization: Macaroon <base64>` or `Authorization: Biscuit <base64>`; the current engine does not interpret either token.

Why "pastries"?
Because capability-based tokens are delicious - they're self-contained, offline-verifiable, and delegation-friendly.
Macaroons are used by the Lightning Network for Bitcoin payments.
Biscuits power Clever Cloud's infrastructure.
Both are battle-tested at scale.

[macaroons]: https://research.google/pubs/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/
[biscuit]: https://biscuitsec.org
[datalog]: https://en.wikipedia.org/wiki/Datalog

### Protocol Identity: SPIFFE

__Roadmap:__ SPIFFE integration and SVID binding to Noise keys are not implemented.

Authentication tokens answer "what can this request do?" but not "who is making it?"
In traditional deployments, workloads authenticate using static credentials - API keys, service account tokens, or certificates manually provisioned by operators.
These secrets end up in config files, environment variables, and CI/CD pipelines, creating a sprawling attack surface.

[SPIFFE][spiffe] (Secure Production Identity Framework for Everyone) takes a different approach: workloads prove their identity through __attestation__, not secrets.
Instead of "here's my password", it's "here's cryptographic proof of what I am" - verified by inspecting process attributes, Kubernetes pod labels, AWS instance metadata, or other environmental signals.

A SPIFFE identity is a URI: `spiffe://example.com/service/api-server`.
The workload receives a short-lived identity document (SVID - SPIFFE Verifiable Identity Document), with its issuer and deployment policy controlling renewal.
No secrets in config files.
No manual certificate management.
If the workload dies and restarts, it re-attests and gets fresh credentials.

```text
┌─────────────────────────────────────────────────────────────────┐
│  Traditional: Static secrets in config                          │
│  API_KEY=sk_live_abc123... ← Leaked? Game over.                 │
├─────────────────────────────────────────────────────────────────┤
│  SPIFFE: Identity from attestation                              │
│  Process starts → Agent inspects (PID, UID, k8s labels)         │
│                 → Issues a short-lived identity document        │
│                 → Renews before expiry                          │
│  No application API keys in config; workload attestation        │
│  provides the basis for issuing its identity.                   │
└─────────────────────────────────────────────────────────────────┘
```

A future integration would need to verify a binding between the SPIFFE identity and the Noise static key.
After an authenticated exchange, an application could authorize based on the client's SPIFFE ID pattern: `spiffe://prod.example.com/service/*` allows any production service, while `spiffe://prod.example.com/service/payment-*` restricts to payment services only.

The intended separation is Noise for channel encryption, SPIFFE for workload identity, and Macaroons or Biscuits for delegated capabilities.
Only the Noise transport and application peer-authorization callbacks are implemented today.

[spiffe]: https://spiffe.io

### Engineered for Performance

The shared connection automata consume completions and emit I/O requests for POSIX, Linux epoll with AIO, Linux io_uring 6.12 or 6.18, kqueue, and IOCP.
HTTP framing, routing, and Noise stay in templated headers under `include/ucall/`.
One library exposes the C ABI once; server and client configuration select the backend and a compiled CPU class.
Per-CPU instantiation units specialize every enabled native engine, while ordinary C calls use their handle's fixed operation table.

The implementation uses:

- [StringZilla](https://www.unum.cloud/docs/stringzilla.md) for SIMD searches, header normalization, SHA-256, and AES-256-GCM.
- [ForkUnion](https://www.unum.cloud/docs/forkunion.md) for capability checks and extended atomic references.
- [SmashTable](https://www.unum.cloud/docs/smashtable.md) for fallible containers and the examples' strictly serializable transactions.

There is no OpenSSL, no libsodium, and no third-party Noise implementation.
The Curve25519 implementation lives separately from StringZilla's symmetric cipher and hash kernels.

[stringzilla]: /docs/stringzilla/

### REST Interface

The C ABI exposes path, query, header, and body views for REST-style APIs:

- __Path Parameters__: Captured by URI templates, e.g., `/users/{user_id}/posts/{post_id}`, through `ucall_server_request_path_parameter()`
- __Query Parameters__: Looked up by name, e.g., `/search?q=rust&limit=10`, through `ucall_server_request_query_parameter()`
- __JSON Body__: Applications parse raw body bytes, e.g., `{"temperature": 0.7, "max_tokens": 100}`, and the Python bindings bind typed handler parameters from it
- __Multipart Body__: Files and metadata in one request, read part by part through `ucall_server_request_part()`
- __Header Fields__: Looked up by case-insensitive name through `ucall_server_request_header()`, or walked in arrival order through `ucall_server_request_fields_count()` and `ucall_server_request_field()`, as a proxy forwards a head whole
- __Raw Query__: The whole query after the first `?` through `ucall_server_request_query()`
- __Peer Address__: The IPv4, IPv6 or AF_UNIX address a connection came from, beside its interface and authenticated Noise key, through `ucall_server_request_peer()`

Paths and query values arrive as sent, and `ucall_decode_percent()` decodes their `%XX` escapes, reading `+` as a space in a query and refusing a `%` without two hexadecimal digits.
Field names arrive lowercased, as the parser keeps them, and values trimmed; a client response's fields read the same way through `ucall_client_response_fields_count()` and `ucall_client_response_field()`.

Routing is exact, as in axum and Envoy: an empty segment is a segment, so `/users`, `/users/` and `//users` are three different paths.
Literal segments match before parameters, which never take an empty segment, and both before a trailing wildcard.
A path no route takes answers 404, and a path whose routes take other verbs answers 405 with an `allow` header listing them.
A GET route serves HEAD as well, an `OPTIONS` request answers 204 with the same `allow` list, and `OPTIONS *` lists every verb.
A request sending `Expect: 100-continue` gets its interim `100 Continue` once the requests before it on its connection were answered, or on a streamed endpoint once its handler first pulls the body.

### Multipart Data & Rich Types

__Roadmap:__ Python rich-type conversion and the following SDK example await implementation or migration.

A `multipart/form-data` body lets an application send files and metadata in a single HTTP request.
The client supplies the boundary parameter, and `ucall_server_request_parts_count()` and `ucall_server_request_part()` split the buffered body on it the first time a handler asks.
Each part keeps its own `Content-Disposition` name and `Content-Type`, and its payload is a view into the receive buffer, never copied, so an image or audio decoder reads it in place.
The intended Python SDK could decode `numpy.ndarray` and `PIL.Image` inputs and outputs.
The following is an illustrative design sketch, requiring future SDK support and application-provided model and index setup, for a multi-modal semantic search platform built on top of Unum's [UForm](https://www.unum.cloud/docs/uform.md) models and [USearch](https://www.unum.cloud/docs/usearch.md) engine.

```python
from ucall import Server
from usearch import Index
from uform import get_model

server = Server()
model = get_model('unum-cloud/uform-vl-multilingual')

@server
def vectorize(description: str, photo: PIL.Image.Image) -> numpy.ndarray:
    image = model.preprocess_image(photo)
    tokens = model.preprocess_text(description)
    joint_embedding = model.encode_multimodal(image=image, text=tokens)

    return joint_embedding.cpu().detach().numpy()

@server
def search(vector: numpy.ndarray, top_k: int = 10):
    matches = index.search(vector, top_k)
    return matches.keys.tolist()
```

The intended wire interface could be called with standard tools like `curl`; this route is part of the sketch above:

```sh
curl -X POST http://localhost:8545/vectorize \
  -F "description=A cat in a hat" \
  -F "photo=@image.jpg"
```

### Client-Side Batching ≠ Server-Side Batching

When building high-throughput applications, it's common to batch multiple requests together on the client side to reduce network overhead.
That batch is one request from one client, which the server must split, dispatch and reassemble itself.
Server-side batching instead collects independent HTTP requests from different clients and every polling loop into each batch endpoint's queue, and passes their retained handles as a counted array.
Each request keeps its own response and transaction boundary, as the [key-value and ledger examples](https://github.com/unum-cloud/UCall/blob/main/examples/README.md) demonstrate.

### Resumable Uploads & Downloads

__Roadmap:__ Resumable-transfer policies and streaming multipart access are not implemented.

There is no one unique way to deal with resumable or chunked uploads and downloads.
The most common approaches for uploads and downloads are sadly not symmetric.

|          | Upload                            | Download                                  |
| -------- | --------------------------------- | ----------------------------------------- |
| Standard | Tus.io                            | HTTP range requests (RFC 7233)            |
| State    | Stateful (server tracks progress) | Stateless (client decides ranges)         |
| Headers  | `Upload-Offset`, `Tus-Resumable`  | `Range`, `Accept-Ranges`, `Content-Range` |
| Methods  | `POST`, `PATCH`, `HEAD`           | `GET` with `Range` header                 |
| Usage    | Vimeo                             | Browsers and download clients             |

Streaming multipart is another upload design, but continuation after a failure requires an application-level protocol rather than multipart framing alone.

Replies can carry byte ranges of files, as descriptors or as paths beneath directories an interface registers, which is what a `Range` response needs.
The event loop reads them with `O_DIRECT`, past the page cache, into windows borrowed from its pool, reading the next window while the previous one sends.
Every reply leaves through zero-copy sends wherever the kernel offers them, and a window returns to the pool once the kernel let go of it.
Under Noise each read leaves a gap behind every record's payload for its tag and the next record's header, so records seal in place inside the window.
IOCP does not yet send file parts over Noise connections.

### Other Functional HTTP Headers

The C ABI exposes request headers and accepts application-supplied response headers.
The following are useful application-level conventions; they do not imply built-in rate limiting, caching, tracing, or security policies.

__Rate Limiting & Flow Control__ (Server → Client):

- `X-RateLimit-Limit`: Maximum requests allowed per time window
- `X-RateLimit-Remaining`: Remaining requests before hitting the limit
- `X-RateLimit-Reset`: Unix timestamp when the rate limit counter resets
- `Retry-After`: Seconds to wait before retrying (with 429/503 status codes)
- `X-Backoff-Ms`: Custom millisecond-precision backoff hint
- `X-Queue-Depth`: Current server queue depth for client-side load awareness

__Request Correlation & Tracing__ (Bidirectional):

- `X-Request-ID` / `X-Correlation-ID`: Unique identifier linking logs and metrics across services
- `X-Trace-ID` / `X-Span-ID`: Application-defined distributed tracing identifiers

__Caching & Validation__:

- `ETag` (Server → Client): Resource fingerprint for cache validation
- `If-None-Match` (Client → Server): Conditional request using ETag, returns 304 if unchanged
- `Last-Modified` (Server → Client): Resource modification timestamp
- `If-Modified-Since` (Client → Server): Conditional request using timestamp
- `Cache-Control`: Caching directives (`max-age`, `no-cache`, `no-store`, `private`, `public`)
- `Vary`: Indicates which request headers affect response caching

__Performance & Observability__ (Server → Client):

- `X-Response-Time`: Server-side processing time in milliseconds
- `Server`: Server software identification (e.g., `UCall/1.0`)

__Security__ (Server → Client):

- `X-Content-Type-Options: nosniff`: Prevents MIME-type sniffing attacks
- `X-Frame-Options`: Clickjacking protection (`DENY`, `SAMEORIGIN`)
- `Content-Security-Policy`: Controls resource loading to prevent XSS attacks

### Streaming

Streamed replies are implemented: `ucall_server_reply_begin()`, `ucall_server_reply_append()` and `ucall_server_reply_end()` send a chunked body from any thread, long after the callback returned.
Streamed request bodies are implemented too: an endpoint registered with `ucall_body_delivery_streamed_k` runs once the head parsed, and each `ucall_server_request_body_chunk()` takes every byte decoded since the last one, finishing later through its outcome when none arrived yet.
The Python and Rust bindings expose both through a detached request a worker finishes, described in their Files & Streams sections.
The connection reads on only as the handler pulls, so a slow handler slows its own peer and nobody else, and no request pipelined behind the body parses before the body ended.
Buffered requests are capped by `max_request_bytes`, which defaults to the largest receive buffer the kernel grants a socket; past it the reply is 413.

### Observability & Metrics

Every event loop sends StatsD/DogStatsD datagrams to an explicitly configured endpoint, on a connected UDP socket of its own driven by the loop's I/O backend.
Metrics are __opt-in__: `metrics_address` names an endpoint such as `127.0.0.1:8125`, and an empty view sends nothing.
A server should never send packets to a port its operator didn't ask for.

Each `metrics_interval_microseconds`, one second by default, a loop reports the deltas of its counters since its last delivered datagram, its live connections per interface, and its refusals by status.
Loop zero also reports every batch endpoint's queue and counters.
Request latencies, sampled at `metrics_sample_rate`, ride the same datagrams, which never pass the path MTU:

```statsd
myapp.requests:1830|c|#env:prod,region:us-east,loop:0
myapp.connections:127|g|#env:prod,region:us-east,loop:0,interface:0
myapp.refused:4|c|#env:prod,region:us-east,loop:0,interface:0,status:413
myapp.latency:0.045|ms|@0.1|#env:prod,region:us-east,endpoint:/sum,method:POST,status:200
myapp.batch.queue:8|g|#env:prod,region:us-east,endpoint:/infer
```

```c
ucall_bytes_view_t const tags[] = {{(ucall_byte_t const*)"env:prod", 8}, {(ucall_byte_t const*)"region:us-east", 14}};
ucall_server_options_t config = {
    .metrics_address = {(ucall_byte_t const*)"127.0.0.1:8125", 14},
    .metrics_sample_rate = 0.1,                          // A latency line for one request in ten
    .metrics_prefix = {(ucall_byte_t const*)"myapp", 5}, // Metric names become "myapp.requests"
    .metrics_tags = tags,                                // Global tags for every line
    .metrics_tags_count = 2,
};
```

A datagram that fails to send is dropped, and the next one carries its counters, so collectors see every request counted once.

## Differences & Limitations

### Where Would You Prefer WebRTC?

WebRTC DataChannels offer message-oriented communication with configurable reliability.
That is a different tradeoff from UCall's TCP byte streams.

TCP's head-of-line blocking matters on lossy networks.
In TCP, if a packet is lost, all subsequent packets are held back until the lost packet is retransmitted and received.
On reliable networks, there should be no noticeable difference.
In the future, WebRTC DataChannels may be added.

### What About HTTP/2?

UCall currently supports HTTP/1.1.
HTTP/2 adds several important features:

- Extended `CONNECT` for protocols that tunnel bidirectional traffic.
- Multiplexing multiple streams over a single TCP connection to improve resource utilization.
- Header compression using `HPACK` to reduce bandwidth usage for common and repetitive headers.

Those are all useful features.
Still, maintaining dynamic decompression tables for `HPACK` isn't trivial.
Moreover, the `CONNECT` has its own nested state machine inside each stream.
Many such streams can be active simultaneously within the same TCP connection.
All of that increases the attack surface and the potential for bugs and didn't align with UCall's design goals.

### Why not HTTP/3 and QUIC?

UCall currently only operates on top of TCP.
Building around UDP has been a long-standing ambition for Unum since inception in 2015, and a QUIC-like protocol with Noise encryption makes a lot of sense, but it's far less compatible with existing infrastructure.
So for now, UCall focuses on TCP-based communication and HTTP/1.1 interoperability.

### What Will You Miss From FastAPI?

FastAPI comes with a rich ecosystem of plugins and extensions for authentication, database integration, OpenAPI schema generation, and more.

UCall supports routes like `/users/{id}` natively, including named parameters and greedy suffixes, matched exactly as the [REST Interface](#rest-interface) section describes.
The current gaps are higher-level SDK ergonomics, automatic schema generation, rich-type conversion, and the authentication integrations discussed above.

### What If You Love TLS?!

If you really need TLS, you can always deploy UCall behind a reverse proxy, like Nginx or Envoy.
They will handle all the TLS termination and forward decrypted HTTP traffic to UCall over a local network interface.
This is a common pattern in production deployments, as it allows to offload TLS processing from the application server.
The following deployment template assumes your application image binds plaintext HTTP on port 8080; replace the image and certificate paths for your deployment:

```yaml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - ucall

  ucall:
    image: your-ucall-application:latest
    expose:
      - "8080"
```

And the corresponding `nginx.conf`:

```nginx
upstream ucall_backend {
    server ucall:8080;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass http://ucall_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}
```

## Citation

If UCall helps your research or product, please cite it:

```bibtex
@software{Vardanian_UCall,
  author = {Vardanian, Ash},
  title = {{UCall: Low-Latency Client-Server Networking Stack Built around Noise Encryption, io\_uring and SIMD}},
  doi = {10.5281/zenodo.7951496},
  url = {https://github.com/unum-cloud/UCall},
  license = {Apache-2.0}
}
```

## License

Licensed under the Apache License, Version 2.0.
See `LICENSE` for details.
