[api] Fix string lifetime issue in Home Assistant service calls with templated values

This commit is contained in:
J. Nick Koston 2025-07-25 23:01:24 -10:00
parent d54db471bd
commit 0155769ffe
No known key found for this signature in database
7 changed files with 45 additions and 18 deletions

View File

@ -759,7 +759,7 @@ message SubscribeHomeassistantServicesRequest {
message HomeassistantServiceMap { message HomeassistantServiceMap {
string key = 1; string key = 1;
string value = 2; string value = 2 [(no_zero_copy) = true];
} }
message HomeassistantServiceResponse { message HomeassistantServiceResponse {

View File

@ -27,4 +27,5 @@ extend google.protobuf.MessageOptions {
extend google.protobuf.FieldOptions { extend google.protobuf.FieldOptions {
optional string field_ifdef = 1042; optional string field_ifdef = 1042;
optional uint32 fixed_array_size = 50007; optional uint32 fixed_array_size = 50007;
optional bool no_zero_copy = 50008 [default=false];
} }

View File

@ -845,11 +845,11 @@ void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const {
#endif #endif
void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->key_ref_); buffer.encode_string(1, this->key_ref_);
buffer.encode_string(2, this->value_ref_); buffer.encode_string(2, this->value);
} }
void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const { void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->key_ref_.size()); ProtoSize::add_string_field(total_size, 1, this->key_ref_.size());
ProtoSize::add_string_field(total_size, 1, this->value_ref_.size()); ProtoSize::add_string_field(total_size, 1, this->value);
} }
void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->service_ref_); buffer.encode_string(1, this->service_ref_);

View File

@ -1061,8 +1061,7 @@ class HomeassistantServiceMap : public ProtoMessage {
public: public:
StringRef key_ref_{}; StringRef key_ref_{};
void set_key(const StringRef &ref) { this->key_ref_ = ref; } void set_key(const StringRef &ref) { this->key_ref_ = ref; }
StringRef value_ref_{}; std::string value{};
void set_value(const StringRef &ref) { this->value_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP

View File

@ -1044,7 +1044,7 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
void HomeassistantServiceMap::dump_to(std::string &out) const { void HomeassistantServiceMap::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap"); MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key_ref_); dump_field(out, "key", this->key_ref_);
dump_field(out, "value", this->value_ref_); dump_field(out, "value", this->value);
} }
void HomeassistantServiceResponse::dump_to(std::string &out) const { void HomeassistantServiceResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceResponse"); MessageDumpHelper helper(out, "HomeassistantServiceResponse");

View File

@ -69,22 +69,19 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
resp.data.emplace_back(); resp.data.emplace_back();
auto &kv = resp.data.back(); auto &kv = resp.data.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->data_template_) { for (auto &it : this->data_template_) {
resp.data_template.emplace_back(); resp.data_template.emplace_back();
auto &kv = resp.data_template.back(); auto &kv = resp.data_template.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->variables_) { for (auto &it : this->variables_) {
resp.variables.emplace_back(); resp.variables.emplace_back();
auto &kv = resp.variables.back(); auto &kv = resp.variables.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
this->parent_->send_homeassistant_service_call(resp); this->parent_->send_homeassistant_service_call(resp);
} }

View File

@ -562,11 +562,16 @@ class StringType(TypeInfo):
@property @property
def public_content(self) -> list[str]: def public_content(self) -> list[str]:
content: list[str] = [] content: list[str] = []
# Add std::string storage if message needs decoding
if self._needs_decode: # Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# Add std::string storage if message needs decoding OR if no_zero_copy is set
if self._needs_decode or no_zero_copy:
content.append(f"std::string {self.field_name}{{}};") content.append(f"std::string {self.field_name}{{}};")
if self._needs_encode: # Only add StringRef if encoding is needed AND no_zero_copy is not set
if self._needs_encode and not no_zero_copy:
content.extend( content.extend(
[ [
# Add StringRef field if message needs encoding # Add StringRef field if message needs encoding
@ -581,13 +586,28 @@ class StringType(TypeInfo):
@property @property
def encode_content(self) -> str: def encode_content(self) -> str:
return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" # Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
if no_zero_copy:
# Use the std::string directly
return f"buffer.encode_string({self.number}, this->{self.field_name});"
else:
# Use the StringRef
return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);"
def dump(self, name): def dump(self, name):
# Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# If name is 'it', this is a repeated field element - always use string # If name is 'it', this is a repeated field element - always use string
if name == "it": if name == "it":
return "append_quoted_string(out, StringRef(it));" return "append_quoted_string(out, StringRef(it));"
# If no_zero_copy is set, always use std::string
if no_zero_copy:
return f'out.append("\'").append(this->{self.field_name}).append("\'");'
# For SOURCE_CLIENT only, always use std::string # For SOURCE_CLIENT only, always use std::string
if not self._needs_encode: if not self._needs_encode:
return f'out.append("\'").append(this->{self.field_name}).append("\'");' return f'out.append("\'").append(this->{self.field_name}).append("\'");'
@ -607,6 +627,13 @@ class StringType(TypeInfo):
@property @property
def dump_content(self) -> str: def dump_content(self) -> str:
# Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# If no_zero_copy is set, always use std::string
if no_zero_copy:
return f'dump_field(out, "{self.name}", this->{self.field_name});'
# For SOURCE_CLIENT only, use std::string # For SOURCE_CLIENT only, use std::string
if not self._needs_encode: if not self._needs_encode:
return f'dump_field(out, "{self.name}", this->{self.field_name});' return f'dump_field(out, "{self.name}", this->{self.field_name});'
@ -622,8 +649,11 @@ class StringType(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
# For SOURCE_CLIENT only messages, use the string field directly # Check if no_zero_copy option is set
if not self._needs_encode: no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# For SOURCE_CLIENT only messages or no_zero_copy, use the string field directly
if not self._needs_encode or no_zero_copy:
return self._get_simple_size_calculation(name, force, "add_string_field") return self._get_simple_size_calculation(name, force, "add_string_field")
# Check if this is being called from a repeated field context # Check if this is being called from a repeated field context