ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ash_acknowledger.cpp
Go to the documentation of this file.
1#include "ash_acknowledger.h"
2
4
6
7// Control byte of an RSTACK, and the only ASH version byte that can follow it
8static constexpr uint8_t ASH_RSTACK_CONTROL = 0xC1;
9static constexpr uint8_t ASH_PROTOCOL_VERSION = 0x02;
10static constexpr size_t ASH_RSTACK_BODY_SIZE = 3; // control, version, reset code
11// A DATA frame carries an EZSP frame of at least 3 bytes after its control byte
12static constexpr size_t ASH_MIN_DATA_BODY_SIZE = 1 + 3;
13static constexpr size_t ASH_CRC_SIZE = 2;
14// Smallest legal frame on the wire: a bare control byte plus its CRC
15static constexpr size_t ASH_MIN_FRAME_SIZE = 1 + ASH_CRC_SIZE;
16
18 this->index_ = 0;
19 this->crc_ = ASH_CRC_INIT;
20 this->escaped_ = false;
21 this->poisoned_ = false;
22 this->discarding_ = false;
23}
24
25bool AshFrameScanner::feed(uint8_t byte) {
26 if (byte == ASH_FLAG_BYTE) {
27 // Running the CRC over the body and its trailing CRC bytes leaves zero when correct
28 const bool valid = !this->discarding_ && !this->poisoned_ && !this->escaped_ &&
29 this->index_ >= ASH_MIN_FRAME_SIZE && this->crc_ == 0;
30 if (valid) {
31 this->frame_length_ = this->index_ - ASH_CRC_SIZE;
32 }
33 this->reset();
34 return valid;
35 }
36
37 // CANCEL also ends a SUBSTITUTE's discard, so the bytes after it start a new frame
38 if (byte == ASH_CANCEL_BYTE) {
39 this->reset();
40 return false;
41 }
42
43 if (this->discarding_ || (byte == ASH_WAKE_BYTE && this->index_ == 0)) {
44 return false;
45 }
46
47 switch (byte) {
48 case ASH_SUBSTITUTE_BYTE:
49 this->discarding_ = true;
50 return false;
51
52 case ASH_XON_BYTE:
53 case ASH_XOFF_BYTE:
54 return false;
55
56 case ASH_ESCAPE_BYTE:
57 this->escaped_ = true;
58 return false;
59
60 default:
61 break;
62 }
63
64 uint8_t value = byte;
65 if (this->escaped_) {
66 this->escaped_ = false;
67 value = byte ^ ASH_XOR_BYTE;
68 }
69
70 if (this->index_ >= MAX_ASH_FRAME_SIZE) {
71 this->poisoned_ = true;
72 return false;
73 }
74
75 if (this->index_ < ASH_HEADER_SIZE) {
76 this->header_[this->index_] = value;
77 }
78 this->index_++;
79 this->crc_ = crc16be(&value, 1, this->crc_);
80 return false;
81}
82
84 this->scanner_.reset();
85 this->rx_sequence_ = 0;
86 this->synced_ = false;
87}
88
89bool AshAcknowledger::feed(uint8_t byte) { return this->scanner_.feed(byte) && this->handle_frame_(); }
90
92 const uint8_t *body = this->scanner_.header();
93 const uint8_t control = body[0];
94
95 // An RSTACK restarts the NCP's frame numbering
96 if (control == ASH_RSTACK_CONTROL) {
97 if (this->scanner_.length() == ASH_RSTACK_BODY_SIZE && body[1] == ASH_PROTOCOL_VERSION) {
98 this->rx_sequence_ = 0;
99 this->synced_ = true;
100 }
101 return false;
102 }
103
104 if ((control & 0x80) != 0) {
105 return false; // ACK/NAK/RST/ERROR: nothing is owed for these
106 }
107
108 if (this->scanner_.length() < ASH_MIN_DATA_BODY_SIZE) {
109 return false;
110 }
111
112 const uint8_t frame_num = (control >> 4) & ASH_MAX_SEQUENCE;
113 const bool re_tx = (control & 0x08) != 0;
114 // Without an RSTACK, the session began before we were watching: follow the NCP's numbering
115 if (!this->synced_) {
116 this->rx_sequence_ = frame_num;
117 this->synced_ = true;
118 }
119 if (frame_num == this->rx_sequence_) {
120 this->rx_sequence_ = (this->rx_sequence_ + 1) & ASH_MAX_SEQUENCE;
121 return true;
122 }
123 return re_tx;
124}
125
126} // namespace esphome::ezsp_proxy_tap
uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, bool refin, bool refout)
Definition helpers.cpp:135
bool valid