ether2ser 0.1.0
Ethernet <-> synchronous V.24 bridge firmware for RP2040 + W5500
Loading...
Searching...
No Matches
hdlc_encoder.c
Go to the documentation of this file.
1/*
2 * ether2ser — Ethernet <-> synchronous V.24 (RS-232/V.28) bridge
3 *
4 * File: src/protocol/hdlc_encoder.c
5 * Purpose: HDLC encoder (framing, escaping, and CRC append).
6 *
7 * SPDX-License-Identifier: Apache-2.0
8 *
9 * Copyright (c) 2026 Florian <f.leuze@outlook.de>
10 */
11
12// Related headers
13#include "hdlc_encoder.h"
14
15// Standard library headers
16#include <limits.h>
17#include <stdbool.h>
18#include <stddef.h>
19#include <stdint.h>
20#include <stdio.h>
21#include <string.h>
22
23// Project Headers
24#include "hdlc_common.h"
25#include "system/common.h"
26
27// Generated headers
28
29#define HDLC_TRY_PUT_BYTE(byte, frame, error_handling) \
30 do \
31 { \
32 if ((frame)->length + 1 > (frame)->capacity) \
33 { \
34 goto error_handling; \
35 } \
36 (frame)->payload[(frame)->length++] = byte; \
37 } while (0)
38
39#define HDLC_BIT_STUFF_ONES_LIMIT 5U
40
41static bool hdlc_escape_if_needed(uint8_t byte, HDLC_FRAME_T* frame)
42{
43 if (byte == HDLC_FLAG_BYTE || byte == HDLC_ESCAPE_BYTE)
44 {
46 HDLC_TRY_PUT_BYTE(byte ^ HDLC_ESCAPE_XOR, frame, abort);
47 }
48 else
49 {
50 HDLC_TRY_PUT_BYTE(byte, frame, abort);
51 }
52 return true;
53abort:
54 return false;
55}
56
57bool hdlc_encode_byte(const uint8_t* payload, const size_t payload_length, HDLC_FRAME_T* frame)
58{
59 if (frame == NULL || (payload == NULL && payload_length > 0) || (frame->capacity < 2) ||
60 frame->length != 0)
61 {
62 goto abort;
63 }
64
65 // Write opening flag
66 HDLC_TRY_PUT_BYTE(HDLC_FLAG_BYTE, frame, abort);
67
68 // Write data (only if there is actual data to write)
69 for (size_t i = 0; i < payload_length; i++)
70 {
71 if (!hdlc_escape_if_needed(payload[i], frame))
72 {
73 goto abort;
74 }
75 }
76
77 uint16_t crc16 = hdlc_crc16(payload, payload_length);
78 if (!hdlc_escape_if_needed((crc16 >> CHAR_BIT) & 0xFF, frame))
79 {
80 goto abort;
81 }
82 if (!hdlc_escape_if_needed(crc16 & 0xFF, frame))
83 {
84 goto abort;
85 }
86
87 // Write closing flag
88 HDLC_TRY_PUT_BYTE(HDLC_FLAG_BYTE, frame, abort);
89 if (frame->length > 0)
90 {
91 LOG_DEBUG("HDLC ENCODE: len=%zu first=0x%02X last=0x%02X\r\n", frame->length,
92 frame->payload[0], frame->payload[frame->length - 1]);
93 }
94 return true;
95abort:
96 if (frame)
97 {
98 frame->length = 0;
99 }
100 return false;
101}
102
103typedef struct
104{
105 uint8_t out_byte;
107 uint8_t ones_run;
110
111#define SHIFT_IN_BIT(out_byte, bitorder, bit, bitpos) \
112 (bitorder) ? ((out_byte) | ((bit) << (bitpos))) : ((out_byte) | ((bit) << (7 - (bitpos))))
113
114#define ENCODER_TRY_FLUSH_BYTE_OUT(encoder, frame, error_handling) \
115 do \
116 { \
117 if ((++(encoder)->out_bits_used) == CHAR_BIT) \
118 { \
119 HDLC_TRY_PUT_BYTE(((encoder)->out_byte), (frame), error_handling); \
120 (encoder)->out_bits_used = 0; \
121 (encoder)->out_byte = 0; \
122 } \
123 } while (0)
124
125#define ENCODER_ZERO_PAD_AND_DRAIN(encoder, frame, error_handling) \
126 do \
127 { \
128 if ((encoder)->out_bits_used > 0) \
129 { \
130 HDLC_TRY_PUT_BYTE((encoder)->out_byte, frame, error_handling); \
131 (encoder)->out_byte = 0; \
132 (encoder)->ones_run = 0; \
133 (encoder)->out_bits_used = 0; \
134 } \
135 } while (0)
136
137static bool hdlc_put_bit(hdlc_encoder_t* encoder, bool bit, HDLC_FRAME_T* frame)
138{
139 uint8_t bitmask = bit ? (encoder->ones_run++, 1) : (encoder->ones_run = 0, 0);
140 encoder->out_byte =
141 SHIFT_IN_BIT(encoder->out_byte, encoder->lsb_first, bitmask, encoder->out_bits_used);
142
143 ENCODER_TRY_FLUSH_BYTE_OUT(encoder, frame, abort);
144 if (encoder->ones_run == HDLC_BIT_STUFF_ONES_LIMIT)
145 {
146 encoder->out_byte =
147 SHIFT_IN_BIT(encoder->out_byte, encoder->lsb_first, 0, encoder->out_bits_used);
148 encoder->ones_run = 0;
149 ENCODER_TRY_FLUSH_BYTE_OUT(encoder, frame, abort);
150 }
151 return true;
152abort:
153 return false;
154}
155
156static bool hdlc_put_byte(hdlc_encoder_t* encoder, uint8_t byte, HDLC_FRAME_T* frame)
157{
158 for (uint8_t i = 0; i < CHAR_BIT; i++)
159 {
160 size_t bit_pos = encoder->lsb_first ? i : (CHAR_BIT - 1U - i);
161 // TODO: This has to be tested on the target
162 bool bit = ((byte >> bit_pos) & 0x01U) != 0U;
163 if (!hdlc_put_bit(encoder, bit, frame))
164 {
165 return false;
166 }
167 }
168 return true;
169}
170
171bool hdlc_encode(const uint8_t* payload, const size_t payload_length, HDLC_FRAME_T* frame,
172 bool lsb_first)
173{
174 if (frame == NULL || (payload == NULL && payload_length > 0) || (frame->capacity < 2) ||
175 frame->length != 0)
176 {
177 goto abort;
178 }
179
180 // Write opening flag
181 HDLC_TRY_PUT_BYTE(HDLC_FLAG_BYTE, frame, abort);
182
183 hdlc_encoder_t encoder = {
184 .out_byte = 0, .out_bits_used = 0, .ones_run = 0, .lsb_first = lsb_first};
185 // Write data (only if there is actual data to write)
186 for (size_t i = 0; i < payload_length; i++)
187 {
188 if (!hdlc_put_byte(&encoder, payload[i], frame))
189 {
190 goto abort;
191 }
192 }
193
194 uint16_t crc16 = hdlc_crc16(payload, payload_length);
195 if (!hdlc_put_byte(&encoder, (crc16 >> CHAR_BIT) & 0xFF, frame) ||
196 !hdlc_put_byte(&encoder, crc16 & 0xFF, frame))
197 {
198 goto abort;
199 }
200
201 // Flush out remainig bits (if any)
202 ENCODER_ZERO_PAD_AND_DRAIN(&encoder, frame, abort);
203
204 // Write closing flag
205 HDLC_TRY_PUT_BYTE(HDLC_FLAG_BYTE, frame, abort);
206 if (frame->length > 0)
207 {
208 LOG_DEBUG("HDLC ENCODE(BIT): len=%zu first=0x%02X last=0x%02X\r\n", frame->length,
209 frame->payload[0], frame->payload[frame->length - 1]);
210 }
211 return true;
212abort:
213 if (frame)
214 {
215 frame->length = 0;
216 }
217 return false;
218}
#define LOG_DEBUG(...)
Definition common.h:165
uint16_t hdlc_crc16(const uint8_t *payload, size_t num_bytes)
Compute HDLC CRC16 (FCS) over a payload.
Definition hdlc_common.c:45
#define HDLC_ESCAPE_BYTE
Definition hdlc_common.h:30
#define HDLC_FLAG_BYTE
Definition hdlc_common.h:29
#define HDLC_ESCAPE_XOR
Definition hdlc_common.h:31
#define ENCODER_ZERO_PAD_AND_DRAIN(encoder, frame, error_handling)
#define ENCODER_TRY_FLUSH_BYTE_OUT(encoder, frame, error_handling)
static bool hdlc_put_byte(hdlc_encoder_t *encoder, uint8_t byte, HDLC_FRAME_T *frame)
#define HDLC_TRY_PUT_BYTE(byte, frame, error_handling)
bool hdlc_encode(const uint8_t *payload, const size_t payload_length, HDLC_FRAME_T *frame, bool lsb_first)
Encode payload into an HDLC bit-stuffed frame.
#define SHIFT_IN_BIT(out_byte, bitorder, bit, bitpos)
bool hdlc_encode_byte(const uint8_t *payload, const size_t payload_length, HDLC_FRAME_T *frame)
Encode payload using byte-escaping HDLC compatibility path.
static bool hdlc_escape_if_needed(uint8_t byte, HDLC_FRAME_T *frame)
#define HDLC_BIT_STUFF_ONES_LIMIT
static bool hdlc_put_bit(hdlc_encoder_t *encoder, bool bit, HDLC_FRAME_T *frame)
Generic HDLC frame buffer descriptor.
Definition hdlc_common.h:38
size_t capacity
Definition hdlc_common.h:44
size_t length
Definition hdlc_common.h:42
uint8_t * payload
Definition hdlc_common.h:40
uint8_t out_bits_used