diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index fae0f2e75a..0be087b52d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -425,7 +425,7 @@ message ListEntitiesFanResponse { bool disabled_by_default = 9; string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 11; - repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"]; + repeated string supported_preset_modes = 12 [(container_pointer) = "std::vector"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; } // Deprecated in API version 1.6 - only used in deprecated fields @@ -1143,7 +1143,7 @@ message ListEntitiesSelectResponse { reserved 4; // Deprecated: was string unique_id string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; - repeated string options = 6 [(container_pointer) = "std::vector"]; + repeated string options = 6 [(container_pointer_no_template) = "FixedVector"]; bool disabled_by_default = 7; EntityCategory entity_category = 8; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index e952ea670b..b33a480171 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -142,6 +142,11 @@ APIError APINoiseFrameHelper::loop() { * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. */ APIError APINoiseFrameHelper::try_read_frame_() { + // Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK) + if (this->rx_buf_len_ == 0) { + this->rx_buf_.clear(); + } + // read header if (rx_header_buf_len_ < 3) { // no header information yet diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 471e6c5404..1635a57cd5 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -54,6 +54,11 @@ APIError APIPlaintextFrameHelper::loop() { * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. */ APIError APIPlaintextFrameHelper::try_read_frame_() { + // Clear buffer when starting a new frame (rx_buf_len_ == 0 means not resuming after WOULD_BLOCK) + if (this->rx_buf_len_ == 0) { + this->rx_buf_.clear(); + } + // read header while (!rx_header_parsed_) { // Now that we know when the socket is ready, we can read up to 3 bytes diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 37bcf5d8a0..3472707d3c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_ENTITY_ICON buffer.encode_string(5, this->icon_ref_); #endif - for (const auto &it : *this->options) { - buffer.encode_string(6, it, true); + for (const char *it : *this->options) { + buffer.encode_string(6, it, strlen(it), true); } buffer.encode_bool(7, this->disabled_by_default); buffer.encode_uint32(8, static_cast(this->entity_category)); @@ -1492,8 +1492,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->icon_ref_.size()); #endif if (!this->options->empty()) { - for (const auto &it : *this->options) { - size.add_length_force(1, it.size()); + for (const char *it : *this->options) { + size.add_length_force(1, strlen(it)); } } size.add_bool(1, this->disabled_by_default); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 3e9a10c1f7..43018ef32c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -725,7 +725,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; - const std::set *supported_preset_modes{}; + const std::vector *supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_select_response"; } #endif - const std::vector *options{}; + const FixedVector *options{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index e803125f53..d94ceaaa9c 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -88,6 +88,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value out.append("\n"); } +static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) { + append_field_prefix(out, field_name, indent); + out.append("'").append(value).append("'"); + out.append("\n"); +} + template static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append(proto_enum_to_string(value)); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index e7585924a5..089995b275 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -7,6 +7,7 @@ #include #include +#include #include #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE @@ -159,22 +160,6 @@ class ProtoVarInt { } } } - void encode(std::vector &out) { - uint64_t val = this->value_; - if (val <= 0x7F) { - out.push_back(val); - return; - } - while (val) { - uint8_t temp = val & 0x7F; - val >>= 7; - if (val) { - out.push_back(temp | 0x80); - } else { - out.push_back(temp); - } - } - } protected: uint64_t value_; @@ -233,8 +218,86 @@ class ProtoWriteBuffer { public: ProtoWriteBuffer(std::vector *buffer) : buffer_(buffer) {} void write(uint8_t value) { this->buffer_->push_back(value); } - void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); } - void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); } + + // Single implementation that all overloads delegate to + void encode_varint(uint64_t value) { + auto buffer = this->buffer_; + size_t start = buffer->size(); + + // Fast paths for common cases (1-3 bytes) + if (value < (1ULL << 7)) { + // 1 byte - very common for field IDs and small lengths + buffer->resize(start + 1); + buffer->data()[start] = static_cast(value); + return; + } + + uint8_t *p; + if (value < (1ULL << 14)) { + // 2 bytes - common for medium field IDs and lengths + buffer->resize(start + 2); + p = buffer->data() + start; + p[0] = (value & 0x7F) | 0x80; + p[1] = (value >> 7) & 0x7F; + return; + } + if (value < (1ULL << 21)) { + // 3 bytes - rare + buffer->resize(start + 3); + p = buffer->data() + start; + p[0] = (value & 0x7F) | 0x80; + p[1] = ((value >> 7) & 0x7F) | 0x80; + p[2] = (value >> 14) & 0x7F; + return; + } + + // Rare case: 4-10 byte values - calculate size from bit position + // Value is guaranteed >= (1ULL << 21), so CLZ is safe (non-zero) + uint32_t size; +#if defined(__GNUC__) || defined(__clang__) + // Use compiler intrinsic for efficient bit position lookup + size = (64 - __builtin_clzll(value) + 6) / 7; +#else + // Fallback for compilers without __builtin_clzll + if (value < (1ULL << 28)) { + size = 4; + } else if (value < (1ULL << 35)) { + size = 5; + } else if (value < (1ULL << 42)) { + size = 6; + } else if (value < (1ULL << 49)) { + size = 7; + } else if (value < (1ULL << 56)) { + size = 8; + } else if (value < (1ULL << 63)) { + size = 9; + } else { + size = 10; + } +#endif + + buffer->resize(start + size); + p = buffer->data() + start; + size_t bytes = 0; + while (value) { + uint8_t temp = value & 0x7F; + value >>= 7; + p[bytes++] = value ? temp | 0x80 : temp; + } + } + + // Common case: uint32_t values (field IDs, lengths, most integers) + void encode_varint(uint32_t value) { this->encode_varint(static_cast(value)); } + + // size_t overload (only enabled if size_t is distinct from uint32_t and uint64_t) + template + void encode_varint(T value) requires(std::is_same_v && !std::is_same_v && + !std::is_same_v) { + this->encode_varint(static_cast(value)); + } + + // Rare case: ProtoVarInt wrapper + void encode_varint(ProtoVarInt value) { this->encode_varint(value.as_uint64()); } /** * Encode a field key (tag/wire type combination). * @@ -249,14 +312,14 @@ class ProtoWriteBuffer { */ void encode_field_raw(uint32_t field_id, uint32_t type) { uint32_t val = (field_id << 3) | (type & WIRE_TYPE_MASK); - this->encode_varint_raw(val); + this->encode_varint(val); } void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) { if (len == 0 && !force) return; this->encode_field_raw(field_id, 2); // type 2: Length-delimited string - this->encode_varint_raw(len); + this->encode_varint(len); // Using resize + memcpy instead of insert provides significant performance improvement: // ~10-11x faster for 16-32 byte strings, ~3x faster for 64-byte strings @@ -278,13 +341,13 @@ class ProtoWriteBuffer { if (value == 0 && !force) return; this->encode_field_raw(field_id, 0); // type 0: Varint - uint32 - this->encode_varint_raw(value); + this->encode_varint(value); } void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { if (value == 0 && !force) return; this->encode_field_raw(field_id, 0); // type 0: Varint - uint64 - this->encode_varint_raw(ProtoVarInt(value)); + this->encode_varint(value); } void encode_bool(uint32_t field_id, bool value, bool force = false) { if (!value && !force)