mirror of
https://github.com/esphome/esphome.git
synced 2025-06-16 07:16:58 +02:00
Use fixed buffer for plaintext protocol like noise protocol (#8800)
This commit is contained in:
parent
9704de6647
commit
2341ff651a
@ -830,6 +830,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// read header
|
||||
while (!rx_header_parsed_) {
|
||||
uint8_t data;
|
||||
// Reading one byte at a time is fastest in practice for ESP32 when
|
||||
// there is no data on the wire (which is the common case).
|
||||
// This results in faster failure detection compared to
|
||||
// attempting to read multiple bytes at once.
|
||||
ssize_t received = socket_->read(&data, 1);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
@ -843,27 +847,60 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_header_buf_.push_back(data);
|
||||
|
||||
// try parse header
|
||||
if (rx_header_buf_[0] != 0x00) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||
return APIError::BAD_INDICATOR;
|
||||
// Successfully read a byte
|
||||
|
||||
// Process byte according to current buffer position
|
||||
if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
|
||||
if (data != 0x00) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", data);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
// We don't store the indicator byte, just increment position
|
||||
rx_header_buf_pos_ = 1; // Set to 1 directly
|
||||
continue; // Need more bytes before we can parse
|
||||
}
|
||||
|
||||
size_t i = 1;
|
||||
// Check buffer overflow before storing
|
||||
if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Header buffer overflow");
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
// Store byte in buffer (adjust index to account for skipped indicator byte)
|
||||
rx_header_buf_[rx_header_buf_pos_ - 1] = data;
|
||||
|
||||
// Increment position after storing
|
||||
rx_header_buf_pos_++;
|
||||
|
||||
// Case 3: If we only have one varint byte, we need more
|
||||
if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
|
||||
continue; // Need more bytes before we can parse
|
||||
}
|
||||
|
||||
// At this point, we have at least 3 bytes total:
|
||||
// - Validated indicator byte (0x00) but not stored
|
||||
// - At least 2 bytes in the buffer for the varints
|
||||
// Buffer layout:
|
||||
// First 1-3 bytes: Message size varint (variable length)
|
||||
// - 2 bytes would only allow up to 16383, which is less than noise's 65535
|
||||
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
|
||||
// Remaining 1-2 bytes: Message type varint (variable length)
|
||||
// We now attempt to parse both varints. If either is incomplete,
|
||||
// we'll continue reading more bytes.
|
||||
|
||||
uint32_t consumed = 0;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
||||
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
|
||||
if (!msg_size_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
continue;
|
||||
}
|
||||
|
||||
i += consumed;
|
||||
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
||||
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
|
||||
if (!msg_type_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
continue;
|
||||
@ -909,7 +946,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
rx_header_buf_.clear();
|
||||
rx_header_buf_pos_ = 0;
|
||||
rx_header_parsed_ = false;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
@ -119,6 +119,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
// Fixed-size header buffer for noise protocol:
|
||||
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
|
||||
uint8_t rx_header_buf_[3];
|
||||
size_t rx_header_buf_len_ = 0;
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
@ -179,7 +182,16 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
std::vector<uint8_t> rx_header_buf_;
|
||||
// Fixed-size header buffer for plaintext protocol:
|
||||
// We only need space for the two varints since we validate the indicator byte separately.
|
||||
// To match noise protocol's maximum message size (65535), we need:
|
||||
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
||||
//
|
||||
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
||||
// attempting to process messages with headers that large would likely crash the
|
||||
// ESP32 due to memory constraints.
|
||||
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type)
|
||||
uint8_t rx_header_buf_pos_ = 0;
|
||||
bool rx_header_parsed_ = false;
|
||||
uint32_t rx_header_parsed_type_ = 0;
|
||||
uint32_t rx_header_parsed_len_ = 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user