DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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
9#include "services/df1/df1.h"
10
11#if DETWS_ENABLE_DF1
12
13uint8_t df1_bcc(const uint8_t *data, size_t len)
14{
15 uint8_t s = 0;
16 for (size_t i = 0; i < len; i++)
17 s = (uint8_t)(s + data[i]);
18 return (uint8_t)(0u - s); // 2's complement (modulo 256)
19}
20
21// Reflected CRC-16 (poly 0xA001 = reflect(0x8005)), continuing from @p crc.
22static uint16_t crc16(uint16_t crc, const uint8_t *data, size_t len)
23{
24 for (size_t i = 0; i < len; i++)
25 {
26 crc ^= data[i];
27 for (int b = 0; b < 8; b++)
28 crc = (crc & 1) ? (uint16_t)((crc >> 1) ^ 0xA001) : (uint16_t)(crc >> 1);
29 }
30 return crc;
31}
32
33uint16_t df1_crc(const uint8_t *data, size_t len)
34{
35 return crc16(0x0000, data, len);
36}
37
38size_t df1_build_frame(uint8_t *buf, size_t cap, const uint8_t *data, size_t data_len, Df1Check check)
39{
40 if (!buf || (data_len && !data))
41 return 0;
42 size_t stuffed = data_len;
43 for (size_t i = 0; i < data_len; i++)
44 if (data[i] == DF1_DLE)
45 stuffed++; // a DLE data byte is doubled on the wire
46 size_t checklen = (check == Df1Check::DF1_CHECK_CRC) ? 2 : 1;
47 size_t total = 2 + stuffed + 2 + checklen; // DLE STX + data + DLE ETX + check
48 if (total > cap)
49 return 0;
50
51 size_t p = 0;
52 buf[p++] = DF1_DLE;
53 buf[p++] = DF1_STX;
54 for (size_t i = 0; i < data_len; i++)
55 {
56 buf[p++] = data[i];
57 if (data[i] == DF1_DLE)
58 buf[p++] = DF1_DLE;
59 }
60 buf[p++] = DF1_DLE;
61 buf[p++] = DF1_ETX;
62
63 if (check == Df1Check::DF1_CHECK_CRC)
64 {
65 uint8_t etx = DF1_ETX;
66 uint16_t c = crc16(0x0000, data, data_len); // CRC over the data ...
67 c = crc16(c, &etx, 1); // ... plus the ETX
68 buf[p++] = (uint8_t)(c & 0xFF); // low byte first
69 buf[p++] = (uint8_t)(c >> 8);
70 }
71 else
72 {
73 buf[p++] = df1_bcc(data, data_len); // BCC excludes the ETX
74 }
75 return p;
76}
77
78bool df1_parse_frame(const uint8_t *buf, size_t len, Df1Check check, uint8_t *out, size_t out_cap, size_t *out_len)
79{
80 size_t checklen = (check == Df1Check::DF1_CHECK_CRC) ? 2 : 1;
81 if (!buf || !out || len < 4 + checklen) // DLE STX DLE ETX + check
82 return false;
83 if (buf[0] != DF1_DLE || buf[1] != DF1_STX)
84 return false;
85
86 size_t i = 2;
87 size_t o = 0;
88 bool ended = false;
89 while (i < len)
90 {
91 if (buf[i] == DF1_DLE)
92 {
93 if (i + 1 >= len)
94 return false;
95 uint8_t next = buf[i + 1];
96 if (next == DF1_DLE) // doubled DLE -> one 0x10 data byte
97 {
98 if (o >= out_cap)
99 return false;
100 out[o++] = DF1_DLE;
101 i += 2;
102 }
103 else if (next == DF1_ETX) // end of data
104 {
105 i += 2;
106 ended = true;
107 break;
108 }
109 else
110 return false; // an unexpected control symbol inside the data
111 }
112 else
113 {
114 if (o >= out_cap)
115 return false;
116 out[o++] = buf[i++];
117 }
118 }
119 if (!ended)
120 return false;
121
122 if (check == Df1Check::DF1_CHECK_CRC)
123 {
124 if (i + 2 > len)
125 return false;
126 uint8_t etx = DF1_ETX;
127 uint16_t c = crc16(0x0000, out, o);
128 c = crc16(c, &etx, 1);
129 uint16_t got = (uint16_t)(buf[i] | ((uint16_t)buf[i + 1] << 8)); // low byte first
130 if (c != got)
131 return false;
132 }
133 else
134 {
135 if (i + 1 > len)
136 return false;
137 if (df1_bcc(out, o) != buf[i])
138 return false;
139 }
140 if (out_len)
141 *out_len = o;
142 return true;
143}
144
145#endif // DETWS_ENABLE_DF1
Allen-Bradley DF1 full-duplex frame codec (DETWS_ENABLE_DF1) - zero-heap framing + DLE byte-stuffing ...