| 136 | // ────────────────────────────────────────────────────────────────────────── |
| 137 | |
| 138 | bool HdlcCodec::processBit(uint8_t nrziTone) |
| 139 | { |
| 140 | // Deferred reset: clear complete/aborted flags at the start of the next call |
| 141 | // so callers can read them after the frame-closing call returns. |
| 142 | if (m_complete) { |
| 143 | m_complete = false; |
| 144 | m_aborted = false; |
| 145 | } |
| 146 | |
| 147 | // NRZI decode: no transition = 1 (mark held), transition = 0 (space). |
| 148 | const uint8_t decoded = (nrziTone == m_prevNrzi) ? 1u : 0u; |
| 149 | m_prevNrzi = nrziTone; |
| 150 | |
| 151 | // Shift new decoded bit into the rolling pattern register, newest at MSB. |
| 152 | m_patDet = static_cast<uint8_t>((m_patDet >> 1) | (decoded << 7)); |
| 153 | |
| 154 | switch (m_state) { |
| 155 | |
| 156 | // ── Searching: scan for the first preamble flag ───────────────────── |
| 157 | case State::Searching: |
| 158 | if (m_patDet == 0x7E) { |
| 159 | m_state = State::InPreamble; |
| 160 | m_preambleCount = 1; |
| 161 | beginFrame(); |
| 162 | } |
| 163 | break; |
| 164 | |
| 165 | // ── InPreamble: count consecutive flags, wait for first frame bit ─── |
| 166 | // |
| 167 | // We require 8 consecutive non-flag bits before entering InFrame. |
| 168 | // This mirrors libmodem's (bitstream_size >= 8) check and prevents a |
| 169 | // false InFrame transition on the very first bit of the next preamble |
| 170 | // flag (which would briefly disrupt pat_det away from 0x7E). |
| 171 | // |
| 172 | // Bits accumulate during this window; they become the first byte of the |
| 173 | // frame. A flag anywhere in the window calls beginFrame() and resets |
| 174 | // the count, discarding those partial bits. |
| 175 | case State::InPreamble: |
| 176 | if (m_patDet == 0x7E) { |
| 177 | // Another consecutive preamble flag. |
| 178 | ++m_preambleCount; |
| 179 | beginFrame(); // clears m_bitsAfterFlag, byte state, etc. |
| 180 | } else { |
| 181 | // Potential frame data — accumulate and count. |
| 182 | ++m_rawBitsInFrame; |
| 183 | ++m_bitsAfterFlag; |
| 184 | accumulateBit(decoded); |
| 185 | // accumulateBit may have set state = Searching on overflow; |
| 186 | // only transition to InFrame if we're still in InPreamble. |
| 187 | if (m_state == State::InPreamble && m_bitsAfterFlag >= 8) |
| 188 | m_state = State::InFrame; |
| 189 | } |
| 190 | break; |
| 191 | |
| 192 | // ── InFrame: collect frame bytes ──────────────────────────────────── |
| 193 | case State::InFrame: |
| 194 | ++m_rawBitsInFrame; // first 8 bits counted in InPreamble; continue here |
| 195 |
no outgoing calls