diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 7b320b626e..bbd2595b7b 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -349,19 +349,16 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { } ProtoVarInt value = *value_res; - // Try regular fields first - for (uint8_t j = 0; j < field_count; j++) { - if (fields[j].field_num == field_id && get_wire_type(fields[j].get_type()) == 0) { - void *field_addr = base + fields[j].get_offset(); - decoded = decode_varint_field(fields[j].get_type(), field_addr, value); - break; - } + // Try regular fields first using binary search + if (const FieldMeta *field = find_field_binary(fields, field_count, field_id, 0)) { + void *field_addr = base + field->get_offset(); + decoded = decode_varint_field(field->get_type(), field_addr, value); } - // If not found, try repeated fields + // If not found, try repeated fields (linear search - usually only 1-2 fields) if (!decoded) { for (uint8_t j = 0; j < repeated_count; j++) { - if (repeated_fields[j].field_num == field_id && get_wire_type(repeated_fields[j].get_type()) == 0) { + if (repeated_fields[j].field_num == field_id && repeated_fields[j].get_wire_type() == 0) { void *field_addr = base + repeated_fields[j].get_offset(); decoded = decode_repeated_varint_field(repeated_fields[j].get_type(), field_addr, value); break; @@ -389,19 +386,16 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { ProtoLengthDelimited value(&buffer[i], field_length); - // Try regular fields first - for (uint8_t j = 0; j < field_count; j++) { - if (fields[j].field_num == field_id && get_wire_type(fields[j].get_type()) == 2) { - void *field_addr = base + fields[j].get_offset(); - decoded = decode_length_field(fields[j].get_type(), field_addr, value, fields[j].get_message_type_id()); - break; - } + // Try regular fields first using binary search + if (const FieldMeta *field = find_field_binary(fields, field_count, field_id, 2)) { + void *field_addr = base + field->get_offset(); + decoded = decode_length_field(field->get_type(), field_addr, value, field->get_message_type_id()); } - // If not found, try repeated fields + // If not found, try repeated fields (linear search - usually only 1-2 fields) if (!decoded) { for (uint8_t j = 0; j < repeated_count; j++) { - if (repeated_fields[j].field_num == field_id && get_wire_type(repeated_fields[j].get_type()) == 2) { + if (repeated_fields[j].field_num == field_id && repeated_fields[j].get_wire_type() == 2) { void *field_addr = base + repeated_fields[j].get_offset(); decoded = decode_repeated_length_field(repeated_fields[j].get_type(), field_addr, value, repeated_fields[j].get_message_type_id()); @@ -427,19 +421,16 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { raw |= uint32_t(buffer[i + 3]) << 24; Proto32Bit value(raw); - // Try regular fields first - for (uint8_t j = 0; j < field_count; j++) { - if (fields[j].field_num == field_id && get_wire_type(fields[j].get_type()) == 5) { - void *field_addr = base + fields[j].get_offset(); - decoded = decode_32bit_field(fields[j].get_type(), field_addr, value); - break; - } + // Try regular fields first using binary search + if (const FieldMeta *field = find_field_binary(fields, field_count, field_id, 5)) { + void *field_addr = base + field->get_offset(); + decoded = decode_32bit_field(field->get_type(), field_addr, value); } - // If not found, try repeated fields + // If not found, try repeated fields (linear search - usually only 1-2 fields) if (!decoded) { for (uint8_t j = 0; j < repeated_count; j++) { - if (repeated_fields[j].field_num == field_id && get_wire_type(repeated_fields[j].get_type()) == 5) { + if (repeated_fields[j].field_num == field_id && repeated_fields[j].get_wire_type() == 5) { void *field_addr = base + repeated_fields[j].get_offset(); decoded = decode_repeated_32bit_field(repeated_fields[j].get_type(), field_addr, value); break; diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index d3f5b81895..784cf62d06 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -271,6 +271,7 @@ struct FieldMeta { // Helper methods inline ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } + inline uint8_t get_wire_type() const { return ::esphome::api::get_wire_type(get_type()); } inline uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { // Reconstruct full offset from packed fields (10-bit offset) @@ -297,6 +298,7 @@ struct RepeatedFieldMeta { // Helper methods inline ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } + inline uint8_t get_wire_type() const { return ::esphome::api::get_wire_type(get_type()); } inline uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { // Reconstruct full offset from packed fields (10-bit offset) @@ -308,6 +310,49 @@ struct RepeatedFieldMeta { inline uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63) }; +// Binary search for field lookup - optimized for performance +template +inline const MetaType *find_field_binary(const MetaType *fields, uint8_t count, uint8_t field_id, uint8_t wire_type) { + uint8_t left = 0; + uint8_t right = count; + + while (left < right) { + uint8_t mid = (left + right) / 2; + uint8_t mid_field = fields[mid].field_num; + + if (mid_field < field_id) { + left = mid + 1; + } else if (mid_field > field_id) { + right = mid; + } else { + // Found field_id, check wire type + if (fields[mid].get_wire_type() == wire_type) { + return &fields[mid]; + } + // Field number matches but wire type doesn't - search nearby entries + // (in case there are multiple fields with same number but different types) + + // Search backwards + for (uint8_t k = mid; k > 0 && fields[k - 1].field_num == field_id; k--) { + if (fields[k - 1].get_wire_type() == wire_type) { + return &fields[k - 1]; + } + } + + // Search forwards + for (uint8_t k = mid + 1; k < count && fields[k].field_num == field_id; k++) { + if (fields[k].get_wire_type() == wire_type) { + return &fields[k]; + } + } + + return nullptr; // Field number found but no matching wire type + } + } + + return nullptr; // Field not found +} + class ProtoWriteBuffer { public: ProtoWriteBuffer(std::vector *buffer) : buffer_(buffer) {}