ESPHome 2026.9.1
Loading...
Searching...
No Matches
ota_esphome.h
Go to the documentation of this file.
1#pragma once
2
4#ifdef USE_OTA
7#ifdef USE_OTA_ENCRYPTION
9#endif
11#include "esphome/core/log.h"
14
15namespace esphome {
16
19 public:
20 enum class OTAState : uint8_t {
21 IDLE,
22 MAGIC_READ, // Reading magic bytes
23 MAGIC_ACK, // Sending OK and version after magic bytes
24 FEATURE_READ, // Reading feature flags from client
25 FEATURE_ACK, // Sending feature acknowledgment
26#ifdef USE_OTA_PASSWORD
27 AUTH_SEND, // Sending authentication request
28 AUTH_READ, // Reading authentication data
29#endif // USE_OTA_PASSWORD
30#ifdef USE_OTA_ENCRYPTION
31 NOISE_HANDSHAKE, // Exchanging Noise handshake frames
32#endif
33 DATA, // BLOCKING! Processing OTA data (update, etc.)
34 };
35#ifdef USE_OTA_PASSWORD
36 void set_auth_password(const std::string &password) { password_ = password; }
37#else
38 // Stub so lambdas referencing set_auth_password() produce a clear error instead of
39 // a cryptic "no member" diagnostic. Only fires if the stub is actually instantiated.
40 template<bool B = false> void set_auth_password(const std::string &) {
41 static_assert(B, "set_auth_password() requires the OTA auth path to be compiled. "
42 "Add 'password: \"\"' (empty string) to your 'ota: - platform: esphome' "
43 "config to enable runtime password rotation.");
44 }
45#endif // USE_OTA_PASSWORD
46
47#ifdef USE_OTA_ENCRYPTION
49 void set_noise_psk(const uint8_t *psk) { this->noise_ctx_.set_psk(psk); }
50#endif
51
53 void set_port(uint16_t port) { this->port_ = port; }
54
55 void setup() override;
56 void dump_config() override;
57 float get_setup_priority() const override;
58 void loop() override;
59
60 uint16_t get_port() const { return this->port_; }
61
62 protected:
63 void handle_handshake_();
64 void handle_data_();
65#ifdef USE_OTA_PASSWORD
66 static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
67 bool handle_auth_send_();
68 bool handle_auth_read_();
69 bool select_auth_type_();
70 void cleanup_auth_();
71 void log_auth_warning_(const LogString *msg);
72#endif // USE_OTA_PASSWORD
73 bool readall_(uint8_t *buf, size_t len);
74 bool writeall_(const uint8_t *buf, size_t len);
75 inline bool write_byte_(uint8_t byte) { return this->writeall_(&byte, 1); }
76
77#ifdef USE_OTA_ENCRYPTION
78 // Heap-allocated only while an encrypted OTA session is active.
79 struct NoiseSession {
82 NoiseCipherState *send_cipher{nullptr};
83 NoiseCipherState *recv_cipher{nullptr};
84 uint16_t frame_len{0}; // total frame size once the header is parsed, 0 until then
85 uint16_t frame_pos{0}; // bytes read or written so far
86 bool writing{false}; // a produced handshake frame is still being flushed
87 uint8_t frame_buf[noise::FRAME_HEADER_SIZE + 1 + noise::MAX_HANDSHAKE_SIZE];
88 };
89 // The api server's live context when it exists, otherwise our own (a build
90 // time key, or the saved key loaded in safe mode)
92 bool noise_start_session_(uint8_t server_feature_flags);
95 size_t noise_frame_payload_len_(const uint8_t *header, size_t min_len, size_t max_len);
97 void noise_send_reject_(const LogString *reason);
98 ssize_t noise_decrypt_(uint8_t *buf, size_t len);
99 ssize_t noise_read_frame_blocking_(uint8_t *buf, size_t min_ciphertext, size_t max_ciphertext);
100 bool noise_readall_(uint8_t *buf, size_t len);
101 ssize_t noise_read_data_(uint8_t *buf, size_t capacity);
102 bool noise_write_byte_(uint8_t byte);
103#endif // USE_OTA_ENCRYPTION
104
105 // Data-phase I/O dispatch: through the noise transport when a session is
106 // active, straight to the socket otherwise.
107 inline bool data_write_byte_(uint8_t byte) {
108#ifdef USE_OTA_ENCRYPTION
109 if (this->noise_ != nullptr)
110 return this->noise_write_byte_(byte);
111#endif
112 return this->write_byte_(byte);
113 }
114 // When encrypted, buf must have room for len + noise::MAC_SIZE bytes.
115 inline bool data_readall_(uint8_t *buf, size_t len) {
116#ifdef USE_OTA_ENCRYPTION
117 if (this->noise_ != nullptr)
118 return this->noise_readall_(buf, len);
119#endif
120 return this->readall_(buf, len);
121 }
122
123 bool try_read_(size_t to_read, const LogString *desc);
124 bool try_write_(size_t to_write, const LogString *desc);
125
126 inline bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; }
127 bool handle_read_error_(ssize_t read, const LogString *desc);
128 bool handle_write_error_(ssize_t written, const LogString *desc);
129 inline void transition_ota_state_(OTAState next_state) {
130 this->ota_state_ = next_state;
131 this->handshake_buf_pos_ = 0; // Reset buffer position for next state
132 }
133
134 void server_failed_(const LogString *msg);
135 void log_socket_error_(const LogString *msg);
136 void log_read_error_(const LogString *what);
137 void log_start_(const LogString *phase);
138 void log_remote_closed_(const LogString *during);
139 void cleanup_connection_();
141 uint8_t error_byte = static_cast<uint8_t>(error);
142 this->client_->write(&error_byte, 1); // Best effort, non-blocking
143 this->cleanup_connection_();
144 }
146
147#ifdef USE_OTA_PASSWORD
148 std::string password_;
150#endif // USE_OTA_PASSWORD
151#ifdef USE_OTA_ENCRYPTION
153#ifdef USE_OTA_ENCRYPTION_PROVISIONED
154 // Backs noise_ctx_ in safe mode, where no api server holds the saved key
156#endif
158#endif // USE_OTA_ENCRYPTION
159
161 std::unique_ptr<socket::Socket> client_;
163
165 static constexpr size_t HANDSHAKE_BUF_SIZE = 5;
166 // Buffer size for OTA data transfer. The upload client derives its maximum
167 // encrypted frame plaintext from this (espota2.NOISE_MAX_PLAINTEXT is this
168 // minus the 16-byte MAC); both must change together.
169 static constexpr size_t OTA_BUFFER_SIZE = 1040;
170#ifdef USE_OTA_ENCRYPTION
171 // espota2.NOISE_MAX_PLAINTEXT; shrinking the buffer would reject every
172 // frame a current CLI sends
173 static constexpr size_t NOISE_CLIENT_MAX_PLAINTEXT = 1024;
174 static_assert(OTA_BUFFER_SIZE >= NOISE_CLIENT_MAX_PLAINTEXT + noise::MAC_SIZE,
175 "OTA_BUFFER_SIZE must fit a full encrypted data frame");
176#endif
177 static constexpr uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
178 // Derived from the feature byte; storing it would pad the trailing bytes
179 bool extended_proto_() const;
180#ifdef USE_OTA_PARTITIONS
183#endif
184 uint16_t port_;
188 uint8_t ota_features_{0};
189#ifdef USE_OTA_PASSWORD
190 uint8_t auth_buf_pos_{0};
191 uint8_t auth_type_{0}; // Store auth type to know which hasher to use
192#endif // USE_OTA_PASSWORD
193};
194
195} // namespace esphome
196#endif
ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using Ardui...
Definition ota_esphome.h:18
static constexpr size_t OTA_BUFFER_SIZE
bool handle_noise_handshake_()
Drive the non-blocking handshake from loop(); returns true once the transport ciphers are ready.
bool would_block_(int error_code) const
uint16_t get_port() const
Definition ota_esphome.h:60
static constexpr size_t SHA256_HEX_SIZE
Definition ota_esphome.h:66
static constexpr uint8_t MAGIC_BYTES[5]
bool writeall_(const uint8_t *buf, size_t len)
bool try_read_(size_t to_read, const LogString *desc)
bool data_readall_(uint8_t *buf, size_t len)
noise::NoiseContext noise_ctx_
ssize_t noise_decrypt_(uint8_t *buf, size_t len)
Decrypt a ciphertext in place; returns the plaintext size or -1.
bool noise_readall_(uint8_t *buf, size_t len)
Blocking read of one frame whose plaintext must be exactly len bytes (control units are one unit per ...
bool noise_start_session_(uint8_t server_feature_flags)
Allocate the session and start the responder handshake.
void set_noise_psk(const uint8_t *psk)
psk points at 32 bytes that live in flash for the life of the program
Definition ota_esphome.h:49
void set_auth_password(const std::string &password)
Definition ota_esphome.h:36
ota::OTABackendPtr backend_
bool try_write_(size_t to_write, const LogString *desc)
bool noise_try_read_frame_()
Non-blocking read of one handshake frame into the session buffer.
static constexpr size_t NOISE_CLIENT_MAX_PLAINTEXT
RAMUniquePtr< NoiseSession > noise_
bool handle_write_error_(ssize_t written, const LogString *desc)
ssize_t noise_read_frame_blocking_(uint8_t *buf, size_t min_ciphertext, size_t max_ciphertext)
Blocking read of one frame whose ciphertext size must be within the given bounds, decrypted in place;...
bool data_write_byte_(uint8_t byte)
size_t noise_frame_payload_len_(const uint8_t *header, size_t min_len, size_t max_len)
Payload length from a frame header, or 0 (logged) when the indicator or the length is out of range.
void log_auth_warning_(const LogString *msg)
float get_setup_priority() const override
void send_error_and_cleanup_(ota::OTAResponseTypes error)
bool handle_read_error_(ssize_t read, const LogString *desc)
ssize_t noise_read_data_(uint8_t *buf, size_t capacity)
Blocking read of one data-phase frame, decrypted in place; returns the plaintext size,...
void log_read_error_(const LogString *what)
bool readall_(uint8_t *buf, size_t len)
void set_port(uint16_t port)
Manually set the port OTA should listen on.
Definition ota_esphome.h:53
void set_auth_password(const std::string &)
Definition ota_esphome.h:40
bool write_byte_(uint8_t byte)
Definition ota_esphome.h:75
RAMUniquePtr< uint8_t[]> auth_buf_
void noise_send_reject_(const LogString *reason)
Best-effort explicit reject frame so the client can log a readable reason.
RAMUniquePtr< noise::psk_t > saved_psk_
bool noise_write_byte_(uint8_t byte)
Blocking write of one response byte as an encrypted frame.
uint8_t handshake_buf_[HANDSHAKE_BUF_SIZE]
static constexpr size_t HANDSHAKE_BUF_SIZE
const noise::NoiseContext & noise_context_() const
void server_failed_(const LogString *msg)
void transition_ota_state_(OTAState next_state)
bool noise_try_write_frame_()
Non-blocking write of the pending session-buffer frame.
socket::ListenSocket * server_
void log_remote_closed_(const LogString *during)
std::unique_ptr< socket::Socket > client_
void log_start_(const LogString *phase)
void log_socket_error_(const LogString *msg)
void set_psk(const uint8_t *psk)
psk points at 32 bytes that outlive the context (PROGMEM or caller owned RAM); nullptr means no key.
Definition noise.h:29
Sans-IO responder side of a Noise_NNpsk0_25519_ChaChaPoly_SHA256 handshake.
__int64 ssize_t
Definition httplib.h:178
decltype(make_ota_backend()) OTABackendPtr
std::unique_ptr< T, RAMDeleter< T > > RAMUniquePtr
unique_ptr over RAMAllocator storage
Definition helpers.h:2131
const void size_t len
Definition hal.h:64
int written
Definition helpers.h:1130
uint16_t uint16_t & capacity
Definition helpers.cpp:25
static void uint32_t
uint8_t frame_buf[noise::FRAME_HEADER_SIZE+1+noise::MAX_HANDSHAKE_SIZE]
Definition ota_esphome.h:87
noise::NoiseResponderHandshake handshake
Definition ota_esphome.h:81