ESPHome 2026.10.2
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
10#ifdef USE_OTA_DEFLATE
11#include "ota_esphome_inflate.h"
12#endif
14#include "esphome/core/log.h"
17
18namespace esphome {
19
22 public:
23 enum class OTAState : uint8_t {
24 IDLE,
25 MAGIC_READ, // Reading magic bytes
26 MAGIC_ACK, // Sending OK and version after magic bytes
27 FEATURE_READ, // Reading feature flags from client
28 FEATURE_ACK, // Sending feature acknowledgment
29#ifdef USE_OTA_PASSWORD
30 AUTH_SEND, // Sending authentication request
31 AUTH_READ, // Reading authentication data
32#endif // USE_OTA_PASSWORD
33#ifdef USE_OTA_ENCRYPTION
34 NOISE_HANDSHAKE, // Exchanging Noise handshake frames
35#endif
36 DATA, // BLOCKING! Processing OTA data (update, etc.)
37 };
38#ifdef USE_OTA_PASSWORD
39 void set_auth_password(const std::string &password) { password_ = password; }
40#else
41 // Stub so lambdas referencing set_auth_password() produce a clear error instead of
42 // a cryptic "no member" diagnostic. Only fires if the stub is actually instantiated.
43 template<bool B = false> void set_auth_password(const std::string &) {
44 static_assert(B, "set_auth_password() requires the OTA auth path to be compiled. "
45 "Add 'password: \"\"' (empty string) to your 'ota: - platform: esphome' "
46 "config to enable runtime password rotation.");
47 }
48#endif // USE_OTA_PASSWORD
49
50#ifdef USE_OTA_ENCRYPTION
52 void set_noise_psk(const uint8_t *psk) { this->noise_ctx_.set_psk(psk); }
53#endif
54
56 void set_port(uint16_t port) { this->port_ = port; }
57
58 void setup() override;
59 void dump_config() override;
60 float get_setup_priority() const override;
61 void loop() override;
62
63 uint16_t get_port() const { return this->port_; }
64
65 protected:
66 void handle_handshake_();
67 void handle_data_();
68#ifdef USE_OTA_PASSWORD
69 static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
70 bool handle_auth_send_();
71 bool handle_auth_read_();
72 bool select_auth_type_();
73 void cleanup_auth_();
74 void log_auth_warning_(const LogString *msg);
75#endif // USE_OTA_PASSWORD
76 bool readall_(uint8_t *buf, size_t len);
77 bool writeall_(const uint8_t *buf, size_t len);
78 inline bool write_byte_(uint8_t byte) { return this->writeall_(&byte, 1); }
79
80#ifdef USE_OTA_ENCRYPTION
81 // Heap-allocated only while an encrypted OTA session is active.
82 struct NoiseSession {
85 NoiseCipherState *send_cipher{nullptr};
86 NoiseCipherState *recv_cipher{nullptr};
87 uint16_t frame_len{0}; // total frame size once the header is parsed, 0 until then
88 uint16_t frame_pos{0}; // bytes read or written so far
89 bool writing{false}; // a produced handshake frame is still being flushed
90 uint8_t frame_buf[noise::FRAME_HEADER_SIZE + 1 + noise::MAX_HANDSHAKE_SIZE];
91 };
92 // The api server's live context when it exists, otherwise our own (a build
93 // time key, or the saved key loaded in safe mode)
95 // True once the feature ack offers noise and the client asked for it
96 bool noise_offered_() const;
98 bool noise_start_session_(uint8_t server_feature_flags);
101 size_t noise_frame_payload_len_(const uint8_t *header, size_t min_len, size_t max_len);
103 void noise_send_reject_(const LogString *reason);
104 ssize_t noise_decrypt_(uint8_t *buf, size_t len);
105 ssize_t noise_read_frame_blocking_(uint8_t *buf, size_t min_ciphertext, size_t max_ciphertext);
106 bool noise_readall_(uint8_t *buf, size_t len);
107 ssize_t noise_read_data_(uint8_t *buf, size_t capacity);
108 bool noise_write_byte_(uint8_t byte);
109#endif // USE_OTA_ENCRYPTION
110
111 // Data-phase I/O dispatch: through the noise transport when a session is
112 // active, straight to the socket otherwise.
113 inline bool data_write_byte_(uint8_t byte) {
114#ifdef USE_OTA_ENCRYPTION
115 if (this->noise_ != nullptr)
116 return this->noise_write_byte_(byte);
117#endif
118 return this->write_byte_(byte);
119 }
120 // When encrypted, buf must have room for len + noise::MAC_SIZE bytes.
121 inline bool data_readall_(uint8_t *buf, size_t len) {
122#ifdef USE_OTA_ENCRYPTION
123 if (this->noise_ != nullptr)
124 return this->noise_readall_(buf, len);
125#endif
126 return this->readall_(buf, len);
127 }
128
129 // Upload accounting shared by the data loop and the inflate read callback
131 size_t ota_size{0}; // bytes the client sends
132 size_t total{0}; // bytes received so far
133#if USE_OTA_VERSION == 2
134 size_t acknowledged{0};
135#endif
138 };
139 // Up to OTA_BUFFER_SIZE bytes into buf; returns bytes read, -1 on failure (logged)
140 ssize_t receive_data_(uint8_t *buf, DataTransfer &xfer);
141 // Raw lwIP cannot service the radio during a sector write, so the ack waits
142 // for the write there; a socket task lets the next block arrive meanwhile
143#ifdef USE_SOCKET_IMPL_LWIP_TCP
144 static constexpr bool ACK_AFTER_WRITE = true;
145#else
146 static constexpr bool ACK_AFTER_WRITE = false;
147#endif
148 void send_chunk_acks_(DataTransfer &xfer);
149 inline void ack_received_(DataTransfer &xfer) {
150 if (!ACK_AFTER_WRITE)
151 this->send_chunk_acks_(xfer);
152 }
153 inline void ack_written_(DataTransfer &xfer) {
154 if (ACK_AFTER_WRITE)
155 this->send_chunk_acks_(xfer);
156 }
157 inline bool read_size_(uint8_t *buf, size_t &size, const LogString *desc);
158 // Writes to the backend and logs a failure
159 ota::OTAResponseTypes write_flash_(uint8_t *data, size_t len);
160
161 bool try_read_(size_t to_read, const LogString *desc);
162 bool try_write_(size_t to_write, const LogString *desc);
163
164 inline bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; }
165 bool handle_read_error_(ssize_t read, const LogString *desc);
166 bool handle_write_error_(ssize_t written, const LogString *desc);
167 inline void transition_ota_state_(OTAState next_state) {
168 this->ota_state_ = next_state;
169 this->handshake_buf_pos_ = 0; // Reset buffer position for next state
170 }
171
172 void server_failed_(const LogString *msg);
173 void log_socket_error_(const LogString *msg);
174 void log_read_error_(const LogString *what);
176 void log_start_(const LogString *phase);
177 void log_remote_closed_(const LogString *during);
178 void cleanup_connection_();
180 uint8_t error_byte = static_cast<uint8_t>(error);
181 this->client_->write(&error_byte, 1); // Best effort, non-blocking
182 this->cleanup_connection_();
183 }
185
186#ifdef USE_OTA_PASSWORD
187 std::string password_;
189#endif // USE_OTA_PASSWORD
190#ifdef USE_OTA_ENCRYPTION
192#ifdef USE_OTA_ENCRYPTION_PROVISIONED
193 // Backs noise_ctx_ in safe mode, where no api server holds the saved key
195#endif
197#endif // USE_OTA_ENCRYPTION
198
200 std::unique_ptr<socket::Socket> client_;
202
204 static constexpr size_t HANDSHAKE_BUF_SIZE = 5;
205 // Buffer size for OTA data transfer. The upload client derives its maximum
206 // encrypted frame plaintext from this (espota2.NOISE_MAX_PLAINTEXT is this
207 // minus the 16-byte MAC); both must change together.
208 static constexpr size_t OTA_BUFFER_SIZE = 1040;
209#ifdef USE_OTA_ENCRYPTION
210 // espota2.NOISE_MAX_PLAINTEXT; shrinking the buffer would reject every
211 // frame a current CLI sends
212 static constexpr size_t NOISE_CLIENT_MAX_PLAINTEXT = 1024;
213 static_assert(OTA_BUFFER_SIZE >= NOISE_CLIENT_MAX_PLAINTEXT + noise::MAC_SIZE,
214 "OTA_BUFFER_SIZE must fit a full encrypted data frame");
215#endif
216#ifdef USE_OTA_DEFLATE
217 // At least 1 << espota2.DEFLATE_WINDOW_BITS; also the inflate output buffer
218 static constexpr size_t OTA_INFLATE_WINDOW_SIZE = 4096;
219 // Heap-allocated only while a deflate upload is negotiated; the decoder
220 // state is the base so the read callback can recover the session
222 // The session outlives the upload it serves, but these three are borrowed
223 // from inflate_data_'s caller and dangle once that call returns; only that
224 // call, and the flush and read callback it drives, may read them
227 uint8_t *in; // caller's buffer for the compressed input
229 size_t written; // inflated bytes in flash
230 size_t flushed; // bytes of the current window already in flash
231 ota::OTAResponseTypes error; // first failure inside the read callback
233 };
234#ifndef CLANG_TIDY // static analysis sets every define at once
235 static_assert(!ota::OTABackend::supports_compression(),
236 "USE_OTA_DEFLATE is for backends that cannot store a gzip image");
237#endif
238 // Writes the decoded bytes not yet in flash without moving dest
240 ota::OTAResponseTypes inflate_data_(uint8_t *in, size_t image_size, DataTransfer &xfer);
242#endif
243
244 static constexpr uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
245 // Derived from the feature byte; storing it would pad the trailing bytes
246 bool extended_proto_() const;
247#ifdef USE_OTA_PARTITIONS
250#endif
251 uint16_t port_;
255 uint8_t ota_features_{0};
256 bool remote_closed_{false}; // the peer hung up cleanly during a blocking read
257#ifdef USE_OTA_PASSWORD
258 uint8_t auth_buf_pos_{0};
259 uint8_t auth_type_{0}; // Store auth type to know which hasher to use
260#endif // USE_OTA_PASSWORD
261};
262
263} // namespace esphome
264#endif
ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using Ardui...
Definition ota_esphome.h:21
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.
ssize_t receive_data_(uint8_t *buf, DataTransfer &xfer)
bool would_block_(int error_code) const
uint16_t get_port() const
Definition ota_esphome.h:63
static constexpr size_t SHA256_HEX_SIZE
Definition ota_esphome.h:69
static constexpr uint8_t MAGIC_BYTES[5]
ota::OTAResponseTypes write_flash_(uint8_t *data, size_t len)
bool writeall_(const uint8_t *buf, size_t len)
bool try_read_(size_t to_read, const LogString *desc)
static constexpr bool ACK_AFTER_WRITE
static constexpr size_t OTA_INFLATE_WINDOW_SIZE
bool data_readall_(uint8_t *buf, size_t len)
noise::NoiseContext noise_ctx_
void ack_written_(DataTransfer &xfer)
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)
Start the responder handshake, on the session reserved at offer time.
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:52
void set_auth_password(const std::string &password)
Definition ota_esphome.h:39
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;...
void send_chunk_acks_(DataTransfer &xfer)
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)
RAMUniquePtr< InflateSession > inflate_
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:56
void set_auth_password(const std::string &)
Definition ota_esphome.h:43
bool write_byte_(uint8_t byte)
Definition ota_esphome.h:78
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.
void ack_received_(DataTransfer &xfer)
uint8_t handshake_buf_[HANDSHAKE_BUF_SIZE]
static constexpr size_t HANDSHAKE_BUF_SIZE
ota::OTAResponseTypes inflate_data_(uint8_t *in, size_t image_size, DataTransfer &xfer)
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.
bool read_size_(uint8_t *buf, size_t &size, const LogString *desc)
socket::ListenSocket * server_
void log_remote_closed_(const LogString *during)
std::unique_ptr< socket::Socket > client_
void log_start_(const LogString *phase)
ota::OTAResponseTypes inflate_flush_(InflateSession &session)
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:32
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:2220
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:33
int written
Definition helpers.h:1193
uint16_t uint16_t & capacity
Definition helpers.cpp:33
static void uint32_t
uint8_t window[OTA_INFLATE_WINDOW_SIZE]
uint8_t frame_buf[noise::FRAME_HEADER_SIZE+1+noise::MAX_HANDSHAKE_SIZE]
Definition ota_esphome.h:90
noise::NoiseResponderHandshake handshake
Definition ota_esphome.h:84