Rename HttpClientLight

This commit is contained in:
Theo Arends
2023-05-21 16:51:16 +02:00
parent 22abc8ad99
commit a39d1eee0e
7 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,17 @@
{
"name": "HttpClient light",
"version": "1.0",
"description": "Forked version of Arduino HttpClient to support BearSSL instead of mbedTLS",
"license": "MIT",
"homepage": "https://github.com/arendst/Tasmota",
"frameworks": "arduino",
"platforms": "espressif32",
"authors":
{
"name": "Stephan Hadinger",
"maintainer": true
},
"build": {
"flags": [ "-I$PROJECT_DIR/include" ]
}
}

View File

@@ -0,0 +1,474 @@
/**
*
* @file HTTPUpdate.cpp based om ESP8266HTTPUpdate.cpp
* @date 16.10.2018
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP32 Http Updater.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "HTTPUpdateLight.h"
#include <StreamString.h>
#include <esp_partition.h>
#include <esp_ota_ops.h> // get running partition
// Tasmota Logging
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
// To do extern "C" uint32_t _SPIFFS_start;
// To do extern "C" uint32_t _SPIFFS_end;
HTTPUpdateLight::HTTPUpdateLight(void)
: _httpClientTimeout(8000), _ledPin(-1)
{
_followRedirects = HTTPC_STRICT_FOLLOW_REDIRECTS;
}
HTTPUpdateLight::HTTPUpdateLight(int httpClientTimeout)
: _httpClientTimeout(httpClientTimeout), _ledPin(-1)
{
_followRedirects = HTTPC_STRICT_FOLLOW_REDIRECTS;
}
HTTPUpdateLight::~HTTPUpdateLight(void)
{
}
// HTTPUpdateResult HTTPUpdateLight::update(WiFiClient& client, const String& url, const String& currentVersion)
// {
// HTTPClient http;
// if(!http.begin(client, url))
// {
// return HTTP_UPDATE_FAILED;
// }
// return handleUpdate(http, currentVersion, false);
// }
// HTTPUpdateResult HTTPUpdateLight::updateSpiffs(HTTPClient& httpClient, const String& currentVersion)
// {
// return handleUpdate(httpClient, currentVersion, true);
// }
// HTTPUpdateResult HTTPUpdateLight::updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion)
// {
// HTTPClient http;
// if(!http.begin(client, url))
// {
// return HTTP_UPDATE_FAILED;
// }
// return handleUpdate(http, currentVersion, true);
// }
HTTPUpdateResult HTTPUpdateLight::update(HTTPClientLight& httpClient,
const String& currentVersion)
{
return handleUpdate(httpClient, currentVersion, false);
}
// HTTPUpdateResult HTTPUpdateLight::update(WiFiClient& client, const String& host, uint16_t port, const String& uri,
// const String& currentVersion)
// {
// HTTPClient http;
// if(!http.begin(client, host, port, uri))
// {
// return HTTP_UPDATE_FAILED;
// }
// return handleUpdate(http, currentVersion, false);
// }
/**
* return error code as int
* @return int error code
*/
int HTTPUpdateLight::getLastError(void)
{
return _lastError;
}
/**
* return error code as String
* @return String error
*/
String HTTPUpdateLight::getLastErrorString(void)
{
if(_lastError == 0) {
return String(); // no error
}
// error from Update class
if(_lastError > 0) {
StreamString error;
TasUpdate.printError(error);
error.trim(); // remove line ending
return String("Update error: ") + error;
}
// error from http client
if(_lastError > -100) {
return String("HTTP error: ") + HTTPClientLight::errorToString(_lastError);
}
switch(_lastError) {
case HTTP_UE_TOO_LESS_SPACE:
return "Not Enough space";
case HTTP_UE_SERVER_NOT_REPORT_SIZE:
return "Server Did Not Report Size";
case HTTP_UE_SERVER_FILE_NOT_FOUND:
return "File Not Found (404)";
case HTTP_UE_SERVER_FORBIDDEN:
return "Forbidden (403)";
case HTTP_UE_SERVER_WRONG_HTTP_CODE:
return "Wrong HTTP Code";
case HTTP_UE_SERVER_FAULTY_MD5:
return "Wrong MD5";
case HTTP_UE_BIN_VERIFY_HEADER_FAILED:
return "Verify Bin Header Failed";
case HTTP_UE_BIN_FOR_WRONG_FLASH:
return "New Binary Does Not Fit Flash Size";
case HTTP_UE_NO_PARTITION:
return "Partition Could Not be Found";
}
return String();
}
extern String getSketchSHA256();
// String getSketchSHA256() {
// const size_t HASH_LEN = 32; // SHA-256 digest length
// uint8_t sha_256[HASH_LEN] = { 0 };
// // get sha256 digest for running partition
// if(esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256) == 0) {
// char buffer[2 * HASH_LEN + 1];
// for(size_t index = 0; index < HASH_LEN; index++) {
// uint8_t nibble = (sha_256[index] & 0xf0) >> 4;
// buffer[2 * index] = nibble < 10 ? char(nibble + '0') : char(nibble - 10 + 'A');
// nibble = sha_256[index] & 0x0f;
// buffer[2 * index + 1] = nibble < 10 ? char(nibble + '0') : char(nibble - 10 + 'A');
// }
// buffer[2 * HASH_LEN] = '\0';
// return String(buffer);
// } else {
// return String();
// }
// }
/**
*
* @param http HTTPClientLight *
* @param currentVersion const char *
* @return HTTPUpdateResult
*/
HTTPUpdateResult HTTPUpdateLight::handleUpdate(HTTPClientLight& http, const String& currentVersion, bool spiffs)
{
HTTPUpdateResult ret = HTTP_UPDATE_FAILED;
// use HTTP/1.0 for update since the update handler not support any transfer Encoding
http.useHTTP10(true);
http.setTimeout(_httpClientTimeout);
http.setFollowRedirects(_followRedirects);
http.setUserAgent("ESP32-http-Update");
http.addHeader("Cache-Control", "no-cache");
http.addHeader("x-ESP32-STA-MAC", WiFi.macAddress());
http.addHeader("x-ESP32-AP-MAC", WiFi.softAPmacAddress());
http.addHeader("x-ESP32-free-space", String(ESP.getFreeSketchSpace()));
http.addHeader("x-ESP32-sketch-size", String(ESP.getSketchSize()));
String sketchMD5 = ESP.getSketchMD5();
if(sketchMD5.length() != 0) {
http.addHeader("x-ESP32-sketch-md5", sketchMD5);
}
// Add also a SHA256
String sketchSHA256 = getSketchSHA256();
if(sketchSHA256.length() != 0) {
http.addHeader("x-ESP32-sketch-sha256", sketchSHA256);
}
http.addHeader("x-ESP32-chip-size", String(ESP.getFlashChipSize()));
http.addHeader("x-ESP32-sdk-version", ESP.getSdkVersion());
if(spiffs) {
http.addHeader("x-ESP32-mode", "spiffs");
} else {
http.addHeader("x-ESP32-mode", "sketch");
}
if(currentVersion && currentVersion[0] != 0x00) {
http.addHeader("x-ESP32-version", currentVersion);
}
const char * headerkeys[] = { "x-MD5" };
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
// track these headers
http.collectHeaders(headerkeys, headerkeyssize);
uint32_t http_connect_time = millis();
int code = http.GET(); // 0 if ok or < 0 if error
int len = http.getSize(); // -1 if no info or > 0 when Content-Length is set by server
// Add specific logging for Tasmota
if (len < 0) { // -1 if no info or > 0 when Content-Length is set by server
if (code <= -1000) { // BearSSL error 46 transformed to -1046
AddLog(LOG_LEVEL_INFO, "OTA: TLS connection error %d after %d ms", -code - 1000, millis() - http_connect_time);
} else if (code == -1) { // HTTPC_ERROR_CONNECTION_REFUSED
AddLog(LOG_LEVEL_INFO, "OTA: Connection timeout after %d ms", millis() - http_connect_time);
} else {
AddLog(LOG_LEVEL_INFO, "OTA: Connection error %d after %d ms", code, millis() - http_connect_time);
}
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("OTA: Connected in %d ms, stack low mark %d"),
millis() - http_connect_time, uxTaskGetStackHighWaterMark(nullptr));
}
if(code <= 0) {
// log_e("HTTP error: %s\n", http.errorToString(code).c_str());
_lastError = code;
http.end();
return HTTP_UPDATE_FAILED;
}
log_d("Header read fin.\n");
log_d("Server header:\n");
log_d(" - code: %d\n", code);
log_d(" - len: %d\n", len);
if(http.hasHeader("x-MD5")) {
log_d(" - MD5: %s\n", http.header("x-MD5").c_str());
}
log_d("ESP32 info:\n");
log_d(" - free Space: %d\n", ESP.getFreeSketchSpace());
log_d(" - current Sketch Size: %d\n", ESP.getSketchSize());
if(currentVersion && currentVersion[0] != 0x00) {
log_d(" - current version: %s\n", currentVersion.c_str() );
}
switch(code) {
case HTTP_CODE_OK: ///< OK (Start Update)
if(len > 0) {
bool startUpdate = true;
if(spiffs) {
const esp_partition_t* _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
if(!_partition){
_lastError = HTTP_UE_NO_PARTITION;
return HTTP_UPDATE_FAILED;
}
if(len > _partition->size) {
log_e("spiffsSize to low (%d) needed: %d\n", _partition->size, len);
startUpdate = false;
}
} else {
int sketchFreeSpace = ESP.getFreeSketchSpace();
if(!sketchFreeSpace){
_lastError = HTTP_UE_NO_PARTITION;
return HTTP_UPDATE_FAILED;
}
if(len > sketchFreeSpace) {
log_e("FreeSketchSpace to low (%d) needed: %d\n", sketchFreeSpace, len);
startUpdate = false;
}
}
if(!startUpdate) {
_lastError = HTTP_UE_TOO_LESS_SPACE;
ret = HTTP_UPDATE_FAILED;
} else {
// Warn main app we're starting up...
if (_cbStart) {
_cbStart();
}
WiFiClient * tcp = http.getStreamPtr();
// To do? WiFiUDP::stopAll();
// To do? WiFiClient::stopAllExcept(tcp);
delay(100);
int command;
if(spiffs) {
command = U_SPIFFS;
log_d("runUpdate spiffs...\n");
} else {
command = U_FLASH;
log_d("runUpdate flash...\n");
}
if(!spiffs) {
/* To do
uint8_t buf[4];
if(tcp->peekBytes(&buf[0], 4) != 4) {
log_e("peekBytes magic header failed\n");
_lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
http.end();
return HTTP_UPDATE_FAILED;
}
*/
// check for valid first magic byte
// if(buf[0] != 0xE9) {
if(tcp->peek() != 0xE9) {
log_e("Magic header does not start with 0xE9\n");
_lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
http.end();
return HTTP_UPDATE_FAILED;
}
/* To do
uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);
// check if new bin fits to SPI flash
if(bin_flash_size > ESP.getFlashChipRealSize()) {
log_e("New binary does not fit SPI Flash size\n");
_lastError = HTTP_UE_BIN_FOR_WRONG_FLASH;
http.end();
return HTTP_UPDATE_FAILED;
}
*/
}
if(runUpdate(*tcp, len, http.header("x-MD5"), command)) {
ret = HTTP_UPDATE_OK;
log_d("Update ok\n");
http.end();
// Warn main app we're all done
if (_cbEnd) {
_cbEnd();
}
if(_rebootOnUpdate && !spiffs) {
ESP.restart();
}
} else {
ret = HTTP_UPDATE_FAILED;
log_e("Update failed\n");
}
}
} else {
_lastError = HTTP_UE_SERVER_NOT_REPORT_SIZE;
ret = HTTP_UPDATE_FAILED;
log_e("Content-Length was 0 or wasn't set by Server?!\n");
}
break;
case HTTP_CODE_NOT_MODIFIED:
///< Not Modified (No updates)
ret = HTTP_UPDATE_NO_UPDATES;
break;
case HTTP_CODE_NOT_FOUND:
_lastError = HTTP_UE_SERVER_FILE_NOT_FOUND;
ret = HTTP_UPDATE_FAILED;
break;
case HTTP_CODE_FORBIDDEN:
_lastError = HTTP_UE_SERVER_FORBIDDEN;
ret = HTTP_UPDATE_FAILED;
break;
default:
_lastError = HTTP_UE_SERVER_WRONG_HTTP_CODE;
ret = HTTP_UPDATE_FAILED;
AddLog(LOG_LEVEL_INFO, "OTA: unsupported HTTP return code %i", code);
// log_e("HTTP Code is (%d)\n", code);
break;
}
http.end();
return ret;
}
/**
* write Update to flash
* @param in Stream&
* @param size uint32_t
* @param md5 String
* @return true if Update ok
*/
bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int command)
{
StreamString error;
if (_cbProgress) {
TasUpdate.onProgress(_cbProgress);
}
// Start Tasmota Factory patch
// if(!Update.begin(size, command, _ledPin, _ledOn)) {
if(!TasUpdate.begin(size, command, _ledPin, _ledOn, NULL, _factory)) {
// End Tasmota Factory patch
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("TasUpdate.begin failed! (%s)\n", error.c_str());
return false;
}
if (_cbProgress) {
_cbProgress(0, size);
}
if(md5.length()) {
if(!TasUpdate.setMD5(md5.c_str())) {
_lastError = HTTP_UE_SERVER_FAULTY_MD5;
log_e("TasUpdate.setMD5 failed! (%s)\n", md5.c_str());
return false;
}
}
// To do: the SHA256 could be checked if the server sends it
if(TasUpdate.writeStream(in) != size) {
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("TasUpdate.writeStream failed! (%s)\n", error.c_str());
return false;
}
if (_cbProgress) {
_cbProgress(size, size);
}
if(!TasUpdate.end()) {
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("TasUpdate.end failed! (%s)\n", error.c_str());
return false;
}
return true;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE)
HTTPUpdateLight httpUpdateLight;
#endif

View File

@@ -0,0 +1,150 @@
/**
*
* @file HTTPUpdate.h based on ESP8266HTTPUpdate.h
* @date 16.10.2018
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP32 Http Updater.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef ___HTTP_UPDATE_LIGHT_H___
#define ___HTTP_UPDATE_LIGHT_H___
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <HttpClientLight.h>
#include <TasUpdate.h>
#include <HTTPUpdate.h>
/// note we use HTTP client errors too so we start at 100
// #define HTTP_UE_TOO_LESS_SPACE (-100)
// #define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101)
// #define HTTP_UE_SERVER_FILE_NOT_FOUND (-102)
// #define HTTP_UE_SERVER_FORBIDDEN (-103)
// #define HTTP_UE_SERVER_WRONG_HTTP_CODE (-104)
// #define HTTP_UE_SERVER_FAULTY_MD5 (-105)
// #define HTTP_UE_BIN_VERIFY_HEADER_FAILED (-106)
// #define HTTP_UE_BIN_FOR_WRONG_FLASH (-107)
// #define HTTP_UE_NO_PARTITION (-108)
// enum HTTPUpdateResult {
// HTTP_UPDATE_FAILED,
// HTTP_UPDATE_NO_UPDATES,
// HTTP_UPDATE_OK
// };
// typedef HTTPUpdateResult t_httpUpdate_return; // backward compatibility
// using HTTPUpdateStartCB = std::function<void()>;
// using HTTPUpdateEndCB = std::function<void()>;
// using HTTPUpdateErrorCB = std::function<void(int)>;
// using HTTPUpdateProgressCB = std::function<void(int, int)>;
class HTTPUpdateLight
{
public:
HTTPUpdateLight(void);
HTTPUpdateLight(int httpClientTimeout);
~HTTPUpdateLight(void);
void rebootOnUpdate(bool reboot)
{
_rebootOnUpdate = reboot;
}
/**
* set redirect follow mode. See `followRedirects_t` enum for avaliable modes.
* @param follow
*/
void setFollowRedirects(followRedirects_t follow)
{
_followRedirects = follow;
}
void setLedPin(int ledPin = -1, uint8_t ledOn = HIGH)
{
_ledPin = ledPin;
_ledOn = ledOn;
}
// Start Tasmota Factory patch
void setFactory(bool factory = false)
{
_factory = factory;
}
// End Tasmota Factory patch
// t_httpUpdate_return update(WiFiClient& client, const String& url, const String& currentVersion = "");
// t_httpUpdate_return update(WiFiClient& client, const String& host, uint16_t port, const String& uri = "/",
// const String& currentVersion = "");
// t_httpUpdate_return updateSpiffs(WiFiClient& client, const String& url, const String& currentVersion = "");
t_httpUpdate_return update(HTTPClientLight& httpClient,
const String& currentVersion = "");
// t_httpUpdate_return updateSpiffs(HTTPClient &httpClient, const String &currentVersion = "");
// Notification callbacks
void onStart(HTTPUpdateStartCB cbOnStart) { _cbStart = cbOnStart; }
void onEnd(HTTPUpdateEndCB cbOnEnd) { _cbEnd = cbOnEnd; }
void onError(HTTPUpdateErrorCB cbOnError) { _cbError = cbOnError; }
void onProgress(HTTPUpdateProgressCB cbOnProgress) { _cbProgress = cbOnProgress; }
int getLastError(void);
String getLastErrorString(void);
protected:
t_httpUpdate_return handleUpdate(HTTPClientLight& http, const String& currentVersion, bool spiffs = false);
bool runUpdate(Stream& in, uint32_t size, String md5, int command = U_FLASH);
// Set the error and potentially use a CB to notify the application
void _setLastError(int err) {
_lastError = err;
if (_cbError) {
_cbError(err);
}
}
int _lastError;
bool _rebootOnUpdate = true;
private:
int _httpClientTimeout;
followRedirects_t _followRedirects;
// Callbacks
HTTPUpdateStartCB _cbStart;
HTTPUpdateEndCB _cbEnd;
HTTPUpdateErrorCB _cbError;
HTTPUpdateProgressCB _cbProgress;
int _ledPin;
uint8_t _ledOn;
// Start Tasmota Factory patch
bool _factory;
// End Tasmota Factory patch
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE)
extern HTTPUpdateLight httpUpdateLight;
#endif
#endif /* ___HTTP_UPDATE_LIGHT_H___ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,277 @@
/**
* HTTPClientLight.h
*
* Created on: 02.11.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the HTTPClient for Arduino.
* Port to ESP32 by Evandro Luis Copercini (2017),
* changed fingerprints to CA verification.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef HTTPClient_Light_H_
#define HTTPClient_Light_H_
#define HTTPCLIENT_1_1_COMPATIBLE
#include <memory>
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h> // import definitions from the original code
#include "tasmota_options.h"
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
/// HTTP client errors
#define HTTPC_ERROR_CONNECTION_REFUSED (-1)
#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
#define HTTPC_ERROR_NOT_CONNECTED (-4)
#define HTTPC_ERROR_CONNECTION_LOST (-5)
#define HTTPC_ERROR_NO_STREAM (-6)
#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
#define HTTPC_ERROR_TOO_LESS_RAM (-8)
#define HTTPC_ERROR_ENCODING (-9)
#define HTTPC_ERROR_STREAM_WRITE (-10)
#define HTTPC_ERROR_READ_TIMEOUT (-11)
/// size for the stream handling
#define HTTP_TCP_BUFFER_SIZE (1460)
/// HTTP codes see RFC7231
// typedef enum {
// HTTP_CODE_CONTINUE = 100,
// HTTP_CODE_SWITCHING_PROTOCOLS = 101,
// HTTP_CODE_PROCESSING = 102,
// HTTP_CODE_OK = 200,
// HTTP_CODE_CREATED = 201,
// HTTP_CODE_ACCEPTED = 202,
// HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
// HTTP_CODE_NO_CONTENT = 204,
// HTTP_CODE_RESET_CONTENT = 205,
// HTTP_CODE_PARTIAL_CONTENT = 206,
// HTTP_CODE_MULTI_STATUS = 207,
// HTTP_CODE_ALREADY_REPORTED = 208,
// HTTP_CODE_IM_USED = 226,
// HTTP_CODE_MULTIPLE_CHOICES = 300,
// HTTP_CODE_MOVED_PERMANENTLY = 301,
// HTTP_CODE_FOUND = 302,
// HTTP_CODE_SEE_OTHER = 303,
// HTTP_CODE_NOT_MODIFIED = 304,
// HTTP_CODE_USE_PROXY = 305,
// HTTP_CODE_TEMPORARY_REDIRECT = 307,
// HTTP_CODE_PERMANENT_REDIRECT = 308,
// HTTP_CODE_BAD_REQUEST = 400,
// HTTP_CODE_UNAUTHORIZED = 401,
// HTTP_CODE_PAYMENT_REQUIRED = 402,
// HTTP_CODE_FORBIDDEN = 403,
// HTTP_CODE_NOT_FOUND = 404,
// HTTP_CODE_METHOD_NOT_ALLOWED = 405,
// HTTP_CODE_NOT_ACCEPTABLE = 406,
// HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
// HTTP_CODE_REQUEST_TIMEOUT = 408,
// HTTP_CODE_CONFLICT = 409,
// HTTP_CODE_GONE = 410,
// HTTP_CODE_LENGTH_REQUIRED = 411,
// HTTP_CODE_PRECONDITION_FAILED = 412,
// HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
// HTTP_CODE_URI_TOO_LONG = 414,
// HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
// HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
// HTTP_CODE_EXPECTATION_FAILED = 417,
// HTTP_CODE_MISDIRECTED_REQUEST = 421,
// HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
// HTTP_CODE_LOCKED = 423,
// HTTP_CODE_FAILED_DEPENDENCY = 424,
// HTTP_CODE_UPGRADE_REQUIRED = 426,
// HTTP_CODE_PRECONDITION_REQUIRED = 428,
// HTTP_CODE_TOO_MANY_REQUESTS = 429,
// HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
// HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
// HTTP_CODE_NOT_IMPLEMENTED = 501,
// HTTP_CODE_BAD_GATEWAY = 502,
// HTTP_CODE_SERVICE_UNAVAILABLE = 503,
// HTTP_CODE_GATEWAY_TIMEOUT = 504,
// HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
// HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
// HTTP_CODE_INSUFFICIENT_STORAGE = 507,
// HTTP_CODE_LOOP_DETECTED = 508,
// HTTP_CODE_NOT_EXTENDED = 510,
// HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
// } t_http_codes;
// typedef enum {
// HTTPC_TE_IDENTITY,
// HTTPC_TE_CHUNKED
// } transferEncoding_t;
/**
* redirection follow mode.
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
* GET or HEAD methods will be redirected (using the same method),
* since the RFC requires end-user confirmation in other cases.
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
* regardless of a used method. New request will use the same method,
* and they will include the same body data and the same headers.
* In the sense of the RFC, it's just like every redirection is confirmed.
*/
// typedef enum {
// HTTPC_DISABLE_FOLLOW_REDIRECTS,
// HTTPC_STRICT_FOLLOW_REDIRECTS,
// HTTPC_FORCE_FOLLOW_REDIRECTS
// } followRedirects_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraitsLight;
typedef std::unique_ptr<TransportTraitsLight> TransportTraitsLightPtr;
#endif
class HTTPClientLight
{
public:
HTTPClientLight();
~HTTPClientLight();
/*
* Since both begin() functions take a reference to client as a parameter, you need to
* ensure the client object lives the entire time of the HTTPClientLight
*/
// bool begin(WiFiClient &client, String url);
// bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool begin(String url);
bool begin(String url, const char* CAcert);
// bool begin(String host, uint16_t port, String uri = "/");
// bool begin(String host, uint16_t port, String uri, const char* CAcert);
// bool begin(String host, uint16_t port, String uri, const char* CAcert, const char* cli_cert, const char* cli_key);
#endif
void end(void);
bool connected(void);
void setReuse(bool reuse); /// keep-alive
void setUserAgent(const String& userAgent);
void setAuthorization(const char * user, const char * password);
void setAuthorization(const char * auth);
void setConnectTimeout(int32_t connectTimeout);
void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String &url);
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int PATCH(uint8_t * payload, size_t size);
int PATCH(String payload);
int POST(uint8_t * payload, size_t size);
int POST(String payload);
int PUT(uint8_t * payload, size_t size);
int PUT(String payload);
int DELETE(uint8_t * payload, size_t size);
int DELETE(String payload);
int sendRequest(const char * type, String payload);
int sendRequest(const char * type, uint8_t * payload = NULL, size_t size = 0);
int sendRequest(const char * type, Stream * stream, size_t size = 0);
void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
/// Response handling
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount);
String header(const char* name); // get request header value by name
String header(size_t i); // get request header value by number
String headerName(size_t i); // get request header name by number
int headers(); // get header count
bool hasHeader(const char* name); // check if header exists
int getSize(void);
const String &getLocation(void);
WiFiClient& getStream(void);
WiFiClient* getStreamPtr(void);
int writeToStream(Stream* stream);
String getString(void);
static String errorToString(int error);
protected:
struct RequestArgument {
String key;
String value;
};
bool beginInternal(String url, const char* expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);
bool connect(void);
bool sendHeader(const char * type);
int handleHeaderResponse();
int writeToStreamDataBlock(Stream * stream, int len);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
TransportTraitsLightPtr _transportTraits;
std::unique_ptr<WiFiClient> _tcpDeprecated;
#endif
WiFiClient* _client = nullptr;
/// request handling
String _host;
uint16_t _port = 0;
int32_t _connectTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; // Do not set to -1 as it fails WiFiClient connect()
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
bool _useHTTP10 = false;
bool _secure = false;
String _uri;
String _protocol;
String _headers;
String _userAgent = "ESP32HTTPClient";
String _base64Authorization;
/// Response handling
RequestArgument* _currentHeaders = nullptr;
size_t _headerKeysCount = 0;
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
};
#endif /* HTTPClient_Light_H_ */

View File

@@ -0,0 +1,194 @@
#ifndef TASUPDATER_H
#define TASUPDATER_H
#include <Arduino.h>
#include <MD5Builder.h>
#include <functional>
#include "esp_partition.h"
#define UPDATE_ERROR_OK (0)
#define UPDATE_ERROR_WRITE (1)
#define UPDATE_ERROR_ERASE (2)
#define UPDATE_ERROR_READ (3)
#define UPDATE_ERROR_SPACE (4)
#define UPDATE_ERROR_SIZE (5)
#define UPDATE_ERROR_STREAM (6)
#define UPDATE_ERROR_MD5 (7)
#define UPDATE_ERROR_MAGIC_BYTE (8)
#define UPDATE_ERROR_ACTIVATE (9)
#define UPDATE_ERROR_NO_PARTITION (10)
#define UPDATE_ERROR_BAD_ARGUMENT (11)
#define UPDATE_ERROR_ABORT (12)
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
#define U_FLASH 0
#define U_SPIFFS 100
#define U_AUTH 200
#define ENCRYPTED_BLOCK_SIZE 16
class TasUpdateClass {
public:
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
TasUpdateClass();
/*
This callback will be called when Update is receiving data
*/
TasUpdateClass& onProgress(THandlerFunction_Progress fn);
/*
Call this to check the space needed for the update
Will return false if there is not enough space
*/
// Start Tasmota Factory patch
// bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL);
bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL, bool factory = false);
// End Tasmota Factory patch
/*
Writes a buffer to the flash and increments the address
Returns the amount written
*/
size_t write(uint8_t *data, size_t len);
/*
Writes the remaining bytes from the Stream to the flash
Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout
Returns the bytes written
Should be equal to the remaining bytes when called
Usable for slow streams like Serial
*/
size_t writeStream(Stream &data);
/*
If all bytes are written
this call will write the config to eboot
and return true
If there is already an update running but is not finished and !evenIfRemaining
or there is an error
this will clear everything and return false
the last error is available through getError()
evenIfRemaining is helpfull when you update without knowing the final size first
*/
bool end(bool evenIfRemaining = false);
/*
Aborts the running update
*/
void abort();
/*
Prints the last error to an output stream
*/
void printError(Print &out);
const char * errorString();
/*
sets the expected MD5 for the firmware (hexString)
*/
bool setMD5(const char * expected_md5);
/*
returns the MD5 String of the successfully ended firmware
*/
String md5String(void){ return _md5.toString(); }
/*
populated the result with the md5 bytes of the successfully ended firmware
*/
void md5(uint8_t * result){ return _md5.getBytes(result); }
//Helpers
uint8_t getError(){ return _error; }
void clearError(){ _error = UPDATE_ERROR_OK; }
bool hasError(){ return _error != UPDATE_ERROR_OK; }
bool isRunning(){ return _size > 0; }
bool isFinished(){ return _progress == _size; }
size_t size(){ return _size; }
size_t progress(){ return _progress; }
size_t remaining(){ return _size - _progress; }
/*
Template to write from objects that expose
available() and read(uint8_t*, size_t) methods
faster than the writeStream method
writes only what is available
*/
template<typename T>
size_t write(T &data){
size_t written = 0;
if (hasError() || !isRunning())
return 0;
size_t available = data.available();
while(available) {
if(_bufferLen + available > remaining()){
available = remaining() - _bufferLen;
}
if(_bufferLen + available > 4096) {
size_t toBuff = 4096 - _bufferLen;
data.read(_buffer + _bufferLen, toBuff);
_bufferLen += toBuff;
if(!_writeBuffer())
return written;
written += toBuff;
} else {
data.read(_buffer + _bufferLen, available);
_bufferLen += available;
written += available;
if(_bufferLen == remaining()) {
if(!_writeBuffer()) {
return written;
}
}
}
if(remaining() == 0)
return written;
available = data.available();
}
return written;
}
/*
check if there is a firmware on the other OTA partition that you can bootinto
*/
bool canRollBack();
/*
set the other OTA partition as bootable (reboot to enable)
*/
bool rollBack();
private:
void _reset();
void _abort(uint8_t err);
bool _writeBuffer();
bool _verifyHeader(uint8_t data);
bool _verifyEnd();
bool _enablePartition(const esp_partition_t* partition);
uint8_t _error;
uint8_t *_buffer;
uint8_t *_skipBuffer;
size_t _bufferLen;
size_t _size;
THandlerFunction_Progress _progress_callback;
uint32_t _progress;
uint32_t _paroffset;
uint32_t _command;
const esp_partition_t* _partition;
String _target_md5;
MD5Builder _md5;
int _ledPin;
uint8_t _ledOn;
};
extern TasUpdateClass TasUpdate;
#endif // TASUPDATER_H

View File

@@ -0,0 +1,404 @@
#include "TasUpdate.h"
#include "Arduino.h"
#include "esp_spi_flash.h"
#include "esp_ota_ops.h"
#include "esp_image_format.h"
static const char * _err2str(uint8_t _error){
if(_error == UPDATE_ERROR_OK){
return ("No Error");
} else if(_error == UPDATE_ERROR_WRITE){
return ("Flash Write Failed");
} else if(_error == UPDATE_ERROR_ERASE){
return ("Flash Erase Failed");
} else if(_error == UPDATE_ERROR_READ){
return ("Flash Read Failed");
} else if(_error == UPDATE_ERROR_SPACE){
return ("Not Enough Space");
} else if(_error == UPDATE_ERROR_SIZE){
return ("Bad Size Given");
} else if(_error == UPDATE_ERROR_STREAM){
return ("Stream Read Timeout");
} else if(_error == UPDATE_ERROR_MD5){
return ("MD5 Check Failed");
} else if(_error == UPDATE_ERROR_MAGIC_BYTE){
return ("Wrong Magic Byte");
} else if(_error == UPDATE_ERROR_ACTIVATE){
return ("Could Not Activate The Firmware");
} else if(_error == UPDATE_ERROR_NO_PARTITION){
return ("Partition Could Not be Found");
} else if(_error == UPDATE_ERROR_BAD_ARGUMENT){
return ("Bad Argument");
} else if(_error == UPDATE_ERROR_ABORT){
return ("Aborted");
}
return ("UNKNOWN");
}
static bool _partitionIsBootable(const esp_partition_t* partition){
uint8_t buf[ENCRYPTED_BLOCK_SIZE];
if(!partition){
return false;
}
if(!ESP.partitionRead(partition, 0, (uint32_t*)buf, ENCRYPTED_BLOCK_SIZE)) {
return false;
}
if(buf[0] != ESP_IMAGE_HEADER_MAGIC) {
return false;
}
return true;
}
bool TasUpdateClass::_enablePartition(const esp_partition_t* partition){
if(!partition){
return false;
}
return ESP.partitionWrite(partition, 0, (uint32_t*) _skipBuffer, ENCRYPTED_BLOCK_SIZE);
}
TasUpdateClass::TasUpdateClass()
: _error(0)
, _buffer(0)
, _bufferLen(0)
, _size(0)
, _progress_callback(NULL)
, _progress(0)
, _paroffset(0)
, _command(U_FLASH)
, _partition(NULL)
{
}
TasUpdateClass& TasUpdateClass::onProgress(THandlerFunction_Progress fn) {
_progress_callback = fn;
return *this;
}
void TasUpdateClass::_reset() {
if (_buffer)
delete[] _buffer;
_buffer = 0;
_bufferLen = 0;
_progress = 0;
_size = 0;
_command = U_FLASH;
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // off
}
}
bool TasUpdateClass::canRollBack(){
if(_buffer){ //Update is running
return false;
}
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition);
}
bool TasUpdateClass::rollBack(){
if(_buffer){ //Update is running
return false;
}
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition);
}
// Start Tasmota Factory patch
//bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) {
bool TasUpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label, bool factory) {
// End Tasmota Factory patch
if(_size > 0){
log_w("already running");
return false;
}
_ledPin = ledPin;
_ledOn = !!ledOn; // 0(LOW) or 1(HIGH)
_reset();
_error = 0;
_target_md5 = emptyString;
_md5 = MD5Builder();
if(size == 0) {
_error = UPDATE_ERROR_SIZE;
return false;
}
if (command == U_FLASH) {
// Start Tasmota Factory patch
// _partition = esp_ota_get_next_update_partition(NULL);
if (factory) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
} else {
_partition = esp_ota_get_next_update_partition(NULL);
}
// End Tasmota Factory patch
if(!_partition){
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
log_d("OTA Partition: %s", _partition->label);
}
else if (command == U_SPIFFS) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, label);
_paroffset = 0;
if(!_partition){
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
_paroffset = 0x1000; //Offset for ffat, assuming size is already corrected
if(!_partition){
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
}
}
else {
_error = UPDATE_ERROR_BAD_ARGUMENT;
log_e("bad command %u", command);
return false;
}
if(size == UPDATE_SIZE_UNKNOWN){
size = _partition->size;
} else if(size > _partition->size){
_error = UPDATE_ERROR_SIZE;
log_e("too large %u > %u", size, _partition->size);
return false;
}
//initialize
_buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE);
if(!_buffer){
log_e("malloc failed");
return false;
}
_size = size;
_command = command;
_md5.begin();
return true;
}
void TasUpdateClass::_abort(uint8_t err){
_reset();
_error = err;
}
void TasUpdateClass::abort(){
_abort(UPDATE_ERROR_ABORT);
}
bool TasUpdateClass::_writeBuffer(){
//first bytes of new firmware
uint8_t skip = 0;
if(!_progress && _command == U_FLASH){
//check magic
if(_buffer[0] != ESP_IMAGE_HEADER_MAGIC){
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
//Stash the first 16 bytes of data and set the offset so they are
//not written at this point so that partially written firmware
//will not be bootable
skip = ENCRYPTED_BLOCK_SIZE;
_skipBuffer = (uint8_t*)malloc(skip);
if(!_skipBuffer){
log_e("malloc failed");
return false;
}
memcpy(_skipBuffer, _buffer, skip);
}
if (!_progress && _progress_callback) {
_progress_callback(0, _size);
}
if(!ESP.partitionEraseRange(_partition, _progress, SPI_FLASH_SEC_SIZE)){
_abort(UPDATE_ERROR_ERASE);
return false;
}
if (!ESP.partitionWrite(_partition, _progress + skip, (uint32_t*)_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) {
_abort(UPDATE_ERROR_WRITE);
return false;
}
//restore magic or md5 will fail
if(!_progress && _command == U_FLASH){
_buffer[0] = ESP_IMAGE_HEADER_MAGIC;
}
_md5.add(_buffer, _bufferLen);
_progress += _bufferLen;
_bufferLen = 0;
if (_progress_callback) {
_progress_callback(_progress, _size);
}
return true;
}
bool TasUpdateClass::_verifyHeader(uint8_t data) {
if(_command == U_FLASH) {
if(data != ESP_IMAGE_HEADER_MAGIC) {
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
return true;
} else if(_command == U_SPIFFS) {
return true;
}
return false;
}
bool TasUpdateClass::_verifyEnd() {
if(_command == U_FLASH) {
if(!_enablePartition(_partition) || !_partitionIsBootable(_partition)) {
_abort(UPDATE_ERROR_READ);
return false;
}
if(esp_ota_set_boot_partition(_partition)){
_abort(UPDATE_ERROR_ACTIVATE);
return false;
}
_reset();
return true;
} else if(_command == U_SPIFFS) {
_reset();
return true;
}
return false;
}
bool TasUpdateClass::setMD5(const char * expected_md5){
if(strlen(expected_md5) != 32)
{
return false;
}
_target_md5 = expected_md5;
return true;
}
bool TasUpdateClass::end(bool evenIfRemaining){
if(hasError() || _size == 0){
return false;
}
if(!isFinished() && !evenIfRemaining){
log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size);
_abort(UPDATE_ERROR_ABORT);
return false;
}
if(evenIfRemaining) {
if(_bufferLen > 0) {
_writeBuffer();
}
_size = progress();
}
_md5.calculate();
if(_target_md5.length()) {
if(_target_md5 != _md5.toString()){
_abort(UPDATE_ERROR_MD5);
return false;
}
}
return _verifyEnd();
}
size_t TasUpdateClass::write(uint8_t *data, size_t len) {
if(hasError() || !isRunning()){
return 0;
}
if(len > remaining()){
_abort(UPDATE_ERROR_SPACE);
return 0;
}
size_t left = len;
while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) {
size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen;
memcpy(_buffer + _bufferLen, data + (len - left), toBuff);
_bufferLen += toBuff;
if(!_writeBuffer()){
return len - left;
}
left -= toBuff;
}
memcpy(_buffer + _bufferLen, data + (len - left), left);
_bufferLen += left;
if(_bufferLen == remaining()){
if(!_writeBuffer()){
return len - left;
}
}
return len;
}
size_t TasUpdateClass::writeStream(Stream &data) {
size_t written = 0;
size_t toRead = 0;
int timeout_failures = 0;
if(hasError() || !isRunning())
return 0;
if(!_verifyHeader(data.peek())) {
_reset();
return 0;
}
if(_ledPin != -1) {
pinMode(_ledPin, OUTPUT);
}
while(remaining()) {
if(_ledPin != -1) {
digitalWrite(_ledPin, _ledOn); // Switch LED on
}
size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen;
if(bytesToRead > remaining()) {
bytesToRead = remaining();
}
/*
Init read&timeout counters and try to read, if read failed, increase counter,
wait 100ms and try to read again. If counter > 300 (30 sec), give up/abort
*/
toRead = 0;
timeout_failures = 0;
while(!toRead) {
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) {
timeout_failures++;
if (timeout_failures >= 300) {
_abort(UPDATE_ERROR_STREAM);
return written;
}
delay(100);
}
}
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // Switch LED off
}
_bufferLen += toRead;
if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
return written;
written += toRead;
delay(1); // Fix solo WDT
}
return written;
}
void TasUpdateClass::printError(Print &out){
out.println(_err2str(_error));
}
const char * TasUpdateClass::errorString(){
return _err2str(_error);
}
TasUpdateClass TasUpdate;