ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
df1.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 df1.cpp
6 * @brief Allen-Bradley DF1 full-duplex frame builder + parser (pure, host-tested).
7 */
8
10#include "shared_primitives/crc.h" // PC_CRC16_ARC
11
12#if PC_ENABLE_DF1
13
14uint8_t pc_df1_bcc(const uint8_t *data, size_t len)
15{
16 uint8_t s = 0;
17 for (size_t i = 0; i < len; i++)
18 {
19 s = (uint8_t)(s + data[i]);
20 }
21 return (uint8_t)(0u - s); // 2's complement (modulo 256)
22}
23
24// DF1's block check is the reflected CRC-16 (poly 0xA001 = reflect(0x8005), init 0, no final XOR),
25// cataloged as CRC-16/ARC. test_crc diffs the shared engine against the loop that used to live here
26// over every length 0..64.
27//
28// The CRC covers the data *plus* a trailing ETX that is not adjacent to it in memory, so the callers
29// below run the engine's begin/update/final split rather than assembling a scratch buffer. Note the
30// running value they carry is the engine's internal register, which for a reflected CRC is held
31// unreflected until final() - it is not interchangeable with the old right-shift intermediate, which
32// is why those call sites convert as a whole rather than swapping one call.
33static uint16_t df1_crc_data_plus_etx(const uint8_t *data, size_t len, uint8_t etx)
34{
35 uint32_t c = pc_crc_begin(&PC_CRC16_ARC);
36 c = pc_crc_update(&PC_CRC16_ARC, c, data, len);
37 c = pc_crc_update(&PC_CRC16_ARC, c, &etx, 1);
38 return (uint16_t)pc_crc_final(&PC_CRC16_ARC, c);
39}
40
41uint16_t pc_df1_crc(const uint8_t *data, size_t len)
42{
43 return (uint16_t)pc_crc(&PC_CRC16_ARC, data, len);
44}
45
46size_t pc_df1_build_frame(uint8_t *buf, size_t cap, const uint8_t *data, size_t data_len, Df1Check check)
47{
48 if (!buf || (data_len && !data))
49 {
50 return 0;
51 }
52 size_t stuffed = data_len;
53 for (size_t i = 0; i < data_len; i++)
54 {
55 if (data[i] == DF1_DLE)
56 {
57 stuffed++; // a DLE data byte is doubled on the wire
58 }
59 }
60 size_t checklen = (check == Df1Check::DF1_CHECK_CRC) ? 2 : 1;
61 size_t total = 2 + stuffed + 2 + checklen; // DLE STX + data + DLE ETX + check
62 if (total > cap)
63 {
64 return 0;
65 }
66
67 size_t p = 0;
68 buf[p++] = DF1_DLE;
69 buf[p++] = DF1_STX;
70 for (size_t i = 0; i < data_len; i++)
71 {
72 buf[p++] = data[i];
73 if (data[i] == DF1_DLE)
74 {
75 buf[p++] = DF1_DLE;
76 }
77 }
78 buf[p++] = DF1_DLE;
79 buf[p++] = DF1_ETX;
80
81 if (check == Df1Check::DF1_CHECK_CRC)
82 {
83 uint16_t c = df1_crc_data_plus_etx(data, data_len, DF1_ETX);
84 buf[p++] = (uint8_t)(c & 0xFF); // low byte first
85 buf[p++] = (uint8_t)(c >> 8);
86 }
87 else
88 {
89 buf[p++] = pc_df1_bcc(data, data_len); // BCC excludes the ETX
90 }
91 return p;
92}
93
94bool pc_df1_parse_frame(const uint8_t *buf, size_t len, Df1Check check, uint8_t *out, size_t out_cap, size_t *out_len)
95{
96 size_t checklen = (check == Df1Check::DF1_CHECK_CRC) ? 2 : 1;
97 if (!buf || !out || len < 4 + checklen) // DLE STX DLE ETX + check
98 {
99 return false;
100 }
101 if (buf[0] != DF1_DLE || buf[1] != DF1_STX)
102 {
103 return false;
104 }
105
106 size_t i = 2;
107 size_t o = 0;
108 bool ended = false;
109 while (i < len)
110 {
111 if (buf[i] == DF1_DLE)
112 {
113 if (i + 1 >= len)
114 {
115 return false;
116 }
117 uint8_t next = buf[i + 1];
118 if (next == DF1_DLE) // doubled DLE -> one 0x10 data byte
119 {
120 if (o >= out_cap)
121 {
122 return false;
123 }
124 out[o++] = DF1_DLE;
125 i += 2;
126 }
127 else if (next == DF1_ETX) // end of data
128 {
129 i += 2;
130 ended = true;
131 break;
132 }
133 else
134 {
135 return false; // an unexpected control symbol inside the data
136 }
137 }
138 else
139 {
140 if (o >= out_cap)
141 {
142 return false;
143 }
144 out[o++] = buf[i++];
145 }
146 }
147 if (!ended)
148 {
149 return false;
150 }
151
152 if (check == Df1Check::DF1_CHECK_CRC)
153 {
154 if (i + 2 > len)
155 {
156 return false;
157 }
158 uint16_t c = df1_crc_data_plus_etx(out, o, DF1_ETX);
159 uint16_t got = (uint16_t)(buf[i] | ((uint16_t)buf[i + 1] << 8)); // low byte first
160 if (c != got)
161 {
162 return false;
163 }
164 }
165 else
166 {
167 if (i + 1 > len)
168 {
169 return false;
170 }
171 if (pc_df1_bcc(out, o) != buf[i])
172 {
173 return false;
174 }
175 }
176 if (out_len)
177 {
178 *out_len = o;
179 }
180 return true;
181}
182
183#endif // PC_ENABLE_DF1
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_ARC
CRC-16/ARC (a.k.a. CRC-16, IBM). check = 0xBB3D.
Definition crc.h:180
uint32_t pc_crc_final(const pc_crc_params *p, uint32_t crc)
Finish a CRC: apply the output reflection and the final XOR.
Definition crc.h:142
uint32_t pc_crc_begin(const pc_crc_params *p)
Start a CRC.
Definition crc.h:108
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
uint32_t pc_crc_update(const pc_crc_params *p, uint32_t crc, const uint8_t *data, size_t len)
Fold len octets at data into the running register crc.
Definition crc.h:119
Allen-Bradley DF1 full-duplex frame codec (PC_ENABLE_DF1) - zero-heap framing + DLE byte-stuffing + B...