mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Use fixed buffer for plaintext protocol like noise protocol (#8800)
This commit is contained in:
		
				
					committed by
					
						 Jesse Hills
						Jesse Hills
					
				
			
			
				
	
			
			
			
						parent
						
							9704de6647
						
					
				
				
					commit
					2341ff651a
				
			| @@ -830,6 +830,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|   // read header |   // read header | ||||||
|   while (!rx_header_parsed_) { |   while (!rx_header_parsed_) { | ||||||
|     uint8_t data; |     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); |     ssize_t received = socket_->read(&data, 1); | ||||||
|     if (received == -1) { |     if (received == -1) { | ||||||
|       if (errno == EWOULDBLOCK || errno == EAGAIN) { |       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||||
| @@ -843,27 +847,60 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|       HELPER_LOG("Connection closed"); |       HELPER_LOG("Connection closed"); | ||||||
|       return APIError::CONNECTION_CLOSED; |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_header_buf_.push_back(data); |  | ||||||
|  |  | ||||||
|     // try parse header |     // Successfully read a byte | ||||||
|     if (rx_header_buf_[0] != 0x00) { |  | ||||||
|  |     // 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; |         state_ = State::FAILED; | ||||||
|       HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); |         HELPER_LOG("Bad indicator byte %u", data); | ||||||
|         return APIError::BAD_INDICATOR; |         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 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 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. | ||||||
|  |  | ||||||
|     size_t i = 1; |  | ||||||
|     uint32_t consumed = 0; |     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()) { |     if (!msg_size_varint.has_value()) { | ||||||
|       // not enough data there yet |       // not enough data there yet | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     i += consumed; |  | ||||||
|     rx_header_parsed_len_ = msg_size_varint->as_uint32(); |     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()) { |     if (!msg_type_varint.has_value()) { | ||||||
|       // not enough data there yet |       // not enough data there yet | ||||||
|       continue; |       continue; | ||||||
| @@ -909,7 +946,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|   // consume msg |   // consume msg | ||||||
|   rx_buf_ = {}; |   rx_buf_ = {}; | ||||||
|   rx_buf_len_ = 0; |   rx_buf_len_ = 0; | ||||||
|   rx_header_buf_.clear(); |   rx_header_buf_pos_ = 0; | ||||||
|   rx_header_parsed_ = false; |   rx_header_parsed_ = false; | ||||||
|   return APIError::OK; |   return APIError::OK; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -119,6 +119,9 @@ class APINoiseFrameHelper : public APIFrameHelper { | |||||||
|   std::unique_ptr<socket::Socket> socket_; |   std::unique_ptr<socket::Socket> socket_; | ||||||
|  |  | ||||||
|   std::string info_; |   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]; |   uint8_t rx_header_buf_[3]; | ||||||
|   size_t rx_header_buf_len_ = 0; |   size_t rx_header_buf_len_ = 0; | ||||||
|   std::vector<uint8_t> rx_buf_; |   std::vector<uint8_t> rx_buf_; | ||||||
| @@ -179,7 +182,16 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | |||||||
|   std::unique_ptr<socket::Socket> socket_; |   std::unique_ptr<socket::Socket> socket_; | ||||||
|  |  | ||||||
|   std::string info_; |   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; |   bool rx_header_parsed_ = false; | ||||||
|   uint32_t rx_header_parsed_type_ = 0; |   uint32_t rx_header_parsed_type_ = 0; | ||||||
|   uint32_t rx_header_parsed_len_ = 0; |   uint32_t rx_header_parsed_len_ = 0; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user