From 43cfdb79193c512e4bcf348c77862fa595b0db2e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Sep 2025 11:47:38 -0500 Subject: [PATCH] Reduce duplicate code in API to check auth and connection --- esphome/components/api/api_pb2_service.cpp | 233 +++++++-------------- esphome/components/api/api_pb2_service.h | 1 + esphome/components/api/proto.h | 4 +- script/api_protobuf/api_protobuf.py | 100 ++++++--- 4 files changed, 147 insertions(+), 191 deletions(-) diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 4afc66dc44..24a7740ec0 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -639,241 +639,139 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) { } } void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { - if (this->check_connection_setup_() && !this->send_device_info_response(msg)) { + if (!this->send_device_info_response(msg)) { this->on_fatal_error(); } } -void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { - if (this->check_authenticated_()) { - this->list_entities(msg); - } -} +void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { this->list_entities(msg); } void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_states(msg); - } -} -void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_logs(msg); - } + this->subscribe_states(msg); } +void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); } #ifdef USE_API_HOMEASSISTANT_SERVICES void APIServerConnection::on_subscribe_homeassistant_services_request( const SubscribeHomeassistantServicesRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_homeassistant_services(msg); - } + this->subscribe_homeassistant_services(msg); } #endif #ifdef USE_API_HOMEASSISTANT_STATES void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_home_assistant_states(msg); - } + this->subscribe_home_assistant_states(msg); } #endif #ifdef USE_API_SERVICES -void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { - if (this->check_authenticated_()) { - this->execute_service(msg); - } -} +void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); } #endif #ifdef USE_API_NOISE void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { - if (this->check_authenticated_() && !this->send_noise_encryption_set_key_response(msg)) { + if (!this->send_noise_encryption_set_key_response(msg)) { this->on_fatal_error(); } } #endif #ifdef USE_BUTTON -void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { - if (this->check_authenticated_()) { - this->button_command(msg); - } -} +void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); } #endif #ifdef USE_CAMERA -void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { - if (this->check_authenticated_()) { - this->camera_image(msg); - } -} +void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); } #endif #ifdef USE_CLIMATE -void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { - if (this->check_authenticated_()) { - this->climate_command(msg); - } -} +void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); } #endif #ifdef USE_COVER -void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { - if (this->check_authenticated_()) { - this->cover_command(msg); - } -} +void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); } #endif #ifdef USE_DATETIME_DATE -void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { - if (this->check_authenticated_()) { - this->date_command(msg); - } -} +void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); } #endif #ifdef USE_DATETIME_DATETIME void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { - if (this->check_authenticated_()) { - this->datetime_command(msg); - } + this->datetime_command(msg); } #endif #ifdef USE_FAN -void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { - if (this->check_authenticated_()) { - this->fan_command(msg); - } -} +void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); } #endif #ifdef USE_LIGHT -void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { - if (this->check_authenticated_()) { - this->light_command(msg); - } -} +void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); } #endif #ifdef USE_LOCK -void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { - if (this->check_authenticated_()) { - this->lock_command(msg); - } -} +void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); } #endif #ifdef USE_MEDIA_PLAYER void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { - if (this->check_authenticated_()) { - this->media_player_command(msg); - } + this->media_player_command(msg); } #endif #ifdef USE_NUMBER -void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { - if (this->check_authenticated_()) { - this->number_command(msg); - } -} +void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); } #endif #ifdef USE_SELECT -void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { - if (this->check_authenticated_()) { - this->select_command(msg); - } -} +void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); } #endif #ifdef USE_SIREN -void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { - if (this->check_authenticated_()) { - this->siren_command(msg); - } -} +void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); } #endif #ifdef USE_SWITCH -void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { - if (this->check_authenticated_()) { - this->switch_command(msg); - } -} +void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); } #endif #ifdef USE_TEXT -void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { - if (this->check_authenticated_()) { - this->text_command(msg); - } -} +void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); } #endif #ifdef USE_DATETIME_TIME -void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { - if (this->check_authenticated_()) { - this->time_command(msg); - } -} +void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); } #endif #ifdef USE_UPDATE -void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { - if (this->check_authenticated_()) { - this->update_command(msg); - } -} +void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); } #endif #ifdef USE_VALVE -void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { - if (this->check_authenticated_()) { - this->valve_command(msg); - } -} +void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( const SubscribeBluetoothLEAdvertisementsRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_bluetooth_le_advertisements(msg); - } + this->subscribe_bluetooth_le_advertisements(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_device_request(msg); - } + this->bluetooth_device_request(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_get_services(msg); - } + this->bluetooth_gatt_get_services(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_read(msg); - } + this->bluetooth_gatt_read(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_write(msg); - } + this->bluetooth_gatt_write(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_read_descriptor(msg); - } + this->bluetooth_gatt_read_descriptor(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_write_descriptor(msg); - } + this->bluetooth_gatt_write_descriptor(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_gatt_notify(msg); - } + this->bluetooth_gatt_notify(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_connections_free_request( const SubscribeBluetoothConnectionsFreeRequest &msg) { - if (this->check_authenticated_() && !this->send_subscribe_bluetooth_connections_free_response(msg)) { + if (!this->send_subscribe_bluetooth_connections_free_response(msg)) { this->on_fatal_error(); } } @@ -881,59 +779,68 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request( #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { - if (this->check_authenticated_()) { - this->unsubscribe_bluetooth_le_advertisements(msg); - } + this->unsubscribe_bluetooth_le_advertisements(msg); } #endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { - if (this->check_authenticated_()) { - this->bluetooth_scanner_set_mode(msg); - } + this->bluetooth_scanner_set_mode(msg); } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { - if (this->check_authenticated_()) { - this->subscribe_voice_assistant(msg); - } + this->subscribe_voice_assistant(msg); } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { - if (this->check_authenticated_() && !this->send_voice_assistant_get_configuration_response(msg)) { + if (!this->send_voice_assistant_get_configuration_response(msg)) { this->on_fatal_error(); } } #endif #ifdef USE_VOICE_ASSISTANT void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { - if (this->check_authenticated_()) { - this->voice_assistant_set_configuration(msg); - } + this->voice_assistant_set_configuration(msg); } #endif #ifdef USE_ALARM_CONTROL_PANEL void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { - if (this->check_authenticated_()) { - this->alarm_control_panel_command(msg); - } + this->alarm_control_panel_command(msg); } #endif #ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { - if (this->check_authenticated_()) { - this->zwave_proxy_frame(msg); - } -} +void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); } #endif #ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { - if (this->check_authenticated_()) { - this->zwave_proxy_request(msg); - } -} +void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } #endif +void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { + // Check authentication/connection requirements for messages + switch (msg_type) { + case HelloRequest::MESSAGE_TYPE: // No setup required +#ifdef USE_API_PASSWORD + case AuthenticationRequest::MESSAGE_TYPE: // No setup required +#endif + case DisconnectRequest::MESSAGE_TYPE: // No setup required + case PingRequest::MESSAGE_TYPE: // No setup required + break; // Skip all checks for these messages + case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only + if (!this->check_connection_setup_()) { + return; // Connection not setup + } + break; + default: + // All other messages require authentication (which includes connection check) + if (!this->check_authenticated_()) { + return; // Authentication failed + } + break; + } + + // Call base implementation to process the message + APIServerConnectionBase::read_message(msg_size, msg_type, msg_data); +} + } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 9379dfee7d..1afcba6664 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -477,6 +477,7 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; #endif + void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; }; } // namespace esphome::api diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 0e5ec61050..3232ec358a 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -827,7 +827,7 @@ class ProtoService { } // Authentication helper methods - bool check_connection_setup_() { + inline bool check_connection_setup_() { if (!this->is_connection_setup()) { this->on_no_setup_connection(); return false; @@ -835,7 +835,7 @@ class ProtoService { return true; } - bool check_authenticated_() { + inline bool check_authenticated_() { #ifdef USE_API_PASSWORD if (!this->check_connection_setup_()) { return false; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index fa04222c5d..f66034310b 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2615,6 +2615,10 @@ static const char *const TAG = "api.service"; hpp_protected = "" cpp += "\n" + # Build a mapping of message input types to their authentication requirements + message_auth_map: dict[str, bool] = {} + message_conn_map: dict[str, bool] = {} + m = serv.method[0] for m in serv.method: func = m.name @@ -2626,6 +2630,10 @@ static const char *const TAG = "api.service"; needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) + # Store authentication requirements for message types + message_auth_map[inp] = needs_auth + message_conn_map[inp] = needs_conn + ifdef = message_ifdef_map.get(inp, ifdefs.get(inp)) if ifdef is not None: @@ -2643,33 +2651,14 @@ static const char *const TAG = "api.service"; cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" - # Start with authentication/connection check if needed - if needs_auth or needs_conn: - # Determine which check to use - if needs_auth: - check_func = "this->check_authenticated_()" - else: - check_func = "this->check_connection_setup_()" - - if is_void: - # For void methods, just wrap with auth check - body = f"if ({check_func}) {{\n" - body += f" this->{func}(msg);\n" - body += "}\n" - else: - # For non-void methods, combine auth check and send response check - body = f"if ({check_func} && !this->send_{func}_response(msg)) {{\n" - body += " this->on_fatal_error();\n" - body += "}\n" + # No authentication check here - it's done in read_message + body = "" + if is_void: + body += f"this->{func}(msg);\n" else: - # No auth check needed, just call the handler - body = "" - if is_void: - body += f"this->{func}(msg);\n" - else: - body += f"if (!this->send_{func}_response(msg)) {{\n" - body += " this->on_fatal_error();\n" - body += "}\n" + body += f"if (!this->send_{func}_response(msg)) {{\n" + body += " this->on_fatal_error();\n" + body += "}\n" cpp += indent(body) + "\n" + "}\n" @@ -2678,6 +2667,65 @@ static const char *const TAG = "api.service"; hpp_protected += "#endif\n" cpp += "#endif\n" + # Generate optimized read_message with authentication checking + # Categorize messages by their authentication requirements + no_conn_ids: set[int] = set() + conn_only_ids: set[int] = set() + + for id_, (_, _, case_msg_name) in cases: + if case_msg_name in message_auth_map: + needs_auth = message_auth_map[case_msg_name] + needs_conn = message_conn_map[case_msg_name] + + if not needs_conn: + no_conn_ids.add(id_) + elif not needs_auth: + conn_only_ids.add(id_) + + # Generate override if we have messages that skip checks + if no_conn_ids or conn_only_ids: + # Helper to generate case statements with ifdefs + def generate_cases(ids: set[int], comment: str) -> str: + result = "" + for id_ in sorted(ids): + _, ifdef, msg_name = RECEIVE_CASES[id_] + if ifdef: + result += f"#ifdef {ifdef}\n" + result += f" case {msg_name}::MESSAGE_TYPE: {comment}\n" + if ifdef: + result += "#endif\n" + return result + + hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" + + cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" + cpp += " // Check authentication/connection requirements for messages\n" + cpp += " switch (msg_type) {\n" + + # Messages that don't need any checks + if no_conn_ids: + cpp += generate_cases(no_conn_ids, "// No setup required") + cpp += " break; // Skip all checks for these messages\n" + + # Messages that only need connection setup + if conn_only_ids: + cpp += generate_cases(conn_only_ids, "// Connection setup only") + cpp += " if (!this->check_connection_setup_()) {\n" + cpp += " return; // Connection not setup\n" + cpp += " }\n" + cpp += " break;\n" + + cpp += " default:\n" + cpp += " // All other messages require authentication (which includes connection check)\n" + cpp += " if (!this->check_authenticated_()) {\n" + cpp += " return; // Authentication failed\n" + cpp += " }\n" + cpp += " break;\n" + cpp += " }\n\n" + cpp += " // Call base implementation to process the message\n" + cpp += f" {class_name}Base::read_message(msg_size, msg_type, msg_data);\n" + cpp += "}\n" + hpp += " protected:\n" hpp += hpp_protected hpp += "};\n"