[api] Use StringRef for user service string arguments (#13974)

This commit is contained in:
J. Nick Koston
2026-03-02 10:32:44 +13:00
committed by GitHub
parent a1760a1980
commit 48b5cae6c4
23 changed files with 406 additions and 56 deletions
+24 -19
View File
@@ -1711,37 +1711,42 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
return;
}
// Null-terminate state in-place for safe c_str() usage (e.g., parse_number in callbacks).
// Safe: decode is complete, byte after string data was already consumed during parse,
// and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte in rx_buf_.
// const_cast is safe: msg references mutable rx_buf_ data; the const& handler
// signature is a generated protobuf pattern, not a true immutability contract.
if (!msg.state.empty()) {
const_cast<char *>(msg.state.c_str())[msg.state.size()] = '\0';
}
for (auto &it : this->parent_->get_state_subs()) {
// Compare entity_id: check length matches and content matches
size_t entity_id_len = strlen(it.entity_id);
if (entity_id_len != msg.entity_id.size() ||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
if (msg.entity_id != it.entity_id) {
continue;
}
// Compare attribute: either both have matching attribute, or both have none
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
if (sub_attr_len != msg.attribute.size() ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
// If subscriber has attribute filter (non-null), message attribute must match it;
// if subscriber has no filter (nullptr), message must have no attribute.
if (it.attribute != nullptr ? msg.attribute != it.attribute : !msg.attribute.empty()) {
continue;
}
// Create null-terminated state for callback (parse_number needs null-termination)
// HA state max length is 255 characters, but attributes can be much longer
// Use stack buffer for common case (states), heap fallback for large attributes
size_t state_len = msg.state.size();
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
if (state_len > 0) {
memcpy(state_buf, msg.state.c_str(), state_len);
}
state_buf[state_len] = '\0';
it.callback(StringRef(state_buf, state_len));
it.callback(msg.state);
}
}
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
// Null-terminate string args in-place for safe c_str() usage in YAML service triggers.
// Safe: full ExecuteServiceRequest decode is complete, all bytes in rx_buf_ consumed,
// and frame helpers reserve RX_BUF_NULL_TERMINATOR extra byte for the last field.
// const_cast is safe: msg references mutable rx_buf_ data; the const& handler
// signature is a generated protobuf pattern, not a true immutability contract.
for (auto &arg : const_cast<ExecuteServiceRequest &>(msg).args) {
if (!arg.string_.empty()) {
const_cast<char *>(arg.string_.c_str())[arg.string_.size()] = '\0';
}
}
bool found = false;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
// Register the call and get a unique server-generated action_call_id