From 6209d4b493a6cae4c37fabeb13a06a75fe762e08 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Oct 2025 16:16:44 -0500 Subject: [PATCH] [api] Optimize frame helpers to eliminate double-move overhead (#11092) --- .../components/api/api_frame_helper_noise.cpp | 82 ++++++++----------- .../components/api/api_frame_helper_noise.h | 2 +- .../api/api_frame_helper_plaintext.cpp | 46 ++++------- .../api/api_frame_helper_plaintext.h | 2 +- 4 files changed, 54 insertions(+), 78 deletions(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index ab27699f06..20ced5c106 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 @@ -185,9 +175,9 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector *frame) { return APIError::BAD_HANDSHAKE_PACKET_LEN; } - // reserve space for body - if (rx_buf_.size() != msg_size) { - rx_buf_.resize(msg_size); + // Reserve space for body + if (this->rx_buf_.size() != msg_size) { + this->rx_buf_.resize(msg_size); } if (rx_buf_len_ < msg_size) { @@ -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,17 @@ 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()); state_ = State::SERVER_HELLO; } @@ -285,24 +274,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 @@ -379,35 +367,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,12 +401,12 @@ 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; 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..85456444f3 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 @@ -150,9 +142,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector *frame) { } // header reading done - // reserve space for body - if (rx_buf_.size() != rx_header_parsed_len_) { - rx_buf_.resize(rx_header_parsed_len_); + // Reserve space for body + if (this->rx_buf_.size() != this->rx_header_parsed_len_) { + this->rx_buf_.resize(this->rx_header_parsed_len_); } if (rx_buf_len_ < rx_header_parsed_len_) { @@ -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,10 @@ 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_; 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;