DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
relay.cpp
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.cpp
6 * @brief TCP relay / DNAT byte pump implementation (see relay.h).
7 */
8
9#include "relay.h"
10
11#if DETWS_ENABLE_RELAY
12
13#include <string.h>
14
15// Read one non-blocking chunk from src and forward it to dst. Sets *src_eof on a src seam error;
16// returns -1 on a dst send error, else 0. A zero-length read leaves the buffers untouched.
17static int pump_refill(DetRelayEnd *src, DetRelayEnd *dst, uint8_t *buf, uint16_t *len, uint16_t *off, bool *src_eof,
18 uint32_t *counter)
19{
20 int r = src->recv(src->ctx, buf, DETWS_RELAY_BUF);
21 if (r < 0)
22 {
23 *src_eof = true;
24 return 0;
25 }
26 if (r > 0)
27 {
28 *len = (uint16_t)r;
29 int s = dst->send(dst->ctx, buf, *len);
30 if (s < 0)
31 return -1;
32 *off = (uint16_t)s;
33 *counter += (uint32_t)s;
34 }
35 return 0;
36}
37
38// Pump one direction (src -> dst) one non-blocking pass: flush pending bytes, then read more.
39// @param dst_shut_sent the "shutdown already called" flag for @p dst (the peer that stops receiving
40// once this direction finishes). Returns -1 on a seam error, else 0.
41static int pump(DetRelayEnd *src, DetRelayEnd *dst, uint8_t *buf, uint16_t *len, uint16_t *off, bool *src_eof,
42 bool *dir_done, bool *dst_shut_sent, uint32_t *counter)
43{
44 if (*dir_done)
45 return 0;
46
47 // 1. flush whatever is already buffered for dst
48 if (*off < *len)
49 {
50 int s = dst->send(dst->ctx, buf + *off, (size_t)(*len - *off));
51 if (s < 0)
52 return -1;
53 *off = (uint16_t)(*off + s);
54 *counter += (uint32_t)s;
55 }
56
57 // 2. buffer drained: read more from src (unless it already hit EOF), then try to send it right
58 // away so one step moves data end to end
59 if (*off >= *len)
60 {
61 *off = 0;
62 *len = 0;
63 if (!*src_eof && pump_refill(src, dst, buf, len, off, src_eof, counter) < 0)
64 return -1;
65 }
66
67 // 3. this direction is finished once src is at EOF and nothing is left to flush
68 if (*src_eof && *off >= *len)
69 {
70 *dir_done = true;
71 if (dst->shutdown && !*dst_shut_sent)
72 {
73 dst->shutdown(dst->ctx); // propagate the half-close to the peer that stops receiving
74 *dst_shut_sent = true;
75 }
76 }
77 return 0;
78}
79
80void det_relay_init(DetRelay *r, const DetRelayEnd *client, const DetRelayEnd *origin)
81{
82 if (!r || !client || !origin)
83 return;
84 memset(r, 0, sizeof(*r));
85 r->a = *client;
86 r->b = *origin;
87}
88
89DetRelayStatus det_relay_step(DetRelay *r)
90{
91 if (!r)
92 return DetRelayStatus::DET_RELAY_ERROR;
93 // a -> b: dst is b, so b's shutdown fires when this direction finishes
94 if (pump(&r->a, &r->b, r->buf_a2b, &r->a2b_len, &r->a2b_off, &r->a_eof, &r->a2b_done, &r->b_shut_sent,
95 &r->bytes_a2b) < 0)
96 return DetRelayStatus::DET_RELAY_ERROR;
97 // b -> a: dst is a
98 if (pump(&r->b, &r->a, r->buf_b2a, &r->b2a_len, &r->b2a_off, &r->b_eof, &r->b2a_done, &r->a_shut_sent,
99 &r->bytes_b2a) < 0)
100 return DetRelayStatus::DET_RELAY_ERROR;
101
102 return (r->a2b_done && r->b2a_done) ? DetRelayStatus::DET_RELAY_DONE : DetRelayStatus::DET_RELAY_RUNNING;
103}
104
105void det_relay_note_eof(DetRelay *r, bool origin)
106{
107 if (!r)
108 return;
109 // The next det_relay_step drains that source's buffered bytes, then finishes its direction.
110 if (origin)
111 r->b_eof = true;
112 else
113 r->a_eof = true;
114}
115
116#endif // DETWS_ENABLE_RELAY
#define DETWS_RELAY_BUF
Per-direction relay buffer size (bytes) for services/relay (DETWS_ENABLE_RELAY).
TCP relay / DNAT port forwarding (DETWS_ENABLE_RELAY) - a bidirectional byte pump.