1+ #pragma once
2+
3+ #include < stdbool.h>
4+ #include < stdint.h>
5+
6+ #include " AudioTools/CoreAudio/Buffers.h"
7+ #include " AudioToolsConfig.h"
8+
9+ namespace audio_tools {
10+
11+ /* *
12+ * @brief High-Level Data Link Control (HDLC) is a bit-oriented code-transparent
13+ * synchronous data link layer protocol for reliable, framed, and error-checked
14+ * communication.
15+ *
16+ * This class implements HDLC framing with:
17+ * - Frame delimiter (0x7E) for marking frame boundaries
18+ * - Byte stuffing to escape special characters in the data
19+ * - 16-bit CRC-CCITT for error detection
20+ * - Transparent Stream interface for easy integration
21+ *
22+ * @ingroup communications
23+ * @author Phil Schatzmann
24+ */
25+
26+ class HDLCStream : public Stream {
27+ public:
28+ /* *
29+ * @brief Construct a new HDLCStream object using a Stream for input and output
30+ *
31+ * @param stream The underlying Stream for both reading and writing
32+ * @param maxFrameSize Maximum size of a single HDLC frame
33+ */
34+ HDLCStream (Stream& stream, size_t maxFrameSize)
35+ : _maxFrameSize(maxFrameSize) {
36+ p_stream = &stream;
37+ p_print = &stream;
38+ tx_frame_buffer.resize (maxFrameSize);
39+ rx_frame_buffer.resize (maxFrameSize);
40+ _rxBuffer.resize (maxFrameSize);
41+ }
42+
43+ /* *
44+ * @brief Construct a new HDLCStream object using a Print for output only
45+ *
46+ * @param stream The underlying Print for writing
47+ * @param maxFrameSize Maximum size of a single HDLC frame
48+ */
49+ HDLCStream (Print& stream, size_t maxFrameSize) : _maxFrameSize(maxFrameSize) {
50+ p_print = &stream;
51+ tx_frame_buffer.resize (maxFrameSize);
52+ rx_frame_buffer.resize (maxFrameSize);
53+ _rxBuffer.resize (maxFrameSize);
54+ }
55+
56+ /* *
57+ * @brief Get the number of bytes available to read from the frame buffer
58+ *
59+ * @return int Number of bytes available
60+ */
61+ int available () override {
62+ if (rx_frame_buffer.available () == 0 ) _processInput ();
63+ return rx_frame_buffer.available ();
64+ }
65+
66+ /* *
67+ * @brief Not supported
68+ *
69+ * @return -1
70+ */
71+ int read () override { return -1 ; }
72+
73+ /* *
74+ * @brief Read a full frame from the stream into a buffer
75+ *
76+ * @param buffer Destination buffer to hold the data
77+ * @param length Maximum number of bytes to read
78+ * @return size_t Actual number of bytes read
79+ */
80+ size_t readBytes (uint8_t * buffer, size_t length) override {
81+ size_t available_bytes = rx_frame_buffer.available ();
82+ // get more data
83+ if (available_bytes == 0 ) {
84+ _processInput ();
85+ available_bytes = rx_frame_buffer.available ();
86+ }
87+
88+ // check that we consume the full frame
89+ if (length < available_bytes) {
90+ LOGE (" readBytes len too small %u instead of %u" , (unsigned )length,
91+ (unsigned )available_bytes);
92+ return 0 ;
93+ }
94+
95+ // provide the data
96+ memcpy (buffer, rx_frame_buffer.data (), available_bytes);
97+ rx_frame_buffer.clear ();
98+ _frameReady = false ;
99+ return available_bytes;
100+ }
101+
102+ /* *
103+ * @brief Not supported
104+ *
105+ * @return -1
106+ */
107+ int peek () override { return -1 ; }
108+
109+ /* *
110+ * @brief Flush the output buffer of the underlying stream
111+ */
112+ void flush () override { p_stream->flush (); }
113+
114+ /* *
115+ * @brief Not supported
116+ *
117+ * @param b The byte to write
118+ * @return 0
119+ */
120+ size_t write (uint8_t b) override { return 0 ; }
121+
122+ /* *
123+ * @brief Write multiple bytes to the stream
124+ *
125+ * @param data Pointer to the data buffer
126+ * @param len Number of bytes to write
127+ * @return size_t Number of bytes written
128+ */
129+ size_t write (const uint8_t * data, size_t len) override {
130+ return writeFrame (data, len);
131+ }
132+
133+ protected:
134+ Stream* p_stream = nullptr ;
135+ Print* p_print = nullptr ;
136+ const size_t _maxFrameSize;
137+ SingleBuffer<uint8_t > tx_frame_buffer;
138+ SingleBuffer<uint8_t > rx_frame_buffer;
139+ Vector<uint8_t > _rxBuffer;
140+ size_t _frameLen = 0 ;
141+ size_t _rxLen = 0 ;
142+ size_t _rxPos = 0 ;
143+ bool _frameReady = false ;
144+
145+ enum RxState { IDLE, RECEIVING, ESCAPED } _rxState = IDLE;
146+
147+ static constexpr uint8_t HDLC_FLAG = 0x7E ;
148+ static constexpr uint8_t HDLC_ESC = 0x7D ;
149+ static constexpr uint8_t HDLC_ESC_XOR = 0x20 ;
150+
151+ /* *
152+ * @brief Calculate CRC-CCITT (16-bit)
153+ *
154+ * @param data Byte to include in CRC calculation
155+ * @param crc Current CRC value
156+ * @return uint16_t Updated CRC value
157+ */
158+ uint16_t _crc16 (uint8_t data, uint16_t crc) {
159+ crc ^= (uint16_t )data << 8 ;
160+ for (int i = 0 ; i < 8 ; i++)
161+ crc = (crc & 0x8000 ) ? (crc << 1 ) ^ 0x1021 : (crc << 1 );
162+ return crc;
163+ }
164+
165+ /* *
166+ * @brief Write a byte with proper HDLC byte stuffing if needed
167+ *
168+ * @param b Byte to write
169+ */
170+ void _writeEscaped (uint8_t b) {
171+ if (b == HDLC_FLAG || b == HDLC_ESC) {
172+ tx_frame_buffer.write (HDLC_ESC);
173+ tx_frame_buffer.write (b ^ HDLC_ESC_XOR);
174+ } else {
175+ tx_frame_buffer.write (b);
176+ }
177+ }
178+
179+ /* *
180+ * @brief Write a complete HDLC frame with proper framing and CRC
181+ *
182+ * @param data Data to be framed
183+ * @param len Length of data
184+ * @return size_t Number of bytes in the original data
185+ */
186+ size_t writeFrame (const uint8_t * data, size_t len) {
187+ if (!data || len == 0 ) return 0 ;
188+
189+ uint16_t crc = 0xFFFF ;
190+ tx_frame_buffer.write (HDLC_FLAG);
191+
192+ for (size_t i = 0 ; i < len; ++i) {
193+ crc = _crc16 (data[i], crc);
194+ _writeEscaped (data[i]);
195+ }
196+
197+ _writeEscaped (crc >> 8 );
198+ _writeEscaped (crc & 0xFF );
199+ tx_frame_buffer.write (HDLC_FLAG);
200+ p_print->write (tx_frame_buffer.data (), tx_frame_buffer.available ());
201+ p_print->flush ();
202+ tx_frame_buffer.clear ();
203+ return len;
204+ }
205+
206+ /* *
207+ * @brief Process incoming bytes, detect frames, validate CRC and prepare data
208+ * for reading
209+ */
210+ void _processInput () {
211+ while (!_frameReady && p_stream->available ()) {
212+ uint8_t b = p_stream->read ();
213+
214+ if (b == HDLC_FLAG) {
215+ if (_rxLen >= 3 ) {
216+ uint16_t recvCrc =
217+ (_rxBuffer[_rxLen - 2 ] << 8 ) | _rxBuffer[_rxLen - 1 ];
218+ uint16_t calcCrc = 0xFFFF ;
219+ for (size_t i = 0 ; i < _rxLen - 2 ; ++i)
220+ calcCrc = _crc16 (_rxBuffer[i], calcCrc);
221+
222+ if (calcCrc == recvCrc) {
223+ for (int j = 0 ; j < _rxLen - 2 ; j++) {
224+ rx_frame_buffer.write (_rxBuffer[j]);
225+ }
226+
227+ _frameLen = _rxLen - 2 ;
228+ _rxPos = 0 ;
229+ _frameReady = true ;
230+ }
231+ }
232+ _rxState = IDLE;
233+ _rxLen = 0 ;
234+ continue ;
235+ }
236+
237+ switch (_rxState) {
238+ case IDLE:
239+ _rxLen = 0 ;
240+ if (b == HDLC_ESC) {
241+ _rxState = ESCAPED;
242+ } else {
243+ _rxState = RECEIVING;
244+ if (_rxLen < _maxFrameSize) _rxBuffer[_rxLen++] = b;
245+ }
246+ break ;
247+
248+ case RECEIVING:
249+ if (b == HDLC_ESC) {
250+ _rxState = ESCAPED;
251+ } else if (_rxLen < _maxFrameSize) {
252+ _rxBuffer[_rxLen++] = b;
253+ }
254+ break ;
255+
256+ case ESCAPED:
257+ if (_rxLen < _maxFrameSize) {
258+ _rxBuffer[_rxLen++] = b ^ HDLC_ESC_XOR;
259+ }
260+ _rxState = RECEIVING;
261+ break ;
262+ }
263+
264+ if (_rxLen >= _maxFrameSize) {
265+ _rxState = IDLE; // overflow
266+ _rxLen = 0 ;
267+ }
268+ }
269+ }
270+ };
271+
272+ } // namespace audio_tools
0 commit comments