From e7001c5eea1ce79110a97ed7bc33b6b18ab5ec30 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Jan 2026 14:05:37 -1000 Subject: [PATCH] [api] Auto-generate zero-copy pointer access for incoming API bytes fields (#12654) --- esphome/components/api/api.proto | 8 +++---- esphome/components/api/api_pb2.h | 8 +++---- script/api_protobuf/api_protobuf.py | 35 +++++++++++++---------------- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index debea5808c..fc05947774 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_API_NOISE"; - bytes key = 1 [(pointer_to_buffer) = true]; + bytes key = 1; } message NoiseEncryptionSetKeyResponse { @@ -796,7 +796,7 @@ message HomeassistantActionResponse { uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest bool success = 2; // Whether the service call succeeded string error_message = 3; // Error message if success = false - bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; + bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; } // ==================== IMPORT HOME ASSISTANT STATES ==================== @@ -1692,7 +1692,7 @@ message BluetoothGATTWriteRequest { uint32 handle = 2; bool response = 3; - bytes data = 4 [(pointer_to_buffer) = true]; + bytes data = 4; } message BluetoothGATTReadDescriptorRequest { @@ -1712,7 +1712,7 @@ message BluetoothGATTWriteDescriptorRequest { uint64 address = 1; uint32 handle = 2; - bytes data = 3 [(pointer_to_buffer) = true]; + bytes data = 3; } message BluetoothGATTNotifyRequest { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9d7a1eb9cb..2579ebbae2 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1069,7 +1069,7 @@ class SubscribeLogsResponse final : public ProtoMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 124; - static constexpr uint8_t ESTIMATED_SIZE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif @@ -1161,7 +1161,7 @@ class HomeassistantActionRequest final : public ProtoMessage { class HomeassistantActionResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 130; - static constexpr uint8_t ESTIMATED_SIZE = 34; + static constexpr uint8_t ESTIMATED_SIZE = 24; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_action_response"; } #endif @@ -2146,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 75; - static constexpr uint8_t ESTIMATED_SIZE = 29; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_request"; } #endif @@ -2182,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 77; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index f22b248747..5b68c6a3d2 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -354,28 +354,23 @@ def create_field_type_info( return FixedArrayRepeatedType(field, size_define) return RepeatedTypeInfo(field) - # Check for mutually exclusive options on bytes fields - if field.type == 12: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - fixed_size = get_field_opt(field, pb.fixed_array_size, None) - - if has_pointer_to_buffer and fixed_size is not None: - raise ValueError( - f"Field '{field.name}' has both pointer_to_buffer and fixed_array_size. " - "These options are mutually exclusive. Use pointer_to_buffer for zero-copy " - "or fixed_array_size for traditional array storage." - ) - - if has_pointer_to_buffer: - # Zero-copy pointer approach - no size needed, will use size_t for length - return PointerToBytesBufferType(field, None) - - if fixed_size is not None: - # Traditional fixed array approach with copy - return FixedArrayBytesType(field, fixed_size) - # Special handling for bytes fields if field.type == 12: + fixed_size = get_field_opt(field, pb.fixed_array_size, None) + + if fixed_size is not None: + # Traditional fixed array approach with copy (takes priority) + return FixedArrayBytesType(field, fixed_size) + + # For SOURCE_CLIENT only messages (decode but no encode), use pointer + # for zero-copy access to the receive buffer + if needs_decode and not needs_encode: + return PointerToBytesBufferType(field, None) + + # For SOURCE_BOTH/SOURCE_SERVER, explicit annotation is still needed + if get_field_opt(field, pb.pointer_to_buffer, False): + return PointerToBytesBufferType(field, None) + return BytesType(field, needs_decode, needs_encode) # Special handling for string fields