From f94703360bbd07e93f8e5b60aee52fa81adf126f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Jun 2025 20:54:13 -0500 Subject: [PATCH] cleanup --- ...17:53:09][D][sensor:104]: 'Lambda Senso.sh | 52 +++++++ ...ltipart_parser_utils.cpp => multipart.cpp} | 132 ++++++++++++++++- .../{multipart_reader.h => multipart.h} | 24 +++- .../web_server_idf/multipart_parser_utils.h | 32 ----- .../web_server_idf/multipart_reader.cpp | 136 ------------------ .../web_server_idf/web_server_idf.cpp | 3 +- 6 files changed, 206 insertions(+), 173 deletions(-) create mode 100644 esphome/components/web_server_idf/[17:53:09][D][sensor:104]: 'Lambda Senso.sh rename esphome/components/web_server_idf/{multipart_parser_utils.cpp => multipart.cpp} (50%) rename esphome/components/web_server_idf/{multipart_reader.h => multipart.h} (70%) delete mode 100644 esphome/components/web_server_idf/multipart_parser_utils.h delete mode 100644 esphome/components/web_server_idf/multipart_reader.cpp diff --git a/esphome/components/web_server_idf/[17:53:09][D][sensor:104]: 'Lambda Senso.sh b/esphome/components/web_server_idf/[17:53:09][D][sensor:104]: 'Lambda Senso.sh new file mode 100644 index 0000000000..c6db42cc4e --- /dev/null +++ b/esphome/components/web_server_idf/[17:53:09][D][sensor:104]: 'Lambda Senso.sh @@ -0,0 +1,52 @@ +[17:53:09][D][sensor:104]: 'Lambda Sensor 15': Sending state 15.00000 with 1 decimals of accuracy +[17:53:09][D][sensor:104]: 'Lambda Sensor 34': Sending state 34.00000 with 1 decimals of accuracy +[17:53:10][D][sensor:104]: 'Lambda Sensor 16': Sending state 16.00000 with 1 decimals of accuracy +[17:53:10][D][sensor:104]: 'Lambda Sensor 7': Sending state 7.00000 with 1 decimals of accuracy +[17:53:12][D][esp-idf:000]: W (92465) httpd_txrx: httpd_sock_err: error in send : 9 +[17:53:12]Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled. + +[17:53:12]Core 0 register dump: +[17:53:12]PC : 0x401a369f PS : 0x00060530 A0 : 0x801705f8 A1 : 0x3ffcc9d0 +WARNING Decoded 0x401a369f: std::local_Rb_tree_increment(std::_Rb_tree_node_base*) at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/src/c++98/tree.cc:65 +[17:53:12]A2 : 0x02000241 A3 : 0x3ffcc9c8 A4 : 0x00000008 A5 : 0x3ffe8b84 +[17:53:12]A6 : 0x30303030 A7 : 0x63383030 A8 : 0x3ffe8778 A9 : 0x02000241 +[17:53:12]A10 : 0xfffffffe A11 : 0x0000003b A12 : 0x3ffe8b7c A13 : 0x00000098 +[17:53:12]A14 : 0x00000000 A15 : 0x3ffe36c4 SAR : 0x00000017 EXCCAUSE: 0x0000001c +[17:53:12]EXCVADDR: 0x02000249 LBEG : 0x40082b85 LEND : 0x40082b8d LCOUNT : 0x00000027 + + +[17:53:12]Backtrace: 0x401a369c:0x3ffcc9d0 0x401705f5:0x3ffcc9f0 0x4010062e:0x3ffcca10 0x400f793a:0x3ffcca30 0x400f08c1:0x3ffcca50 0x400f094d:0x3ffcca80 0x401a03ad:0x3ffccac0 0x401a0461:0x3ffccae0 0x40101566:0x3ffccb00 0x4010586a:0x3ffccb30 0x400e6f76:0x3ffccb50 +WARNING Found stack trace! Trying to decode it +WARNING Decoded 0x401a369c: std::local_Rb_tree_increment(std::_Rb_tree_node_base*) at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/src/c++98/tree.cc:62 +WARNING Decoded 0x401705f5: std::_Rb_tree_increment(std::_Rb_tree_node_base const*) at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/src/c++98/tree.cc:89 +WARNING Decoded 0x4010062e: std::_Rb_tree_const_iterator::operator++() at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp-elf/xtensa-esp-elf/include/c++/13.2.0/bits/stl_tree.h:368 + (inlined by) esphome::web_server_idf::AsyncEventSource::try_send_nodefer(char const*, char const*, unsigned long, unsigned long) at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/components/web_server_idf/web_server_idf.cpp:516 +WARNING Decoded 0x400f793a: std::_Function_handler::_M_invoke(std::_Any_data const&, unsigned char&&, char const*&&, char const*&&) at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/components/web_server/web_server.cpp:247 (discriminator 1) + (inlined by) __invoke_impl&, unsigned char, char const*, char const*> at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp-elf/xtensa-esp-elf/include/c++/13.2.0/bits/invoke.h:61 (discriminator 1) + (inlined by) __invoke_r&, unsigned char, char const*, char const*> at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp-elf/xtensa-esp-elf/include/c++/13.2.0/bits/invoke.h:111 (discriminator 1) + (inlined by) _M_invoke at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp-elf/xtensa-esp-elf/include/c++/13.2.0/bits/std_function.h:290 (discriminator 1) +WARNING Decoded 0x400f08c1: std::function::operator()(unsigned char, char const*, char const*) const at /Users/bdraco/.platformio/packages/toolchain-xtensa-esp-elf/xtensa-esp-elf/include/c++/13.2.0/bits/std_function.h:591 + (inlined by) esphome::CallbackManager::call(unsigned char, char const*, char const*) at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/core/helpers.h:431 +WARNING Decoded 0x400f094d: esphome::logger::Logger::loop() at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/components/logger/logger.cpp:188 + (inlined by) esphome::logger::Logger::loop() at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/components/logger/logger.cpp:155 +WARNING Decoded 0x401a03ad: esphome::Component::call_loop() at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/core/component.cpp:84 +WARNING Decoded 0x401a0461: esphome::Component::call() at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/core/component.cpp:112 +WARNING Decoded 0x40101566: esphome::Application::loop() at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/core/application.cpp:128 +WARNING Decoded 0x4010586a: loop() at /Users/bdraco/esphome/.esphome/build/ol/ol.yaml:1345 +WARNING Decoded 0x400e6f76: esphome::loop_task(void*) at /Users/bdraco/esphome/.esphome/build/ol/src/esphome/components/esp32/core.cpp:86 (discriminator 1) + + + + +[17:53:14]ELF file SHA256: 009865893 + +[17:53:14]Rebooting... +[17:53:14]ets Jul 29 2019 12:21:46 + +[17:53:14]rst:0xc (SW_CPU_RESET),boot:0x1b (SPI_FAST_FLASH_BOOT) +[17:53:14]configsip: 0, SPIWP:0xee +[17:53:14]clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 +[17:53:14]mode:DIO, clock div:2 +[17:53:14]load:0x3fff0030,len:6072 +[17:53:14]load:0x40078000,len:14960 +[17:53:14]load:0x40080400,len:4 diff --git a/esphome/components/web_server_idf/multipart_parser_utils.cpp b/esphome/components/web_server_idf/multipart.cpp similarity index 50% rename from esphome/components/web_server_idf/multipart_parser_utils.cpp rename to esphome/components/web_server_idf/multipart.cpp index a0869648f8..eb84016cf1 100644 --- a/esphome/components/web_server_idf/multipart_parser_utils.cpp +++ b/esphome/components/web_server_idf/multipart.cpp @@ -1,12 +1,140 @@ #include "esphome/core/defines.h" #if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) -#include "multipart_parser_utils.h" +#include "multipart.h" #include "parser_utils.h" #include "esphome/core/log.h" +#include +#include "multipart_parser.h" namespace esphome { namespace web_server_idf { +static const char *const TAG = "multipart"; + +// ========== MultipartReader Implementation ========== + +MultipartReader::MultipartReader(const std::string &boundary) { + // Initialize settings with callbacks + memset(&settings_, 0, sizeof(settings_)); + settings_.on_header_field = on_header_field; + settings_.on_header_value = on_header_value; + settings_.on_part_data_begin = on_part_data_begin; + settings_.on_part_data = on_part_data; + settings_.on_part_data_end = on_part_data_end; + settings_.on_headers_complete = on_headers_complete; + + ESP_LOGV(TAG, "Initializing multipart parser with boundary: '%s' (len: %zu)", boundary.c_str(), boundary.length()); + + // Create parser with boundary + parser_ = multipart_parser_init(boundary.c_str(), &settings_); + if (parser_) { + multipart_parser_set_data(parser_, this); + } else { + ESP_LOGE(TAG, "Failed to initialize multipart parser"); + } +} + +MultipartReader::~MultipartReader() { + if (parser_) { + multipart_parser_free(parser_); + } +} + +size_t MultipartReader::parse(const char *data, size_t len) { + if (!parser_) { + ESP_LOGE(TAG, "Parser not initialized"); + return 0; + } + + size_t parsed = multipart_parser_execute(parser_, data, len); + + if (parsed != len) { + ESP_LOGW(TAG, "Parser consumed %zu of %zu bytes - possible error", parsed, len); + } + + return parsed; +} + +void MultipartReader::process_header_(const std::string &value) { + // Process the completed header (field + value pair) + if (str_startswith_case_insensitive(current_header_field_, "content-disposition")) { + // Parse name and filename from Content-Disposition + current_part_.name = extract_header_param(value, "name"); + current_part_.filename = extract_header_param(value, "filename"); + } else if (str_startswith_case_insensitive(current_header_field_, "content-type")) { + current_part_.content_type = str_trim(value); + } + + // Clear field for next header + current_header_field_.clear(); +} + +int MultipartReader::on_header_field(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + + // Store the header field name + reader->current_header_field_.assign(at, length); + return 0; +} + +int MultipartReader::on_header_value(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + + // Process the header immediately with the value + std::string value(at, length); + reader->process_header_(value); + + return 0; +} + +int MultipartReader::on_headers_complete(multipart_parser *parser) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + + ESP_LOGV(TAG, "Part headers complete: name='%s', filename='%s', content_type='%s'", + reader->current_part_.name.c_str(), reader->current_part_.filename.c_str(), + reader->current_part_.content_type.c_str()); + + return 0; +} + +int MultipartReader::on_part_data_begin(multipart_parser *parser) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + ESP_LOGV(TAG, "Part data begin"); + return 0; +} + +int MultipartReader::on_part_data(multipart_parser *parser, const char *at, size_t length) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + + // Only process file uploads + if (reader->has_file() && reader->data_callback_) { + // IMPORTANT: The 'at' pointer points to data within the parser's input buffer. + // This data is only valid during this callback. The callback handler MUST + // process or copy the data immediately - it cannot store the pointer for + // later use as the buffer will be overwritten. + reader->data_callback_(reinterpret_cast(at), length); + } + + return 0; +} + +int MultipartReader::on_part_data_end(multipart_parser *parser) { + MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); + + ESP_LOGV(TAG, "Part data end"); + + if (reader->part_complete_callback_) { + reader->part_complete_callback_(); + } + + // Clear part info for next part + reader->current_part_ = Part{}; + + return 0; +} + +// ========== Utility Functions ========== + // Case-insensitive string prefix check bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix) { if (str.length() < prefix.length()) { @@ -171,4 +299,4 @@ std::string str_trim(const std::string &str) { } // namespace web_server_idf } // namespace esphome -#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) \ No newline at end of file diff --git a/esphome/components/web_server_idf/multipart_reader.h b/esphome/components/web_server_idf/multipart.h similarity index 70% rename from esphome/components/web_server_idf/multipart_reader.h rename to esphome/components/web_server_idf/multipart.h index 9d8f52cb1c..0cf727584e 100644 --- a/esphome/components/web_server_idf/multipart_reader.h +++ b/esphome/components/web_server_idf/multipart.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace esphome { namespace web_server_idf { @@ -63,6 +65,26 @@ class MultipartReader { void process_header_(const std::string &value); }; +// ========== Utility Functions ========== + +// Case-insensitive string prefix check +bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix); + +// Find a substring case-insensitively +size_t str_find_case_insensitive(const std::string &haystack, const std::string &needle, size_t pos = 0); + +// Extract a parameter value from a header line +// Handles both quoted and unquoted values +std::string extract_header_param(const std::string &header, const std::string ¶m); + +// Parse boundary from Content-Type header +// Returns true if boundary found, false otherwise +// boundary_start and boundary_len will point to the boundary value +bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len); + +// Trim whitespace from both ends of a string +std::string str_trim(const std::string &str); + } // namespace web_server_idf } // namespace esphome -#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) \ No newline at end of file diff --git a/esphome/components/web_server_idf/multipart_parser_utils.h b/esphome/components/web_server_idf/multipart_parser_utils.h deleted file mode 100644 index 26f7d05b96..0000000000 --- a/esphome/components/web_server_idf/multipart_parser_utils.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include "esphome/core/defines.h" -#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) - -#include -#include -#include - -namespace esphome { -namespace web_server_idf { - -// Case-insensitive string prefix check -bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix); - -// Find a substring case-insensitively -size_t str_find_case_insensitive(const std::string &haystack, const std::string &needle, size_t pos = 0); - -// Extract a parameter value from a header line -// Handles both quoted and unquoted values -std::string extract_header_param(const std::string &header, const std::string ¶m); - -// Parse boundary from Content-Type header -// Returns true if boundary found, false otherwise -// boundary_start and boundary_len will point to the boundary value -bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len); - -// Trim whitespace from both ends of a string -std::string str_trim(const std::string &str); - -} // namespace web_server_idf -} // namespace esphome -#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) diff --git a/esphome/components/web_server_idf/multipart_reader.cpp b/esphome/components/web_server_idf/multipart_reader.cpp deleted file mode 100644 index 4810f34738..0000000000 --- a/esphome/components/web_server_idf/multipart_reader.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "esphome/core/defines.h" -#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) -#include "multipart_reader.h" -#include "multipart_parser_utils.h" -#include "esphome/core/log.h" -#include -#include "multipart_parser.h" - -namespace esphome { -namespace web_server_idf { - -static const char *const TAG = "multipart_reader"; - -MultipartReader::MultipartReader(const std::string &boundary) { - // Initialize settings with callbacks - memset(&settings_, 0, sizeof(settings_)); - settings_.on_header_field = on_header_field; - settings_.on_header_value = on_header_value; - settings_.on_part_data_begin = on_part_data_begin; - settings_.on_part_data = on_part_data; - settings_.on_part_data_end = on_part_data_end; - settings_.on_headers_complete = on_headers_complete; - - ESP_LOGV(TAG, "Initializing multipart parser with boundary: '%s' (len: %zu)", boundary.c_str(), boundary.length()); - - // Create parser with boundary - parser_ = multipart_parser_init(boundary.c_str(), &settings_); - if (parser_) { - multipart_parser_set_data(parser_, this); - } else { - ESP_LOGE(TAG, "Failed to initialize multipart parser"); - } -} - -MultipartReader::~MultipartReader() { - if (parser_) { - multipart_parser_free(parser_); - } -} - -size_t MultipartReader::parse(const char *data, size_t len) { - if (!parser_) { - ESP_LOGE(TAG, "Parser not initialized"); - return 0; - } - - size_t parsed = multipart_parser_execute(parser_, data, len); - - if (parsed != len) { - ESP_LOGW(TAG, "Parser consumed %zu of %zu bytes - possible error", parsed, len); - } - - return parsed; -} - -void MultipartReader::process_header_(const std::string &value) { - // Process the completed header (field + value pair) - if (str_startswith_case_insensitive(current_header_field_, "content-disposition")) { - // Parse name and filename from Content-Disposition - current_part_.name = extract_header_param(value, "name"); - current_part_.filename = extract_header_param(value, "filename"); - } else if (str_startswith_case_insensitive(current_header_field_, "content-type")) { - current_part_.content_type = str_trim(value); - } - - // Clear field for next header - current_header_field_.clear(); -} - -int MultipartReader::on_header_field(multipart_parser *parser, const char *at, size_t length) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - - // Store the header field name - reader->current_header_field_.assign(at, length); - return 0; -} - -int MultipartReader::on_header_value(multipart_parser *parser, const char *at, size_t length) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - - // Process the header immediately with the value - std::string value(at, length); - reader->process_header_(value); - - return 0; -} - -int MultipartReader::on_headers_complete(multipart_parser *parser) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - - ESP_LOGV(TAG, "Part headers complete: name='%s', filename='%s', content_type='%s'", - reader->current_part_.name.c_str(), reader->current_part_.filename.c_str(), - reader->current_part_.content_type.c_str()); - - return 0; -} - -int MultipartReader::on_part_data_begin(multipart_parser *parser) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - ESP_LOGV(TAG, "Part data begin"); - return 0; -} - -int MultipartReader::on_part_data(multipart_parser *parser, const char *at, size_t length) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - - // Only process file uploads - if (reader->has_file() && reader->data_callback_) { - // IMPORTANT: The 'at' pointer points to data within the parser's input buffer. - // This data is only valid during this callback. The callback handler MUST - // process or copy the data immediately - it cannot store the pointer for - // later use as the buffer will be overwritten. - reader->data_callback_(reinterpret_cast(at), length); - } - - return 0; -} - -int MultipartReader::on_part_data_end(multipart_parser *parser) { - MultipartReader *reader = static_cast(multipart_parser_get_data(parser)); - - ESP_LOGV(TAG, "Part data end"); - - if (reader->part_complete_callback_) { - reader->part_complete_callback_(); - } - - // Clear part info for next part - reader->current_part_ = Part{}; - - return 0; -} - -} // namespace web_server_idf -} // namespace esphome -#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index b7f4f2d836..82e73e035a 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -17,8 +17,7 @@ #include "parser_utils.h" #ifdef USE_WEBSERVER_OTA -#include "multipart_reader.h" -#include "multipart_parser_utils.h" +#include "multipart.h" #endif #ifdef USE_WEBSERVER