mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 16:00:23 +00:00
[voice_assistant] Handle downloads without a content length
A chunked response reports no usable content length, 0 on ESP-IDF and SIZE_MAX on Arduino, so both the manifest and the model were rejected. Bound the model read by the size Home Assistant advertised, falling back to the content length when it advertised none, and read a manifest of unknown length up to the existing cap. The read still has to deliver the expected number of bytes, and the SHA256 check is unchanged. Also move http_request_ out of the block documented as main loop only, since the download task uses it.
This commit is contained in:
@@ -1398,13 +1398,17 @@ void VoiceAssistant::model_load_task(void *params) {
|
||||
fail();
|
||||
continue;
|
||||
}
|
||||
size_t manifest_size = manifest_container->content_length;
|
||||
if (manifest_size == 0 || manifest_size > MAX_MANIFEST_SIZE) {
|
||||
ESP_LOGW(TAG, "Manifest for %s has an invalid content length %zu", id.c_str(), manifest_size);
|
||||
// A chunked response carries no usable content length: ESP-IDF reports 0 and Arduino reports SIZE_MAX.
|
||||
// Read up to the cap in that case and rely on get_bytes_read() below for the size that actually arrived.
|
||||
const size_t manifest_length = manifest_container->content_length;
|
||||
const bool manifest_length_known = manifest_length != 0 && manifest_length != SIZE_MAX;
|
||||
if (manifest_length_known && manifest_length > MAX_MANIFEST_SIZE) {
|
||||
ESP_LOGW(TAG, "Manifest for %s is larger than %zu bytes", id.c_str(), MAX_MANIFEST_SIZE);
|
||||
manifest_container->end();
|
||||
fail();
|
||||
continue;
|
||||
}
|
||||
const size_t manifest_size = manifest_length_known ? manifest_length : MAX_MANIFEST_SIZE;
|
||||
std::string manifest_str;
|
||||
manifest_str.resize(manifest_size);
|
||||
auto manifest_read =
|
||||
@@ -1412,7 +1416,7 @@ void VoiceAssistant::model_load_task(void *params) {
|
||||
manifest_size, MODEL_DOWNLOAD_CHUNK_SIZE, this_va->http_request_->get_timeout());
|
||||
size_t manifest_bytes = manifest_container->get_bytes_read();
|
||||
manifest_container->end();
|
||||
if (manifest_read.status != http_request::HttpReadStatus::OK) {
|
||||
if (manifest_read.status != http_request::HttpReadStatus::OK || manifest_bytes == 0) {
|
||||
ESP_LOGW(TAG, "Failed to read manifest for %s", id.c_str());
|
||||
fail();
|
||||
continue;
|
||||
@@ -1492,17 +1496,24 @@ void VoiceAssistant::model_load_task(void *params) {
|
||||
fail();
|
||||
continue;
|
||||
}
|
||||
size_t model_size = container->content_length;
|
||||
// Bound the read by the size Home Assistant advertised. A chunked response carries no usable content
|
||||
// length (0 on ESP-IDF, SIZE_MAX on Arduino), so it cannot size the buffer on its own.
|
||||
const size_t content_length = container->content_length;
|
||||
const bool content_length_known = content_length != 0 && content_length != SIZE_MAX;
|
||||
size_t model_size = cached_ww.model_size;
|
||||
if (model_size == 0) {
|
||||
ESP_LOGW(TAG, "Model %s reported a zero-length body", id.c_str());
|
||||
// Home Assistant advertised no size, so the content length is all we have to go on.
|
||||
model_size = content_length_known ? content_length : 0;
|
||||
} else if (content_length_known && content_length != model_size) {
|
||||
ESP_LOGW(TAG, "Model %s content length %zu disagrees with the advertised %" PRIu32 " (SHA256 is authoritative)",
|
||||
id.c_str(), content_length, cached_ww.model_size);
|
||||
}
|
||||
if (model_size == 0) {
|
||||
ESP_LOGW(TAG, "Model %s has no known size", id.c_str());
|
||||
container->end();
|
||||
fail();
|
||||
continue;
|
||||
}
|
||||
if (cached_ww.model_size != 0 && cached_ww.model_size != model_size) {
|
||||
ESP_LOGW(TAG, "Model %s size %zu disagrees with the advertised %" PRIu32 " (SHA256 is authoritative)", id.c_str(),
|
||||
model_size, cached_ww.model_size);
|
||||
}
|
||||
auto model_data = std::make_shared<micro_wake_word::ModelData>();
|
||||
if (!model_data->allocate(model_size)) {
|
||||
ESP_LOGW(TAG, "Failed to allocate %zu bytes for model %s", model_size, id.c_str());
|
||||
|
||||
@@ -378,6 +378,9 @@ class VoiceAssistant final : public Component {
|
||||
#ifdef USE_MICRO_WAKE_WORD
|
||||
micro_wake_word::MicroWakeWord *micro_wake_word_{nullptr};
|
||||
#ifdef USE_VOICE_ASSISTANT_RUNTIME_MODEL
|
||||
// Used from the background download task, not just the main loop, so that a download never blocks the loop.
|
||||
http_request::HttpRequestComponent *http_request_{nullptr};
|
||||
|
||||
/* Runtime model management. Every member and method below is touched only on the main loop; the
|
||||
background download task communicates results back via defer() with everything captured by value. */
|
||||
|
||||
@@ -405,8 +408,6 @@ class VoiceAssistant final : public Component {
|
||||
// FreeRTOS task body: downloads and validates queued models, handing each off to the main loop via defer().
|
||||
static void model_load_task(void *params);
|
||||
TaskHandle_t model_load_task_handle_{nullptr};
|
||||
|
||||
http_request::HttpRequestComponent *http_request_{nullptr};
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user