From e7579d4045ae1929a9e3f578b3bfa5d0da212820 Mon Sep 17 00:00:00 2001 From: Diogo Martins Date: Sat, 6 Jun 2026 13:19:47 +0100 Subject: [PATCH 1/2] [Rust] Add tokio-ws - WebSocket Hand-rolled WebSocket echo server on raw tokio, with no WebSocket library. The RFC 6455 handshake (including from-scratch SHA-1 + base64), frame parser/masking, and echo write path are implemented directly on a tokio TcpStream. Serving model is one current_thread runtime per core with SO_REUSEPORT sharding, matching the other engine-tier entries. Subscribes to echo-ws and echo-ws-pipeline. Passes validate-ws.py (7/7) locally and via the Docker build. Co-Authored-By: Claude Opus 4.8 (1M context) --- frameworks/tokio-ws/.dockerignore | 2 + frameworks/tokio-ws/.gitignore | 1 + frameworks/tokio-ws/Cargo.lock | 168 +++++++++++++ frameworks/tokio-ws/Cargo.toml | 14 ++ frameworks/tokio-ws/Dockerfile | 13 + frameworks/tokio-ws/README.md | 46 ++++ frameworks/tokio-ws/meta.json | 14 ++ frameworks/tokio-ws/src/main.rs | 401 ++++++++++++++++++++++++++++++ 8 files changed, 659 insertions(+) create mode 100644 frameworks/tokio-ws/.dockerignore create mode 100644 frameworks/tokio-ws/.gitignore create mode 100644 frameworks/tokio-ws/Cargo.lock create mode 100644 frameworks/tokio-ws/Cargo.toml create mode 100644 frameworks/tokio-ws/Dockerfile create mode 100644 frameworks/tokio-ws/README.md create mode 100644 frameworks/tokio-ws/meta.json create mode 100644 frameworks/tokio-ws/src/main.rs diff --git a/frameworks/tokio-ws/.dockerignore b/frameworks/tokio-ws/.dockerignore new file mode 100644 index 00000000..c7f83c2c --- /dev/null +++ b/frameworks/tokio-ws/.dockerignore @@ -0,0 +1,2 @@ +target +.git diff --git a/frameworks/tokio-ws/.gitignore b/frameworks/tokio-ws/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/frameworks/tokio-ws/.gitignore @@ -0,0 +1 @@ +/target diff --git a/frameworks/tokio-ws/Cargo.lock b/frameworks/tokio-ws/Cargo.lock new file mode 100644 index 00000000..0037bb1d --- /dev/null +++ b/frameworks/tokio-ws/Cargo.lock @@ -0,0 +1,168 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "httparena-tokio-ws" +version = "0.1.0" +dependencies = [ + "socket2 0.5.10", + "tokio", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.6.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/frameworks/tokio-ws/Cargo.toml b/frameworks/tokio-ws/Cargo.toml new file mode 100644 index 00000000..80e15a5f --- /dev/null +++ b/frameworks/tokio-ws/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "httparena-tokio-ws" +version = "0.1.0" +edition = "2021" + +[dependencies] +socket2 = { version = "0.5", features = ["all"] } +tokio = { version = "1", default-features = false, features = ["io-util", "net", "rt"] } + +[profile.release] +codegen-units = 1 +lto = "thin" +opt-level = 3 +panic = "abort" diff --git a/frameworks/tokio-ws/Dockerfile b/frameworks/tokio-ws/Dockerfile new file mode 100644 index 00000000..d7792124 --- /dev/null +++ b/frameworks/tokio-ws/Dockerfile @@ -0,0 +1,13 @@ +FROM rust:1.95 AS build +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN mkdir src && echo "fn main() {}" > src/main.rs \ + && cargo build --release \ + && rm -rf src target/release/httparena-tokio-ws target/release/deps/httparena_tokio_ws* +COPY src ./src +RUN RUSTFLAGS="-C target-cpu=native" cargo build --release + +FROM debian:bookworm-slim +COPY --from=build /app/target/release/httparena-tokio-ws /server +EXPOSE 8080 +CMD ["/server"] diff --git a/frameworks/tokio-ws/README.md b/frameworks/tokio-ws/README.md new file mode 100644 index 00000000..043365aa --- /dev/null +++ b/frameworks/tokio-ws/README.md @@ -0,0 +1,46 @@ +# tokio-ws + +A WebSocket echo server written directly on **raw tokio**, with the WebSocket +protocol **hand-rolled** — no `tokio-tungstenite`, no `wtx`, no WS library at all. + +It exists as an `engine`-tier reference point: the lowest-level way to serve the +`echo-ws` profile on the tokio reactor, so the leaderboard shows what the runtime +itself can do once the framing is reduced to the minimum. + +## What's implemented by hand + +- **RFC 6455 handshake** — request parsing, `Sec-WebSocket-Accept` derivation, + and the `101 Switching Protocols` reply. SHA-1 and base64 are both written + from scratch (`src/main.rs`), so the only dependencies are `tokio` and + `socket2`. +- **Frame codec** — a streaming parser that handles 7/16/64-bit lengths, + client→server unmasking, and partial frames split across `read()`s. Echoes are + re-emitted as unmasked server frames, preserving FIN + opcode (so fragmented + messages pass through transparently). +- **Control frames** — `Ping` is answered with `Pong`; `Close` is echoed and the + connection ends. + +## Serving model + +One `current_thread` tokio runtime per core, each binding `0.0.0.0:8080` with +`SO_REUSEPORT` so the kernel shards new connections across cores — no shared +accept queue, no cross-core work-stealing. `TCP_NODELAY` is set per connection, +and outgoing echoes are batched per read so a pipelined burst flushes in one +write. This mirrors the other one-thread-per-core engine entries (e.g. +`rust-epoll`). + +## Endpoint + +| Method | Path | Behavior | +|--------|-------|-------------------------------------------| +| GET | `/ws` | WebSocket upgrade, then echo every frame | + +A non-upgrade `GET /ws` is rejected with `400`; other paths return `404`. + +## Build & run + +```bash +cargo build --release +./target/release/httparena-tokio-ws # listens on :8080 +python3 ../../scripts/validate-ws.py localhost 8080 /ws +``` diff --git a/frameworks/tokio-ws/meta.json b/frameworks/tokio-ws/meta.json new file mode 100644 index 00000000..5f801932 --- /dev/null +++ b/frameworks/tokio-ws/meta.json @@ -0,0 +1,14 @@ +{ + "display_name": "tokio-ws", + "language": "Rust", + "type": "engine", + "engine": "tokio", + "description": "Hand-rolled WebSocket echo server on raw tokio — no WebSocket library. A current-thread runtime per core with SO_REUSEPORT sharding, a from-scratch RFC 6455 handshake (hand-written SHA-1 + base64) and a manual frame parser/masking + echo write path.", + "repo": "https://github.com/tokio-rs/tokio", + "enabled": true, + "tests": [ + "echo-ws", + "echo-ws-pipeline" + ], + "maintainers": [] +} diff --git a/frameworks/tokio-ws/src/main.rs b/frameworks/tokio-ws/src/main.rs new file mode 100644 index 00000000..04f5b724 --- /dev/null +++ b/frameworks/tokio-ws/src/main.rs @@ -0,0 +1,401 @@ +//! tokio-ws — a hand-rolled WebSocket echo server on raw tokio. +//! +//! No WebSocket library: the RFC 6455 handshake (including a from-scratch SHA-1 +//! and base64), the frame parser, masking, and the echo write path are all +//! implemented here directly on a `tokio::net::TcpStream`. The serving model is +//! one `current_thread` runtime per core with `SO_REUSEPORT` sharding, matching +//! the other low-level "engine" entries. +//! +//! Endpoint: `GET /ws` upgrade, then echo every Text/Binary frame back verbatim +//! (unmasked, as a server frame). Listens on `0.0.0.0:8080`. + +use socket2::{Domain, Protocol, Socket, Type}; +use std::net::SocketAddr; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; + +const ADDR: &str = "0.0.0.0:8080"; +const WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +const MAX_FRAME: u64 = 16 << 20; // 16 MiB — DoS guard on the declared length +const READ_CHUNK: usize = 16 * 1024; + +fn main() { + let threads = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1); + + let mut handles = Vec::with_capacity(threads); + for _ in 0..threads { + handles.push(std::thread::spawn(|| { + tokio::runtime::Builder::new_current_thread() + .enable_io() + .build() + .expect("build runtime") + .block_on(serve()); + })); + } + for h in handles { + let _ = h.join(); + } +} + +/// One sharded listener + accept loop per core. +async fn serve() { + let listener = bind_reuseport().expect("bind 0.0.0.0:8080"); + loop { + match listener.accept().await { + Ok((stream, _)) => { + tokio::spawn(handle(stream)); + } + // Transient accept errors (EMFILE etc.) — keep the loop alive. + Err(_) => continue, + } + } +} + +/// A reusable, SO_REUSEPORT listener so every core gets its own accept queue. +fn bind_reuseport() -> std::io::Result { + let addr: SocketAddr = ADDR.parse().expect("valid addr"); + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?; + socket.set_reuse_address(true)?; + socket.set_reuse_port(true)?; + socket.set_nonblocking(true)?; + socket.bind(&addr.into())?; + socket.listen(1024)?; + TcpListener::from_std(socket.into()) +} + +async fn handle(mut stream: TcpStream) { + let _ = stream.set_nodelay(true); + let mut buf: Vec = Vec::with_capacity(8192); + if !handshake(&mut stream, &mut buf).await { + return; + } + // `buf` carries any bytes the client pipelined after the handshake. + let _ = echo_loop(&mut stream, buf).await; +} + +// ── Handshake ──────────────────────────────────────────────────────────────── + +/// Read request headers, validate the upgrade, and reply 101 (or 4xx). On +/// success, `buf` is left holding only the post-handshake byte stream. +async fn handshake(stream: &mut TcpStream, buf: &mut Vec) -> bool { + let mut tmp = [0u8; 4096]; + let hdr_end = loop { + if let Some(p) = find_header_end(buf) { + break p; + } + if buf.len() > 16 * 1024 { + return false; // headers too large + } + match stream.read(&mut tmp).await { + Ok(0) => return false, + Ok(n) => buf.extend_from_slice(&tmp[..n]), + Err(_) => return false, + } + }; + + let head = match std::str::from_utf8(&buf[..hdr_end]) { + Ok(t) => t, + Err(_) => return false, + }; + + let mut lines = head.split("\r\n"); + let request_line = lines.next().unwrap_or(""); + let path = request_line.split(' ').nth(1).unwrap_or(""); + + let mut key: Option<&str> = None; + let mut upgrade_ws = false; + for line in lines { + if let Some((name, value)) = line.split_once(':') { + let name = name.trim(); + let value = value.trim(); + if name.eq_ignore_ascii_case("sec-websocket-key") { + key = Some(value); + } else if name.eq_ignore_ascii_case("upgrade") + && value.eq_ignore_ascii_case("websocket") + { + upgrade_ws = true; + } + } + } + + if path != "/ws" { + let _ = stream + .write_all(b"HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n") + .await; + return false; + } + + let key = match (key, upgrade_ws) { + (Some(k), true) => k, + // Non-upgrade GET /ws (or missing key) must be rejected, not upgraded. + _ => { + let _ = stream + .write_all( + b"HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", + ) + .await; + return false; + } + }; + + let accept = ws_accept(key); + let resp = format!( + "HTTP/1.1 101 Switching Protocols\r\n\ + Upgrade: websocket\r\n\ + Connection: Upgrade\r\n\ + Sec-WebSocket-Accept: {accept}\r\n\r\n" + ); + if stream.write_all(resp.as_bytes()).await.is_err() { + return false; + } + + buf.drain(..hdr_end + 4); // drop the consumed request, keep any trailing frames + true +} + +fn find_header_end(buf: &[u8]) -> Option { + buf.windows(4).position(|w| w == b"\r\n\r\n") +} + +fn ws_accept(key: &str) -> String { + let mut input = String::with_capacity(key.len() + WS_GUID.len()); + input.push_str(key); + input.push_str(WS_GUID); + base64_encode(&sha1(input.as_bytes())) +} + +// ── Frame echo loop ────────────────────────────────────────────────────────── + +#[derive(Clone, Copy)] +struct Frame { + fin: bool, + opcode: u8, + mask: Option<[u8; 4]>, + payload_off: usize, + payload_len: usize, + total: usize, +} + +enum Parse { + Frame(Frame), + Incomplete, + Error, +} + +async fn echo_loop(stream: &mut TcpStream, mut buf: Vec) -> std::io::Result<()> { + let mut pos = 0usize; + let mut out: Vec = Vec::with_capacity(8192); + let mut tmp = [0u8; READ_CHUNK]; + + loop { + // Drain every complete frame currently buffered, batching the echoes. + loop { + let f = match parse_frame(&buf[pos..]) { + Parse::Frame(f) => f, + Parse::Incomplete => break, + Parse::Error => return Ok(()), // malformed/oversized → drop the conn + }; + + let start = pos + f.payload_off; + let end = start + f.payload_len; + if let Some(mask) = f.mask { + for i in 0..f.payload_len { + buf[start + i] ^= mask[i & 3]; + } + } + + match f.opcode { + // Continuation / Text / Binary → echo verbatim, unmasked. + 0x0 | 0x1 | 0x2 => { + push_header(&mut out, f.fin, f.opcode, f.payload_len); + out.extend_from_slice(&buf[start..end]); + } + // Ping → Pong with the same payload. + 0x9 => { + push_header(&mut out, true, 0xA, f.payload_len); + out.extend_from_slice(&buf[start..end]); + } + // Close → echo the close frame and finish. + 0x8 => { + push_header(&mut out, true, 0x8, f.payload_len); + out.extend_from_slice(&buf[start..end]); + stream.write_all(&out).await?; + return Ok(()); + } + // Pong / unknown control → ignore. + _ => {} + } + pos += f.total; + } + + if !out.is_empty() { + stream.write_all(&out).await?; + out.clear(); + } + if pos > 0 { + buf.drain(..pos); + pos = 0; + } + + let n = stream.read(&mut tmp).await?; + if n == 0 { + return Ok(()); // client closed + } + buf.extend_from_slice(&tmp[..n]); + } +} + +/// Parse a single frame header + payload out of `buf`. Offsets are relative to +/// the start of `buf` (i.e. the caller's `pos`). +fn parse_frame(buf: &[u8]) -> Parse { + if buf.len() < 2 { + return Parse::Incomplete; + } + let b0 = buf[0]; + let b1 = buf[1]; + let fin = b0 & 0x80 != 0; + let opcode = b0 & 0x0F; + let masked = b1 & 0x80 != 0; + let len7 = (b1 & 0x7F) as usize; + + let (payload_len, mut off) = if len7 < 126 { + (len7, 2usize) + } else if len7 == 126 { + if buf.len() < 4 { + return Parse::Incomplete; + } + (u16::from_be_bytes([buf[2], buf[3]]) as usize, 4) + } else { + if buf.len() < 10 { + return Parse::Incomplete; + } + let l = u64::from_be_bytes([ + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], + ]); + if l > MAX_FRAME { + return Parse::Error; + } + (l as usize, 10) + }; + + let mask = if masked { + if buf.len() < off + 4 { + return Parse::Incomplete; + } + let m = [buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]; + off += 4; + Some(m) + } else { + None + }; + + if buf.len() < off + payload_len { + return Parse::Incomplete; + } + + Parse::Frame(Frame { + fin, + opcode, + mask, + payload_off: off, + payload_len, + total: off + payload_len, + }) +} + +/// Write a server-side (unmasked) frame header for `opcode`/`len`. +fn push_header(out: &mut Vec, fin: bool, opcode: u8, len: usize) { + out.push(if fin { 0x80 } else { 0 } | (opcode & 0x0F)); + if len < 126 { + out.push(len as u8); + } else if len <= u16::MAX as usize { + out.push(126); + out.extend_from_slice(&(len as u16).to_be_bytes()); + } else { + out.push(127); + out.extend_from_slice(&(len as u64).to_be_bytes()); + } +} + +// ── Hand-rolled SHA-1 + base64 (handshake only) ────────────────────────────── + +fn sha1(data: &[u8]) -> [u8; 20] { + let mut h: [u32; 5] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476, 0xC3D2_E1F0]; + let bit_len = (data.len() as u64).wrapping_mul(8); + + let mut msg = data.to_vec(); + msg.push(0x80); + while msg.len() % 64 != 56 { + msg.push(0); + } + msg.extend_from_slice(&bit_len.to_be_bytes()); + + let mut w = [0u32; 80]; + for chunk in msg.chunks_exact(64) { + for (i, word) in chunk.chunks_exact(4).enumerate() { + w[i] = u32::from_be_bytes([word[0], word[1], word[2], word[3]]); + } + for i in 16..80 { + w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1); + } + + let (mut a, mut b, mut c, mut d, mut e) = (h[0], h[1], h[2], h[3], h[4]); + for (i, &wi) in w.iter().enumerate() { + let (f, k) = match i { + 0..=19 => ((b & c) | ((!b) & d), 0x5A82_7999), + 20..=39 => (b ^ c ^ d, 0x6ED9_EBA1), + 40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1B_BCDC), + _ => (b ^ c ^ d, 0xCA62_C1D6), + }; + let tmp = a + .rotate_left(5) + .wrapping_add(f) + .wrapping_add(e) + .wrapping_add(k) + .wrapping_add(wi); + e = d; + d = c; + c = b.rotate_left(30); + b = a; + a = tmp; + } + + h[0] = h[0].wrapping_add(a); + h[1] = h[1].wrapping_add(b); + h[2] = h[2].wrapping_add(c); + h[3] = h[3].wrapping_add(d); + h[4] = h[4].wrapping_add(e); + } + + let mut out = [0u8; 20]; + for (i, word) in h.iter().enumerate() { + out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes()); + } + out +} + +fn base64_encode(data: &[u8]) -> String { + const T: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut out = String::with_capacity((data.len() + 2) / 3 * 4); + for chunk in data.chunks(3) { + let b0 = chunk[0] as u32; + let b1 = *chunk.get(1).unwrap_or(&0) as u32; + let b2 = *chunk.get(2).unwrap_or(&0) as u32; + let n = (b0 << 16) | (b1 << 8) | b2; + out.push(T[((n >> 18) & 63) as usize] as char); + out.push(T[((n >> 12) & 63) as usize] as char); + out.push(if chunk.len() > 1 { + T[((n >> 6) & 63) as usize] as char + } else { + '=' + }); + out.push(if chunk.len() > 2 { + T[(n & 63) as usize] as char + } else { + '=' + }); + } + out +} From f3f9141a813e0e4a1de7b5b6414f86438b89676a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Jun 2026 13:45:08 +0000 Subject: [PATCH 2/2] Benchmark results: tokio-ws --- site/data/echo-ws-16384.json | 19 ++++++++++++++++ site/data/echo-ws-4096.json | 19 ++++++++++++++++ site/data/echo-ws-512.json | 19 ++++++++++++++++ site/data/echo-ws-pipeline-16384.json | 19 ++++++++++++++++ site/data/echo-ws-pipeline-4096.json | 19 ++++++++++++++++ site/data/echo-ws-pipeline-512.json | 19 ++++++++++++++++ site/data/frameworks.json | 22 ++++++++++++++++--- .../logs/echo-ws-pipeline/16384/tokio-ws.log | 0 .../logs/echo-ws-pipeline/4096/tokio-ws.log | 0 .../logs/echo-ws-pipeline/512/tokio-ws.log | 0 site/static/logs/echo-ws/16384/tokio-ws.log | 0 site/static/logs/echo-ws/4096/tokio-ws.log | 0 site/static/logs/echo-ws/512/tokio-ws.log | 0 13 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 site/static/logs/echo-ws-pipeline/16384/tokio-ws.log create mode 100644 site/static/logs/echo-ws-pipeline/4096/tokio-ws.log create mode 100644 site/static/logs/echo-ws-pipeline/512/tokio-ws.log create mode 100644 site/static/logs/echo-ws/16384/tokio-ws.log create mode 100644 site/static/logs/echo-ws/4096/tokio-ws.log create mode 100644 site/static/logs/echo-ws/512/tokio-ws.log diff --git a/site/data/echo-ws-16384.json b/site/data/echo-ws-16384.json index 2d5ec3f4..8e6eb428 100644 --- a/site/data/echo-ws-16384.json +++ b/site/data/echo-ws-16384.json @@ -385,6 +385,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 4143535, + "avg_latency": "3.93ms", + "p99_latency": "4.67ms", + "cpu": "6399.5%", + "memory": "590MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "28.05MB/s", + "reconnects": 0, + "status_2xx": 20717677, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/echo-ws-4096.json b/site/data/echo-ws-4096.json index a1f66307..b755903f 100644 --- a/site/data/echo-ws-4096.json +++ b/site/data/echo-ws-4096.json @@ -385,6 +385,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 4433246, + "avg_latency": "923us", + "p99_latency": "1.19ms", + "cpu": "6228.7%", + "memory": "176MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "29.59MB/s", + "reconnects": 0, + "status_2xx": 22166232, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/echo-ws-512.json b/site/data/echo-ws-512.json index d1d4ac76..9c9fc606 100644 --- a/site/data/echo-ws-512.json +++ b/site/data/echo-ws-512.json @@ -385,6 +385,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 4352944, + "avg_latency": "117us", + "p99_latency": "213us", + "cpu": "6332.2%", + "memory": "43MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "29.05MB/s", + "reconnects": 0, + "status_2xx": 21764720, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/echo-ws-pipeline-16384.json b/site/data/echo-ws-pipeline-16384.json index 6403dddf..8f7ac1a6 100644 --- a/site/data/echo-ws-pipeline-16384.json +++ b/site/data/echo-ws-pipeline-16384.json @@ -246,6 +246,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 61865334, + "avg_latency": "4.22ms", + "p99_latency": "5.03ms", + "cpu": "6397.0%", + "memory": "592MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "413.28MB/s", + "reconnects": 0, + "status_2xx": 309326672, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/echo-ws-pipeline-4096.json b/site/data/echo-ws-pipeline-4096.json index 4b27dfdd..568b09c5 100644 --- a/site/data/echo-ws-pipeline-4096.json +++ b/site/data/echo-ws-pipeline-4096.json @@ -246,6 +246,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 66930970, + "avg_latency": "978us", + "p99_latency": "1.32ms", + "cpu": "6240.6%", + "memory": "173MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "446.59MB/s", + "reconnects": 0, + "status_2xx": 334654854, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/echo-ws-pipeline-512.json b/site/data/echo-ws-pipeline-512.json index 67b31090..4f6cd427 100644 --- a/site/data/echo-ws-pipeline-512.json +++ b/site/data/echo-ws-pipeline-512.json @@ -246,6 +246,25 @@ "status_4xx": 0, "status_5xx": 0 }, + { + "framework": "tokio-ws", + "language": "Rust", + "rps": 65856262, + "avg_latency": "123us", + "p99_latency": "238us", + "cpu": "6216.1%", + "memory": "42MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "439.66MB/s", + "reconnects": 0, + "status_2xx": 329281312, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, { "framework": "trillium", "language": "Rust", diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 7ed81f2e..9dc56124 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -597,6 +597,13 @@ "type": "tuned", "engine": "true-async-server" }, + "tokio-ws": { + "dir": "tokio-ws", + "description": "Hand-rolled WebSocket echo server on raw tokio \u2014 no WebSocket library. A current-thread runtime per core with SO_REUSEPORT sharding, a from-scratch RFC 6455 handshake (hand-written SHA-1 + base64) and a manual frame parser/masking + echo write path.", + "repo": "https://github.com/tokio-rs/tokio", + "type": "engine", + "engine": "tokio" + }, "tonic-grpc": { "dir": "tonic-grpc", "description": "Rust gRPC server using tonic, a native Rust gRPC implementation built on top of hyper and tower.", @@ -675,11 +682,20 @@ "engine": "workerman" }, "wtx": { - "dir": "wtx-http2", - "description": "WTX - HTTP/2 Framework Server", + "dir": "wtx-grpc", + "description": "WTX - gRPC Server", "repo": "https://github.com/c410-f3r/wtx", "type": "production", - "engine": "wtx" + "engine": "wtx", + "variants": [ + { + "dir": "wtx-http2", + "description": "WTX - HTTP/2 Framework Server", + "repo": "https://github.com/c410-f3r/wtx", + "type": "production", + "engine": "wtx" + } + ] }, "zeemo": { "dir": "zeemo", diff --git a/site/static/logs/echo-ws-pipeline/16384/tokio-ws.log b/site/static/logs/echo-ws-pipeline/16384/tokio-ws.log new file mode 100644 index 00000000..e69de29b diff --git a/site/static/logs/echo-ws-pipeline/4096/tokio-ws.log b/site/static/logs/echo-ws-pipeline/4096/tokio-ws.log new file mode 100644 index 00000000..e69de29b diff --git a/site/static/logs/echo-ws-pipeline/512/tokio-ws.log b/site/static/logs/echo-ws-pipeline/512/tokio-ws.log new file mode 100644 index 00000000..e69de29b diff --git a/site/static/logs/echo-ws/16384/tokio-ws.log b/site/static/logs/echo-ws/16384/tokio-ws.log new file mode 100644 index 00000000..e69de29b diff --git a/site/static/logs/echo-ws/4096/tokio-ws.log b/site/static/logs/echo-ws/4096/tokio-ws.log new file mode 100644 index 00000000..e69de29b diff --git a/site/static/logs/echo-ws/512/tokio-ws.log b/site/static/logs/echo-ws/512/tokio-ws.log new file mode 100644 index 00000000..e69de29b