From 8821529f6eaa7687bea24b2d48fa1bbfe63fdb2b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 6 Oct 2025 17:19:10 -0500 Subject: [PATCH] [api] Optimize frame helpers to eliminate double-move overhead --- .../components/api/api_frame_helper_noise.cpp | 79 ++++++++----------- .../components/api/api_frame_helper_noise.h | 2 +- .../api/api_frame_helper_plaintext.cpp | 41 ++++------ .../api/api_frame_helper_plaintext.h | 2 +- 4 files changed, 52 insertions(+), 72 deletions(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index ab27699f06..ab9d6e3269 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -132,26 +132,16 @@ APIError APINoiseFrameHelper::loop() { return APIFrameHelper::loop(); } -/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter +/** Read a packet into the rx_buf_. * - * @param frame: The struct to hold the frame information in. - * msg_start: points to the start of the payload - this pointer is only valid until the next - * try_receive_raw_ call - * - * @return 0 if a full packet is in rx_buf_ - * @return -1 if error, check errno. + * @return APIError::OK if a full packet is in rx_buf_ * * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later. * errno ENOMEM: Not enough memory for reading packet. * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. */ -APIError APINoiseFrameHelper::try_read_frame_(std::vector *frame) { - if (frame == nullptr) { - HELPER_LOG("Bad argument for try_read_frame_"); - return APIError::BAD_ARG; - } - +APIError APINoiseFrameHelper::try_read_frame_() { // read header if (rx_header_buf_len_ < 3) { // no header information yet @@ -205,12 +195,12 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector *frame) { } } - LOG_PACKET_RECEIVED(rx_buf_); - *frame = std::move(rx_buf_); - // consume msg - rx_buf_ = {}; - rx_buf_len_ = 0; - rx_header_buf_len_ = 0; + LOG_PACKET_RECEIVED(this->rx_buf_); + + // Clear state for next frame (rx_buf_ still contains data for caller) + this->rx_buf_len_ = 0; + this->rx_header_buf_len_ = 0; + return APIError::OK; } @@ -232,18 +222,18 @@ APIError APINoiseFrameHelper::state_action_() { } if (state_ == State::CLIENT_HELLO) { // waiting for client hello - std::vector frame; - aerr = try_read_frame_(&frame); + aerr = this->try_read_frame_(); if (aerr != APIError::OK) { return handle_handshake_frame_error_(aerr); } // ignore contents, may be used in future for flags // Resize for: existing prologue + 2 size bytes + frame data - size_t old_size = prologue_.size(); - prologue_.resize(old_size + 2 + frame.size()); - prologue_[old_size] = (uint8_t) (frame.size() >> 8); - prologue_[old_size + 1] = (uint8_t) frame.size(); - std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size()); + size_t old_size = this->prologue_.size(); + this->prologue_.resize(old_size + 2 + this->rx_buf_.size()); + this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8); + this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size(); + std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size()); + this->rx_buf_.clear(); state_ = State::SERVER_HELLO; } @@ -285,24 +275,23 @@ APIError APINoiseFrameHelper::state_action_() { int action = noise_handshakestate_get_action(handshake_); if (action == NOISE_ACTION_READ_MESSAGE) { // waiting for handshake msg - std::vector frame; - aerr = try_read_frame_(&frame); + aerr = this->try_read_frame_(); if (aerr != APIError::OK) { return handle_handshake_frame_error_(aerr); } - if (frame.empty()) { + if (this->rx_buf_.empty()) { send_explicit_handshake_reject_(LOG_STR("Empty handshake message")); return APIError::BAD_HANDSHAKE_ERROR_BYTE; - } else if (frame[0] != 0x00) { - HELPER_LOG("Bad handshake error byte: %u", frame[0]); + } else if (this->rx_buf_[0] != 0x00) { + HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]); send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte")); return APIError::BAD_HANDSHAKE_ERROR_BYTE; } NoiseBuffer mbuf; noise_buffer_init(mbuf); - noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1); + noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); if (err != 0) { // Special handling for MAC failure @@ -311,6 +300,7 @@ APIError APINoiseFrameHelper::state_action_() { return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"), APIError::HANDSHAKESTATE_READ_FAILED); } + this->rx_buf_.clear(); aerr = check_handshake_finished_(); if (aerr != APIError::OK) @@ -379,35 +369,33 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reaso state_ = orig_state; } APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { - int err; - APIError aerr; - aerr = state_action_(); + APIError aerr = this->state_action_(); if (aerr != APIError::OK) { return aerr; } - if (state_ != State::DATA) { + if (this->state_ != State::DATA) { return APIError::WOULD_BLOCK; } - std::vector frame; - aerr = try_read_frame_(&frame); + aerr = this->try_read_frame_(); if (aerr != APIError::OK) return aerr; NoiseBuffer mbuf; noise_buffer_init(mbuf); - noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size()); - err = noise_cipherstate_decrypt(recv_cipher_, &mbuf); + noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size()); + int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf); APIError decrypt_err = handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED); - if (decrypt_err != APIError::OK) + if (decrypt_err != APIError::OK) { return decrypt_err; + } uint16_t msg_size = mbuf.size; - uint8_t *msg_data = frame.data(); + uint8_t *msg_data = this->rx_buf_.data(); if (msg_size < 4) { - state_ = State::FAILED; + this->state_ = State::FAILED; HELPER_LOG("Bad data packet: size %d too short", msg_size); return APIError::BAD_DATA_PACKET; } @@ -415,15 +403,16 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; if (data_len > msg_size - 4) { - state_ = State::FAILED; + this->state_ = State::FAILED; HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size); return APIError::BAD_DATA_PACKET; } - buffer->container = std::move(frame); + buffer->container = std::move(this->rx_buf_); buffer->data_offset = 4; buffer->data_len = data_len; buffer->type = type; + this->rx_buf_.clear(); return APIError::OK; } APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 71a217c4ca..e3243e4fa5 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -28,7 +28,7 @@ class APINoiseFrameHelper final : public APIFrameHelper { protected: APIError state_action_(); - APIError try_read_frame_(std::vector *frame); + APIError try_read_frame_(); APIError write_frame_(const uint8_t *data, uint16_t len); APIError init_handshake_(); APIError check_handshake_finished_(); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index ff72f3cb55..ff87741763 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -47,21 +47,13 @@ APIError APIPlaintextFrameHelper::loop() { return APIFrameHelper::loop(); } -/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter - * - * @param frame: The struct to hold the frame information in. - * msg: store the parsed frame in that struct +/** Read a packet into the rx_buf_. * * @return See APIError * * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. */ -APIError APIPlaintextFrameHelper::try_read_frame_(std::vector *frame) { - if (frame == nullptr) { - HELPER_LOG("Bad argument for try_read_frame_"); - return APIError::BAD_ARG; - } - +APIError APIPlaintextFrameHelper::try_read_frame_() { // read header while (!rx_header_parsed_) { // Now that we know when the socket is ready, we can read up to 3 bytes @@ -170,24 +162,22 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector *frame) { } } - LOG_PACKET_RECEIVED(rx_buf_); - *frame = std::move(rx_buf_); - // consume msg - rx_buf_ = {}; - rx_buf_len_ = 0; - rx_header_buf_pos_ = 0; - rx_header_parsed_ = false; + LOG_PACKET_RECEIVED(this->rx_buf_); + + // Clear state for next frame (rx_buf_ still contains data for caller) + this->rx_buf_len_ = 0; + this->rx_header_buf_pos_ = 0; + this->rx_header_parsed_ = false; + return APIError::OK; } -APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { - APIError aerr; - if (state_ != State::DATA) { +APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { + if (this->state_ != State::DATA) { return APIError::WOULD_BLOCK; } - std::vector frame; - aerr = try_read_frame_(&frame); + APIError aerr = this->try_read_frame_(); if (aerr != APIError::OK) { if (aerr == APIError::BAD_INDICATOR) { // Make sure to tell the remote that we don't @@ -220,10 +210,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { return aerr; } - buffer->container = std::move(frame); + buffer->container = std::move(this->rx_buf_); buffer->data_offset = 0; - buffer->data_len = rx_header_parsed_len_; - buffer->type = rx_header_parsed_type_; + buffer->data_len = this->rx_header_parsed_len_; + buffer->type = this->rx_header_parsed_type_; + this->rx_buf_.clear(); return APIError::OK; } APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index 55a6d0f744..bba981d26b 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -24,7 +24,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper { APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; protected: - APIError try_read_frame_(std::vector *frame); + APIError try_read_frame_(); // Group 2-byte aligned types uint16_t rx_header_parsed_type_ = 0;