DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
dnc_stream.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 dnc_stream.cpp
6 * @brief DNC drip-feed engine (see dnc_stream.h). Frames a program with the dnc codec and paces it
7 * against reverse-channel XON/XOFF over a send/recv seam.
8 */
9
10#include "dnc_stream.h"
11
12#if DETWS_ENABLE_DNC
13
14#include <string.h>
15
16// Drain any reverse-channel bytes into the flow state (non-blocking); false on a recv error.
17static bool flow_drain(DncFlow *flow, DncRecvFn recv, void *ctx)
18{
19 uint8_t tmp[16];
20 int r = recv(ctx, tmp, sizeof(tmp));
21 if (r < 0)
22 return false;
23 for (int i = 0; i < r; i++)
24 dnc_flow_feed(flow, tmp[i]);
25 return true;
26}
27
28// Send @p n bytes, first honoring XOFF: update flow, and while paused poll recv for the XON.
29static bool emit(DncFlow *flow, DncSendFn send, DncRecvFn recv, void *ctx, const uint8_t *data, size_t n)
30{
31 if (!flow_drain(flow, recv, ctx))
32 return false;
33 uint32_t polls = 0;
34 while (!dnc_flow_can_send(flow))
35 {
36 if (++polls > DETWS_DNC_XOFF_MAX_POLLS)
37 return false; // XOFF never cleared
38 if (!flow_drain(flow, recv, ctx))
39 return false;
40 }
41 return send(ctx, data, n) == (int)n;
42}
43
44// Emit @p count NUL runout (leader/trailer) bytes in chunks, honoring flow control.
45static bool emit_runout(DncFlow *flow, DncSendFn send, DncRecvFn recv, void *ctx, uint16_t count)
46{
47 uint8_t zeros[32];
48 memset(zeros, 0, sizeof(zeros));
49 while (count)
50 {
51 uint16_t chunk = count < sizeof(zeros) ? count : (uint16_t)sizeof(zeros);
52 if (!emit(flow, send, recv, ctx, zeros, chunk))
53 return false;
54 count -= chunk;
55 }
56 return true;
57}
58
59DncStreamResult dnc_stream(const DncCfg *cfg, const char *program, size_t prog_len, DncSendFn send, DncRecvFn recv,
60 void *ctx)
61{
62 if (!cfg || !send || !recv || (prog_len && !program))
63 return DncStreamResult::DNC_STREAM_ERR_ARG;
64
65 DncFlow flow;
66 dnc_flow_init(&flow);
67 uint8_t buf[DETWS_DNC_LINE_MAX + 8];
68
69 // leader runout
70 if (cfg->leader_len && !emit_runout(&flow, send, recv, ctx, cfg->leader_len))
71 return DncStreamResult::DNC_STREAM_ERR_IO;
72
73 // program-start marker
74 size_t n = dnc_encode_marker(cfg, buf, sizeof(buf));
75 if (n == 0)
76 return DncStreamResult::DNC_STREAM_ERR_ENCODE;
77 if (!emit(&flow, send, recv, ctx, buf, n))
78 return DncStreamResult::DNC_STREAM_ERR_IO;
79
80 // one block per source line
81 size_t i = 0;
82 while (i < prog_len)
83 {
84 size_t j = i;
85 while (j < prog_len && program[j] != '\n')
86 j++;
87 size_t line_len = j - i;
88 if (line_len && program[i + line_len - 1] == '\r')
89 line_len--; // strip a trailing CR (CRLF sources)
90 n = dnc_encode_block(cfg, program + i, line_len, buf, sizeof(buf));
91 if (n == 0)
92 return DncStreamResult::DNC_STREAM_ERR_ENCODE; // untranslatable char or over-long block - fail closed
93 if (!emit(&flow, send, recv, ctx, buf, n))
94 return DncStreamResult::DNC_STREAM_ERR_IO;
95 i = j + 1; // skip the LF
96 }
97
98 // program-end marker (byte-identical to the start marker)
99 n = dnc_encode_marker(cfg, buf, sizeof(buf));
100 if (!emit(&flow, send, recv, ctx, buf, n))
101 return DncStreamResult::DNC_STREAM_ERR_IO;
102
103 // trailer runout
104 if (cfg->leader_len && !emit_runout(&flow, send, recv, ctx, cfg->leader_len))
105 return DncStreamResult::DNC_STREAM_ERR_IO;
106
107 return DncStreamResult::DNC_STREAM_OK;
108}
109
110#endif // DETWS_ENABLE_DNC
#define DETWS_DNC_LINE_MAX
Largest G-code block (one line) the DNC decoder reassembles (DETWS_ENABLE_DNC).
#define DETWS_DNC_XOFF_MAX_POLLS
Safety cap on how many times the DNC stream engine polls the reverse channel while paused by an XOFF,...
DNC drip-feed engine (DETWS_ENABLE_DNC) - stream a whole G-code program over a transport,...