ESPHome 2026.10.0-dev
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#if defined(USE_OTA_ENCRYPTION) && !defined(USE_OTA_ENCRYPTION_FROM_API)
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 the api has encryption, else our own
91 bool noise_start_session_(uint8_t server_feature_flags);
94 size_t noise_frame_payload_len_(const uint8_t *header, size_t min_len, size_t max_len);
96 void noise_send_reject_(const LogString *reason);
97 ssize_t noise_decrypt_(uint8_t *buf, size_t len);
98 ssize_t noise_read_frame_blocking_(uint8_t *buf, size_t min_ciphertext, size_t max_ciphertext);
99 bool noise_readall_(uint8_t *buf, size_t len);
100 ssize_t noise_read_data_(uint8_t *buf, size_t capacity);
101 bool noise_write_byte_(uint8_t byte);
102#endif // USE_OTA_ENCRYPTION
103
104 // Data-phase I/O dispatch: through the noise transport when a session is
105 // active, straight to the socket otherwise.
106 inline bool data_write_byte_(uint8_t byte) {
107#ifdef USE_OTA_ENCRYPTION
108 if (this->noise_ != nullptr)
109 return this->noise_write_byte_(byte);
110#endif
111 return this->write_byte_(byte);
112 }
113 // When encrypted, buf must have room for len + noise::MAC_SIZE bytes.
114 inline bool data_readall_(uint8_t *buf, size_t len) {
115#ifdef USE_OTA_ENCRYPTION
116 if (this->noise_ != nullptr)
117 return this->noise_readall_(buf, len);
118#endif
119 return this->readall_(buf, len);
120 }
121
122 bool try_read_(size_t to_read, const LogString *desc);
123 bool try_write_(size_t to_write, const LogString *desc);
124
125 inline bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; }
126 bool handle_read_error_(ssize_t read, const LogString *desc);
127 bool handle_write_error_(ssize_t written, const LogString *desc);
128 inline void transition_ota_state_(OTAState next_state) {
129 this->ota_state_ = next_state;
130 this->handshake_buf_pos_ = 0; // Reset buffer position for next state
131 }
132
133 void server_failed_(const LogString *msg);
134 void log_socket_error_(const LogString *msg);
135 void log_read_error_(const LogString *what);
136 void log_start_(const LogString *phase);
137 void log_remote_closed_(const LogString *during);
138 void cleanup_connection_();
140 uint8_t error_byte = static_cast<uint8_t>(error);
141 this->client_->write(&error_byte, 1); // Best effort, non-blocking
142 this->cleanup_connection_();
143 }
145
146#ifdef USE_OTA_PASSWORD
147 std::string password_;
148 std::unique_ptr<uint8_t[]> auth_buf_;
149#endif // USE_OTA_PASSWORD
150#ifdef USE_OTA_ENCRYPTION
151#ifndef USE_OTA_ENCRYPTION_FROM_API
153#endif
154 std::unique_ptr<NoiseSession> noise_;
155#endif // USE_OTA_ENCRYPTION
156
158 std::unique_ptr<socket::Socket> client_;
160
162 static constexpr size_t HANDSHAKE_BUF_SIZE = 5;
163 // Buffer size for OTA data transfer. The upload client derives its maximum
164 // encrypted frame plaintext from this (espota2.NOISE_MAX_PLAINTEXT is this
165 // minus the 16-byte MAC); both must change together.
166 static constexpr size_t OTA_BUFFER_SIZE = 1040;
167#ifdef USE_OTA_ENCRYPTION
168 // espota2.NOISE_MAX_PLAINTEXT; shrinking the buffer would reject every
169 // frame a current CLI sends
170 static constexpr size_t NOISE_CLIENT_MAX_PLAINTEXT = 1024;
171 static_assert(OTA_BUFFER_SIZE >= NOISE_CLIENT_MAX_PLAINTEXT + noise::MAC_SIZE,
172 "OTA_BUFFER_SIZE must fit a full encrypted data frame");
173#endif
174 static constexpr uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
175 // Derived from the feature byte; storing it would pad the trailing bytes
176 bool extended_proto_() const;
177#ifdef USE_OTA_PARTITIONS
180#endif
181 uint16_t port_;
185 uint8_t ota_features_{0};
186#ifdef USE_OTA_PASSWORD
187 uint8_t auth_buf_pos_{0};
188 uint8_t auth_type_{0}; // Store auth type to know which hasher to use
189#endif // USE_OTA_PASSWORD
190};
191
192} // namespace esphome
193#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
std::unique_ptr< uint8_t[]> auth_buf_
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)
std::unique_ptr< NoiseSession > noise_
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
void noise_send_reject_(const LogString *reason)
Best-effort explicit reject frame so the client can log a readable reason.
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
const void size_t len
Definition hal.h:64
int written
Definition helpers.h:1099
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