17namespace http_request {
19static const char *
const TAG =
"http_request.ota";
41 if (this->
url_.empty()) {
42 ESP_LOGE(TAG,
"URL not set; cannot start update");
46 ESP_LOGI(TAG,
"Starting update");
47#ifdef USE_OTA_STATE_LISTENER
51 auto ota_status = this->
do_ota_();
55#ifdef USE_OTA_STATE_LISTENER
63#ifdef USE_OTA_STATE_LISTENER
73 const std::shared_ptr<HttpContainer> &container) {
75 ESP_LOGV(TAG,
"Aborting OTA backend");
78 ESP_LOGV(TAG,
"Aborting HTTP connection");
84 uint32_t last_progress = 0;
85 uint32_t update_start_time =
millis();
87 char md5_receive_str[33];
93 ESP_LOGD(TAG,
"MD5 expected: %s", this->
md5_expected_.c_str());
96 if (url_with_auth.empty()) {
99 ESP_LOGVV(TAG,
"url_with_auth: %s", url_with_auth.c_str());
100 ESP_LOGI(TAG,
"Connecting to: %s", this->
url_.c_str());
102 auto container = this->
parent_->get(url_with_auth);
104 if (container ==
nullptr || container->status_code !=
HTTP_STATUS_OK) {
110 ESP_LOGV(TAG,
"MD5Digest initialized\n"
111 "OTA backend begin");
113 auto error_code = backend->begin(container->content_length);
115 ESP_LOGW(TAG,
"backend->begin error: %d", error_code);
116 this->
cleanup_(std::move(backend), container);
122 uint32_t last_data_time =
millis();
123 const uint32_t read_timeout = this->
parent_->get_timeout();
125 while (container->get_bytes_read() < container->content_length) {
128 ESP_LOGVV(TAG,
"bytes_read_ = %u, body_length_ = %u, bufsize_or_error = %i", container->get_bytes_read(),
129 container->content_length, bufsize_or_error);
135 auto result =
http_read_loop_result(bufsize_or_error, last_data_time, read_timeout, container->is_read_complete());
146 ESP_LOGE(TAG,
"Timeout reading data");
148 ESP_LOGE(TAG,
"Error reading data: %d", bufsize_or_error);
150 this->
cleanup_(std::move(backend), container);
157 md5_receive.
add(buf, bufsize_or_error);
161 error_code = backend->write(buf, bufsize_or_error);
165 ESP_LOGE(TAG,
"Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
166 container->get_bytes_read() - bufsize_or_error, container->content_length);
167 this->
cleanup_(std::move(backend), container);
173 if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
175 float percentage = container->get_bytes_read() * 100.0f / container->content_length;
176 ESP_LOGD(TAG,
"Progress: %0.1f%%", percentage);
177#ifdef USE_OTA_STATE_LISTENER
183 ESP_LOGI(TAG,
"Done in %.0f seconds",
float(
millis() - update_start_time) / 1000);
187 md5_receive.
get_hex(md5_receive_str);
189 if (strncmp(this->
md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
190 ESP_LOGE(TAG,
"MD5 computed: %s - Aborting due to MD5 mismatch", this->
md5_computed_.c_str());
191 this->
cleanup_(std::move(backend), container);
194 backend->set_update_md5(md5_receive_str);
204 error_code = backend->end();
206 ESP_LOGW(TAG,
"Error ending update! error_code: %d", error_code);
207 this->
cleanup_(std::move(backend), container);
211 ESP_LOGI(TAG,
"Update complete");
217static std::string url_encode(
const std::string &str) {
219 result.reserve(str.size());
221 if (std::isalnum(
static_cast<unsigned char>(c)) || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~') {
236 if (this->
username_.empty() || this->password_.empty()) {
240 auto start_char = url.find(
"://");
241 if ((start_char == std::string::npos) || (start_char < 4)) {
242 ESP_LOGE(TAG,
"Incorrect URL prefix");
246 ESP_LOGD(TAG,
"Using basic HTTP authentication");
250 url.substr(0, start_char) + this->
username_ +
":" + this->
password_ +
"@" + url.substr(start_char);
251 return url_with_auth;
260 if (url_with_auth.empty()) {
264 ESP_LOGVV(TAG,
"url_with_auth: %s", url_with_auth.c_str());
265 ESP_LOGI(TAG,
"Connecting to: %s", this->
md5_url_.c_str());
266 auto container = this->
parent_->get(url_with_auth);
267 if (container ==
nullptr) {
268 ESP_LOGE(TAG,
"Failed to connect to MD5 URL");
271 size_t length = container->content_length;
277 ESP_LOGE(TAG,
"MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE,
length);
283 auto result =
http_read_fully(container.get(), (uint8_t *) this->md5_expected_.data(), MD5_SIZE, MD5_SIZE,
284 this->parent_->get_timeout());
289 ESP_LOGE(TAG,
"Timeout reading MD5");
291 ESP_LOGE(TAG,
"Error reading MD5: %d", result.error_code);
299 if ((url.length() < 8) || !url.starts_with(
"http") || (url.find(
"://") == std::string::npos)) {
300 ESP_LOGE(TAG,
"URL is invalid and/or must be prefixed with 'http://' or 'https://'");
void feed_wdt(uint32_t time=0)
void get_hex(char *output)
Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes.
HttpRequestComponent * parent_
void set_url(const std::string &url)
void dump_config() override
std::string md5_computed_
void cleanup_(std::unique_ptr< ota::OTABackend > backend, const std::shared_ptr< HttpContainer > &container)
static const uint16_t HTTP_RECV_BUFFER
void set_password(const std::string &password)
bool validate_url_(const std::string &url)
void set_username(const std::string &username)
std::string get_url_with_auth_(const std::string &url)
std::string md5_expected_
void set_md5_url(const std::string &md5_url)
void calculate() override
Compute the digest, based on the provided data.
void add(const uint8_t *data, size_t len) override
Add bytes of data for the digest.
void init() override
Initialize a new MD5 digest computation.
void notify_state_(OTAState state, float progress, uint8_t error)
@ TIMEOUT
Timeout waiting for data, caller should exit loop.
@ COMPLETE
All content has been read, caller should exit loop.
@ RETRY
No data yet, already delayed, caller should continue loop.
@ DATA
Data was read, process it.
HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms, bool is_read_complete)
Process a read result with timeout tracking and delay handling.
HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size, uint32_t timeout_ms)
Read data from HTTP container into buffer with timeout handling Handles feed_wdt, yield,...
@ TIMEOUT
Timeout waiting for data.
@ OK
Read completed successfully.
std::unique_ptr< ota::OTABackend > make_ota_backend()
@ OTA_RESPONSE_ERROR_MD5_MISMATCH
Providing packet encoding functions for exchanging data with a remote host.
char format_hex_pretty_char(uint8_t v)
Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
void IRAM_ATTR HOT yield()
void IRAM_ATTR HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.