Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESP8266HTTPUpdate: Get available firmware version from update server #8968

Merged
merged 13 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions doc/ota_updates/readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -592,34 +592,42 @@ With this information the script now can check if an update is needed. It is als

<?PHP

header('Content-type: text/plain; charset=utf8', true);

function check_header($name, $value = false) {
if(!isset($_SERVER[$name])) {
global $headers;
if (!isset($headers[$name])) {
return false;
}
if($value && $_SERVER[$name] != $value) {
if ($value && $headers[$name] != $value) {
return false;
}
return true;
}

function sendFile($path) {
function sendFile($path, $version) {
header($_SERVER["SERVER_PROTOCOL"].' 200 OK', true, 200);
header('Content-Type: application/octet-stream', true);
header('Content-Disposition: attachment; filename='.basename($path));
header('Content-Length: '.filesize($path), true);
header('x-MD5: '.md5_file($path), true);
header('x-version: '.$version, true);
readfile($path);
}

if(!check_header('User-Agent', 'ESP8266-http-Update')) {


$headers = getallheaders();

header('Content-type: text/plain; charset=utf8', true);

//if (!check_header('HTTP_USER_AGENT', 'ESP8266-http-Update')) {
if (!check_header('User-Agent', 'ESP8266-http-Update')) {
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden', true, 403);
echo "only for ESP8266 updater!\n";
echo "Only for ESP8266 updater!\n";
echo "User-Agent: ".$headers['User-Agent']." != ESP8266-http-Update\n";
exit();
}

if(

if (
!check_header('x-ESP8266-mode') ||
!check_header('x-ESP8266-STA-MAC') ||
!check_header('x-ESP8266-AP-MAC') ||
!check_header('x-ESP8266-free-space') ||
Expand All @@ -629,32 +637,54 @@ With this information the script now can check if an update is needed. It is als
!check_header('x-ESP8266-sdk-version')
) {
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden', true, 403);
echo "only for ESP8266 updater! (header)\n";
echo "Only for ESP8266 updater! (header missing)\n";
exit();
}

$db = array(
"18:FE:AA:AA:AA:AA" => "DOOR-7-g14f53a19",
"18:FE:AA:AA:AA:BB" => "TEMP-1.0.0"
);

if(!isset($db[$_SERVER['x-ESP8266-STA-MAC']])) {
header($_SERVER["SERVER_PROTOCOL"].' 500 ESP MAC not configured for updates', true, 500);

$db_string = '{
"18:FE:AA:AA:AA:AA": {"file": "DOOR-7-g14f53a19.bin", "version": 1},
"18:FE:AA:AA:AA:BB": {"file": "TEMP-1.0.0".bin", "version": 1}}';
// $db_string = file_get_contents("arduino-db.json");
$db = json_decode($db_string, true);
$mode = $headers['x-ESP8266-mode'];
$mac = $headers['x-ESP8266-STA-MAC'];

if (!isset($db[$mac])) {
header($_SERVER["SERVER_PROTOCOL"].' 404 ESP MAC not configured for updates', true, 404);
echo "MAC ".$mac." not configured for updates\n";
exit();
}

$localBinary = "./bin/".$db[$_SERVER['x-ESP8266-STA-MAC']].".bin";

// Check if version has been set and does not match, if not, check if
// MD5 hash between local binary and ESP8266 binary do not match if not.
// then no update has been found.
if((!check_header('x-ESP8266-sdk-version') && $db[$_SERVER['x-ESP8266-STA-MAC']] != $_SERVER['x-ESP8266-version'])
|| $_SERVER["x-ESP8266-sketch-md5"] != md5_file($localBinary)) {
sendFile($localBinary);

$localBinary = $db[$mac]['file'];
$localVersion = $db[$mac]['version'];

if (!is_readable($localBinary)) {
header($_SERVER["SERVER_PROTOCOL"].' 404 File not found', true, 404);
echo "File ".$localBinary." not found\n";
exit();
}

if ($mode == 'sketch') {
// Check if version has been set and does not match, if not, check if
// MD5 hash between local binary and ESP8266 binary do not match if not.
// then no update has been found.
if ((check_header('x-ESP8266-version') && $headers['x-ESP8266-version'] != $localVersion)) {
// || $headers["x-ESP8266-sketch-md5"] != md5_file($localBinary)) {
sendFile($localBinary, $localVersion);
} else {
header($_SERVER["SERVER_PROTOCOL"].' 304 Not Modified', true, 304);
echo "File ".$localBinary." not modified\n";
}
} else if ($mode == 'version') {
header($_SERVER["SERVER_PROTOCOL"].' 200 OK', true, 200);
header('x-MD5: '.md5_file($localBinary), true);
header('x-version: '.$localVersion, true);
} else {
header($_SERVER["SERVER_PROTOCOL"].' 304 Not Modified', true, 304);
header($_SERVER["SERVER_PROTOCOL"].' 404 Mode not supported', true, 404);
echo "mode: ".$mode." not supported\n";
exit();
}

header($_SERVER["SERVER_PROTOCOL"].' 500 no version for ESP MAC', true, 500);
?>

Stream Interface
----------------
Expand Down
110 changes: 110 additions & 0 deletions libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
DEBUG_HTTP_UPDATE("[httpUpdate] ESP8266 info:\n");
DEBUG_HTTP_UPDATE("[httpUpdate] - free Space: %d\n", ESP.getFreeSketchSpace());
DEBUG_HTTP_UPDATE("[httpUpdate] - current Sketch Size: %d\n", ESP.getSketchSize());
DEBUG_HTTP_UPDATE("[httpUpdate] - current Sketch MD5: %s\n", ESP.getSketchMD5().c_str());

if(currentVersion && currentVersion[0] != 0x00) {
DEBUG_HTTP_UPDATE("[httpUpdate] - current version: %s\n", currentVersion.c_str() );
Expand Down Expand Up @@ -440,6 +441,115 @@ bool ESP8266HTTPUpdate::runUpdate(Stream& in, uint32_t size, const String& md5,
return true;
}

/**
* @brief Get avialable firmware version from update server
* @author Holger Mueller
* @date 2023-08-03
*
* @param client WiFiClient to use (see HTTPClient::begin)
* @param host Update host name or IP (see HTTPClient::begin)
* @param port Port on host (see HTTPClient::begin)
* @param uri Update URI on server (see HTTPClient::begin)
* @param current_version Current firmware version
* @param available_version Firmware version available on update server
* @return ESP8266HTTPUpdate::HTTPUpdateResult, HTTP_UPDATE_OK in case of success
*/
HTTPUpdateResult ESP8266HTTPUpdate::getAvailableVersion(WiFiClient& client, const String& host, uint16_t port, const String& uri, const String& current_version, String& available_version) {
HTTPUpdateResult ret = HTTP_UPDATE_FAILED;
HTTPClient http;
http.begin(client, host, port, uri);

// 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(F("ESP8266-http-Update"));
http.addHeader(F("x-ESP8266-Chip-ID"), String(ESP.getChipId()));
http.addHeader(F("x-ESP8266-STA-MAC"), WiFi.macAddress());
http.addHeader(F("x-ESP8266-AP-MAC"), WiFi.softAPmacAddress());
http.addHeader(F("x-ESP8266-free-space"), String(ESP.getFreeSketchSpace()));
http.addHeader(F("x-ESP8266-sketch-size"), String(ESP.getSketchSize()));
http.addHeader(F("x-ESP8266-sketch-md5"), String(ESP.getSketchMD5()));
http.addHeader(F("x-ESP8266-chip-size"), String(ESP.getFlashChipRealSize()));
http.addHeader(F("x-ESP8266-sdk-version"), ESP.getSdkVersion());

http.addHeader(F("x-ESP8266-mode"), F("version"));

if (current_version && current_version[0] != 0x00) {
http.addHeader(F("x-ESP8266-version"), current_version);
}

if (!_user.isEmpty() && !_password.isEmpty()) {
http.setAuthorization(_user.c_str(), _password.c_str());
}

if (!_auth.isEmpty()) {
http.setAuthorization(_auth.c_str());
}

const char* headerkeys[] = {"x-MD5", "x-version"};
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);

// track these headers
http.collectHeaders(headerkeys, headerkeyssize);

int code = http.GET();

if (code <= 0) {
DEBUG_HTTP_UPDATE("[httpUpdate] HTTP error: %s\n", http.errorToString(code).c_str());
_setLastError(code);
http.end();
return HTTP_UPDATE_FAILED;
}

DEBUG_HTTP_UPDATE("[httpUpdate] Header read fin.\n");
DEBUG_HTTP_UPDATE("[httpUpdate] Server header:\n");
DEBUG_HTTP_UPDATE("[httpUpdate] - code: %d\n", code);
DEBUG_HTTP_UPDATE("[httpUpdate] - len: %d\n", http.getSize());
if (code != HTTP_CODE_OK) {
DEBUG_HTTP_UPDATE("[httpUpdate] - payload: %s\n", http.getString().c_str());
}

switch (code) {
case HTTP_CODE_OK: ///< OK (check for version)
if (http.hasHeader("x-version")) {
available_version = http.header("x-version");
ret = HTTP_UPDATE_OK;
DEBUG_HTTP_UPDATE("[httpUpdate] - current version: %s\n", current_version.c_str());
DEBUG_HTTP_UPDATE("[httpUpdate] - server version: %s\n", available_version.c_str());
} else {
_setLastError(HTTP_UE_SERVER_NOT_REPORT_VERSION);
ret = HTTP_UPDATE_FAILED;
DEBUG_HTTP_UPDATE("[httpUpdate] Server did not respond with a firmware version\n");
}
if (http.hasHeader("x-MD5")) {
DEBUG_HTTP_UPDATE("[httpUpdate] - current Sketch MD5: %s\n", ESP.getSketchMD5().c_str());
DEBUG_HTTP_UPDATE("[httpUpdate] - server Sketch MD5: %s\n", http.header("x-MD5").c_str());
}
break;
case HTTP_CODE_NOT_FOUND:
_setLastError(HTTP_UE_SERVER_FILE_NOT_FOUND);
ret = HTTP_UPDATE_FAILED;
break;
case HTTP_CODE_FORBIDDEN:
_setLastError(HTTP_UE_SERVER_FORBIDDEN);
ret = HTTP_UPDATE_FAILED;
break;
case HTTP_CODE_UNAUTHORIZED:
_setLastError(HTTP_UE_SERVER_UNAUTHORIZED);
ret = HTTP_UPDATE_FAILED;
break;
default:
_setLastError(HTTP_UE_SERVER_WRONG_HTTP_CODE);
ret = HTTP_UPDATE_FAILED;
DEBUG_HTTP_UPDATE("[httpUpdate] HTTP Code is (%d)\n", code);
break;
}

http.end();
return ret;
}

#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE)
ESP8266HTTPUpdate ESPhttpUpdate;
#endif
29 changes: 16 additions & 13 deletions libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,18 @@
#endif

/// note we use HTTP client errors too so we start at 100
//TODO - in v3.0.0 make this an enum
constexpr int HTTP_UE_TOO_LESS_SPACE = (-100);
constexpr int HTTP_UE_SERVER_NOT_REPORT_SIZE = (-101);
constexpr int HTTP_UE_SERVER_FILE_NOT_FOUND = (-102);
constexpr int HTTP_UE_SERVER_FORBIDDEN = (-103);
constexpr int HTTP_UE_SERVER_WRONG_HTTP_CODE = (-104);
constexpr int HTTP_UE_SERVER_FAULTY_MD5 = (-105);
constexpr int HTTP_UE_BIN_VERIFY_HEADER_FAILED = (-106);
constexpr int HTTP_UE_BIN_FOR_WRONG_FLASH = (-107);
constexpr int HTTP_UE_SERVER_UNAUTHORIZED = (-108);
enum HTTPUpdateError {
HTTP_UE_SERVER_NOT_REPORT_VERSION = -109, // server did not respond with a firmware version
HTTP_UE_SERVER_UNAUTHORIZED, // -108
HTTP_UE_BIN_FOR_WRONG_FLASH, // -107
HTTP_UE_BIN_VERIFY_HEADER_FAILED, // -106
HTTP_UE_SERVER_FAULTY_MD5, // -105
HTTP_UE_SERVER_WRONG_HTTP_CODE, // -104
HTTP_UE_SERVER_FORBIDDEN, // -103
HTTP_UE_SERVER_FILE_NOT_FOUND, // -102
HTTP_UE_SERVER_NOT_REPORT_SIZE, // -101
HTTP_UE_TOO_LESS_SPACE // -100
};

enum HTTPUpdateResult {
HTTP_UPDATE_FAILED,
Expand Down Expand Up @@ -122,7 +124,9 @@ class ESP8266HTTPUpdate
t_httpUpdate_return updateFS(WiFiClient& client, const String& url, const String& currentVersion = "");
t_httpUpdate_return update(HTTPClient& httpClient, const String& currentVersion = "");
t_httpUpdate_return updateFS(HTTPClient& httpClient, const String& currentVersion = "");


t_httpUpdate_return getAvailableVersion(WiFiClient& client, const String& host, uint16_t port, const String& uri, const String& current_version, String& available_version);

// Notification callbacks
void onStart(HTTPUpdateStartCB cbOnStart) { _cbStart = cbOnStart; }
void onEnd(HTTPUpdateEndCB cbOnEnd) { _cbEnd = cbOnEnd; }
Expand Down Expand Up @@ -153,10 +157,9 @@ class ESP8266HTTPUpdate
String _password;
String _auth;
String _md5Sum;
private:
int _httpClientTimeout;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;

private:
// Callbacks
HTTPUpdateStartCB _cbStart;
HTTPUpdateEndCB _cbEnd;
Expand Down