Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston 2025-07-19 15:29:42 -10:00
commit b70007852d
No known key found for this signature in database
2 changed files with 98 additions and 104 deletions

View File

@ -76,6 +76,16 @@ APIError APIFrameHelper::loop() {
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
} }
// Common socket write error handling
APIError APIFrameHelper::handle_socket_write_error_() {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED;
}
// Helper method to buffer data from IOVs // Helper method to buffer data from IOVs
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
uint16_t offset) { uint16_t offset) {
@ -137,15 +147,13 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
ssize_t sent = this->socket_->writev(iov, iovcnt); ssize_t sent = this->socket_->writev(iov, iovcnt);
if (sent == -1) { if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { APIError err = this->handle_socket_write_error_();
if (err == APIError::WOULD_BLOCK) {
// Socket would block, buffer the data // Socket would block, buffer the data
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
return APIError::OK; // Success, data buffered return APIError::OK; // Success, data buffered
} }
// Socket error return err; // Socket write failed
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
} else if (static_cast<uint16_t>(sent) < total_write_len) { } else if (static_cast<uint16_t>(sent) < total_write_len) {
// Partially sent, buffer the remaining data // Partially sent, buffer the remaining data
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent)); this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
@ -167,14 +175,7 @@ APIError APIFrameHelper::try_send_tx_buf_() {
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining()); ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
if (sent == -1) { if (sent == -1) {
if (errno != EWOULDBLOCK && errno != EAGAIN) { return this->handle_socket_write_error_();
// Real socket error (not just would block)
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
}
// Socket would block, we'll try again later
return APIError::WOULD_BLOCK;
} else if (sent == 0) { } else if (sent == 0) {
// Nothing sent but not an error // Nothing sent but not an error
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@ -292,6 +293,29 @@ APIError APINoiseFrameHelper::init() {
state_ = State::CLIENT_HELLO; state_ = State::CLIENT_HELLO;
return APIError::OK; return APIError::OK;
} }
// Helper for handling handshake frame errors
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte");
return aerr;
}
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
return aerr;
}
// Helper for handling noise library errors
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
return api_err;
}
return APIError::OK;
}
/// Run through handshake messages (if in that phase) /// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() { APIError APINoiseFrameHelper::loop() {
// During handshake phase, process as many actions as possible until we can't progress // During handshake phase, process as many actions as possible until we can't progress
@ -299,12 +323,12 @@ APIError APINoiseFrameHelper::loop() {
// WOULD_BLOCK when no more data is available to read // WOULD_BLOCK when no more data is available to read
while (state_ != State::DATA && this->socket_->ready()) { while (state_ != State::DATA && this->socket_->ready()) {
APIError err = state_action_(); APIError err = state_action_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
}
if (err == APIError::WOULD_BLOCK) { if (err == APIError::WOULD_BLOCK) {
break; break;
} }
if (err != APIError::OK) {
return err;
}
} }
// Use base class implementation for buffer sending // Use base class implementation for buffer sending
@ -325,7 +349,7 @@ APIError APINoiseFrameHelper::loop() {
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/ */
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -388,7 +412,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif #endif
frame->msg = std::move(rx_buf_); *frame = std::move(rx_buf_);
// consume msg // consume msg
rx_buf_ = {}; rx_buf_ = {};
rx_buf_len_ = 0; rx_buf_len_ = 0;
@ -414,24 +438,17 @@ APIError APINoiseFrameHelper::state_action_() {
} }
if (state_ == State::CLIENT_HELLO) { if (state_ == State::CLIENT_HELLO) {
// waiting for client hello // waiting for client hello
ParsedFrame frame; std::vector<uint8_t> frame;
aerr = try_read_frame_(&frame); aerr = try_read_frame_(&frame);
if (aerr == APIError::BAD_INDICATOR) { if (aerr != APIError::OK) {
send_explicit_handshake_reject_("Bad indicator byte"); return handle_handshake_frame_error_(aerr);
return aerr;
} }
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
if (aerr != APIError::OK)
return aerr;
// ignore contents, may be used in future for flags // ignore contents, may be used in future for flags
// Reserve space for: existing prologue + 2 size bytes + frame data // Reserve space for: existing prologue + 2 size bytes + frame data
prologue_.reserve(prologue_.size() + 2 + frame.msg.size()); prologue_.reserve(prologue_.size() + 2 + frame.size());
prologue_.push_back((uint8_t) (frame.msg.size() >> 8)); prologue_.push_back((uint8_t) (frame.size() >> 8));
prologue_.push_back((uint8_t) frame.msg.size()); prologue_.push_back((uint8_t) frame.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); prologue_.insert(prologue_.end(), frame.begin(), frame.end());
state_ = State::SERVER_HELLO; state_ = State::SERVER_HELLO;
} }
@ -469,41 +486,33 @@ APIError APINoiseFrameHelper::state_action_() {
int action = noise_handshakestate_get_action(handshake_); int action = noise_handshakestate_get_action(handshake_);
if (action == NOISE_ACTION_READ_MESSAGE) { if (action == NOISE_ACTION_READ_MESSAGE) {
// waiting for handshake msg // waiting for handshake msg
ParsedFrame frame; std::vector<uint8_t> frame;
aerr = try_read_frame_(&frame); aerr = try_read_frame_(&frame);
if (aerr == APIError::BAD_INDICATOR) { if (aerr != APIError::OK) {
send_explicit_handshake_reject_("Bad indicator byte"); return handle_handshake_frame_error_(aerr);
return aerr;
} }
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
if (aerr != APIError::OK)
return aerr;
if (frame.msg.empty()) { if (frame.empty()) {
send_explicit_handshake_reject_("Empty handshake message"); send_explicit_handshake_reject_("Empty handshake message");
return APIError::BAD_HANDSHAKE_ERROR_BYTE; return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame.msg[0] != 0x00) { } else if (frame[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); HELPER_LOG("Bad handshake error byte: %u", frame[0]);
send_explicit_handshake_reject_("Bad handshake error byte"); send_explicit_handshake_reject_("Bad handshake error byte");
return APIError::BAD_HANDSHAKE_ERROR_BYTE; return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} }
NoiseBuffer mbuf; NoiseBuffer mbuf;
noise_buffer_init(mbuf); noise_buffer_init(mbuf);
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) { if (err != 0) {
state_ = State::FAILED; // Special handling for MAC failure
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
if (err == NOISE_ERROR_MAC_FAILURE) { if (err == NOISE_ERROR_MAC_FAILURE) {
send_explicit_handshake_reject_("Handshake MAC failure"); send_explicit_handshake_reject_("Handshake MAC failure");
} else { } else {
send_explicit_handshake_reject_("Handshake error"); send_explicit_handshake_reject_("Handshake error");
} }
return APIError::HANDSHAKESTATE_READ_FAILED; return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
} }
aerr = check_handshake_finished_(); aerr = check_handshake_finished_();
@ -516,11 +525,10 @@ APIError APINoiseFrameHelper::state_action_() {
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr); err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
if (err != 0) { APIError aerr_write =
state_ = State::FAILED; handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str()); if (aerr_write != APIError::OK)
return APIError::HANDSHAKESTATE_WRITE_FAILED; return aerr_write;
}
buffer[0] = 0x00; // success buffer[0] = 0x00; // success
aerr = write_frame_(buffer, mbuf.size + 1); aerr = write_frame_(buffer, mbuf.size + 1);
@ -569,23 +577,21 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
ParsedFrame frame; std::vector<uint8_t> frame;
aerr = try_read_frame_(&frame); aerr = try_read_frame_(&frame);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
NoiseBuffer mbuf; NoiseBuffer mbuf;
noise_buffer_init(mbuf); noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size()); noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf); err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
if (err != 0) { APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
state_ = State::FAILED; if (decrypt_err != APIError::OK)
HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str()); return decrypt_err;
return APIError::CIPHERSTATE_DECRYPT_FAILED;
}
uint16_t msg_size = mbuf.size; uint16_t msg_size = mbuf.size;
uint8_t *msg_data = frame.msg.data(); uint8_t *msg_data = frame.data();
if (msg_size < 4) { if (msg_size < 4) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Bad data packet: size %d too short", msg_size); HELPER_LOG("Bad data packet: size %d too short", msg_size);
@ -600,7 +606,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::BAD_DATA_PACKET; return APIError::BAD_DATA_PACKET;
} }
buffer->container = std::move(frame.msg); buffer->container = std::move(frame);
buffer->data_offset = 4; buffer->data_offset = 4;
buffer->data_len = data_len; buffer->data_len = data_len;
buffer->type = type; buffer->type = type;
@ -662,11 +668,9 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
4 + packet.payload_size + frame_footer_size_); 4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) { APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
}
// Fill in the encrypted size // Fill in the encrypted size
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8); buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
@ -718,35 +722,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0; nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER); err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
if (err != 0) { APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
const auto &psk = ctx_->get_psk(); const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
if (err != 0) { aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
if (err != 0) { aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
// set_prologue copies it into handshakestate, so we can get rid of it now // set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {}; prologue_ = {};
err = noise_handshakestate_start(handshake_); err = noise_handshakestate_start(handshake_);
if (err != 0) { aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
return APIError::OK; return APIError::OK;
} }
@ -762,11 +758,9 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_BAD_STATE; return APIError::HANDSHAKESTATE_BAD_STATE;
} }
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
if (err != 0) { APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
state_ = State::FAILED; if (aerr != APIError::OK)
HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str()); return aerr;
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
}
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_); frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
@ -833,7 +827,7 @@ APIError APIPlaintextFrameHelper::loop() {
* *
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/ */
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -951,7 +945,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif #endif
frame->msg = std::move(rx_buf_); *frame = std::move(rx_buf_);
// consume msg // consume msg
rx_buf_ = {}; rx_buf_ = {};
rx_buf_len_ = 0; rx_buf_len_ = 0;
@ -966,7 +960,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
ParsedFrame frame; std::vector<uint8_t> frame;
aerr = try_read_frame_(&frame); aerr = try_read_frame_(&frame);
if (aerr != APIError::OK) { if (aerr != APIError::OK) {
if (aerr == APIError::BAD_INDICATOR) { if (aerr == APIError::BAD_INDICATOR) {
@ -991,7 +985,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return aerr; return aerr;
} }
buffer->container = std::move(frame.msg); buffer->container = std::move(frame);
buffer->data_offset = 0; buffer->data_offset = 0;
buffer->data_len = rx_header_parsed_len_; buffer->data_len = rx_header_parsed_len_;
buffer->type = rx_header_parsed_type_; buffer->type = rx_header_parsed_type_;

View File

@ -109,11 +109,6 @@ class APIFrameHelper {
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); } bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
protected: protected:
// Struct for holding parsed frame data
struct ParsedFrame {
std::vector<uint8_t> msg;
};
// Buffer containing data to be sent // Buffer containing data to be sent
struct SendBuffer { struct SendBuffer {
std::unique_ptr<uint8_t[]> data; std::unique_ptr<uint8_t[]> data;
@ -133,6 +128,9 @@ class APIFrameHelper {
// Helper method to buffer data from IOVs // Helper method to buffer data from IOVs
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset); void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
// Common socket write error handling
APIError handle_socket_write_error_();
template<typename StateEnum> template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf, APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state); const std::string &info, StateEnum &state, StateEnum failed_state);
@ -205,11 +203,13 @@ class APINoiseFrameHelper : public APIFrameHelper {
protected: protected:
APIError state_action_(); APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame); APIError try_read_frame_(std::vector<uint8_t> *frame);
APIError write_frame_(const uint8_t *data, uint16_t len); APIError write_frame_(const uint8_t *data, uint16_t len);
APIError init_handshake_(); APIError init_handshake_();
APIError check_handshake_finished_(); APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason); void send_explicit_handshake_reject_(const std::string &reason);
APIError handle_handshake_frame_error_(APIError aerr);
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
// Pointers first (4 bytes each) // Pointers first (4 bytes each)
NoiseHandshakeState *handshake_{nullptr}; NoiseHandshakeState *handshake_{nullptr};
@ -257,7 +257,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
uint8_t frame_footer_size() override { return frame_footer_size_; } uint8_t frame_footer_size() override { return frame_footer_size_; }
protected: protected:
APIError try_read_frame_(ParsedFrame *frame); APIError try_read_frame_(std::vector<uint8_t> *frame);
// Group 2-byte aligned types // Group 2-byte aligned types
uint16_t rx_header_parsed_type_ = 0; uint16_t rx_header_parsed_type_ = 0;