# Native TLS 1.3 IO Backend

```c
#define FIO_IO
#include "fio-stl/include.h"
```

IO-layer TLS 1.3 integration — for standalone crypto library see [`190 tls13.md`](./190 tls13.md).

This module wires the native TLS 1.3 state machines into the facil.io IO reactor
as a drop-in transport backend. When OpenSSL is unavailable at build time it
registers itself automatically; otherwise it is available on demand via
`fio_tls13_io_functions()`. No external dependencies required.

For the IO/TLS stack overview see [./400 io-overview.md](./400 io-overview.md).  
For the OpenSSL backend (preferred when available) see [./405 openssl.md](./405 openssl.md).

---

## Activation

The module compiles when all of the following are true:

| Condition | Notes |
|-----------|-------|
| `FIO_IO` is defined | pulls in the IO reactor |
| `H___FIO_TLS13___H` guard is satisfied | `190 tls13.h` was included first |
| `FIO_NO_TLS` is **not** defined | opt-out guard checked by `include.h` before this backend is included |

A module-level constructor (`FIO_CONSTRUCTOR`) runs automatically before
`main()` **only when OpenSSL is absent** (`HAVE_OPENSSL` not defined and
`405 openssl.h` not included). It calls:

```c
fio_io_tls_default_functions(&FIO___TLS13_IO_FUNCS);
```

This makes every subsequent `fio_io_listen` / `fio_io_connect` call with a
`tls` argument use the native TLS 1.3 engine without any extra configuration.

When OpenSSL *is* present, the native backend compiles but does **not**
auto-register. Use `fio_tls13_io_functions()` to switch explicitly if needed.

---

## Public API

### `fio_tls13_io_functions`

```c
fio_io_functions_s fio_tls13_io_functions(void);
```

Returns the `fio_io_functions_s` vtable that wires the native TLS 1.3 engine
into the IO reactor:

| Field | Role |
|-------|------|
| `build_context` | Converts `fio_io_tls_s` into a per-listener/connector context |
| `free_context` | Deferred free of the context (via `fio_io_defer`) |
| `start` | Per-connection: allocate state, run ClientHello or await ServerHello |
| `read` | Non-blocking decrypt: advances handshake if needed, then decrypts records |
| `write` | Non-blocking encrypt: batches up to 4 TLS records (64 KB) per syscall |
| `flush` | Drains any pending handshake or encrypted bytes from internal buffers |
| `finish` | Sends a TLS `close_notify` alert before the TCP close |
| `cleanup` | Frees per-connection TLS state |

Normally you never call this directly — the constructor handles registration.
Use it to override the global default or set a per-protocol backend:

```c
/* Override: force native TLS 1.3 even when OpenSSL is present */
fio_io_functions_s tls13_funcs = fio_tls13_io_functions();
fio_io_tls_default_functions(&tls13_funcs);
```

Or set it on a specific protocol without changing the global default:

```c
fio_io_functions_s tls13_funcs = fio_tls13_io_functions();
MY_PROTOCOL.io_functions = tls13_funcs; /* per-protocol override */

fio_io_listen(.url      = "0.0.0.0:8443",
              .protocol = &MY_PROTOCOL,
              .tls      = tls);
```

---

## Configuring TLS — `fio_io_tls_s`

TLS parameters are carried in a `fio_io_tls_s` object (defined in
`401 io api.h`). Build one before calling `fio_io_listen` or `fio_io_connect`:

```c
/* Allocate (reference counted) */
fio_io_tls_s *tls = fio_io_tls_new();

/* Certificate — PEM files */
fio_io_tls_cert_add(tls,
    "www.example.com", /* server_name (SNI) */
    "cert.pem",        /* public certificate or chain */
    "key.pem",         /* private key */
    NULL);             /* PEM password, or NULL */

/* ALPN protocol negotiation */
fio_io_tls_alpn_add(tls, "h2",       on_http2_selected);
fio_io_tls_alpn_add(tls, "http/1.1", on_http1_selected);

/* Peer certificate verification */
fio_io_tls_trust_add(tls, NULL);      /* use system trust store */
fio_io_tls_trust_add(tls, "ca.pem"); /* or a specific CA bundle */

/* Listen */
fio_io_listen(.url      = "0.0.0.0:443",
              .protocol = &MY_PROTOCOL,
              .tls      = tls);

fio_io_tls_free(tls); /* release your reference; the listener holds its own */
fio_io_start(0);
```

`fio_io_tls_s` is reference-counted. The backend duplicates the reference
during `build_context`; freeing yours after `fio_io_listen` is always safe.

---

## Certificates

### Loading from PEM files

When both `public_cert_file` and `private_key_file` are provided to
`fio_io_tls_cert_add`, the backend reads and parses the PEM files, loading
every `CERTIFICATE` block in the file as a DER chain entry. Three private key
types are supported:

| Key type | Constant | Notes |
|----------|----------|-------|
| ECDSA P-256 | `FIO_TLS13_SIGNATURE_ECDSA_SECP256R1_SHA256` | Recommended; small and fast |
| Ed25519 | `FIO_TLS13_SIGNATURE_ED25519` | Fastest signatures |
| RSA (any size) | `FIO_TLS13_SIGNATURE_RSA_PSS_RSAE_SHA256` | Requires `H___FIO_RSA___H` |

If PEM parsing fails, the backend logs `FIO_LOG_WARNING` and falls back to a
self-signed certificate. If the fallback also fails the context build fails
and `build_context` returns `NULL`.

### Self-signed fallback

When a **server** has no certificates configured, the backend generates a
self-signed P-256 ECDSA certificate on the fly using `fio_x509_self_signed_cert`
(requires `H___FIO_X509___H`):

| Property | Value |
|----------|-------|
| Key algorithm | ECDSA P-256 (128-bit security ≈ RSA-3072) |
| Signature | SHA-256 |
| SAN | set to `server_name` (defaults to `"localhost"`) |

Self-signed certificates are fine for development. Browsers will warn. Use a
CA-issued certificate (e.g. Let's Encrypt) in production.

If `H___FIO_X509___H` is not available when no PEM files are configured, the
context build fails with `FIO_LOG_ERROR`.

---

## ALPN

Register protocols with `fio_io_tls_alpn_add` before listen/connect. The
first registered protocol is the preferred default.

The backend collects all registered names into a comma-separated string (up
to 255 bytes per name; 255 characters total plus the NUL terminator). On handshake the server matches the
client's offered list against the registered names in registration order and
calls the corresponding `on_selected` callback on the `fio_io_s *` when
negotiation succeeds.

Protocol names must be 1–255 bytes. The internal list overflows when the
comma-separated list would exceed 255 characters — excess protocols are dropped with `FIO_LOG_ERROR`.

---

## Trust and Peer Verification

Trust configured with `fio_io_tls_trust_add` always refers to **peer**
verification: on **client** connections the backend verifies the server
certificate, on **server** connections it requests, requires, and verifies
the client certificate (mutual TLS). An empty trust list means **no peer
verification** — matching the OpenSSL backend.

| Scenario | Behaviour |
|----------|-----------|
| `fio_io_tls_trust_add(tls, NULL)` | system CA bundle used (loaded once, global) |
| `fio_io_tls_trust_add(tls, "ca.pem")` | user-supplied CA bundle (per-context) |
| No `fio_io_tls_trust_add` call, client mode | no server verification + `FIO_LOG_SECURITY` warning |
| No `fio_io_tls_trust_add` call, server mode | no client certificate requested |
| Trust configured, server mode | client certificate required and verified |

For mTLS, client certificate chains are verified against the trust store
(`fio_x509_verify_chain`) and the client's `CertificateVerify` signature is
validated against the leaf certificate's public key (Ed25519, ECDSA P-256 /
P-384, and RSA-PSS / PKCS#1 SHA-256 / SHA-384 schemes). A client that fails
verification is rejected with a TLS alert during the handshake.

After the handshake, inspect the peer's chain with
`fio_io_peer_info_next(io, &info)` (see the IO API docs) — each call parses
the next certificate in place (zero-copy) into a `fio_x509_cert_s`, exposing
the subject CN/DN, issuer, SAN entries, public key, validity, and a SHA-256
fingerprint for pinning, plus a `verified` flag for authorization decisions.

The system CA bundle is loaded **once** into a process-wide singleton
(`fio___tls13_sys_trust`) and shared read-only across all connections —
loading ~128 certs per outgoing connection is avoided. It is freed at process
exit via a `FIO_CALL_AT_EXIT` callback.

Platform CA bundle lookup order (POSIX):

```
/etc/ssl/cert.pem                                       (macOS, FreeBSD)
/etc/ssl/certs/ca-certificates.crt                      (Debian/Ubuntu)
/etc/pki/tls/certs/ca-bundle.crt                        (RHEL/CentOS)
/etc/ssl/ca-bundle.pem                                   (openSUSE)
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem       (RHEL newer)
/usr/local/etc/ssl/cert.pem                              (FreeBSD ports)
```

On **Windows** the `ROOT` system certificate store is enumerated via the
CryptoAPI (`CertOpenSystemStoreA` / `CertEnumCertificatesInStore`). On MSVC,
`Crypt32.lib` is linked automatically via `#pragma comment`; other Windows
toolchains may need an explicit `Crypt32` link flag.

---

## Lifecycle and Error Behavior

```
fio_io_listen / fio_io_connect
    │
    └─► build_context (fio_io_tls_s → fio___tls13_context_s, once per listener)
            │
            └─► per-connection start
                    │
                    ├─ client: ClientHello sent immediately
                    ├─ server: awaits ClientHello
                    │
                    ├─ read   (advances handshake records; decrypts app data after)
                    ├─ write  (encrypts; up to 4 records batched per syscall)
                    ├─ flush  (drains handshake + enc_buf to socket)
                    │
                    ├─ finish  (sends encrypted close_notify alert, best-effort)
                    └─ cleanup (frees fio___tls13_connection_s)
```

**Internal buffers per connection** (flexible array member, single allocation):

| Region | Purpose | Size |
|--------|---------|------|
| `recv_buf` | Incoming encrypted data | `FIO_IO_BUFFER_PER_WRITE` (~64 KB) |
| `app_buf` | Decrypted plaintext ready to deliver | `FIO_IO_BUFFER_PER_WRITE` |
| `send_buf` | Outgoing handshake bytes | `FIO_IO_BUFFER_PER_WRITE` |
| `enc_buf` | Pre-allocated encryption output (4 max records) | ~66 KB |

**Error codes surfaced to the IO layer:**

- `return 0` from `read` — peer closed cleanly (EOF).
- `return -1` with `errno = EWOULDBLOCK` — no data yet; reactor retries on next readable event.
- `return -1` with `errno = ECONNRESET` — TLS handshake or decryption error; connection will be closed.

All internal errors are logged: `FIO_LOG_ERROR` for hard failures,
`FIO_LOG_WARNING` for soft failures (e.g. PEM fallback), `FIO_LOG_DEBUG2` /
`FIO_LOG_DDEBUG2` for per-connection detail.

**KeyUpdate** (RFC 8446 §4.6.3): when the peer requests a key update, a
`KeyUpdate` response is prepended to the next `write` syscall alongside
application data so they go out in a single call.

---

## Minimal Server Example

```c
#define FIO_LOG
#define FIO_IO
#include "fio-stl/include.h"

static void on_data(fio_io_s *io) {
  char buf[4096];
  size_t n = fio_io_read(io, buf, sizeof(buf));
  if (n)
    fio_io_write(io, buf, n); /* echo */
}

static fio_io_protocol_s ECHO_PROTO = {
    .on_data    = on_data,
    .on_timeout = fio_io_touch,
};

int main(void) {
  /* No certificate → self-signed P-256 generated automatically */
  fio_io_tls_s *tls = fio_io_tls_new();

  fio_io_listen(.url      = "0.0.0.0:8443",
                .protocol = &ECHO_PROTO,
                .tls      = tls);
  fio_io_tls_free(tls);

  FIO_LOG_INFO("TLS echo server on :8443");
  fio_io_start(0);
}
```

For a production server, load real certificates:

```c
fio_io_tls_cert_add(tls, "example.com", "cert.pem", "key.pem", NULL);
```

---

## TLS Client Example

```c
#define FIO_LOG
#define FIO_IO
#include "fio-stl/include.h"

static void on_attach(fio_io_s *io) {
  /* Handshake happens transparently through the TLS transport hooks. */
  const char req[] = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
  fio_io_write(io, req, sizeof(req) - 1);
}

static void on_data(fio_io_s *io) {
  char buf[4096];
  size_t n = fio_io_read(io, buf, sizeof(buf) - 1);
  if (n) {
    buf[n] = '\0';
    FIO_LOG_INFO("response:\n%s", buf);
    fio_io_close(io);
  }
}

static fio_io_protocol_s CLIENT_PROTO = {
    .on_attach  = on_attach,
    .on_data    = on_data,
    .on_timeout = fio_io_touch,
};

int main(void) {
  fio_io_tls_s *tls = fio_io_tls_new();
  /* SNI hostname — system CA store is used automatically for verification */
  fio_io_tls_cert_add(tls, "example.com", NULL, NULL, NULL);

  fio_io_connect(.url      = "example.com:443",
                 .protocol = &CLIENT_PROTO,
                 .tls      = tls);
  fio_io_tls_free(tls);

  fio_io_start(0);
}
```


---

## Comparison with OpenSSL Backend

| Feature | Native TLS 1.3 | OpenSSL 3.x |
|---------|---------------|-------------|
| External dependency | None | OpenSSL 3.x |
| TLS versions | 1.3 only | 1.0–1.3 |
| Certificate key types | P-256, Ed25519, RSA | All |
| ALPN | Yes | Yes |
| Self-signed auto-cert | Yes (P-256) | Yes (P-256) |
| System trust store | Yes (multi-platform) | Yes |
| Session resumption (0-RTT) | No | Yes |
| OCSP stapling | No | Yes |
| Binary size impact | Smaller | Larger |
| Auto-registered when | OpenSSL absent | OpenSSL present |

The native backend is suitable for environments where TLS 1.3-only is
acceptable and minimising dependencies matters. If you need legacy TLS
versions, session resumption, OCSP stapling, or full certificate type
coverage, use the OpenSSL backend.

---

## Disambiguation

| Document | Scope |
|----------|-------|
| **This document** | Native TLS 1.3 IO backend — plugs TLS into the reactor via `fio_io_functions_s` |
| [./405 openssl.md](./405 openssl.md) | OpenSSL 3.x IO backend (same interface, preferred when available) |
| [./190 tls13.md](./190 tls13.md) | Standalone TLS 1.3 crypto library — key schedule, record layer, handshake state machines |
| [./400 io-overview.md](./400 io-overview.md) | IO + TLS stack overview |
