the-fourth-server
A lightweight, async binary TCP server/client library for Rust built on Tokio.
Packets are framed and serialized with bincode, routed to typed handlers on the server, and optionally encrypted end-to-end. Both plain TCP and WebSocket transports are supported, and the client side compiles to WASM.
Features
- Binary framing — length-delimited frames via
tokio-util - Typed routing — requests are dispatched to handlers by a user-defined structure-type enum
- Pluggable codec — implement
TfCodecto add custom framing or per-connection setup (e.g. encryption handshake) - SPAKE2 + AES-256-GCM encryption — built-in password-authenticated key exchange with replay protection
- Transport flexibility — plain TCP, TLS (
tokio-rustls), or WebSocket upgrade - WASM client — the client compiles to
wasm32using WebSocket transport - Stream handoff — a handler can take ownership of the raw stream for protocol upgrades or proxying
- Optional logging — enable the
loggingfeature to get structured logs via thelogfacade
Transports
| Mode | Server | Client (native) | Client (WASM) |
|---|---|---|---|
Plain TCP (tcp://) |
yes | yes | no |
TLS (tls://) |
yes | yes | no |
WebSocket (ws://) |
yes | yes | yes |
WebSocket over TLS (wss://) |
yes | yes | yes |
TLS and WebSocket are composed at the Transport layer, so passing a ServerConfig together with ServerMode::WebSocket gives you wss:// with no extra wiring. The client uses wss:// in the URL and the underlying library handles TLS automatically.
Getting started
Add the dependency:
[]
= "0.3"
Enable logging (optional):
[]
= { = "0.3", = ["logging"] }
Concepts
Structure types
Every message carries a structure type tag — a user-defined enum that implements the StructureType trait. The router uses this tag (plus the handler ID) to dispatch the packet to the correct handler.
use ;
// implement StructureType for MyType (see tf-examples for a full example)
Codec
TfCodec extends tokio_util::codec::{Encoder, Decoder} with an initial_setup async method called once per new connection. This is where per-connection negotiation (e.g. the SPAKE2 handshake) happens. You can write your own codec if you need it
Two codecs are provided:
| Codec | Description |
|---|---|
LengthDelimitedCodec |
Plain framing from tokio-util — no encryption |
Spake2Encrypted |
SPAKE2 handshake → HKDF → AES-256-GCM with replay protection |
Server
use Arc;
use ;
use TfServerRouter;
use RwLock;
use LengthDelimitedCodec;
// 1. Build the router
let mut router: =
new;
router.add_route;
router.commit_routes;
// 2. Start the server
let mut server = new.await?;
let handle = server.start.await;
handle.await.unwrap;
Handler
use Handler;
use async_trait;
;
Client
use ;
use s_type;
use LengthDelimitedCodec;
let client = new.await?;
let = channel;
client.dispatch_request.await?;
let response_bytes = rx.await.unwrap;
Encrypted transport (SPAKE2 + AES-256-GCM)
Spake2Encrypted performs a password-authenticated key exchange before any application data is sent. No pre-shared keys or PKI are required — just a shared password per client identity.
Server side:
use ;
;
let codec = create_server;
Client side:
use ;
;
let codec = create_client;
After the handshake, all frames are encrypted with AES-256-GCM. Each frame carries an 8-byte monotonic counter used as the nonce; replayed or reordered packets are rejected.
WebSocket transport
Pass ServerMode::WebSocket / ClientMode::WebSocket to upgrade the TCP connection to WebSocket before any application framing. This is the only mode available in WASM builds.
// server
new.await?;
// client (native or WASM)
new.await?;
Examples
Working examples are in the tf-examples/ workspace member:
| File | Description |
|---|---|
server_ex.rs |
WebSocket server with SPAKE2 encryption and three handlers |
client_ex.rs |
Client sending test and large-payload requests |
s_type_ex.rs |
Example structure-type enum definition |