esphome 2025.9.3__py3-none-any.whl → 2025.10.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +94 -31
- esphome/address_cache.py +142 -0
- esphome/automation.py +130 -32
- esphome/build_gen/platformio.py +1 -3
- esphome/codegen.py +1 -0
- esphome/components/animation/animation.cpp +2 -2
- esphome/components/api/__init__.py +166 -3
- esphome/components/api/api_connection.cpp +84 -41
- esphome/components/api/api_connection.h +22 -16
- esphome/components/api/api_frame_helper.cpp +33 -19
- esphome/components/api/api_frame_helper.h +19 -4
- esphome/components/api/api_frame_helper_noise.cpp +41 -53
- esphome/components/api/api_frame_helper_noise.h +1 -1
- esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
- esphome/components/api/api_frame_helper_plaintext.h +1 -1
- esphome/components/api/api_pb2.cpp +189 -15
- esphome/components/api/api_pb2.h +132 -20
- esphome/components/api/api_pb2_dump.cpp +97 -9
- esphome/components/api/api_pb2_service.cpp +118 -160
- esphome/components/api/api_pb2_service.h +31 -3
- esphome/components/api/api_server.cpp +68 -10
- esphome/components/api/api_server.h +32 -4
- esphome/components/api/custom_api_device.h +8 -8
- esphome/components/api/homeassistant_service.h +123 -6
- esphome/components/api/proto.h +6 -2
- esphome/components/api/user_services.h +2 -2
- esphome/components/as7341/sensor.py +1 -1
- esphome/components/audio/__init__.py +1 -1
- esphome/components/audio/audio.cpp +1 -1
- esphome/components/audio/audio_decoder.cpp +9 -9
- esphome/components/bl0906/bl0906.cpp +2 -2
- esphome/components/bl0942/bl0942.cpp +2 -2
- esphome/components/ble_client/__init__.py +1 -1
- esphome/components/bluetooth_proxy/__init__.py +4 -30
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
- esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
- esphome/components/camera_encoder/__init__.py +2 -4
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
- esphome/components/canbus/canbus.cpp +7 -5
- esphome/components/canbus/canbus.h +7 -7
- esphome/components/captive_portal/__init__.py +18 -1
- esphome/components/captive_portal/captive_portal.cpp +40 -46
- esphome/components/captive_portal/captive_portal.h +20 -22
- esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
- esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
- esphome/components/ccs811/ccs811.cpp +1 -1
- esphome/components/climate/climate.cpp +10 -7
- esphome/components/cm1106/cm1106.cpp +1 -1
- esphome/components/copy/lock/copy_lock.cpp +1 -1
- esphome/components/cover/cover.cpp +1 -0
- esphome/components/daikin_arc/daikin_arc.cpp +19 -12
- esphome/components/dashboard_import/dashboard_import.cpp +1 -1
- esphome/components/dashboard_import/dashboard_import.h +1 -1
- esphome/components/deep_sleep/__init__.py +9 -2
- esphome/components/deep_sleep/deep_sleep_component.h +11 -9
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
- esphome/components/ektf2232/touchscreen/__init__.py +8 -5
- esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
- esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
- esphome/components/epaper_spi/__init__.py +1 -0
- esphome/components/epaper_spi/display.py +80 -0
- esphome/components/epaper_spi/epaper_spi.cpp +227 -0
- esphome/components/epaper_spi/epaper_spi.h +93 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
- esphome/components/es7210/es7210.cpp +3 -3
- esphome/components/esp32/__init__.py +256 -340
- esphome/components/esp32/boards.py +81 -0
- esphome/components/esp32/preferences.cpp +23 -17
- esphome/components/esp32_ble/__init__.py +167 -44
- esphome/components/esp32_ble/ble.cpp +47 -3
- esphome/components/esp32_ble/ble.h +18 -0
- esphome/components/esp32_ble/ble_advertising.cpp +7 -3
- esphome/components/esp32_ble/ble_advertising.h +4 -0
- esphome/components/esp32_ble/ble_uuid.cpp +16 -42
- esphome/components/esp32_ble_beacon/__init__.py +3 -4
- esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
- esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
- esphome/components/esp32_ble_server/__init__.py +28 -14
- esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
- esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
- esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
- esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
- esphome/components/esp32_ble_server/ble_server.cpp +59 -24
- esphome/components/esp32_ble_server/ble_server.h +38 -20
- esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
- esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
- esphome/components/esp32_ble_tracker/__init__.py +25 -80
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -8
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
- esphome/components/esp32_camera/__init__.py +1 -3
- esphome/components/esp32_can/esp32_can.cpp +22 -4
- esphome/components/esp32_can/esp32_can.h +3 -0
- esphome/components/esp32_hosted/__init__.py +2 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +135 -65
- esphome/components/esp32_improv/esp32_improv_component.h +7 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
- esphome/components/esp8266/__init__.py +3 -3
- esphome/components/esphome/ota/__init__.py +21 -2
- esphome/components/esphome/ota/ota_esphome.cpp +456 -146
- esphome/components/esphome/ota/ota_esphome.h +49 -2
- esphome/components/ethernet/__init__.py +39 -22
- esphome/components/ethernet/ethernet_component.cpp +28 -5
- esphome/components/ethernet/ethernet_component.h +5 -1
- esphome/components/external_components/__init__.py +8 -6
- esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
- esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
- esphome/components/font/__init__.py +5 -5
- esphome/components/graph/graph.cpp +1 -1
- esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
- esphome/components/haier/hon_climate.cpp +2 -2
- esphome/components/haier/hon_climate.h +1 -1
- esphome/components/hdc1080/hdc1080.cpp +42 -34
- esphome/components/hdc1080/hdc1080.h +1 -3
- esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
- esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
- esphome/components/http_request/__init__.py +3 -3
- esphome/components/htu21d/htu21d.cpp +13 -18
- esphome/components/htu21d/htu21d.h +1 -1
- esphome/components/i2s_audio/__init__.py +1 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
- esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
- esphome/components/improv_serial/improv_serial_component.cpp +12 -15
- esphome/components/improv_serial/improv_serial_component.h +6 -8
- esphome/components/json/json_util.cpp +42 -44
- esphome/components/json/json_util.h +57 -0
- esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
- esphome/components/key_collector/key_collector.h +4 -4
- esphome/components/libretiny/__init__.py +6 -6
- esphome/components/libretiny/preferences.cpp +23 -16
- esphome/components/light/light_call.cpp +98 -120
- esphome/components/light/light_call.h +17 -7
- esphome/components/lm75b/__init__.py +0 -0
- esphome/components/lm75b/lm75b.cpp +39 -0
- esphome/components/lm75b/lm75b.h +19 -0
- esphome/components/lm75b/sensor.py +34 -0
- esphome/components/lock/lock.h +12 -6
- esphome/components/logger/__init__.py +15 -27
- esphome/components/logger/logger.cpp +10 -20
- esphome/components/logger/logger.h +105 -62
- esphome/components/logger/logger_esp32.cpp +0 -48
- esphome/components/logger/logger_zephyr.cpp +2 -3
- esphome/components/logger/select/logger_level_select.cpp +6 -7
- esphome/components/logger/select/logger_level_select.h +7 -0
- esphome/components/ltr501/ltr501.cpp +7 -6
- esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
- esphome/components/matrix_keypad/matrix_keypad.h +4 -4
- esphome/components/max7219digit/max7219digit.cpp +1 -1
- esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
- esphome/components/mcp2515/mcp2515.cpp +31 -3
- esphome/components/mcp2515/mcp2515_defs.h +3 -1
- esphome/components/md5/md5.cpp +0 -26
- esphome/components/md5/md5.h +10 -20
- esphome/components/mdns/__init__.py +93 -19
- esphome/components/mdns/mdns_component.cpp +57 -94
- esphome/components/mdns/mdns_component.h +35 -11
- esphome/components/mdns/mdns_esp32.cpp +7 -13
- esphome/components/mdns/mdns_esp8266.cpp +7 -7
- esphome/components/mdns/mdns_libretiny.cpp +3 -4
- esphome/components/mdns/mdns_rp2040.cpp +3 -4
- esphome/components/mipi/__init__.py +1 -5
- esphome/components/mipi_spi/display.py +24 -8
- esphome/components/mipi_spi/mipi_spi.h +3 -3
- esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
- esphome/components/mmc5603/mmc5603.cpp +3 -3
- esphome/components/modbus/modbus.cpp +27 -13
- esphome/components/modbus/modbus.h +5 -3
- esphome/components/modbus/modbus_definitions.h +86 -0
- esphome/components/modbus_controller/__init__.py +29 -1
- esphome/components/modbus_controller/const.py +4 -0
- esphome/components/modbus_controller/modbus_controller.cpp +38 -13
- esphome/components/modbus_controller/modbus_controller.h +18 -29
- esphome/components/mpr121/mpr121.cpp +41 -42
- esphome/components/mpr121/mpr121.h +0 -1
- esphome/components/nau7802/nau7802.cpp +2 -2
- esphome/components/network/__init__.py +7 -3
- esphome/components/nextion/display.py +4 -4
- esphome/components/nextion/nextion.cpp +8 -8
- esphome/components/number/__init__.py +2 -0
- esphome/components/number/number_call.cpp +23 -12
- esphome/components/number/number_call.h +5 -0
- esphome/components/online_image/bmp_image.cpp +2 -1
- esphome/components/online_image/jpeg_image.cpp +4 -2
- esphome/components/opentherm/opentherm.cpp +5 -5
- esphome/components/opentherm/opentherm.h +3 -3
- esphome/components/openthread/openthread.cpp +11 -10
- esphome/components/openthread/openthread.h +0 -1
- esphome/components/ota/ota_backend.h +1 -0
- esphome/components/packages/__init__.py +10 -8
- esphome/components/packet_transport/packet_transport.cpp +2 -0
- esphome/components/pid/pid_controller.cpp +1 -1
- esphome/components/prometheus/prometheus_handler.cpp +239 -239
- esphome/components/psram/__init__.py +30 -28
- esphome/components/qmc5883l/qmc5883l.cpp +15 -0
- esphome/components/qmc5883l/qmc5883l.h +3 -0
- esphome/components/qmc5883l/sensor.py +31 -12
- esphome/components/remote_base/gobox_protocol.cpp +3 -3
- esphome/components/remote_receiver/__init__.py +14 -2
- esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
- esphome/components/remote_receiver/remote_receiver.h +4 -0
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
- esphome/components/remote_transmitter/__init__.py +2 -2
- esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
- esphome/components/rp2040/__init__.py +11 -11
- esphome/components/rtttl/rtttl.cpp +2 -2
- esphome/components/scd30/sensor.py +1 -1
- esphome/components/script/__init__.py +1 -1
- esphome/components/script/script.h +7 -7
- esphome/components/select/select.cpp +5 -4
- esphome/components/select/select_call.cpp +1 -1
- esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
- esphome/components/sensor/__init__.py +2 -0
- esphome/components/sha256/__init__.py +22 -0
- esphome/components/sha256/sha256.cpp +116 -0
- esphome/components/sha256/sha256.h +60 -0
- esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
- esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
- esphome/components/spi/__init__.py +0 -3
- esphome/components/split_buffer/__init__.py +5 -0
- esphome/components/split_buffer/split_buffer.cpp +133 -0
- esphome/components/split_buffer/split_buffer.h +40 -0
- esphome/components/sps30/sps30.cpp +14 -10
- esphome/components/sps30/sps30.h +2 -0
- esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
- esphome/components/st7789v/st7789v.cpp +3 -2
- esphome/components/statsd/statsd.cpp +1 -1
- esphome/components/substitutions/__init__.py +3 -1
- esphome/components/substitutions/jinja.py +13 -3
- esphome/components/sx126x/__init__.py +16 -0
- esphome/components/sx126x/sx126x.cpp +15 -1
- esphome/components/sx126x/sx126x.h +9 -1
- esphome/components/sx126x/sx126x_reg.h +2 -0
- esphome/components/text_sensor/text_sensor.cpp +16 -0
- esphome/components/text_sensor/text_sensor.h +3 -10
- esphome/components/tormatic/tormatic_cover.cpp +1 -1
- esphome/components/tuya/select/tuya_select.cpp +1 -1
- esphome/components/tuya/tuya.cpp +29 -4
- esphome/components/uart/__init__.py +37 -27
- esphome/components/uart/uart.h +6 -0
- esphome/components/uart/uart_component.cpp +8 -0
- esphome/components/uart/uart_component.h +28 -0
- esphome/components/uart/uart_component_esp_idf.cpp +64 -10
- esphome/components/uart/uart_component_esp_idf.h +5 -2
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
- esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
- esphome/components/usb_host/__init__.py +12 -2
- esphome/components/usb_host/usb_host.h +89 -14
- esphome/components/usb_host/usb_host_client.cpp +157 -22
- esphome/components/usb_host/usb_host_component.cpp +1 -1
- esphome/components/usb_uart/__init__.py +0 -1
- esphome/components/usb_uart/ch34x.cpp +4 -4
- esphome/components/usb_uart/cp210x.cpp +3 -3
- esphome/components/usb_uart/usb_uart.cpp +88 -32
- esphome/components/usb_uart/usb_uart.h +30 -6
- esphome/components/valve/valve.cpp +1 -0
- esphome/components/veml7700/veml7700.cpp +7 -6
- esphome/components/version/version_text_sensor.cpp +2 -1
- esphome/components/voice_assistant/voice_assistant.cpp +3 -2
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
- esphome/components/web_server/list_entities.cpp +3 -4
- esphome/components/web_server/list_entities.h +8 -10
- esphome/components/web_server/ota/__init__.py +1 -1
- esphome/components/web_server/ota/ota_web_server.cpp +9 -3
- esphome/components/web_server/web_server.cpp +509 -404
- esphome/components/web_server/web_server.h +5 -6
- esphome/components/web_server/web_server_v1.cpp +21 -19
- esphome/components/web_server_base/__init__.py +5 -2
- esphome/components/web_server_base/web_server_base.h +27 -7
- esphome/components/web_server_idf/__init__.py +1 -1
- esphome/components/web_server_idf/multipart.cpp +2 -2
- esphome/components/web_server_idf/multipart.h +2 -2
- esphome/components/web_server_idf/utils.cpp +2 -2
- esphome/components/web_server_idf/utils.h +2 -2
- esphome/components/web_server_idf/web_server_idf.cpp +118 -26
- esphome/components/web_server_idf/web_server_idf.h +12 -10
- esphome/components/wifi/__init__.py +13 -11
- esphome/components/wifi/wifi_component.cpp +74 -56
- esphome/components/wifi/wifi_component.h +4 -4
- esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
- esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
- esphome/components/wireguard/__init__.py +1 -1
- esphome/components/wts01/__init__.py +0 -0
- esphome/components/wts01/sensor.py +41 -0
- esphome/components/wts01/wts01.cpp +91 -0
- esphome/components/wts01/wts01.h +27 -0
- esphome/components/zephyr/__init__.py +5 -5
- esphome/components/zwave_proxy/__init__.py +43 -0
- esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
- esphome/components/zwave_proxy/zwave_proxy.h +93 -0
- esphome/config.py +79 -24
- esphome/config_validation.py +13 -15
- esphome/const.py +9 -2
- esphome/core/__init__.py +33 -22
- esphome/core/component.cpp +28 -18
- esphome/core/component_iterator.h +2 -1
- esphome/core/config.py +15 -15
- esphome/core/defines.h +21 -0
- esphome/core/entity_helpers.py +9 -6
- esphome/core/hash_base.h +56 -0
- esphome/core/helpers.cpp +19 -3
- esphome/core/helpers.h +26 -0
- esphome/core/scheduler.cpp +5 -21
- esphome/core/scheduler.h +19 -8
- esphome/core/string_ref.h +1 -1
- esphome/core/time.cpp +5 -5
- esphome/cpp_generator.py +4 -29
- esphome/dashboard/const.py +21 -4
- esphome/dashboard/core.py +10 -8
- esphome/dashboard/dns.py +15 -0
- esphome/dashboard/entries.py +15 -21
- esphome/dashboard/models.py +76 -0
- esphome/dashboard/settings.py +7 -7
- esphome/dashboard/status/mdns.py +46 -2
- esphome/dashboard/web_server.py +367 -93
- esphome/espota2.py +112 -32
- esphome/external_files.py +6 -7
- esphome/git.py +8 -0
- esphome/helpers.py +124 -77
- esphome/loader.py +8 -9
- esphome/pins.py +2 -2
- esphome/platformio_api.py +56 -18
- esphome/storage_json.py +26 -21
- esphome/types.py +30 -2
- esphome/util.py +32 -16
- esphome/vscode.py +8 -8
- esphome/wizard.py +10 -10
- esphome/writer.py +50 -15
- esphome/yaml_util.py +37 -31
- esphome/zeroconf.py +12 -3
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +12 -12
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +340 -320
- esphome/components/event_emitter/__init__.py +0 -5
- esphome/components/event_emitter/event_emitter.cpp +0 -14
- esphome/components/event_emitter/event_emitter.h +0 -63
- esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
- esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
- esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
- esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
- esphome/components/uart/uart_component_esp32_arduino.h +0 -60
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
- esphome/core/string_ref.cpp +0 -12
- esphome/dashboard/util/file.py +0 -63
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,13 @@
|
|
1
1
|
#include "ota_esphome.h"
|
2
2
|
#ifdef USE_OTA
|
3
|
+
#ifdef USE_OTA_PASSWORD
|
4
|
+
#ifdef USE_OTA_MD5
|
3
5
|
#include "esphome/components/md5/md5.h"
|
6
|
+
#endif
|
7
|
+
#ifdef USE_OTA_SHA256
|
8
|
+
#include "esphome/components/sha256/sha256.h"
|
9
|
+
#endif
|
10
|
+
#endif
|
4
11
|
#include "esphome/components/network/util.h"
|
5
12
|
#include "esphome/components/ota/ota_backend.h"
|
6
13
|
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
@@ -10,6 +17,7 @@
|
|
10
17
|
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
11
18
|
#include "esphome/core/application.h"
|
12
19
|
#include "esphome/core/hal.h"
|
20
|
+
#include "esphome/core/helpers.h"
|
13
21
|
#include "esphome/core/log.h"
|
14
22
|
#include "esphome/core/util.h"
|
15
23
|
|
@@ -20,9 +28,19 @@ namespace esphome {
|
|
20
28
|
|
21
29
|
static const char *const TAG = "esphome.ota";
|
22
30
|
static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
|
23
|
-
static constexpr
|
31
|
+
static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size for OTA data transfer
|
32
|
+
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
|
24
33
|
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
25
34
|
|
35
|
+
#ifdef USE_OTA_PASSWORD
|
36
|
+
#ifdef USE_OTA_MD5
|
37
|
+
static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
|
38
|
+
#endif
|
39
|
+
#ifdef USE_OTA_SHA256
|
40
|
+
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
|
41
|
+
#endif
|
42
|
+
#endif // USE_OTA_PASSWORD
|
43
|
+
|
26
44
|
void ESPHomeOTAComponent::setup() {
|
27
45
|
#ifdef USE_OTA_STATE_CALLBACK
|
28
46
|
ota::register_ota_platform(this);
|
@@ -63,7 +81,7 @@ void ESPHomeOTAComponent::setup() {
|
|
63
81
|
return;
|
64
82
|
}
|
65
83
|
|
66
|
-
err = this->server_->listen(
|
84
|
+
err = this->server_->listen(1); // Only one client at a time
|
67
85
|
if (err != 0) {
|
68
86
|
this->log_socket_error_(LOG_STR("listen"));
|
69
87
|
this->mark_failed();
|
@@ -95,13 +113,22 @@ void ESPHomeOTAComponent::loop() {
|
|
95
113
|
}
|
96
114
|
|
97
115
|
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
116
|
+
#ifdef USE_OTA_SHA256
|
117
|
+
static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
|
118
|
+
#endif
|
119
|
+
|
120
|
+
// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0)
|
121
|
+
// This allows users to downgrade via OTA if they encounter issues after updating.
|
122
|
+
// Without this, users would need to do a serial flash to downgrade.
|
123
|
+
// TODO: Remove this flag and all associated code in 2026.1.0
|
124
|
+
#define ALLOW_OTA_DOWNGRADE_MD5
|
98
125
|
|
99
126
|
void ESPHomeOTAComponent::handle_handshake_() {
|
100
|
-
/// Handle the
|
127
|
+
/// Handle the OTA handshake and authentication.
|
101
128
|
///
|
102
129
|
/// This method is non-blocking and will return immediately if no data is available.
|
103
|
-
/// It
|
104
|
-
///
|
130
|
+
/// It manages the state machine through connection, magic bytes validation, feature
|
131
|
+
/// negotiation, and authentication before entering the blocking data transfer phase.
|
105
132
|
|
106
133
|
if (this->client_ == nullptr) {
|
107
134
|
// We already checked server_->ready() in loop(), so we can accept directly
|
@@ -126,7 +153,8 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
|
126
153
|
}
|
127
154
|
this->log_start_(LOG_STR("handshake"));
|
128
155
|
this->client_connect_time_ = App.get_loop_component_start_time();
|
129
|
-
this->
|
156
|
+
this->handshake_buf_pos_ = 0; // Reset handshake buffer position
|
157
|
+
this->ota_state_ = OTAState::MAGIC_READ;
|
130
158
|
}
|
131
159
|
|
132
160
|
// Check for handshake timeout
|
@@ -137,46 +165,99 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
|
137
165
|
return;
|
138
166
|
}
|
139
167
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
168
|
+
switch (this->ota_state_) {
|
169
|
+
case OTAState::MAGIC_READ: {
|
170
|
+
// Try to read remaining magic bytes (5 total)
|
171
|
+
if (!this->try_read_(5, LOG_STR("read magic"))) {
|
172
|
+
return;
|
173
|
+
}
|
145
174
|
|
146
|
-
|
147
|
-
|
175
|
+
// Validate magic bytes
|
176
|
+
static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
|
177
|
+
if (memcmp(this->handshake_buf_, MAGIC_BYTES, 5) != 0) {
|
178
|
+
ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
|
179
|
+
this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
|
180
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_MAGIC);
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
|
184
|
+
// Magic bytes valid, move to next state
|
185
|
+
this->transition_ota_state_(OTAState::MAGIC_ACK);
|
186
|
+
this->handshake_buf_[0] = ota::OTA_RESPONSE_OK;
|
187
|
+
this->handshake_buf_[1] = USE_OTA_VERSION;
|
188
|
+
[[fallthrough]];
|
148
189
|
}
|
149
190
|
|
150
|
-
|
151
|
-
//
|
152
|
-
if (
|
153
|
-
|
154
|
-
} else {
|
155
|
-
ESP_LOGW(TAG, "Remote closed during handshake");
|
191
|
+
case OTAState::MAGIC_ACK: {
|
192
|
+
// Send OK and version - 2 bytes
|
193
|
+
if (!this->try_write_(2, LOG_STR("ack magic"))) {
|
194
|
+
return;
|
156
195
|
}
|
157
|
-
|
158
|
-
|
196
|
+
// All bytes sent, create backend and move to next state
|
197
|
+
this->backend_ = ota::make_ota_backend();
|
198
|
+
this->transition_ota_state_(OTAState::FEATURE_READ);
|
199
|
+
[[fallthrough]];
|
159
200
|
}
|
160
201
|
|
161
|
-
|
162
|
-
|
202
|
+
case OTAState::FEATURE_READ: {
|
203
|
+
// Read features - 1 byte
|
204
|
+
if (!this->try_read_(1, LOG_STR("read feature"))) {
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
this->ota_features_ = this->handshake_buf_[0];
|
208
|
+
ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
|
209
|
+
this->transition_ota_state_(OTAState::FEATURE_ACK);
|
210
|
+
this->handshake_buf_[0] =
|
211
|
+
((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression())
|
212
|
+
? ota::OTA_RESPONSE_SUPPORTS_COMPRESSION
|
213
|
+
: ota::OTA_RESPONSE_HEADER_OK;
|
214
|
+
[[fallthrough]];
|
215
|
+
}
|
163
216
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
217
|
+
case OTAState::FEATURE_ACK: {
|
218
|
+
// Acknowledge header - 1 byte
|
219
|
+
if (!this->try_write_(1, LOG_STR("ack feature"))) {
|
220
|
+
return;
|
221
|
+
}
|
222
|
+
#ifdef USE_OTA_PASSWORD
|
223
|
+
// If password is set, move to auth phase
|
224
|
+
if (!this->password_.empty()) {
|
225
|
+
this->transition_ota_state_(OTAState::AUTH_SEND);
|
226
|
+
} else
|
227
|
+
#endif
|
228
|
+
{
|
229
|
+
// No password, move directly to data phase
|
230
|
+
this->transition_ota_state_(OTAState::DATA);
|
231
|
+
}
|
232
|
+
[[fallthrough]];
|
233
|
+
}
|
234
|
+
|
235
|
+
#ifdef USE_OTA_PASSWORD
|
236
|
+
case OTAState::AUTH_SEND: {
|
237
|
+
// Non-blocking authentication send
|
238
|
+
if (!this->handle_auth_send_()) {
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
this->transition_ota_state_(OTAState::AUTH_READ);
|
242
|
+
[[fallthrough]];
|
243
|
+
}
|
244
|
+
|
245
|
+
case OTAState::AUTH_READ: {
|
246
|
+
// Non-blocking authentication read & verify
|
247
|
+
if (!this->handle_auth_read_()) {
|
248
|
+
return;
|
249
|
+
}
|
250
|
+
this->transition_ota_state_(OTAState::DATA);
|
251
|
+
[[fallthrough]];
|
176
252
|
}
|
253
|
+
#endif
|
254
|
+
|
255
|
+
case OTAState::DATA:
|
256
|
+
this->handle_data_();
|
257
|
+
return;
|
177
258
|
|
178
|
-
|
179
|
-
|
259
|
+
default:
|
260
|
+
break;
|
180
261
|
}
|
181
262
|
}
|
182
263
|
|
@@ -184,104 +265,21 @@ void ESPHomeOTAComponent::handle_data_() {
|
|
184
265
|
/// Handle the OTA data transfer and update process.
|
185
266
|
///
|
186
267
|
/// This method is blocking and will not return until the OTA update completes,
|
187
|
-
/// fails, or times out. It
|
188
|
-
///
|
268
|
+
/// fails, or times out. It receives the firmware data, writes it to flash,
|
269
|
+
/// and reboots on success.
|
270
|
+
///
|
271
|
+
/// Authentication has already been handled in the non-blocking states AUTH_SEND/AUTH_READ.
|
189
272
|
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
190
273
|
bool update_started = false;
|
191
274
|
size_t total = 0;
|
192
275
|
uint32_t last_progress = 0;
|
193
|
-
uint8_t buf[
|
276
|
+
uint8_t buf[OTA_BUFFER_SIZE];
|
194
277
|
char *sbuf = reinterpret_cast<char *>(buf);
|
195
278
|
size_t ota_size;
|
196
|
-
uint8_t ota_features;
|
197
|
-
std::unique_ptr<ota::OTABackend> backend;
|
198
|
-
(void) ota_features;
|
199
279
|
#if USE_OTA_VERSION == 2
|
200
280
|
size_t size_acknowledged = 0;
|
201
281
|
#endif
|
202
282
|
|
203
|
-
// Send OK and version - 2 bytes
|
204
|
-
buf[0] = ota::OTA_RESPONSE_OK;
|
205
|
-
buf[1] = USE_OTA_VERSION;
|
206
|
-
this->writeall_(buf, 2);
|
207
|
-
|
208
|
-
backend = ota::make_ota_backend();
|
209
|
-
|
210
|
-
// Read features - 1 byte
|
211
|
-
if (!this->readall_(buf, 1)) {
|
212
|
-
this->log_read_error_(LOG_STR("features"));
|
213
|
-
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
214
|
-
}
|
215
|
-
ota_features = buf[0]; // NOLINT
|
216
|
-
ESP_LOGV(TAG, "Features: 0x%02X", ota_features);
|
217
|
-
|
218
|
-
// Acknowledge header - 1 byte
|
219
|
-
buf[0] = ota::OTA_RESPONSE_HEADER_OK;
|
220
|
-
if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) {
|
221
|
-
buf[0] = ota::OTA_RESPONSE_SUPPORTS_COMPRESSION;
|
222
|
-
}
|
223
|
-
|
224
|
-
this->writeall_(buf, 1);
|
225
|
-
|
226
|
-
#ifdef USE_OTA_PASSWORD
|
227
|
-
if (!this->password_.empty()) {
|
228
|
-
buf[0] = ota::OTA_RESPONSE_REQUEST_AUTH;
|
229
|
-
this->writeall_(buf, 1);
|
230
|
-
md5::MD5Digest md5{};
|
231
|
-
md5.init();
|
232
|
-
sprintf(sbuf, "%08" PRIx32, random_uint32());
|
233
|
-
md5.add(sbuf, 8);
|
234
|
-
md5.calculate();
|
235
|
-
md5.get_hex(sbuf);
|
236
|
-
ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf);
|
237
|
-
|
238
|
-
// Send nonce, 32 bytes hex MD5
|
239
|
-
if (!this->writeall_(reinterpret_cast<uint8_t *>(sbuf), 32)) {
|
240
|
-
ESP_LOGW(TAG, "Auth: Writing nonce failed");
|
241
|
-
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
242
|
-
}
|
243
|
-
|
244
|
-
// prepare challenge
|
245
|
-
md5.init();
|
246
|
-
md5.add(this->password_.c_str(), this->password_.length());
|
247
|
-
// add nonce
|
248
|
-
md5.add(sbuf, 32);
|
249
|
-
|
250
|
-
// Receive cnonce, 32 bytes hex MD5
|
251
|
-
if (!this->readall_(buf, 32)) {
|
252
|
-
ESP_LOGW(TAG, "Auth: Reading cnonce failed");
|
253
|
-
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
254
|
-
}
|
255
|
-
sbuf[32] = '\0';
|
256
|
-
ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf);
|
257
|
-
// add cnonce
|
258
|
-
md5.add(sbuf, 32);
|
259
|
-
|
260
|
-
// calculate result
|
261
|
-
md5.calculate();
|
262
|
-
md5.get_hex(sbuf);
|
263
|
-
ESP_LOGV(TAG, "Auth: Result is %s", sbuf);
|
264
|
-
|
265
|
-
// Receive result, 32 bytes hex MD5
|
266
|
-
if (!this->readall_(buf + 64, 32)) {
|
267
|
-
ESP_LOGW(TAG, "Auth: Reading response failed");
|
268
|
-
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
269
|
-
}
|
270
|
-
sbuf[64 + 32] = '\0';
|
271
|
-
ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64);
|
272
|
-
|
273
|
-
bool matches = true;
|
274
|
-
for (uint8_t i = 0; i < 32; i++)
|
275
|
-
matches = matches && buf[i] == buf[64 + i];
|
276
|
-
|
277
|
-
if (!matches) {
|
278
|
-
ESP_LOGW(TAG, "Auth failed! Passwords do not match");
|
279
|
-
error_code = ota::OTA_RESPONSE_ERROR_AUTH_INVALID;
|
280
|
-
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
281
|
-
}
|
282
|
-
}
|
283
|
-
#endif // USE_OTA_PASSWORD
|
284
|
-
|
285
283
|
// Acknowledge auth OK - 1 byte
|
286
284
|
buf[0] = ota::OTA_RESPONSE_AUTH_OK;
|
287
285
|
this->writeall_(buf, 1);
|
@@ -309,7 +307,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|
309
307
|
#endif
|
310
308
|
|
311
309
|
// This will block for a few seconds as it locks flash
|
312
|
-
error_code =
|
310
|
+
error_code = this->backend_->begin(ota_size);
|
313
311
|
if (error_code != ota::OTA_RESPONSE_OK)
|
314
312
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
315
313
|
update_started = true;
|
@@ -325,7 +323,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|
325
323
|
}
|
326
324
|
sbuf[32] = '\0';
|
327
325
|
ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
|
328
|
-
|
326
|
+
this->backend_->set_update_md5(sbuf);
|
329
327
|
|
330
328
|
// Acknowledge MD5 OK - 1 byte
|
331
329
|
buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK;
|
@@ -333,26 +331,24 @@ void ESPHomeOTAComponent::handle_data_() {
|
|
333
331
|
|
334
332
|
while (total < ota_size) {
|
335
333
|
// TODO: timeout check
|
336
|
-
size_t
|
334
|
+
size_t remaining = ota_size - total;
|
335
|
+
size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE;
|
337
336
|
ssize_t read = this->client_->read(buf, requested);
|
338
337
|
if (read == -1) {
|
339
|
-
if (errno
|
338
|
+
if (this->would_block_(errno)) {
|
340
339
|
this->yield_and_feed_watchdog_();
|
341
340
|
continue;
|
342
341
|
}
|
343
|
-
ESP_LOGW(TAG, "Read
|
342
|
+
ESP_LOGW(TAG, "Read err %d", errno);
|
344
343
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
345
344
|
} else if (read == 0) {
|
346
|
-
|
347
|
-
// "When a stream socket peer has performed an orderly shutdown, the return value will
|
348
|
-
// be 0 (the traditional "end-of-file" return)."
|
349
|
-
ESP_LOGW(TAG, "Remote closed connection");
|
345
|
+
ESP_LOGW(TAG, "Remote closed");
|
350
346
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
351
347
|
}
|
352
348
|
|
353
|
-
error_code =
|
349
|
+
error_code = this->backend_->write(buf, read);
|
354
350
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
355
|
-
ESP_LOGW(TAG, "Flash write
|
351
|
+
ESP_LOGW(TAG, "Flash write err %d", error_code);
|
356
352
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
357
353
|
}
|
358
354
|
total += read;
|
@@ -381,9 +377,9 @@ void ESPHomeOTAComponent::handle_data_() {
|
|
381
377
|
buf[0] = ota::OTA_RESPONSE_RECEIVE_OK;
|
382
378
|
this->writeall_(buf, 1);
|
383
379
|
|
384
|
-
error_code =
|
380
|
+
error_code = this->backend_->end();
|
385
381
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
386
|
-
ESP_LOGW(TAG, "
|
382
|
+
ESP_LOGW(TAG, "End update err %d", error_code);
|
387
383
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
388
384
|
}
|
389
385
|
|
@@ -412,8 +408,8 @@ error:
|
|
412
408
|
this->writeall_(buf, 1);
|
413
409
|
this->cleanup_connection_();
|
414
410
|
|
415
|
-
if (
|
416
|
-
|
411
|
+
if (this->backend_ != nullptr && update_started) {
|
412
|
+
this->backend_->abort();
|
417
413
|
}
|
418
414
|
|
419
415
|
this->status_momentary_error("onerror", 5000);
|
@@ -434,12 +430,12 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
|
|
434
430
|
|
435
431
|
ssize_t read = this->client_->read(buf + at, len - at);
|
436
432
|
if (read == -1) {
|
437
|
-
if (errno
|
438
|
-
ESP_LOGW(TAG, "
|
433
|
+
if (!this->would_block_(errno)) {
|
434
|
+
ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno);
|
439
435
|
return false;
|
440
436
|
}
|
441
437
|
} else if (read == 0) {
|
442
|
-
ESP_LOGW(TAG, "Remote closed
|
438
|
+
ESP_LOGW(TAG, "Remote closed");
|
443
439
|
return false;
|
444
440
|
} else {
|
445
441
|
at += read;
|
@@ -461,8 +457,8 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
|
461
457
|
|
462
458
|
ssize_t written = this->client_->write(buf + at, len - at);
|
463
459
|
if (written == -1) {
|
464
|
-
if (errno
|
465
|
-
ESP_LOGW(TAG, "
|
460
|
+
if (!this->would_block_(errno)) {
|
461
|
+
ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno);
|
466
462
|
return false;
|
467
463
|
}
|
468
464
|
} else {
|
@@ -487,11 +483,74 @@ void ESPHomeOTAComponent::log_start_(const LogString *phase) {
|
|
487
483
|
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
|
488
484
|
}
|
489
485
|
|
486
|
+
void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
|
487
|
+
ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
|
488
|
+
}
|
489
|
+
|
490
|
+
bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
|
491
|
+
if (read == -1 && this->would_block_(errno)) {
|
492
|
+
return false; // No data yet, try again next loop
|
493
|
+
}
|
494
|
+
|
495
|
+
if (read <= 0) {
|
496
|
+
read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
|
497
|
+
this->cleanup_connection_();
|
498
|
+
return false;
|
499
|
+
}
|
500
|
+
return true;
|
501
|
+
}
|
502
|
+
|
503
|
+
bool ESPHomeOTAComponent::handle_write_error_(ssize_t written, const LogString *desc) {
|
504
|
+
if (written == -1) {
|
505
|
+
if (this->would_block_(errno)) {
|
506
|
+
return false; // Try again next loop
|
507
|
+
}
|
508
|
+
this->log_socket_error_(desc);
|
509
|
+
this->cleanup_connection_();
|
510
|
+
return false;
|
511
|
+
}
|
512
|
+
return true;
|
513
|
+
}
|
514
|
+
|
515
|
+
bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
|
516
|
+
// Read bytes into handshake buffer, starting at handshake_buf_pos_
|
517
|
+
size_t bytes_to_read = to_read - this->handshake_buf_pos_;
|
518
|
+
ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
|
519
|
+
|
520
|
+
if (!this->handle_read_error_(read, desc)) {
|
521
|
+
return false;
|
522
|
+
}
|
523
|
+
|
524
|
+
this->handshake_buf_pos_ += read;
|
525
|
+
// Return true only if we have all the requested bytes
|
526
|
+
return this->handshake_buf_pos_ >= to_read;
|
527
|
+
}
|
528
|
+
|
529
|
+
bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
|
530
|
+
// Write bytes from handshake buffer, starting at handshake_buf_pos_
|
531
|
+
size_t bytes_to_write = to_write - this->handshake_buf_pos_;
|
532
|
+
ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
|
533
|
+
|
534
|
+
if (!this->handle_write_error_(written, desc)) {
|
535
|
+
return false;
|
536
|
+
}
|
537
|
+
|
538
|
+
this->handshake_buf_pos_ += written;
|
539
|
+
// Return true only if we have written all the requested bytes
|
540
|
+
return this->handshake_buf_pos_ >= to_write;
|
541
|
+
}
|
542
|
+
|
490
543
|
void ESPHomeOTAComponent::cleanup_connection_() {
|
491
544
|
this->client_->close();
|
492
545
|
this->client_ = nullptr;
|
493
546
|
this->client_connect_time_ = 0;
|
494
|
-
this->
|
547
|
+
this->handshake_buf_pos_ = 0;
|
548
|
+
this->ota_state_ = OTAState::IDLE;
|
549
|
+
this->ota_features_ = 0;
|
550
|
+
this->backend_ = nullptr;
|
551
|
+
#ifdef USE_OTA_PASSWORD
|
552
|
+
this->cleanup_auth_();
|
553
|
+
#endif
|
495
554
|
}
|
496
555
|
|
497
556
|
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
@@ -499,5 +558,256 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
|
499
558
|
delay(1);
|
500
559
|
}
|
501
560
|
|
561
|
+
#ifdef USE_OTA_PASSWORD
|
562
|
+
void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
|
563
|
+
|
564
|
+
bool ESPHomeOTAComponent::select_auth_type_() {
|
565
|
+
#ifdef USE_OTA_SHA256
|
566
|
+
bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
|
567
|
+
|
568
|
+
#ifdef ALLOW_OTA_DOWNGRADE_MD5
|
569
|
+
// Allow fallback to MD5 if client doesn't support SHA256
|
570
|
+
if (client_supports_sha256) {
|
571
|
+
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
|
572
|
+
return true;
|
573
|
+
}
|
574
|
+
#ifdef USE_OTA_MD5
|
575
|
+
this->log_auth_warning_(LOG_STR("Using deprecated MD5"));
|
576
|
+
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
|
577
|
+
return true;
|
578
|
+
#else
|
579
|
+
this->log_auth_warning_(LOG_STR("SHA256 required"));
|
580
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
581
|
+
return false;
|
582
|
+
#endif // USE_OTA_MD5
|
583
|
+
|
584
|
+
#else // !ALLOW_OTA_DOWNGRADE_MD5
|
585
|
+
// Require SHA256
|
586
|
+
if (!client_supports_sha256) {
|
587
|
+
this->log_auth_warning_(LOG_STR("SHA256 required"));
|
588
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
589
|
+
return false;
|
590
|
+
}
|
591
|
+
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
|
592
|
+
return true;
|
593
|
+
#endif // ALLOW_OTA_DOWNGRADE_MD5
|
594
|
+
|
595
|
+
#else // !USE_OTA_SHA256
|
596
|
+
#ifdef USE_OTA_MD5
|
597
|
+
// Only MD5 available
|
598
|
+
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
|
599
|
+
return true;
|
600
|
+
#else
|
601
|
+
// No auth methods available
|
602
|
+
this->log_auth_warning_(LOG_STR("No auth methods available"));
|
603
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
604
|
+
return false;
|
605
|
+
#endif // USE_OTA_MD5
|
606
|
+
#endif // USE_OTA_SHA256
|
607
|
+
}
|
608
|
+
|
609
|
+
bool ESPHomeOTAComponent::handle_auth_send_() {
|
610
|
+
// Initialize auth buffer if not already done
|
611
|
+
if (!this->auth_buf_) {
|
612
|
+
// Select auth type based on client capabilities and configuration
|
613
|
+
if (!this->select_auth_type_()) {
|
614
|
+
return false;
|
615
|
+
}
|
616
|
+
|
617
|
+
// Generate nonce - hasher must be created and used in same stack frame
|
618
|
+
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
|
619
|
+
// 1. Hash objects must NEVER be passed to another function (different stack frame)
|
620
|
+
// 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
|
621
|
+
// 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
|
622
|
+
// Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
|
623
|
+
//
|
624
|
+
// Buffer layout after AUTH_READ completes:
|
625
|
+
// [0]: auth_type (1 byte)
|
626
|
+
// [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
|
627
|
+
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
|
628
|
+
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
|
629
|
+
|
630
|
+
// Declare both hash objects in same stack frame, use pointer to select.
|
631
|
+
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
|
632
|
+
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
|
633
|
+
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
|
634
|
+
#ifdef USE_OTA_SHA256
|
635
|
+
sha256::SHA256 sha_hasher;
|
636
|
+
#endif
|
637
|
+
#ifdef USE_OTA_MD5
|
638
|
+
md5::MD5Digest md5_hasher;
|
639
|
+
#endif
|
640
|
+
HashBase *hasher = nullptr;
|
641
|
+
|
642
|
+
#ifdef USE_OTA_SHA256
|
643
|
+
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
644
|
+
hasher = &sha_hasher;
|
645
|
+
}
|
646
|
+
#endif
|
647
|
+
#ifdef USE_OTA_MD5
|
648
|
+
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
|
649
|
+
hasher = &md5_hasher;
|
650
|
+
}
|
651
|
+
#endif
|
652
|
+
|
653
|
+
const size_t hex_size = hasher->get_size() * 2;
|
654
|
+
const size_t nonce_len = hasher->get_size() / 4;
|
655
|
+
const size_t auth_buf_size = 1 + 3 * hex_size;
|
656
|
+
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
|
657
|
+
this->auth_buf_pos_ = 0;
|
658
|
+
|
659
|
+
char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
|
660
|
+
if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
|
661
|
+
this->log_auth_warning_(LOG_STR("Random failed"));
|
662
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN);
|
663
|
+
return false;
|
664
|
+
}
|
665
|
+
|
666
|
+
hasher->init();
|
667
|
+
hasher->add(buf, nonce_len);
|
668
|
+
hasher->calculate();
|
669
|
+
this->auth_buf_[0] = this->auth_type_;
|
670
|
+
hasher->get_hex(buf);
|
671
|
+
|
672
|
+
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
673
|
+
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
|
674
|
+
memcpy(log_buf, buf, hex_size);
|
675
|
+
log_buf[hex_size] = '\0';
|
676
|
+
ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf);
|
677
|
+
#endif
|
678
|
+
}
|
679
|
+
|
680
|
+
// Try to write auth_type + nonce
|
681
|
+
size_t hex_size = this->get_auth_hex_size_();
|
682
|
+
const size_t to_write = 1 + hex_size;
|
683
|
+
size_t remaining = to_write - this->auth_buf_pos_;
|
684
|
+
|
685
|
+
ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
|
686
|
+
if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
|
687
|
+
return false;
|
688
|
+
}
|
689
|
+
|
690
|
+
this->auth_buf_pos_ += written;
|
691
|
+
|
692
|
+
// Check if we still have more to write
|
693
|
+
if (this->auth_buf_pos_ < to_write) {
|
694
|
+
return false; // More to write, try again next loop
|
695
|
+
}
|
696
|
+
|
697
|
+
// All written, prepare for reading phase
|
698
|
+
this->auth_buf_pos_ = 0;
|
699
|
+
return true;
|
700
|
+
}
|
701
|
+
|
702
|
+
bool ESPHomeOTAComponent::handle_auth_read_() {
|
703
|
+
size_t hex_size = this->get_auth_hex_size_();
|
704
|
+
const size_t to_read = hex_size * 2; // CNonce + Response
|
705
|
+
|
706
|
+
// Try to read remaining bytes (CNonce + Response)
|
707
|
+
// We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
|
708
|
+
size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
|
709
|
+
size_t remaining = to_read - this->auth_buf_pos_;
|
710
|
+
ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
|
711
|
+
|
712
|
+
if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
|
713
|
+
return false;
|
714
|
+
}
|
715
|
+
|
716
|
+
this->auth_buf_pos_ += read;
|
717
|
+
|
718
|
+
// Check if we still need more data
|
719
|
+
if (this->auth_buf_pos_ < to_read) {
|
720
|
+
return false; // More to read, try again next loop
|
721
|
+
}
|
722
|
+
|
723
|
+
// We have all the data, verify it
|
724
|
+
const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
|
725
|
+
const char *cnonce = nonce + hex_size;
|
726
|
+
const char *response = cnonce + hex_size;
|
727
|
+
|
728
|
+
// CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions).
|
729
|
+
// Declare both hash objects in same stack frame, use pointer to select.
|
730
|
+
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
|
731
|
+
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
|
732
|
+
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
|
733
|
+
#ifdef USE_OTA_SHA256
|
734
|
+
sha256::SHA256 sha_hasher;
|
735
|
+
#endif
|
736
|
+
#ifdef USE_OTA_MD5
|
737
|
+
md5::MD5Digest md5_hasher;
|
738
|
+
#endif
|
739
|
+
HashBase *hasher = nullptr;
|
740
|
+
|
741
|
+
#ifdef USE_OTA_SHA256
|
742
|
+
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
743
|
+
hasher = &sha_hasher;
|
744
|
+
}
|
745
|
+
#endif
|
746
|
+
#ifdef USE_OTA_MD5
|
747
|
+
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
|
748
|
+
hasher = &md5_hasher;
|
749
|
+
}
|
750
|
+
#endif
|
751
|
+
|
752
|
+
hasher->init();
|
753
|
+
hasher->add(this->password_.c_str(), this->password_.length());
|
754
|
+
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
|
755
|
+
hasher->calculate();
|
756
|
+
|
757
|
+
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
758
|
+
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
|
759
|
+
// Log CNonce
|
760
|
+
memcpy(log_buf, cnonce, hex_size);
|
761
|
+
log_buf[hex_size] = '\0';
|
762
|
+
ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf);
|
763
|
+
|
764
|
+
// Log computed hash
|
765
|
+
hasher->get_hex(log_buf);
|
766
|
+
log_buf[hex_size] = '\0';
|
767
|
+
ESP_LOGV(TAG, "Auth: Result is %s", log_buf);
|
768
|
+
|
769
|
+
// Log received response
|
770
|
+
memcpy(log_buf, response, hex_size);
|
771
|
+
log_buf[hex_size] = '\0';
|
772
|
+
ESP_LOGV(TAG, "Auth: Response is %s", log_buf);
|
773
|
+
#endif
|
774
|
+
|
775
|
+
// Compare response
|
776
|
+
bool matches = hasher->equals_hex(response);
|
777
|
+
|
778
|
+
if (!matches) {
|
779
|
+
this->log_auth_warning_(LOG_STR("Password mismatch"));
|
780
|
+
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
781
|
+
return false;
|
782
|
+
}
|
783
|
+
|
784
|
+
// Authentication successful - clean up auth state
|
785
|
+
this->cleanup_auth_();
|
786
|
+
|
787
|
+
return true;
|
788
|
+
}
|
789
|
+
|
790
|
+
size_t ESPHomeOTAComponent::get_auth_hex_size_() const {
|
791
|
+
#ifdef USE_OTA_SHA256
|
792
|
+
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
793
|
+
return SHA256_HEX_SIZE;
|
794
|
+
}
|
795
|
+
#endif
|
796
|
+
#ifdef USE_OTA_MD5
|
797
|
+
return MD5_HEX_SIZE;
|
798
|
+
#else
|
799
|
+
#ifndef USE_OTA_SHA256
|
800
|
+
#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled"
|
801
|
+
#endif
|
802
|
+
#endif
|
803
|
+
}
|
804
|
+
|
805
|
+
void ESPHomeOTAComponent::cleanup_auth_() {
|
806
|
+
this->auth_buf_ = nullptr;
|
807
|
+
this->auth_buf_pos_ = 0;
|
808
|
+
this->auth_type_ = 0;
|
809
|
+
}
|
810
|
+
#endif // USE_OTA_PASSWORD
|
811
|
+
|
502
812
|
} // namespace esphome
|
503
813
|
#endif
|