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:
1 parent
a65f953f17
commit
39f82ade31
3 files changed
+17
-4
No files matched your search
Vendored
+9
-2
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user