udpsclient: size the TCP receive buffer from max_packet_bytes so large MARTe frames are not dropped

A TCP frame is never fragmented; a real TCPStreamer cycle (240 KB) exceeded the UDP datagram
limit and made the client reconnect forever, seeing CONFIG but never DATA.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-06 14:07:54 +01:00
1 parent a65f953f17
commit 39f82ade31
3 files changed
+17 -4

No files matched your search

+9 -2
View File
@@ -198,6 +198,7 @@ struct udps_client {
size_t vals_cap; size_t vals_cap;
uint8_t *rxbuf; uint8_t *rxbuf;
size_t rxbuf_cap; /**< bytes allocated for rxbuf */
udps_held_t held[UDPS_REORDER_SLOTS]; udps_held_t held[UDPS_REORDER_SLOTS];
size_t held_bytes; /**< payload bytes currently held */ size_t held_bytes; /**< payload bytes currently held */
@@ -1256,7 +1257,7 @@ static int read_tcp_frame(udps_client_t *c) {
if (udps_parse_header(c->rxbuf, UDPS_HEADER_SIZE, &h) != 0) { if (udps_parse_header(c->rxbuf, UDPS_HEADER_SIZE, &h) != 0) {
return -1; /* desynchronised: the stream cannot be resynced */ return -1; /* desynchronised: the stream cannot be resynced */
} }
if (h.payload_bytes > (uint32_t)(UDPS_RX_BUF_BYTES - UDPS_HEADER_SIZE)) { if (h.payload_bytes > c->rxbuf_cap - UDPS_HEADER_SIZE) {
return -1; return -1;
} }
if (h.payload_bytes > 0u && if (h.payload_bytes > 0u &&
@@ -1350,7 +1351,13 @@ udps_client_t *udps_client_create(const udps_client_config_t *cfg) {
return NULL; return NULL;
} }
c->rxbuf = (uint8_t *)malloc(UDPS_RX_BUF_BYTES); /* A UDP datagram is at most UDPS_MAX_DATAGRAM_BYTES; a TCP frame is not fragmented
* and may be as large as max_packet_bytes (MARTe's TCPStreamer allows 1 MiB). */
c->rxbuf_cap = UDPS_RX_BUF_BYTES;
if (c->cfg.use_tcp && (size_t)c->cfg.max_packet_bytes + UDPS_HEADER_SIZE > c->rxbuf_cap) {
c->rxbuf_cap = (size_t)c->cfg.max_packet_bytes + UDPS_HEADER_SIZE;
}
c->rxbuf = (uint8_t *)malloc(c->rxbuf_cap);
if (c->rxbuf == NULL) { if (c->rxbuf == NULL) {
free(c); free(c);
return NULL; return NULL;
+4 -2
View File
@@ -276,8 +276,10 @@ is intentional. Six things were changed:
`read_tcp_frame()` for the frames, the reorder window forced to 0 (TCP is ordered, so a `read_tcp_frame()` for the frames, the reorder window forced to 0 (TCP is ordered, so a
counter gap is a server-side drop and is reported at once), multicast forced off, no counter gap is a server-side drop and is reported at once), multicast forced off, no
DISCONNECT frame on close (a write to a closed peer would raise SIGPIPE), no keepalive ACK DISCONNECT frame on close (a write to a closed peer would raise SIGPIPE), no keepalive ACK
and no datagram tap. A frame must fit `UDPS_MAX_DATAGRAM_BYTES` (the server's and no datagram tap. The receive buffer is `max_packet_bytes + header` (1 MiB by default,
`MaxPayloadSize` default of 60000 does). A stream name over 255 bytes is refused at create. matching the server's frame ceiling) in TCP mode, since a TCP frame is never fragmented and
a MARTe cycle of large arrays is one frame; it is `UDPS_MAX_DATAGRAM_BYTES` only for UDP. A
stream name over 255 bytes is refused at create.
`deps/plotter` is upstream's own repository and this app's figure support was added `deps/plotter` is upstream's own repository and this app's figure support was added
there rather than worked around here: `Figure::set_min_y_gutter()` (a floor under the there rather than worked around here: `Figure::set_min_y_gutter()` (a floor under the
+4
View File
@@ -2316,6 +2316,10 @@ run as `--source tcps --source-opt port=44500` against `tools/mock_tcpstreamer.p
`verification.md`. A service run (`--stream fast` on both sides) reads the same two lines; the mock logs `verification.md`. A service run (`--stream fast` on both sides) reads the same two lines; the mock logs
`SUBSCRIBE 'fast'`. With no mock the stream line reads `connected=0 signals=0 names= frames=0 ok=0`: a reading, `SUBSCRIBE 'fast'`. With no mock the stream line reads `connected=0 signals=0 names= frames=0 ok=0`: a reading,
not a failure (the retries are quiet on stderr). not a failure (the retries are quiet on stderr).
A mock run with `--rate 1000000 --batch 20000` sends frames of about 560 KB; the stream line must still
read `frames>0 ok=1`. `frames=0` with the mock logging a new client every few hundred ms is the signature
of a receive buffer smaller than the server's frame (the client drops the connection, reconnects, and
gets CONFIG again, never DATA).
- `stream: ok=1`: connected, the five names the mock sends (its `time` master channel is a signal of its own, so - `stream: ok=1`: connected, the five names the mock sends (its `time` master channel is a signal of its own, so
the list is `time,sine,square,ramp,noise`, not four), and frames counted. `connected=0` with a mock running is the list is `time,sine,square,ramp,noise`, not four), and frames counted. `connected=0` with a mock running is