diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 730ca04..8154b36 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -48,6 +48,7 @@ jobs: source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json libraries: | - name: Arduino_DebugUtils + - name: ArduinoHttpClient sketch-paths: | - examples/OTA_Arduino_Server - examples/OTA_GitHub_Server @@ -58,6 +59,7 @@ jobs: - name: arduino:esp32 libraries: | - name: Arduino_DebugUtils + - name: ArduinoHttpClient sketch-paths: | - examples/OTA_Arduino_Server - examples/OTA_GitHub_Server diff --git a/examples/OTA_Arduino_Server/root_ca.h b/examples/OTA_Arduino_Server/root_ca.h index ff7d2f7..984e71d 100644 --- a/examples/OTA_Arduino_Server/root_ca.h +++ b/examples/OTA_Arduino_Server/root_ca.h @@ -1,88 +1,66 @@ const char* root_ca = \ -/* Baltimore CyberTrust Root */ +/* Google Trust Services LLC */ "-----BEGIN CERTIFICATE-----\n" \ -"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\n" \ "-----END CERTIFICATE-----\n" \ -/* Amazon Root CA 1 */ +/* GTS Root R1 */ "-----BEGIN CERTIFICATE-----\n" \ -"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD\n" \ -"VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1\n" \ -"MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv\n" \ -"bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" \ -"ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH\n" \ -"FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ\n" \ -"gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t\n" \ -"dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce\n" \ -"VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB\n" \ -"/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3\n" \ -"DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM\n" \ -"CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy\n" \ -"8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa\n" \ -"2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2\n" \ -"xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5\n" \ -"-----END CERTIFICATE-----\n" \ -/* Amazon Root CA 2 */ -"-----BEGIN CERTIFICATE-----\n" \ -"MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD\n" \ -"VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1\n" \ -"MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv\n" \ -"bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\n" \ -"ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4\n" \ -"kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp\n" \ -"N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9\n" \ -"AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd\n" \ -"fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx\n" \ -"kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS\n" \ -"btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0\n" \ -"Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN\n" \ -"c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+\n" \ -"3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw\n" \ -"DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA\n" \ -"A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n" \ -"+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE\n" \ -"YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW\n" \ -"xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ\n" \ -"gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW\n" \ -"aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV\n" \ -"Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3\n" \ -"KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi\n" \ -"JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=\n" \ -"-----END CERTIFICATE-----\n" \ -/* Amazon Root CA 3 */ -"-----BEGIN CERTIFICATE-----\n" \ -"MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG\n" \ -"EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy\n" \ -"NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ\n" \ -"MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB\n" \ -"f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr\n" \ -"Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43\n" \ -"rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc\n" \ -"eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==\n" \ -"-----END CERTIFICATE-----\n" \ -/* Amazon Root CA 4 */ -"-----BEGIN CERTIFICATE-----\n" \ -"MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG\n" \ -"EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy\n" \ -"NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ\n" \ -"MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN\n" \ -"/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri\n" \ -"83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \ -"HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA\n" \ -"MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1\n" \ -"AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n" \ +"MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX\n" \ +"MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE\n" \ +"CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx\n" \ +"OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT\n" \ +"GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx\n" \ +"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63\n" \ +"ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS\n" \ +"iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k\n" \ +"KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ\n" \ +"DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk\n" \ +"j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5\n" \ +"cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW\n" \ +"CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499\n" \ +"iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei\n" \ +"Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap\n" \ +"sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b\n" \ +"9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP\n" \ +"BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf\n" \ +"BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw\n" \ +"JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH\n" \ +"MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al\n" \ +"oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy\n" \ +"MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF\n" \ +"AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9\n" \ +"NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9\n" \ +"WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw\n" \ +"9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy\n" \ +"+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi\n" \ +"d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=\n" \ "-----END CERTIFICATE-----\n"; diff --git a/library.properties b/library.properties index 4d7dd98..72d82d1 100644 --- a/library.properties +++ b/library.properties @@ -8,3 +8,4 @@ category=Communication url=https://github.com/arduino-libraries/Arduino_ESP32_OTA architectures=esp32 includes=Arduino_ESP32_OTA.h +depends=ArduinoHttpClient diff --git a/src/Arduino_ESP32_OTA.cpp b/src/Arduino_ESP32_OTA.cpp index d500961..39dd327 100644 --- a/src/Arduino_ESP32_OTA.cpp +++ b/src/Arduino_ESP32_OTA.cpp @@ -22,8 +22,6 @@ #include #include "Arduino_ESP32_OTA.h" #include "tls/amazon_root_ca.h" -#include "decompress/lzss.h" -#include "decompress/utility.h" #include "esp_ota_ops.h" /****************************************************************************** @@ -31,10 +29,9 @@ ******************************************************************************/ Arduino_ESP32_OTA::Arduino_ESP32_OTA() -:_client{nullptr} -,_ota_header{0} -,_ota_size(0) -,_crc32(0) +: _context(nullptr) +, _client(nullptr) +, _http_client(nullptr) ,_ca_cert{amazon_root_ca} ,_ca_cert_bundle{nullptr} ,_magic(0) @@ -42,18 +39,16 @@ Arduino_ESP32_OTA::Arduino_ESP32_OTA() } +Arduino_ESP32_OTA::~Arduino_ESP32_OTA(){ + clean(); +} + /****************************************************************************** PUBLIC MEMBER FUNCTIONS ******************************************************************************/ Arduino_ESP32_OTA::Error Arduino_ESP32_OTA::begin(uint32_t magic) { - /* initialize private variables */ - otaInit(); - - /* ... initialize CRC ... */ - crc32Init(); - /* ... configure board Magic number */ setMagic(magic); @@ -93,38 +88,29 @@ void Arduino_ESP32_OTA::setMagic(uint32_t magic) _magic = magic; } -uint8_t Arduino_ESP32_OTA::read_byte_from_network() -{ - bool is_http_data_timeout = false; - for(unsigned long const start = millis();;) - { - is_http_data_timeout = (millis() - start) > ARDUINO_ESP32_OTA_BINARY_BYTE_RECEIVE_TIMEOUT_ms; - if (is_http_data_timeout) { - DEBUG_ERROR("%s: timeout waiting data", __FUNCTION__); - return -1; - } - if (_client->available()) { - const uint8_t data = _client->read(); - crc32Update(data); - return data; - } - } -} - void Arduino_ESP32_OTA::write_byte_to_flash(uint8_t data) { Update.write(&data, 1); } -int Arduino_ESP32_OTA::download(const char * ota_url) +int Arduino_ESP32_OTA::startDownload(const char * ota_url) { - URI url(ota_url); - int port = 0; + assert(_context == nullptr); + assert(_client == nullptr); + assert(_http_client == nullptr); + + Error err = Error::None; + int statusCode; + int res; + + _context = new Context(ota_url, [this](uint8_t data){ + _context->writtenBytes++; + write_byte_to_flash(data); + }); - if (url.protocol_ == "http") { + if(strcmp(_context->parsed_url.schema(), "http") == 0) { _client = new WiFiClient(); - port = 80; - } else if (url.protocol_ == "https") { + } else if(strcmp(_context->parsed_url.schema(), "https") == 0) { _client = new WiFiClientSecure(); if (_ca_cert != nullptr) { static_cast(_client)->setCACert(_ca_cert); @@ -133,162 +119,230 @@ int Arduino_ESP32_OTA::download(const char * ota_url) } else { DEBUG_VERBOSE("%s: CA not configured for download client"); } - port = 443; } else { - DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, ota_url); - return static_cast(Error::UrlParseError); + err = Error::UrlParseError; + goto exit; } - if (!_client->connect(url.host_.c_str(), port)) - { - DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); - delete _client; - _client = nullptr; - return static_cast(Error::ServerConnectError); + _http_client = new HttpClient(*_client, _context->parsed_url.host(), _context->parsed_url.port()); + + res= _http_client->get(_context->parsed_url.path()); + + if(res == HTTP_ERROR_CONNECTION_FAILED) { + DEBUG_VERBOSE("OTA ERROR: http client error connecting to server \"%s:%d\"", + _context->parsed_url.host(), _context->parsed_url.port()); + err = Error::ServerConnectError; + goto exit; + } else if(res == HTTP_ERROR_TIMED_OUT) { + DEBUG_VERBOSE("OTA ERROR: http client timeout \"%s\"", _context->url); + err = Error::OtaHeaderTimeout; + goto exit; + } else if(res != HTTP_SUCCESS) { + DEBUG_VERBOSE("OTA ERROR: http client returned %d on get \"%s\"", res, _context->url); + err = Error::OtaDownload; + goto exit; } - _client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); - _client->println(String("Host: ") + url.host_.c_str()); - _client->println("Connection: close"); - _client->println(); - - /* Receive HTTP header. */ - String http_header; - bool is_header_complete = false, - is_http_header_timeout = false; - for (unsigned long const start = millis(); !is_header_complete;) - { - is_http_header_timeout = (millis() - start) > ARDUINO_ESP32_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; - if (is_http_header_timeout) break; - - if (_client->available()) - { - char const c = _client->read(); - - http_header += c; - if (http_header.endsWith("\r\n\r\n")) - is_header_complete = true; - } + statusCode = _http_client->responseStatusCode(); + + if(statusCode != 200) { + DEBUG_VERBOSE("OTA ERROR: get response on \"%s\" returned status %d", _context->url, statusCode); + err = Error::HttpResponse; + goto exit; } - if (!is_header_complete) - { - DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":""); - delete _client; - _client = nullptr; - return static_cast(Error::HttpHeaderError); + // The following call is required to save the header value , keep it + if(_http_client->contentLength() == HttpClient::kNoContentLengthHeader) { + DEBUG_VERBOSE("OTA ERROR: the response header doesn't contain \"ContentLength\" field"); + err = Error::HttpHeaderError; + goto exit; } - /* Check HTTP response status code */ - char const * http_response_ptr = strstr(http_header.c_str(), "HTTP/1.1"); - if (!http_response_ptr) - { - DEBUG_ERROR("%s: Failure to extract http response from header", __FUNCTION__); - return static_cast(Error::ParseHttpHeader); +exit: + if(err != Error::None) { + clean(); + return static_cast(err); + } else { + return _http_client->contentLength(); } - /* Find start of numerical value. */ - char * ptr = const_cast(http_response_ptr); - for (ptr += strlen("HTTP/1.1"); (*ptr != '\0') && !isDigit(*ptr); ptr++) { } - /* Extract numerical value. */ - String http_response_str; - for (; isDigit(*ptr); ptr++) http_response_str += *ptr; - int const http_response = atoi(http_response_str.c_str()); - - if (http_response != 200) { - DEBUG_ERROR("%s: HTTP response status code = %d", __FUNCTION__, http_response); - return static_cast(Error::HttpResponse); +} + +int Arduino_ESP32_OTA::downloadPoll() +{ + int http_res = static_cast(Error::None);; + int res = 0; + + if(_http_client->available() == 0) { + goto exit; } - /* Extract content length from HTTP header. A typical entry looks like - * "Content-Length: 123456" - */ - char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length"); - if (!content_length_ptr) - { - DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__); - delete _client; - _client = nullptr; - return static_cast(Error::ParseHttpHeader); + http_res = _http_client->read(_context->buffer, _context->buf_len); + + if(http_res < 0) { + DEBUG_VERBOSE("OTA ERROR: Download read error %d", http_res); + res = static_cast(Error::OtaDownload); + goto exit; } - /* Find start of numerical value. */ - ptr = const_cast(content_length_ptr); - for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { } - /* Extract numerical value. */ - String content_length_str; - for (; isDigit(*ptr); ptr++) content_length_str += *ptr; - int const content_length_val = atoi(content_length_str.c_str()); - DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val); - - /* Read the OTA header ... */ - bool is_ota_header_timeout = false; - unsigned long const start = millis(); - for (int i = 0; i < sizeof(OtaHeader);) - { - is_ota_header_timeout = (millis() - start) > ARDUINO_ESP32_OTA_BINARY_HEADER_RECEIVE_TIMEOUT_ms; - if (is_ota_header_timeout) break; - - if (_client->available()) - { - _ota_header.buf[i++] = _client->read(); + + for(uint8_t* cursor=(uint8_t*)_context->buffer; cursor<_context->buffer+http_res; ) { + switch(_context->downloadState) { + case OtaDownloadHeader: { + uint32_t copied = http_res < sizeof(_context->header.buf) ? http_res : sizeof(_context->header.buf); + memcpy(_context->header.buf+_context->headerCopiedBytes, _context->buffer, copied); + cursor += copied; + _context->headerCopiedBytes += copied; + + // when finished go to next state + if(sizeof(_context->header.buf) == _context->headerCopiedBytes) { + _context->downloadState = OtaDownloadFile; + + _context->calculatedCrc32 = crc_update( + _context->calculatedCrc32, + &(_context->header.header.magic_number), + sizeof(_context->header) - offsetof(OtaHeader, header.magic_number) + ); + + if(_context->header.header.magic_number != _magic) { + _context->downloadState = OtaDownloadMagicNumberMismatch; + res = static_cast(Error::OtaHeaderMagicNumber); + + goto exit; + } + } + + break; + } + case OtaDownloadFile: + _context->decoder.decompress(cursor, http_res - (cursor-_context->buffer)); // TODO verify return value + + _context->calculatedCrc32 = crc_update( + _context->calculatedCrc32, + cursor, + http_res - (cursor-_context->buffer) + ); + + cursor += http_res - (cursor-_context->buffer); + _context->downloadedSize += (cursor-_context->buffer); + + // TODO there should be no more bytes available when the download is completed + if(_context->downloadedSize == _http_client->contentLength()) { + _context->downloadState = OtaDownloadCompleted; + res = 1; + } + + if(_context->downloadedSize > _http_client->contentLength()) { + _context->downloadState = OtaDownloadError; + res = static_cast(Error::OtaDownload); + } + // TODO fail if we exceed a timeout? and available is 0 (client is broken) + break; + case OtaDownloadCompleted: + res = 1; + goto exit; + default: + _context->downloadState = OtaDownloadError; + res = static_cast(Error::OtaDownload); + goto exit; } } - /* ... check for header download timeout ... */ - if (is_ota_header_timeout) { - delete _client; - _client = nullptr; - return static_cast(Error::OtaHeaderTimeout); +exit: + if(_context->downloadState == OtaDownloadError || + _context->downloadState == OtaDownloadMagicNumberMismatch) { + clean(); // need to clean everything because the download failed + } else if(_context->downloadState == OtaDownloadCompleted) { + // only need to delete clients and not the context, since it will be needed + if(_client != nullptr) { + delete _client; + _client = nullptr; + } + + if(_http_client != nullptr) { + delete _http_client; + _http_client = nullptr; + } } - /* ... then check if OTA header length field matches HTTP content length... */ - if (_ota_header.header.len != (content_length_val - sizeof(_ota_header.header.len) - sizeof(_ota_header.header.crc32))) { - delete _client; - _client = nullptr; - return static_cast(Error::OtaHeaderLength); + return res; +} + +int Arduino_ESP32_OTA::downloadProgress() +{ + if(_context->error != Error::None) { + return static_cast(_context->error); + } else { + return _context->downloadedSize; } +} - /* ... and OTA magic number */ - if (_ota_header.header.magic_number != _magic) - { - delete _client; - _client = nullptr; - return static_cast(Error::OtaHeaterMagicNumber); +size_t Arduino_ESP32_OTA::downloadSize() +{ + return _http_client!=nullptr ? _http_client->contentLength() : 0; +} + +int Arduino_ESP32_OTA::download(const char * ota_url) +{ + int err = startDownload(ota_url); + + if(err < 0) { + return err; } - /* ... start CRC32 from OTA MAGIC ... */ - _crc32 = crc_update(_crc32, &_ota_header.header.magic_number, 12); + int res = 0; + while((res = downloadPoll()) <= 0); - /* Download and decode OTA file */ - _ota_size = lzss_download(this, content_length_val - sizeof(_ota_header)); + return res == 1? _context->writtenBytes : res; +} - if(_ota_size <= content_length_val - sizeof(_ota_header)) - { +void Arduino_ESP32_OTA::clean() +{ + if(_client != nullptr) { delete _client; _client = nullptr; - return static_cast(Error::OtaDownload); } - delete _client; - _client = nullptr; - return _ota_size; + if(_http_client != nullptr) { + delete _http_client; + _http_client = nullptr; + } + + if(_context != nullptr) { + delete _context; + _context = nullptr; + } } -Arduino_ESP32_OTA::Error Arduino_ESP32_OTA::update() +Arduino_ESP32_OTA::Error Arduino_ESP32_OTA::verify() { + assert(_context != nullptr); + /* ... then finalize ... */ - crc32Finalize(); + _context->calculatedCrc32 ^= 0xFFFFFFFF; - if(!crc32Verify()) { + /* Verify the crc */ + if(_context->header.header.crc32 != _context->calculatedCrc32) { DEBUG_ERROR("%s: CRC32 mismatch", __FUNCTION__); return Error::OtaHeaderCrc; } + clean(); + + return Error::None; +} + +Arduino_ESP32_OTA::Error Arduino_ESP32_OTA::update() +{ + Arduino_ESP32_OTA::Error res = Error::None; + if(_context != nullptr && (res = verify()) != Error::None) { + return res; + } + if (!Update.end(true)) { DEBUG_ERROR("%s: Failure to apply OTA update", __FUNCTION__); return Error::OtaStorageEnd; } - return Error::None; + return res; } void Arduino_ESP32_OTA::reset() @@ -307,28 +361,20 @@ bool Arduino_ESP32_OTA::isCapable() PROTECTED MEMBER FUNCTIONS ******************************************************************************/ -void Arduino_ESP32_OTA::otaInit() -{ - _ota_size = 0; - _ota_header = {0}; -} - -void Arduino_ESP32_OTA::crc32Init() -{ - _crc32 = 0xFFFFFFFF; -} - -void Arduino_ESP32_OTA::crc32Update(const uint8_t data) -{ - _crc32 = crc_update(_crc32, &data, 1); -} - -void Arduino_ESP32_OTA::crc32Finalize() -{ - _crc32 ^= 0xFFFFFFFF; -} +Arduino_ESP32_OTA::Context::Context( + const char* url, std::function putc) + : url((char*)malloc(strlen(url)+1)) + , parsed_url(url) + , downloadState(OtaDownloadHeader) + , calculatedCrc32(0xFFFFFFFF) + , headerCopiedBytes(0) + , downloadedSize(0) + , error(Error::None) + , decoder(putc) { + strcpy(this->url, url); + } -bool Arduino_ESP32_OTA::crc32Verify() -{ - return (_crc32 == _ota_header.header.crc32); -} +Arduino_ESP32_OTA::Context::~Context(){ + free(url); + url = nullptr; +} \ No newline at end of file diff --git a/src/Arduino_ESP32_OTA.h b/src/Arduino_ESP32_OTA.h index 34046d8..f82c6e1 100644 --- a/src/Arduino_ESP32_OTA.h +++ b/src/Arduino_ESP32_OTA.h @@ -25,6 +25,10 @@ #include #include #include "decompress/utility.h" +#include "decompress/lzss.h" +#include +#include +#include /****************************************************************************** DEFINES @@ -63,43 +67,93 @@ class Arduino_ESP32_OTA ParseHttpHeader = -8, OtaHeaderLength = -9, OtaHeaderCrc = -10, - OtaHeaterMagicNumber = -11, + OtaHeaderMagicNumber = -11, OtaDownload = -12, OtaHeaderTimeout = -13, HttpResponse = -14 }; + enum OTADownloadState: uint8_t { + OtaDownloadHeader, + OtaDownloadFile, + OtaDownloadCompleted, + OtaDownloadMagicNumberMismatch, + OtaDownloadError + }; + Arduino_ESP32_OTA(); - virtual ~Arduino_ESP32_OTA() { } + virtual ~Arduino_ESP32_OTA(); Arduino_ESP32_OTA::Error begin(uint32_t magic = ARDUINO_ESP32_OTA_MAGIC); void setMagic(uint32_t magic); void setCACert(const char *rootCA); void setCACertBundle(const uint8_t * bundle); + + // blocking version for the download + // returns the size of the downloaded binary int download(const char * ota_url); - uint8_t read_byte_from_network(); + + // start a download in a non blocking fashion + // call downloadPoll, until it returns OtaDownloadCompleted + // returns the value in content-length http header + int startDownload(const char * ota_url); + + // This function is used to make the download progress. + // it returns 0, if the download is in progress + // it returns 1, if the download is completed + // it returns <0 if an error occurred, following Error enum values + virtual int downloadPoll(); + + // this function is used to get the progress of the download + // it returns a positive value when the download is progressing correctly + // it returns a negative value on error following Error enum values + int downloadProgress(); + + // this function is used to get the size of the download + // 0 if no download is in progress + size_t downloadSize(); + virtual void write_byte_to_flash(uint8_t data); + Arduino_ESP32_OTA::Error verify(); Arduino_ESP32_OTA::Error update(); void reset(); static bool isCapable(); protected: + struct Context { + Context( + const char* url, + std::function putc); + + ~Context(); + + char* url; + ParsedUrl parsed_url; + OtaHeader header; + OTADownloadState downloadState; + uint32_t calculatedCrc32; + uint32_t headerCopiedBytes; + uint32_t downloadedSize; + uint32_t writtenBytes; + + // If an error occurred during download it is reported in this field + Error error; + + // LZSS decoder + LZSSDecoder decoder; - void otaInit(); - void crc32Init(); - void crc32Update(const uint8_t data); - void crc32Finalize(); - bool crc32Verify(); + const size_t buf_len = 64; + uint8_t buffer[64]; + } *_context; private: Client * _client; - OtaHeader _ota_header; - size_t _ota_size; - uint32_t _crc32; + HttpClient* _http_client; const char * _ca_cert; const uint8_t * _ca_cert_bundle; uint32_t _magic; + void clean(); }; #endif /* ARDUINO_ESP32_OTA_H_ */ diff --git a/src/decompress/lzss.cpp b/src/decompress/lzss.cpp index d453c15..768b1c0 100644 --- a/src/decompress/lzss.cpp +++ b/src/decompress/lzss.cpp @@ -1,169 +1,163 @@ -/* Original code: https://oku.edu.mie-u.ac.jp/~okumura/compression/lzss.c */ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + This implementation took inspiration from https://okumuralab.org/~okumura/compression/lzss.c source code +*/ /************************************************************************************** INCLUDE **************************************************************************************/ - #include "lzss.h" +#include + /************************************************************************************** - DEFINE + LZSS DECODER CLASS IMPLEMENTATION **************************************************************************************/ -#define EI 11 /* typically 10..13 */ -#define EJ 4 /* typically 4..5 */ -#define P 1 /* If match length <= P then output one character */ -#define N (1 << EI) /* buffer size */ -#define L ((1 << EJ) + 1) /* lookahead buffer size */ - -#define LZSS_EOF (-1) +// get the number of bits the algorithm will try to get given the state +uint8_t LZSSDecoder::bits_required(LZSSDecoder::FSM_STATES s) { + switch(s) { + case FSM_0: + return 1; + case FSM_1: + return 8; + case FSM_2: + return EI; + case FSM_3: + return EJ; + default: + return 0; + } +} -/************************************************************************************** - GLOBAL VARIABLES - **************************************************************************************/ +LZSSDecoder::LZSSDecoder(std::function getc_cbk, std::function putc_cbk) +: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(getc_cbk) { + for (int i = 0; i < N - F; i++) buffer[i] = ' '; + r = N - F; +} -/* Used to bind local module function to actual class instance */ -static Arduino_ESP32_OTA * esp_ota_obj_ptr = 0; -static size_t LZSS_FILE_SIZE = 0; +LZSSDecoder::LZSSDecoder(std::function putc_cbk) +: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(nullptr) { + for (int i = 0; i < N - F; i++) buffer[i] = ' '; + r = N - F; +} -int bit_buffer = 0, bit_mask = 128; -unsigned char buffer[N * 2]; +LZSSDecoder::status LZSSDecoder::handle_state() { + LZSSDecoder::status res = IN_PROGRESS; -static size_t bytes_written_fputc = 0; -static size_t bytes_read_fgetc = 0; + int c = getbit(bits_required(this->state)); -/************************************************************************************** - PRIVATE FUNCTIONS - **************************************************************************************/ + if(c == LZSS_BUFFER_EMPTY) { + res = NOT_COMPLETED; + } else if (c == LZSS_EOF) { + res = DONE; + this->state = FSM_EOF; + } else { + switch(this->state) { + case FSM_0: + if(c) { + this->state = FSM_1; + } else { + this->state = FSM_2; + } + break; + case FSM_1: + putc(c); + buffer[r++] = c; + r &= (N - 1); // equivalent to r = r % N when N is a power of 2 + + this->state = FSM_0; + break; + case FSM_2: + this->i = c; + this->state = FSM_3; + break; + case FSM_3: { + int j = c; + + // This is where the actual decompression takes place: we look into the local buffer for reuse + // of byte chunks. This can be improved by means of memcpy and by changing the putc function + // into a put_buf function in order to avoid buffering on the other end. + // TODO improve this section of code + for (int k = 0; k <= j + 1; k++) { + c = buffer[(this->i + k) & (N - 1)]; // equivalent to buffer[(i+k) % N] when N is a power of 2 + putc(c); + buffer[r++] = c; + r &= (N - 1); // equivalent to r = r % N + } + this->state = FSM_0; + + break; + } + case FSM_EOF: + break; + } + } -void lzss_fputc(int const c) -{ - esp_ota_obj_ptr->write_byte_to_flash((uint8_t)c); - - /* write byte callback */ - bytes_written_fputc++; + return res; } -int lzss_fgetc() -{ - /* lzss_file_size is set within SSUBoot:main - * and contains the size of the LZSS file. Once - * all those bytes have been read its time to return - * LZSS_EOF in order to signal that the end of - * the file has been reached. - */ - if (bytes_read_fgetc == LZSS_FILE_SIZE) - return LZSS_EOF; - - /* read byte callback */ - uint8_t const c = esp_ota_obj_ptr->read_byte_from_network(); - bytes_read_fgetc++; - - return c; -} +LZSSDecoder::status LZSSDecoder::decompress(uint8_t* const buffer, uint32_t size) { + if(!get_char_cbk) { + this->in_buffer = buffer; + this->available += size; + } -/************************************************************************************** - LZSS FUNCTIONS - **************************************************************************************/ + status res = IN_PROGRESS; -void putbit1(void) -{ - bit_buffer |= bit_mask; - if ((bit_mask >>= 1) == 0) { - lzss_fputc(bit_buffer); - bit_buffer = 0; bit_mask = 128; - } -} + while((res = handle_state()) == IN_PROGRESS); -void putbit0(void) -{ - if ((bit_mask >>= 1) == 0) { - lzss_fputc(bit_buffer); - bit_buffer = 0; bit_mask = 128; - } -} + this->in_buffer = nullptr; -void output1(int c) -{ - int mask; - - putbit1(); - mask = 256; - while (mask >>= 1) { - if (c & mask) putbit1(); - else putbit0(); - } + return res; } -void output2(int x, int y) -{ - int mask; - - putbit0(); - mask = N; - while (mask >>= 1) { - if (x & mask) putbit1(); - else putbit0(); - } - mask = (1 << EJ); - while (mask >>= 1) { - if (y & mask) putbit1(); - else putbit0(); - } -} +int LZSSDecoder::getbit(uint8_t n) { // get n bits from buffer + int x=0, c; -int getbit(int n) /* get n bits */ -{ - int i, x; - static int buf, mask = 0; - - x = 0; - for (i = 0; i < n; i++) { - if (mask == 0) { - if ((buf = lzss_fgetc()) == LZSS_EOF) return LZSS_EOF; - mask = 128; - } - x <<= 1; - if (buf & mask) x++; - mask >>= 1; - } - return x; -} + // if the local bit buffer doesn't have enough bit get them + while(buf_size < n) { + switch(c=getc()) { + case LZSS_EOF: + case LZSS_BUFFER_EMPTY: + return c; + } + buf <<= 8; -void lzss_decode(void) -{ - int i, j, k, r, c; - - for (i = 0; i < N - L; i++) buffer[i] = ' '; - r = N - L; - while ((c = getbit(1)) != LZSS_EOF) { - if (c) { - if ((c = getbit(8)) == LZSS_EOF) break; - lzss_fputc(c); - buffer[r++] = c; r &= (N - 1); - } else { - if ((i = getbit(EI)) == LZSS_EOF) break; - if ((j = getbit(EJ)) == LZSS_EOF) break; - for (k = 0; k <= j + 1; k++) { - c = buffer[(i + k) & (N - 1)]; - lzss_fputc(c); - buffer[r++] = c; r &= (N - 1); - } + buf |= (uint8_t)c; + buf_size += sizeof(uint8_t)*8; } - } + + // the result is the content of the buffer starting from msb to n successive bits + x = buf >> (buf_size-n); + + // remove from the buffer the read bits with a mask + buf &= (1<<(buf_size-n))-1; + + buf_size-=n; + + return x; } -/************************************************************************************** - PUBLIC FUNCTIONS - **************************************************************************************/ +int LZSSDecoder::getc() { + int c; -int lzss_download(Arduino_ESP32_OTA * instance, size_t const lzss_file_size) -{ - esp_ota_obj_ptr = instance; - LZSS_FILE_SIZE = lzss_file_size; - bytes_written_fputc = 0; - bytes_read_fgetc = 0; - lzss_decode(); - return bytes_written_fputc; + if(get_char_cbk) { + c = get_char_cbk(); + } else if(in_buffer == nullptr || available == 0) { + c = LZSS_BUFFER_EMPTY; + } else { + c = *in_buffer; + in_buffer++; + available--; + } + return c; } diff --git a/src/decompress/lzss.h b/src/decompress/lzss.h index e88cf20..01c4fdc 100644 --- a/src/decompress/lzss.h +++ b/src/decompress/lzss.h @@ -1,16 +1,108 @@ -#ifndef SSU_LZSS_H_ -#define SSU_LZSS_H_ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once /************************************************************************************** INCLUDE **************************************************************************************/ - -#include "Arduino_ESP32_OTA.h" +#include +#include +#include /************************************************************************************** FUNCTION DEFINITION **************************************************************************************/ -int lzss_download(Arduino_ESP32_OTA * instance, size_t const lzss_file_size); +/************************************************************************************** + LZSS DECODER CLASS + **************************************************************************************/ + + +class LZSSDecoder { +public: + + /** + * Build an LZSS decoder by providing a callback for storing the decoded bytes + * @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite + */ + LZSSDecoder(std::function putc_cbk); + + /** + * Build an LZSS decoder providing a callback for getting a char and putting a char + * in this way you need to call decompress with no parameters + * @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite + * @param getc_cbk: a callback that returns the next char to consume + * -1 means EOF, -2 means buffer is temporairly finished + */ + LZSSDecoder(std::function getc_cbk, std::function putc_cbk); + + /** + * this enum describes the result of the computation of a single FSM computation + * DONE: the decompression is completed + * IN_PROGRESS: the decompression cycle completed successfully, ready to compute next + * NOT_COMPLETED: the current cycle didn't complete because the available data is not enough + */ + enum status: uint8_t { + DONE, + IN_PROGRESS, + NOT_COMPLETED + }; + + /** + * decode the provided buffer until buffer ends, then pause the process + * @return DONE if the decompression is completed, NOT_COMPLETED if not + */ + status decompress(uint8_t* const buffer=nullptr, uint32_t size=0); + + static const int LZSS_EOF = -1; + static const int LZSS_BUFFER_EMPTY = -2; +private: + // TODO provide a way for the user to set these parameters + static const int EI = 11; /* typically 10..13 */ + static const int EJ = 4; /* typically 4..5 */ + static const int N = (1 << EI); /* buffer size */ + static const int F = ((1 << EJ) + 1); /* lookahead buffer size */ + + // algorithm specific buffer used to store text that could be later referenced and copied + uint8_t buffer[N * 2]; + + // this function gets 1 single char from the input buffer + int getc(); + uint8_t* in_buffer = nullptr; + uint32_t available = 0; + + status handle_state(); + + // get 1 bit from the available input buffer + int getbit(uint8_t n); + // the following 2 are variables used by getbits + uint32_t buf, buf_size=0; + + enum FSM_STATES: uint8_t { + FSM_0 = 0, + FSM_1 = 1, + FSM_2 = 2, + FSM_3 = 3, + FSM_EOF + } state; + + // these variable are used in a decode session and specific to the old C implementation + // there is no documentation about their meaning + int i, r; + + std::function put_char_cbk; + std::function get_char_cbk; + + inline void putc(const uint8_t c) { if(put_char_cbk) { put_char_cbk(c); } } -#endif /* SSU_LZSS_H_ */ + // get the number of bits the FSM will require given its state + uint8_t bits_required(FSM_STATES s); +}; diff --git a/src/decompress/utility.cpp b/src/decompress/utility.cpp index b8dbd0f..77bd3f3 100644 --- a/src/decompress/utility.cpp +++ b/src/decompress/utility.cpp @@ -79,32 +79,3 @@ uint32_t crc_update(uint32_t crc, const void * data, size_t data_len) return crc & 0xffffffff; } - -/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ -using namespace std; - -// ctors, copy, equality, ... -// TODO: change me into something embedded friendly (this function adds ~100KB to flash) -void URI::parse(const string& url_s) -{ - const string prot_end("://"); - string::const_iterator prot_i = search(url_s.begin(), url_s.end(), - prot_end.begin(), prot_end.end()); - protocol_.reserve(distance(url_s.begin(), prot_i)); - transform(url_s.begin(), prot_i, - back_inserter(protocol_), - ptr_fun(tolower)); // protocol is icase - if( prot_i == url_s.end() ) - return; - advance(prot_i, prot_end.length()); - string::const_iterator path_i = find(prot_i, url_s.end(), '/'); - host_.reserve(distance(prot_i, path_i)); - transform(prot_i, path_i, - back_inserter(host_), - ptr_fun(tolower)); // host is icase - string::const_iterator query_i = find(path_i, url_s.end(), '?'); - path_.assign(path_i, query_i); - if( query_i != url_s.end() ) - ++query_i; - query_.assign(query_i, url_s.end()); -} diff --git a/src/decompress/utility.h b/src/decompress/utility.h index 54d2331..9275bb4 100644 --- a/src/decompress/utility.h +++ b/src/decompress/utility.h @@ -1,24 +1,12 @@ #ifndef ESP32_OTA_UTILITY_H_ #define ESP32_OTA_UTILITY_H_ +#include +#include + /************************************************************************************** INCLUDE **************************************************************************************/ -#include -#include -#include -#include -#include - -struct URI { - public: - URI(const std::string& url_s) { - this->parse(url_s); - } - std::string protocol_, host_, path_, query_; - private: - void parse(const std::string& url_s); -}; union HeaderVersion {