DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
relay.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file relay.h
6 * @brief TCP relay / DNAT port forwarding (DETWS_ENABLE_RELAY) - a bidirectional byte pump.
7 *
8 * Publishes an internal `host:port` through the server: an inbound (accepted) connection is relayed
9 * to an origin (an outbound connection to the internal service), moving bytes in both directions.
10 * The engine is pure - it touches the two sockets only through send/recv seams - so it is
11 * host-testable and rides `det_client` on the device. The app drives it: each poll tick (or whenever
12 * a socket is readable/writable) it calls det_relay_step() until the relay reports DONE, then closes
13 * both sockets.
14 *
15 * Correctness details:
16 * - **Backpressure**: a `send` seam may accept fewer bytes than offered; the un-accepted bytes are
17 * carried in a per-direction buffer and retried on the next step before more are read.
18 * - **Independent half-close**: each direction finishes when its source signals EOF and its buffer
19 * drains. When a direction finishes, the opposite peer's optional `shutdown` seam is called once
20 * (propagating the half-close so the origin sees the client's FIN); the relay is DONE only when
21 * both directions have finished.
22 *
23 * @author Douglas Quigg (dstroy0)
24 * @date 2026
25 */
26
27#ifndef DETERMINISTICESPASYNCWEBSERVER_RELAY_H
28#define DETERMINISTICESPASYNCWEBSERVER_RELAY_H
29
30#include "ServerConfig.h"
31
32#if DETWS_ENABLE_RELAY
33
34#include <stddef.h>
35#include <stdint.h>
36
37/** @brief det_relay_step() outcome. */
38enum class DetRelayStatus : int32_t
39{
40 DET_RELAY_ERROR = -1, ///< a send/recv seam reported an error; the caller should close both sides
41 DET_RELAY_RUNNING = 0, ///< still relaying (keep stepping)
42 DET_RELAY_DONE = 1, ///< both directions finished (EOF + drained); the caller closes both sides
43};
44
45/**
46 * @brief Read up to @p cap bytes from the peer into @p buf.
47 * @return bytes read (> 0), 0 if none are available now, or < 0 once the peer has closed its send
48 * side (EOF) or errored.
49 */
50using DetRelayRecvFn = int (*)(void *ctx, uint8_t *buf, size_t cap);
51
52/**
53 * @brief Write up to @p len bytes to the peer.
54 * @return bytes accepted (> 0, may be < @p len under backpressure), 0 if none can be accepted right
55 * now, or < 0 on error.
56 */
57using DetRelaySendFn = int (*)(void *ctx, const uint8_t *buf, size_t len);
58
59/** @brief Optional: signal the peer that no more data will be sent to it (a write-side half-close). */
60using DetRelayShutdownFn = void (*)(void *ctx);
61
62/** @brief One end of a relay (a socket, behind seams). @p shutdown may be null. */
63struct DetRelayEnd
64{
65 DetRelayRecvFn recv;
66 DetRelaySendFn send;
67 DetRelayShutdownFn shutdown;
68 void *ctx;
69};
70
71/** @brief A relay between two ends. Owns the per-direction carry buffers; zero heap. */
72struct DetRelay
73{
74 DetRelayEnd a;
75 DetRelayEnd b;
76 uint8_t buf_a2b[DETWS_RELAY_BUF];
77 uint8_t buf_b2a[DETWS_RELAY_BUF];
78 uint16_t a2b_len; ///< bytes read from a pending send to b
79 uint16_t a2b_off; ///< how many of those already sent
80 uint16_t b2a_len;
81 uint16_t b2a_off;
82 bool a_eof; ///< the recv side of a has hit EOF
83 bool b_eof; ///< the recv side of b has hit EOF
84 bool a2b_done; ///< the a->b direction has finished (EOF + drained)
85 bool b2a_done; ///< the b->a direction has finished (EOF + drained)
86 bool a_shut_sent; ///< the shutdown seam of a has been called
87 bool b_shut_sent; ///< the shutdown seam of b has been called
88 uint32_t bytes_a2b; ///< bytes relayed a->b (observability)
89 uint32_t bytes_b2a; ///< bytes relayed b->a (observability)
90};
91
92/**
93 * @brief Initialize a relay between @p client (the inbound connection) and @p origin (the outbound
94 * connection to the internal service). Both ends are copied.
95 */
96void det_relay_init(DetRelay *r, const DetRelayEnd *client, const DetRelayEnd *origin);
97
98/**
99 * @brief Do one non-blocking pass: flush any pending bytes and read more, in both directions.
100 * @return a ::DetRelayStatus. Call repeatedly (per poll tick) until DONE or ERROR, then close both.
101 */
102DetRelayStatus det_relay_step(DetRelay *r);
103
104/**
105 * @brief Signal that a peer's send side has closed, when the transport reports EOF out of band (a
106 * close callback) rather than through @c recv returning < 0.
107 *
108 * Some transports (e.g. the server's `det_conn`, which delivers a close as an `on_close` event, not
109 * as a short read) cannot report EOF via the recv seam. Call this from that event so the direction
110 * that peer sources finishes cleanly: the bytes already buffered are still flushed, then the opposite
111 * peer's `shutdown` fires and the relay reaches DONE once both directions have finished.
112 *
113 * @param origin false for the client (inbound) side, true for the origin (outbound) side.
114 */
115void det_relay_note_eof(DetRelay *r, bool origin);
116
117#endif // DETWS_ENABLE_RELAY
118
119#endif // DETERMINISTICESPASYNCWEBSERVER_RELAY_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_RELAY_BUF
Per-direction relay buffer size (bytes) for services/relay (DETWS_ENABLE_RELAY).