ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_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 {
23 return false;
24 }
25 for (int i = 0; i < r; i++)
26 {
27 pc_dnc_flow_feed(flow, tmp[i]);
28 }
29 return true;
30}
31
32// Send @p n bytes, first honoring XOFF: update flow, and while paused poll recv for the XON.
33static bool emit(DncFlow *flow, DncSendFn send, DncRecvFn recv, void *ctx, const uint8_t *data, size_t n)
34{
35 if (!flow_drain(flow, recv, ctx))
36 {
37 return false;
38 }
39 uint32_t polls = 0;
40 while (!pc_dnc_flow_can_send(flow))
41 {
42 if (++polls > PC_DNC_XOFF_MAX_POLLS)
43 {
44 return false; // XOFF never cleared
45 }
46 if (!flow_drain(flow, recv, ctx))
47 {
48 return false;
49 }
50 }
51 return send(ctx, data, n) == (int)n;
52}
53
54// Emit @p count NUL runout (leader/trailer) bytes in chunks, honoring flow control.
55static bool emit_runout(DncFlow *flow, DncSendFn send, DncRecvFn recv, void *ctx, uint16_t count)
56{
57 uint8_t zeros[32];
58 memset(zeros, 0, sizeof(zeros));
59 while (count)
60 {
61 uint16_t chunk = count < sizeof(zeros) ? count : (uint16_t)sizeof(zeros);
62 if (!emit(flow, send, recv, ctx, zeros, chunk))
63 {
64 return false;
65 }
66 count -= chunk;
67 }
68 return true;
69}
70
71DncStreamResult dnc_stream(const DncCfg *cfg, const char *program, size_t prog_len, DncSendFn send, DncRecvFn recv,
72 void *ctx)
73{
74 if (!cfg || !send || !recv || (prog_len && !program))
75 {
76 return DncStreamResult::DNC_STREAM_ERR_ARG;
77 }
78
79 DncFlow flow;
80 pc_dnc_flow_init(&flow);
81 uint8_t buf[PC_DNC_LINE_MAX + 8];
82
83 // leader runout
84 if (cfg->leader_len && !emit_runout(&flow, send, recv, ctx, cfg->leader_len))
85 {
86 return DncStreamResult::DNC_STREAM_ERR_IO;
87 }
88
89 // program-start marker
90 // A marker is at most three bytes ('%' or EOR, plus the CR/LF end-of-block) and buf is
91 // PC_DNC_LINE_MAX + 8, so the encode has no failing arm to reach here; the check is what keeps
92 // that true if the marker ever grows.
93 size_t n = pc_dnc_encode_marker(cfg, buf, sizeof(buf));
94 if (n == 0) // GCOVR_EXCL_LINE a 3-byte marker always fits buf, see above
95 {
96 return DncStreamResult::DNC_STREAM_ERR_ENCODE; // GCOVR_EXCL_LINE unreachable body of the guard above
97 }
98 if (!emit(&flow, send, recv, ctx, buf, n))
99 {
100 return DncStreamResult::DNC_STREAM_ERR_IO;
101 }
102
103 // one block per source line
104 size_t i = 0;
105 while (i < prog_len)
106 {
107 size_t j = i;
108 while (j < prog_len && program[j] != '\n')
109 {
110 j++;
111 }
112 size_t line_len = j - i;
113 if (line_len && program[i + line_len - 1] == '\r')
114 {
115 line_len--; // strip a trailing CR (CRLF sources)
116 }
117 n = pc_dnc_encode_block(cfg, program + i, line_len, buf, sizeof(buf));
118 if (n == 0)
119 {
120 return DncStreamResult::DNC_STREAM_ERR_ENCODE; // untranslatable char or over-long block - fail closed
121 }
122 if (!emit(&flow, send, recv, ctx, buf, n))
123 {
124 return DncStreamResult::DNC_STREAM_ERR_IO;
125 }
126 i = j + 1; // skip the LF
127 }
128
129 // program-end marker (byte-identical to the start marker)
130 n = pc_dnc_encode_marker(cfg, buf, sizeof(buf));
131 if (!emit(&flow, send, recv, ctx, buf, n))
132 {
133 return DncStreamResult::DNC_STREAM_ERR_IO;
134 }
135
136 // trailer runout
137 if (cfg->leader_len && !emit_runout(&flow, send, recv, ctx, cfg->leader_len))
138 {
139 return DncStreamResult::DNC_STREAM_ERR_IO;
140 }
141
142 return DncStreamResult::DNC_STREAM_OK;
143}
144
145#endif // PC_ENABLE_DNC
DNC drip-feed engine (PC_ENABLE_DNC) - stream a whole G-code program over a transport,...
#define PC_DNC_XOFF_MAX_POLLS
Safety cap on how many times the DNC stream engine polls the reverse channel while paused by an XOFF,...
#define PC_DNC_LINE_MAX
Largest G-code block (one line) the DNC decoder reassembles (PC_ENABLE_DNC).