aeronet Roadmap - Planned / Not Implemented¶
High priority¶
- Security Hardening Audit:
- TLS fingerprinting hardening (avoid leaking version/cipher info in edge cases)
- Memory scrubbing for sensitive data (handshake keys, session tickets)
- Fuzzing harness integration (libFuzzer + AFL)
Medium priority¶
- Pluggable logging sink API (non-access logs) - spdlog backend supports custom sinks/formatters; an aeronet-native sink registration API is not yet exposed.
- Enhanced parser diagnostics (byte offset in parse errors for better debugging)
- Enhance
telemetrywith more detailed HTTP/2 metrics: per-stream stats, HPACK compression ratios, frame type distributions. - Support tags/labels for metrics
- Manage TE header in the client: currently the TE header is reserved in the framework, so it cannot be sent. It could be a good idea to have a flag in the config to allow sending TE headers in the client with trailers essentially (because trailers are natively supported by aeronet). If
TE: trailersis sent, in HTTP/1.1 we also need to addConnection: TE.
Low priority¶
- Windows event loop performance: The Windows backend uses WSAPoll (readiness‑based, like epoll/kqueue) which is functionally correct but less performant than IOCP for high‑concurrency workloads. A future IOCP backend would require a fundamental architecture shift from readiness to completion semantics.
- macOS
EVFILT_TIMERintegration:TimerFd::armPeriodic()on macOS is currently a no-op and relies on poll timeouts. Using kqueue's nativeEVFILT_TIMERwould improve timer precision but requires event-loop refactoring to accommodate heterogeneous kqueue filter types. - Direct compression option for HEAD: optional config to allow HEAD responses to match GET headers (Content-Encoding + compressed Content-Length) when desired.
Performance optimization backlog¶
Status: repository-wide source audit completed on 2026-07-22; the opportunities below are not yet implemented or proven faster. They are ordered by expected leverage, not by unverified percentage estimates.
P1 - copies, allocations and asymptotic hot paths¶
- HTTP/2 outbound frame representation -
Http2Connection::sendData()currently copies every payload behind a 9-byte frame header in_outputBuffer, andencodeHeaders()moves then recopies oversized HPACK blocks when emitting CONTINUATION frames. Design a queued-fragment / gather-write representation that can retain payload ownership across partial writes and flow-control stalls, then use scatter I/O where the transport supports it. This shared HTTP/2 core serves both client and server, so validate response streaming, client uploads, plain TCP, TLS and short-write paths. - HPACK dynamic table churn and lookup -
HpackDynamicTable::add()front-inserts into avector(O(n) relocation), whileHpackEncoder::findHeader()linearly scans dynamic entries for every encoded header. Benchmark a circular contiguous table, segmented queue and the current vector at realistic 4 KiB and enlarged table sizes; separately test an optional name/value index. Select the design on combined insert/evict/indexed-access/lookup results rather than assuming adequewins despite its weaker locality. - HTTP client response ownership - suitably-sized HTTP/1.1 chunk reassembly and decompression outputs now rotate
their scratch allocations into
HttpResponse, preserving an equal-capacity replacement while removing the final body copy. Oversized scratch stays reusable and falls back to copying a small body, avoiding duplicate high-water retention. Identity bodies embedded in the HTTP/1.1 receive buffer and assembled HTTP/2 bodies still need a safe ownership-transfer or result-owned-buffer path. Cover identity, chunked, compressed, empty and 1 MiB+ responses. This complements the request-side wire-buffer unification already listed in the HTTP client section below. - Bound deferred output and zerocopy retention - audit per-stream HTTP/2 pending data and
ConnectionState::zerocopyPendingBuffersunder a peer that stops reading or delays error-queue completions. Add explicit high-water marks that pause reads, reject work, or fall back to copied writes rather than allowing retained payloads and already-computed responses to grow without a configured bound. Validate memory plateaus under slow-reader tests before measuring normal-load overhead.
P2 - cache locality, dispatch and repeated scans¶
- HTTP/2 stream lookup and hot/cold layout - extend the stream benchmark beyond insert/erase to randomized lookup, DATA/WINDOW_UPDATE processing and close/prune churn at 1, 100, 1,000 and 10,000 active streams. Use the profile to decide between the current flat hash map, a tiny recent-stream cache, denser indexing, or splitting frequently touched state from callbacks, header storage and other cold fields.
- Transport dispatch without RTTI probes -
ITransportvirtualizes every read/write and several server paths alsodynamic_casttoPlainTransportorTlsTransport. Prototype a tagged final transport/variant or cached capabilities (plain,TLS,sendfile, kTLS) and compare cycles, branch misses and binary size on plain and TLS workloads. Adopt it only if the gain pays for the larger dispatch surface and preserves optional TLS compilation. - HTTP header mutation/search -
HttpMessagerepeatedly scans the flat CRLF buffer for lookup, append, remove and override operations. First profile realistic 4/8/16/32-header construction and middleware mutation. If scans dominate, evaluate known-header offsets or a lazy compact index that is invalidated on mutation without adding allocation or meaningfulsizeof(HttpMessage)cost to the common case. - TCP cork syscall policy - Linux response dispatch currently wraps eligible sends in
TcpCorkGuard, issuing cork and uncorksetsockoptcalls even for responses that may already fit in one gathered write. Benchmark disabled, always-on and size/write-count-threshold policies for 0 B, 512 B, 4 KiB and streaming responses; retain cork only where packet reduction outweighs the extra syscalls and latency. - JWT/JWKS verification primitives -
Jwks::find()scans every key for each token and base64url decoding classifies every character through branches. Add end-to-end JWT decode/verify benchmarks by algorithm, token size and JWKS size, then compare linear lookup with a compact sorted/hash index and branch classification with a 256-byte decode table. Include missing-kidand malformed-input cases so failure paths do not regress. - Hot object layout audit - record
sizeof, alignment and cache-line access profiles forConnectionState,Http2Stream,WebSocketHandler,HttpMessage,HttpRequestViewand client connection state at 10 K simulated connections/streams. Move genuinely cold optional state behind existing natural-empty/pool mechanisms only when cache results beat the added indirection.ConnectionStatelifecycle flags are already bit-packed; do not reopen that item without new layout evidence. - Compression/decompression retained memory - benchmark codec context reset, scratch-buffer growth and peak retained
capacity across alternating tiny/large bodies and many keep-alive sessions. Tune growth/shrink thresholds per codec,
and continue the Brotli reset/reuse research, only when allocation traces show a benefit over the existing reusable
BufferCache/ObjectArrayPoolpaths.
P3 - platform and build experiments¶
- Configurable/adaptive HTTP client reads - the client currently requests fixed 16 KiB chunks for HTTP/1.1 and HTTP/2 receives. Benchmark configurable 4/16/64 KiB and adaptive growth under small responses, bulk transfers, TLS and constrained-memory workloads before exposing a knob or policy.
- Repeat-connection latency - add a bounded TTL DNS cache and client-side TLS session reuse only after benchmarks separate resolver, TCP and handshake time. Expiry, address rotation and failed-resumption fallback must remain explicit.
- Profile-guided builds - evaluate PGO (and BOLT on supported Linux toolchains) in the benchmark binaries against the existing Release + IPO baseline. Keep this an opt-in build/documentation path: installed library code must not assume the benchmark host's CPU or workload.
- Platform event backends - continue the dedicated IOCP, native macOS
EVFILT_TIMER, and Linuxio_uringwork already listed elsewhere in this roadmap. Treat these as transport/backend projects with end-to-end results, not local syscall substitutions.
Benchmark ideas¶
Existing hpack_bench, http2_flow_control_bench and WebSocket mask benchmarks cover basic steady-state operations, so
extend them instead of duplicating them. The WebSocket large-frame SIMD masking gap previously listed here is complete.
| Benchmark gap | Decision it must support |
|---|---|
| HPACK insert/evict churn plus hit/miss lookup at multiple table sizes | Dynamic-table container and optional index |
| HTTP/2 DATA + large HEADERS/CONTINUATION output with partial writes | Fragment queue / scatter-write representation |
| Stream randomized lookup, frame processing and prune churn through 10 K streams | Stream map/cache and hot/cold split |
| Client identity/chunked/compressed responses from 0 B through 100 MiB | Response-buffer ownership transfer |
| Static-file cache hit/miss/thrash at capacities 64 through 4,096 | O(1) or amortized eviction policy |
| Transport dispatch and response-size syscall counts, plain + TLS | Tagged transport and TCP cork threshold |
| Header lookup/mutation with 4 through 32 fields | Flat scan versus compact/lazy indexing |
| JWT decode/verify with 1 through 1,000 JWKs | kid index and base64url decode strategy |
| Slow-reader and delayed zerocopy-completion memory plateau | Deferred-output high-water marks |
| Codec alternating-size sessions with allocation and peak-capacity counters | Scratch growth/shrink and context reuse |
| Fragmented + compressed WebSocket message processing | End-to-end frame/reassembly path beyond mask-only throughput |
Hot-structure sizeof and 10 K-object random-access profile |
Layout changes without speculative padding claims |
Long-term / Nice-to-have¶
- HTTP/3 / QUIC - a separate transport layer (future major feature); scoped as a research spike below before any commitment.
- Fuzz Harness Integration - libFuzzer targets for HTTP/1.1 and HTTP/2 parsing
- OCSP Stapling & Advanced TLS - Passive stapling with cached responses, CRL hooks, key logging (debug only)
- Per-SNI mTLS Policies - Different client cert requirements per SNI hostname
- Advanced Metrics - ~~Histogram/percentile latency buckets~~ ✔ (implemented via
TelemetryContext::histogram()+TelemetryConfig::addHistogramBuckets()); per-route stats not yet implemented
HTTP/3 / QUIC - research spike¶
Not started, and not recommended for the near term. HTTP/3 is not an increment on the existing stack: it
replaces the transport. QUIC is UDP + user-space loss recovery / congestion control + connection migration +
TLS 1.3 fused into the transport, and it uses QPACK, not HPACK - so the current HPACK / frame codecs and
the readiness-based epoll/kqueue TlsTransport seam mostly do not carry over (QUIC is more naturally
timer/completion-driven and wants GSO/GRO for throughput). Writing QUIC from scratch is a multi-year,
security-critical effort; the realistic path is wrapping an existing stack behind a new gated transport
module, which trades against aeronet's minimal-heavy-dependency ethos and therefore needs a deliberate
decision rather than a default.
Higher-leverage work comes first and reuses the existing architecture: the coroutine client + true HTTP/2
multiplexing, and io_uring.
If it becomes strategic, the first step is a time-boxed design spike (not a full plan) answering three questions:
- Dependency choice -
ngtcp2+nghttp3(the curl/nginx pairing),quiche, orlsquic: build system, license, binary-size and API-ergonomics trade-offs. - Event-loop integration - how a UDP + timer-driven QUIC transport slots into the current readiness-based loop (UDP socket handling, per-connection timers, GSO/GRO, pacing).
- Handler API reuse - whether the existing
HttpRequestView/Router/ handler surface can sit unchanged on top of an HTTP/3 mapping (QPACK encode/decode, request/response streams), so HTTP/3 is a transport swap rather than a second application API.
Deliverable of the spike: a go/no-go recommendation with a dependency choice and a rough transport-layer design, before committing to implementation.
Optional feature modules (compile-time gated)¶
The following features are inspired by capabilities that make frameworks like Drogon, Crow, and Beast popular for building full applications. Each is designed to be opt-in via a compile-time flag (OFF by default) so they add zero overhead when unused, consistent with aeronet's modular philosophy.
HTTP Client (AERONET_ENABLE_HTTP_CLIENT)¶
Status: Delivered (aeronet/client, enabled by default). The synchronous aeronet::HttpClient speaks
HTTP/1.1 and HTTP/2 natively over aeronet's own non-blocking transport + event loop, with HTTPS via the
shared TlsTransport, per-origin keep-alive pooling, redirect following, a retry + exponential-backoff
policy, transparent response decompression / request compression, cleartext forward-proxy (CONNECT
tunneling), and an opt-in time-based response cache for idempotent requests. Every request returns an
HttpClientResult (std::expected<HttpResponse, HttpClientErrc>) - value-based errors, no throwing on the
request path. See the README HTTP client section and docs/FEATURES.md for the full surface.
Example:
aeronet::HttpClient client;
auto result = client.get("https://api.example.com/data");
if (result && result->status() == 200) {
std::string_view body = result->bodyInMemory();
}
Still planned:
Client performance work from the repository-wide audit (response-buffer ownership, receive sizing, and DNS/TLS reuse) is tracked in the performance optimization backlog above.
- Coroutine-friendly API (
co_await client.get(...)) integrated with a running server event loop, so handlers can issue upstream calls without blocking. This is also what unlocks true HTTP/2 multiplexing in the client: the native HTTP/2 engine has landed, but the synchronous model runs one stream at a time per connection. - Unify the request buffer with the wire bytes: teach
HttpMessagea request-line first-line mode so the client builds the request directly in the buffer it writes, with zero reassembly (today request fields are copied once into the send buffer). - Pluggable timeouts / cancellation for in-flight client exchanges.
- Client-side telemetry: surface pool hit/miss, retry counts, cache hits/misses, redirect hops and
decompression stats through the same
TelemetryContextinfrastructure the server already uses (the client currently emits none). - Reverse-proxy / forwarding building on the client (see the reverse-proxy item below).
Server-Sent Events (SSE) convenience layer¶
Thin abstraction over existing chunked streaming - no new compile flag needed. Auto-formats text/event-stream content type with id:, event:, data: field framing and retry: reconnect hints. API sugar on HttpResponseWriter:
router.setPath(http::Method::GET, "/events", [](const HttpRequestView&, HttpResponseWriter& w) {
SseWriter sse(w);
sse.event("update", R"({"temp": 22.5})");
sse.event("update", R"({"temp": 23.1})", "evt-002"); // with id
});
Growing in popularity as a lighter alternative to WebSocket for server → client push (dashboards, notifications, live feeds).
Rate Limiting middleware (AERONET_ENABLE_RATE_LIMIT)¶
Built-in token bucket or sliding window rate limiter. Per-IP and/or per-route. Returns 429 Too Many Requests with Retry-After header. Configurable burst / sustained rates. Pluggable backend interface (in-memory default, extensible to Redis or shared stores). Currently only TLS handshake rate limiting exists (TLSConfig::handshakeRateLimitPerSecond); this would extend to HTTP request level.
Status: Delivered (in the next release): middleware-first RateLimitRequestMiddlewareBuilder with an in-memory token-bucket backend (429 + Retry-After), a configurable client-key strategy, global or per-route / per-group installation, plus an optional Redis sliding-window contract (RedisSlidingWindowRateLimitStore) for distributed synchronization.
Cookie helpers & Session store (AERONET_ENABLE_SESSIONS)¶
- Cookie builder: RFC 6265-compliant
Set-Cookiehelper withSameSite,Secure,HttpOnly,MaxAge,Path,Domainattributes. Currently only raw header parsing exists. - Session store: In-memory session store with configurable TTL and pluggable backend interface. HMAC-signed session cookie for integrity. Both Drogon and Crow include this built-in; it is the most basic state management primitive for web applications.
Response caching middleware (AERONET_ENABLE_RESPONSE_CACHE)¶
In-memory LRU cache keyed by method + path + Vary headers. Respects Cache-Control directives (max-age, no-store, no-cache, private). Configurable max entries and memory budget. Per-route opt-in via middleware registration. ETag / If-None-Match validation for cached entries. Useful for APIs with expensive computation behind cacheable endpoints.
Regex route constraints¶
Status: Delivered in 1.3.0 as route parameter constraints - inline {name:pattern} syntax
(e.g. /users/{id:[0-9]+}), compiled at registration, with a fast custom matcher for common character
classes and a std::regex fallback. A non-matching parameter yields 404.
Route groups & prefix mounting¶
Status: Delivered in 1.3.0 (Router::group, nested groups, shared middleware/config inheritance).
Organize routes under a common prefix with shared middleware, reducing boilerplate for versioned APIs. Inspired by Express.js Router, Gin Group(), Axum Router::nest():
Router router;
auto api = router.group("/api/v1");
api.addRequestMiddleware(authMiddleware);
api.setPath(http::Method::GET, "/users", listUsersHandler); // matches /api/v1/users
api.setPath(http::Method::GET, "/users/{id}", getUserHandler); // matches /api/v1/users/{id}
Every major framework provides this (Express, Gin, Axum, Actix-web, FastAPI, Spring Boot). Without it, handler registrations repeat prefixes and middleware is duplicated across routes.
Per-route request timeout¶
Status: Delivered in 1.3.0 - PathHandlerEntry::timeout() (also settable per group and via JSON/YAML
config) enforces a per-route handler deadline during periodic sweeps (per-connection for HTTP/1.1, per-stream
for HTTP/2), returning 408 Request Timeout when it expires.
Content negotiation (Accept header)¶
Parse Accept header q-values to select response content type (JSON, YAML, XML, plain text). Return 406 Not Acceptable when no format matches. Currently only Accept-Encoding is negotiated; Accept (media type) is left to user code. Frameworks like Spring, Rails, Phoenix, and ASP.NET handle this transparently.
router.setPath(http::Method::GET, "/data", [](const HttpRequestView& req) {
auto negotiated = req.negotiate({"application/json", "text/yaml", "text/plain"});
// returns best match, or std::nullopt → 406
});
Request ID / correlation ID¶
Generate a unique ID per request (UUID or monotonic counter), set it on the response (X-Request-Id), and forward it to OpenTelemetry spans. If the client provides X-Request-Id, the server adopts it. Essential for distributed tracing and log correlation across microservices. Provided by Nginx ($request_id), Envoy, Express (express-request-id), Spring (spring-cloud-sleuth), FastAPI, etc.
IP-based access control middleware¶
Per-route allowlist/denylist by source IP or CIDR range. Returns 403 Forbidden for denied IPs. Useful for admin endpoints, internal APIs, and compliance. Nginx (allow/deny), HAProxy (acl), Caddy, Express (express-ip-filter), and Go all support this natively or as first-class middleware.
IpAccessPolicy policy;
policy.allow("10.0.0.0/8").allow("192.168.1.0/24").denyAll();
router.setPath(http::Method::GET, "/admin", adminHandler).accessPolicy(std::move(policy));
Authentication helpers (Basic / Bearer / JWT)¶
JWT (JWS profile) delivered (aeronet/jwt, AERONET_ENABLE_JWT): the full JWS algorithm suite
(HMAC HS*, RSA RS*/PS*, ECDSA ES*, EdDSA), claim validation (exp/nbf/iat/iss/aud/sub
with leeway + injectable clock), JWK/JWKS parsing with kid selection, and the mandatory security posture
(reject alg:none, family-based anti-confusion, constant-time HMAC, signature-before-claims). No dedicated
opt-in flag (cmake_dependent_option, ON whenever OpenSSL + Glaze are present); JWE out of scope. See
docs/FEATURES.md (JWT section) and aeronet/jwt/test/.
Still planned: a server-side middleware that parses the Authorization header (Basic credentials or
Bearer tokens) and wires the JWT verifier into a pluggable validator interface; plus a client-side
JWKS-fetch + cache helper building on the HTTP client.
Reverse proxy / HTTP forwarding mode¶
Forward incoming requests to upstream backends, rewriting headers (X-Forwarded-For, X-Forwarded-Proto, Via). Builds naturally on the delivered HTTP client module. Load balancing strategies (round-robin, least-connections). This turns aeronet from a pure application server into an edge/gateway server - a common deployment pattern (Nginx, Caddy, Envoy, Traefik, HAProxy).
Inbound request body streaming to handler¶
Deliver chunked request bodies to handlers as they arrive instead of buffering the full payload first. Enables processing uploads in bounded memory. Currently noted as a limitation in FEATURES.md. Express (streams), Go (io.Reader), Axum (BodyStream), Actix-web (Payload), and Rust Hyper all expose streaming request bodies.
Streaming multipart parsing¶
Process multipart/form-data uploads part-by-part as data arrives, rather than buffering the entire payload. Critical for large file uploads where the full body does not fit in memory. Currently flagged as a future item in FEATURES.md. Popular in Multer (Node.js), Actix-multipart, Spring StreamingMultipartResolver, and Go multipart.Reader.
Per-route body size limits¶
Status: Delivered in 1.3.0 - per-route maxBodyBytes / maxHeaderBytes overrides on PathHandlerEntry
and RouteGroup, enforced across HTTP/1.1 (sync + async) and HTTP/2 with 413 / 431.
Graceful config reload via signal (SIGHUP)¶
Reload the JSON/YAML config file on SIGHUP without restarting the server. Apply mutable config changes (timeouts, limits, compression settings, TLS certs) hot. Pairs with the existing Glaze config loader and postConfigUpdate(). Nginx, HAProxy, Caddy, and systemd-based services all support SIGHUP-driven reload.
WebSocket broadcast / pub-sub helpers¶
Built-in topic-based broadcast for WebSocket connections. WsRoom / WsTopic abstraction where connections subscribe to topics and the server broadcasts to all subscribers. Common pattern in Socket.IO, uWebSockets, Phoenix Channels, and SignalR. Reduces boilerplate for chat, dashboards, and real-time notification use cases.
ETag support for dynamic responses¶
Compute a weak or strong ETag from the response body (e.g. hash) and handle If-None-Match / 304 Not Modified for dynamic endpoints, not just static files. Reduces bandwidth for API responses that rarely change. Express (etag by default), Rails, ASP.NET, and Spring all auto-generate ETags for regular responses.
TLS enhancements (detailed roadmap)¶
Phase 3 (Advanced / Enterprise)¶
- OCSP stapling (passive, cached)
- Optional CRL / revocation hooks
- Key log (debug only)
- Security hardening audits (zeroization, memory scrub confirmations)
Phase 4 (Future Protocol / Extensibility)¶
- Per-SNI mTLS policies
- Session ticket key rotation scheduling & multi-key window
- (Stretch) Exploring QUIC/HTTP/3 (would likely be a separate transport layer, so only mention if strategic)
Realistic Network Testing¶
Goals
- Improve confidence that aeronet behaves correctly under latency, jitter, packet loss, reordering, partial delivery, and connection resets.
- Catch protocol-level bugs (HTTP/1.1 and HTTP/2), TLS handshake fragility, flow-control and resource-leak issues that do not appear on a perfect local loopback.
Approach and Components
- ✅ Deterministic simulated-network unit tests (DONE):
TestPipe+TestTransport: in-memory transport withFaultPolicyfor deterministic partial reads/writes, EAGAIN, and connection resets.FaultInjectingTransport: decorator wrapping real transports for integration tests with the full event loop.- Transport test hook (
g_transportDecorator): compile-time gated (AERONET_ENABLE_TEST_HOOKS) global atomic that decorates transports at accept time. - 26 unit tests + 11 integration tests covering partial delivery, EAGAIN, resets, combined faults, pipelining.
-
Already found and fixed a latent HTTP/1.1 parser bug (header boundary off-by-one).
-
Proxy-based user-space fault injection (medium priority):
- Integrate Toxiproxy or a small custom proxy harness for tests that exercise full binaries without requiring NET_ADMIN privileges.
-
Use the proxy to inject latency, connection cuts, truncation and partial writes to validate end-to-end behaviors.
-
Kernel-level integration using
tc netemand network namespaces (lower priority): - Create e2e tests that run client & server in separate Linux network namespaces connected by a veth pair with
tc netemrules applied. - Simulate real TCP behaviours (retransmits, delayed ACKs, segment coalescing, zero-window events) that only the kernel stack exhibits.
- These tests are heavier and run in nightly or scheduled CI only; they are optional for PRs because they require privileged runners and can be flaky.
Test Design & Best Practices
- Start with syscall error injection tests (EINTR, EAGAIN, EPIPE, ECONNRESET) and partial I/O.
- Prefer deterministic
TestTransportunit tests for core protocol logic: easier to reproduce and debug. - For integration tests, capture detailed artifacts on failures: pcap, logs, and deterministic seeds used by the test harness.
- Expose test-time configuration hooks (shorter timeouts, deterministic timers) so tests run quickly and reliably.
CI Policy
- PRs: run all deterministic unit tests (including
TestTransportsimulated-network tests) and integration fault-injection tests. - Nightly: run proxy-based and
tc netemintegration suites; mark these jobs non-blocking for PRs.
Milestones
- ✅ Add
TestTransportabstraction and deterministic unit tests covering partial I/O, EAGAIN, and resets. - ✅ Add
FaultInjectingTransport+ global hook for integration tests with real sockets. - ✅ Integration tests for HTTP/1.1 under faults (partial reads/writes, resets, pipelining).
- Add HTTP/2 direct protocol tests using
TestTransport+processInput()for frame-level fault injection. - Add simple Toxiproxy-based harness and a handful of end-to-end tests (connection cut, high latency, truncated responses).
- Add
ip netns+tc netemscripts intests/e2e/and integrate nightly CI job. - Collect failure artifacts (pcap + logs) and add tooling to reproduce failing scenarios locally with the same netem parameters.
Known Limitations
- Read-EAGAIN incompatible with EPOLLET in integration tests: When
FaultInjectingTransportreturns{0, ReadReady}(simulated EAGAIN), the server breaks to wait for a new EPOLLIN edge. But with EPOLLET, the socket already has data so no edge fires - causing a hang. Workaround: EAGAIN on reads is tested only at unit level; integration tests use partial reads (which work becauseEPOLL_CTL_ADDwith existing data triggers an initial event).
Acceptance Criteria
- Protocol correctness under simulated faults: no state corruption, proper error propagation, and graceful teardown.
- No resource leaks (sockets, memory) in faulted runs.
- Deterministic unit tests reproduce failures locally with a seed and do not rely on privileged resources.
Notes
- The transport test hook is compile-time gated (
#ifdef AERONET_ENABLE_TEST_HOOKS) - zero cost in production builds. - Proxy-based tests are useful when privileged operations are not available in CI.