SatCat5
codec_hdlc.cc
1 // Copyright 2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/codec_hdlc.h>
7 
12 
13 // Set default encoding parameters.
14 // Note: Set ACTRL=1 if control characters 0x00 - 0x1F may be mangled.
15 // (This is required for RFC1662 but increases byte-stuffing overhead.)
16 #ifndef SATCAT5_HDLC_DEFAULT_ACTRL
17 #define SATCAT5_HDLC_DEFAULT_ACTRL 0
18 #endif
19 
20 #ifndef SATCAT5_HDLC_DEFAULT_CRC32
21 #define SATCAT5_HDLC_DEFAULT_CRC32 1
22 #endif
23 
24 static constexpr u8 HDLC_END = 0x7E;
25 static constexpr u8 HDLC_ESC = 0x7D;
26 static constexpr u8 HDLC_MASK = 0x20;
27 static constexpr u16 HDLC_ABORT = 256*HDLC_ESC + HDLC_END;
28 
29 HdlcEncoder::HdlcEncoder(Writeable* dst)
31  , m_bstuff(dst)
32  , m_crc32(&m_bstuff)
33  , m_crc16(&m_bstuff, 0xFFFF, 0xFFFF)
34 {
35  set_mode_crc32(SATCAT5_HDLC_DEFAULT_CRC32);
36 }
37 
38 void HdlcEncoder::set_mode_crc32(bool mode32)
39 {
40  // Redirect incoming API calls to the designated CRC calculation.
41  // (HDLC framing encodes CRC first, then performs byte-stuffing.)
42  Writeable* dst32 = &m_crc32;
43  Writeable* dst16 = &m_crc16;
44  write_dst(mode32 ? dst32 : dst16);
45 }
46 
47 HdlcEncoder::ByteStuff::ByteStuff(satcat5::io::Writeable* dst)
48  : m_dst(dst)
49  , m_actrl(SATCAT5_HDLC_DEFAULT_ACTRL)
50 {
51  // Nothing else to initialize.
52 }
53 
55 {
56  // Worst-case is that every byte is escaped, plus end-of-frame marker.
57  unsigned avail = m_dst->get_write_space();
58  if (!avail) return 0;
59  return (avail-1) / 2;
60 }
61 
63 {
64  // Downstream block may do nothing on write_abort(), so attempt
65  // to force an error in the output stream regardless.
66  m_dst->write_u16(HDLC_ABORT);
67  m_dst->write_abort();
68 }
69 
71 {
72  // Finalize the current frame if valid, abort otherwise.
73  // Note: A persistent overflow flag is not required here, because the
74  // upstream CRC block will always overflow first, triggering an abort.
75  m_dst->write_u8(HDLC_END);
76  return m_dst->write_finalize();
77 }
78 
80 {
81  if (data == HDLC_END || data == HDLC_ESC) {
82  // Always escale the END and ESC tokens.
83  m_dst->write_u8(HDLC_ESC);
84  m_dst->write_u8(data ^ HDLC_MASK);
85  } else if (m_actrl && data < HDLC_MASK) {
86  // If ACTRL flag is set, escape anything below 0x20.
87  m_dst->write_u8(HDLC_ESC);
88  m_dst->write_u8(data ^ HDLC_MASK);
89  } else {
90  // Normal passthrough.
91  m_dst->write_u8(data);
92  }
93 }
94 
96  : m_crc32(dst)
97  , m_crc16(dst, 0xFFFF, 0xFFFF)
98  , m_state(State::HDLC_EOF)
99  , m_actrl(SATCAT5_HDLC_DEFAULT_ACTRL)
100  , m_crc(0)
101 {
102  set_mode_crc32(SATCAT5_HDLC_DEFAULT_CRC32);
103 }
104 
106 {
107  // Worst case is one-to-one, no special tokens in input.
108  return m_crc->get_write_space();
109 }
110 
112 {
113  // Write framed output to the selected CRC.
114  Writeable* dst32 = &m_crc32;
115  Writeable* dst16 = &m_crc16;
116  m_crc = mode32 ? dst32 : dst16;
117 }
118 
120 {
121  // Byte-stuffing state machine (RFC1662 Section 4.2)
122  if (data == HDLC_END) {
123  // Finalize complete frame, or abort on incomplete data.
124  // (This includes back-to-back END tokens, which are harmless.)
125  if (m_state == State::HDLC_RDY) {
126  m_crc->write_finalize();
127  } else if (m_state != State::HDLC_EOF) {
128  m_crc->write_abort();
129  }
130  m_state = State::HDLC_EOF;
131  } else if (m_state == State::HDLC_ERR) {
132  // After overflow, discard data until next END.
133  } else if (m_actrl && data < HDLC_MASK) {
134  // If ACTRL is set, discard unescaped control characters.
135  } else if (data == HDLC_ESC) {
136  m_state = State::HDLC_ESC; // Escape next byte
137  } else if (m_state == State::HDLC_ESC) {
138  m_crc->write_u8(data ^ HDLC_MASK); // Escaped byte
139  m_state = State::HDLC_RDY;
140  } else {
141  m_crc->write_u8(data); // Normal byte
142  m_state = State::HDLC_RDY;
143  }
144 }
145 
147 {
148  // Discard any further data until next end-of-frame.
149  m_state = State::HDLC_ERR;
150  // Purging the destination buffer ensures we can continue parsing.
151  m_crc->write_abort();
152 }
Inline HDLC decoder (framing layer only).
Definition: codec_hdlc.h:76
void write_next(u8 data) override
Write the next byte to the underlying buffer or device.
Definition: codec_hdlc.cc:119
void set_mode_crc32(bool mode32)
Select CRC16 or CRC32 checksum?
Definition: codec_hdlc.cc:111
unsigned get_write_space() const override
How many bytes can be written without blocking?
Definition: codec_hdlc.cc:105
void write_overflow() override
Optional error handling for write overflow.
Definition: codec_hdlc.cc:146
HdlcDecoder(satcat5::io::Writeable *dst)
Permanently link this encoder to an output object.
Definition: codec_hdlc.cc:95
unsigned get_write_space() const override
How many bytes can be written without blocking?
Definition: codec_hdlc.cc:54
void write_abort() override
If possible, abort the current partially-written packet.
Definition: codec_hdlc.cc:62
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: codec_hdlc.cc:70
void write_next(u8 data) override
Write the next byte to the underlying buffer or device.
Definition: codec_hdlc.cc:79
Inline HDLC encoder (framing layer only).
Definition: codec_hdlc.h:45
void set_mode_crc32(bool mode32)
Select CRC16 or CRC32 checksum?
Definition: codec_hdlc.cc:38
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
virtual unsigned get_write_space() const =0
How many bytes can be written without blocking?
virtual void write_abort()
If possible, abort the current partially-written packet.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
Wrapper class for forwarding writes to another object.
Definition: io_writeable.h:218
Inline HDLC encoder and decoder objects.