tt
This commit is contained in:
parent
9e2873829c
commit
9d2fb8bc50
291
HCUpdate.h.backup
Normal file
291
HCUpdate.h.backup
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#if defined(ESP32)
|
||||
#ifndef ESP32UPDATER_H
|
||||
#define ESP32UPDATER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <functional>
|
||||
#include <WiFiClientSecure.h>
|
||||
#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_ERROR_DECRYPT (13)
|
||||
|
||||
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
|
||||
|
||||
#define U_FLASH 0
|
||||
#define U_SPIFFS 100
|
||||
#define U_AUTH 200
|
||||
|
||||
#define ENCRYPTED_BLOCK_SIZE 16
|
||||
#define ENCRYPTED_TWEAK_BLOCK_SIZE 32
|
||||
#define ENCRYPTED_KEY_SIZE 32
|
||||
|
||||
#define U_AES_DECRYPT_NONE 0
|
||||
#define U_AES_DECRYPT_AUTO 1
|
||||
#define U_AES_DECRYPT_ON 2
|
||||
#define U_AES_DECRYPT_MODE_MASK 3
|
||||
#define U_AES_IMAGE_DECRYPTING_BIT 4
|
||||
|
||||
#define SPI_SECTORS_PER_BLOCK 16 // usually large erase block is 32k/64k
|
||||
#define SPI_FLASH_BLOCK_SIZE (SPI_SECTORS_PER_BLOCK * SPI_FLASH_SEC_SIZE)
|
||||
|
||||
enum HTTPUpdateResult {
|
||||
HTTP_UPDATE_FAILED,
|
||||
HTTP_UPDATE_NO_UPDATES,
|
||||
HTTP_UPDATE_OK
|
||||
};
|
||||
|
||||
class UpdateClass {
|
||||
public:
|
||||
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
|
||||
|
||||
UpdateClass();
|
||||
UpdateClass(int httpClientTimeout);
|
||||
|
||||
int update(WiFiClientSecure& client, String &url, uint16_t port, String& uri,
|
||||
String ¤tVersion, short nDeviceType, bool bForceUpdate);
|
||||
int handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType);
|
||||
int http_downloadUpdate(HTTPClient &httpClient);
|
||||
|
||||
|
||||
/*
|
||||
This callback will be called when Update is receiving data
|
||||
*/
|
||||
UpdateClass &onProgress(THandlerFunction_Progress fn);
|
||||
|
||||
/*
|
||||
Call this to check the space needed for the update
|
||||
Will return false if there is not enough space
|
||||
*/
|
||||
bool begin(size_t size = UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL);
|
||||
|
||||
/*
|
||||
Setup decryption configuration
|
||||
Crypt Key is 32bytes(256bits) block of data, use the same key as used to encrypt image file
|
||||
Crypt Address, use the same value as used to encrypt image file
|
||||
Crypt Config, use the same value as used to encrypt image file
|
||||
Crypt Mode, used to select if image files should be decrypted or not
|
||||
*/
|
||||
bool setupCrypt(const uint8_t *cryptKey = 0, size_t cryptAddress = 0, uint8_t cryptConfig = 0xf, int cryptMode = U_AES_DECRYPT_AUTO);
|
||||
|
||||
/*
|
||||
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 helpful when you update without knowing the final size first
|
||||
*/
|
||||
bool end(bool evenIfRemaining = false);
|
||||
|
||||
/*
|
||||
sets AES256 key(32 bytes) used for decrypting image file
|
||||
*/
|
||||
bool setCryptKey(const uint8_t *cryptKey);
|
||||
|
||||
/*
|
||||
sets crypt mode used on image files
|
||||
*/
|
||||
bool setCryptMode(const int cryptMode);
|
||||
|
||||
/*
|
||||
sets address used for decrypting image file
|
||||
*/
|
||||
void setCryptAddress(const size_t cryptAddress) {
|
||||
_cryptAddress = cryptAddress & 0x00fffff0;
|
||||
}
|
||||
|
||||
/*
|
||||
sets crypt config used for decrypting image file
|
||||
*/
|
||||
void setCryptConfig(const uint8_t cryptConfig) {
|
||||
_cryptCfg = cryptConfig & 0x0f;
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
void _cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key);
|
||||
bool _decryptBuffer();
|
||||
bool _writeBuffer();
|
||||
bool _verifyHeader(uint8_t data);
|
||||
bool _verifyEnd();
|
||||
bool _enablePartition(const esp_partition_t *partition);
|
||||
bool _chkDataInBlock(const uint8_t *data, size_t len) const; // check if block contains any data or is empty
|
||||
|
||||
int _httpClientTimeout;
|
||||
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
|
||||
uint8_t _error;
|
||||
uint8_t *_cryptKey;
|
||||
uint8_t *_cryptBuffer;
|
||||
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;
|
||||
|
||||
uint8_t _cryptMode;
|
||||
size_t _cryptAddress;
|
||||
uint8_t _cryptCfg;
|
||||
};
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
|
||||
extern UpdateClass Update;
|
||||
#endif
|
||||
|
||||
#endif // defined(ESP32UPDATER_H)
|
||||
#endif // defined(ESP32)
|
||||
787
HCUpdater.cpp.backup
Normal file
787
HCUpdater.cpp.backup
Normal file
|
|
@ -0,0 +1,787 @@
|
|||
#if defined(ESP32)
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "HermitCrab.h"
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "spi_flash_mmap.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_image_format.h"
|
||||
#include "mbedtls/aes.h"
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "HCUpdate.h"
|
||||
|
||||
#define TAG_FW "FW Upate"
|
||||
|
||||
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");
|
||||
} else if (_error == UPDATE_ERROR_DECRYPT) {
|
||||
return ("Decryption error");
|
||||
}
|
||||
return ("UNKNOWN");
|
||||
}
|
||||
|
||||
int UpdateClass::update(WiFiClientSecure& client, String& host, uint16_t port, String&uri, String& currentVersion, short nDeviceType, bool bForceUpdate)
|
||||
{
|
||||
HTTPClient http;
|
||||
if (!http.begin(client, host, port, uri)) {
|
||||
ESP_LOGI(TAG_FW,"OTA - httpClient begin failed\n");
|
||||
return HTTP_UPDATE_FAILED;
|
||||
}
|
||||
int size = handleUpdate(http, currentVersion, nDeviceType);
|
||||
|
||||
ESP_LOGI(TAG_FW,"OTA - size(%d) Server Version: %s Mode: %s\n", size, http.header("version").c_str(),
|
||||
http.header("update") && http.header("update").toInt() == 1 ? "Download" : "Check Only" );
|
||||
|
||||
//is there an image to download
|
||||
if (size >= 0) {
|
||||
if (!http.header("update") || http.header("update").toInt() == 0) {
|
||||
ESP_LOGI(TAG_FW,"OTA - No Firmware available");
|
||||
} else if (!http.header("version") || strcmp(http.header("version").c_str(), HC__VERSION) <= 0) {
|
||||
ESP_LOGI(TAG_FW,"OTA - Firmware is upto Date");
|
||||
} else {
|
||||
//image avaliabe to download & update
|
||||
if (!bForceUpdate) {
|
||||
ESP_LOGI(TAG_FW,"OTA - Found V%s Firmware\n", http.header("version").c_str());
|
||||
} else {
|
||||
ESP_LOGI(TAG_FW,"OTA - Downloading & Installing V%s Firmware\n", http.header("version").c_str());
|
||||
}
|
||||
|
||||
if (bForceUpdate) {
|
||||
if (http_downloadUpdate(http) == 0) {
|
||||
http.end(); //end connection
|
||||
ESP_LOGI(TAG_FW,"OTA - Firmware Update Successful, rebooting");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.end(); //end connection
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UpdateClass::handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType)
|
||||
{
|
||||
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-DEVICE-TYPE", String(nDeviceType));
|
||||
http.addHeader("X-ESP32-VERSION", HC__VERSION);
|
||||
http.addHeader("X-ESP32-STA-MAC", WiFi.macAddress());
|
||||
|
||||
unsigned long nChipId = 0;
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
nChipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
http.addHeader(F("X-ESP32-Chip-ID"), String(nChipId));
|
||||
http.addHeader(F("x-ESP32-free-space"), String(ESP.getFreeSketchSpace()));
|
||||
http.addHeader(F("x-ESP32-sketch-size"), String(ESP.getSketchSize()));
|
||||
http.addHeader(F("x-ESP32-sketch-md5"), String(ESP.getSketchMD5()));
|
||||
//http.addHeader(F("x-ESP32-chip-size"), String(ESP.getFlashChipRealSize()));
|
||||
http.addHeader(F("x-ESP32-sdk-version"), ESP.getSdkVersion());
|
||||
|
||||
//set headers to look for to get returned values in servers http response to our http request
|
||||
const char *headerkeys[] = {"update", "version"}; //server returns update 0=no update found, 1=update found, version=version of update found
|
||||
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);
|
||||
http.collectHeaders(headerkeys, headerkeyssize);
|
||||
|
||||
int code = http.GET();
|
||||
int len = http.getSize();
|
||||
|
||||
if (code == HTTP_CODE_OK) {
|
||||
return (len > 0 ? len : 0); //return 0 or length of image to download
|
||||
} else if (code < 0) {
|
||||
ESP_LOGI(TAG_FW,"Error: %s\n", http.errorToString(code).c_str());
|
||||
ESP_LOGI(TAG_FW, "%s", http.getString());
|
||||
return code; //error code should be minus between -1 to -11
|
||||
} else {
|
||||
ESP_LOGI(TAG_FW,"Error: HTTP Server response code %i\n", code);
|
||||
ESP_LOGI(TAG_FW, "%s", http.getString());
|
||||
return -code; //return code should be minus between -100 to -511
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int UpdateClass::http_downloadUpdate(HTTPClient &httpClient) {
|
||||
WiFiClient *stream = httpClient.getStreamPtr();
|
||||
int written = 0;
|
||||
|
||||
// Check content length and begin update
|
||||
int fileSize = httpClient.getSize();
|
||||
if (fileSize <= 0) {
|
||||
ESP_LOGI(TAG_FW,"Invalid content length");
|
||||
return 1;
|
||||
}
|
||||
if (!Update.begin(fileSize, U_FLASH)) {
|
||||
ESP_LOGI(TAG_FW,"Update begin failed!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Download and write the update
|
||||
while (httpClient.connected() && written < fileSize) {
|
||||
size_t availableBytes = stream->available();
|
||||
if (availableBytes) {
|
||||
uint8_t buffer[128]; // Buffer to hold incoming data
|
||||
int bytesRead = stream->readBytes(buffer, min(availableBytes, sizeof(buffer)));
|
||||
int bytesWritten = Update.write(buffer, bytesRead);
|
||||
if (bytesWritten != bytesRead) {
|
||||
ESP_LOGI(TAG_FW,"Update write failed!");
|
||||
Update.end();
|
||||
return 1;
|
||||
}
|
||||
written += bytesWritten;
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize update
|
||||
if (Update.end()) {
|
||||
if (Update.isFinished()) {
|
||||
ESP_LOGI(TAG_FW,"Update successful!");
|
||||
return 0;
|
||||
} else {
|
||||
ESP_LOGI(TAG_FW,"Update not finished!");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG_FW,"Update end failed!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 UpdateClass::_enablePartition(const esp_partition_t *partition) {
|
||||
if (!partition) {
|
||||
return false;
|
||||
}
|
||||
return ESP.partitionWrite(partition, 0, (uint32_t *)_skipBuffer, ENCRYPTED_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
UpdateClass::UpdateClass()
|
||||
: _error(0)
|
||||
, _cryptKey(0)
|
||||
, _cryptBuffer(0)
|
||||
, _buffer(0)
|
||||
, _skipBuffer(0)
|
||||
, _bufferLen(0)
|
||||
, _size(0)
|
||||
, _progress_callback(NULL)
|
||||
, _progress(0)
|
||||
, _paroffset(0)
|
||||
, _command(U_FLASH)
|
||||
, _partition(NULL)
|
||||
, _cryptMode(U_AES_DECRYPT_AUTO)
|
||||
, _cryptAddress(0)
|
||||
, _cryptCfg(0xf)
|
||||
, _httpClientTimeout(8000)
|
||||
{}
|
||||
|
||||
UpdateClass::UpdateClass(int httpClientTimeout)
|
||||
: _error(0)
|
||||
, _cryptKey(0)
|
||||
, _cryptBuffer(0)
|
||||
, _buffer(0)
|
||||
, _skipBuffer(0)
|
||||
, _bufferLen(0)
|
||||
, _size(0)
|
||||
, _progress_callback(NULL)
|
||||
, _progress(0)
|
||||
, _paroffset(0)
|
||||
, _command(U_FLASH)
|
||||
, _partition(NULL)
|
||||
, _cryptMode(U_AES_DECRYPT_AUTO)
|
||||
, _cryptAddress(0)
|
||||
, _cryptCfg(0xf)
|
||||
, _httpClientTimeout(httpClientTimeout)
|
||||
{}
|
||||
|
||||
UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress fn) {
|
||||
_progress_callback = fn;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UpdateClass::_reset() {
|
||||
if (_buffer) {
|
||||
delete[] _buffer;
|
||||
}
|
||||
if (_skipBuffer) {
|
||||
delete[] _skipBuffer;
|
||||
}
|
||||
|
||||
_cryptBuffer = nullptr;
|
||||
_buffer = nullptr;
|
||||
_skipBuffer = nullptr;
|
||||
_bufferLen = 0;
|
||||
_progress = 0;
|
||||
_size = 0;
|
||||
_command = U_FLASH;
|
||||
|
||||
if (_ledPin != -1) {
|
||||
digitalWrite(_ledPin, !_ledOn); // off
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateClass::canRollBack() {
|
||||
if (_buffer) { //Update is running
|
||||
return false;
|
||||
}
|
||||
const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL);
|
||||
return _partitionIsBootable(partition);
|
||||
}
|
||||
|
||||
bool UpdateClass::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);
|
||||
}
|
||||
|
||||
bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) {
|
||||
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) {
|
||||
_partition = esp_ota_get_next_update_partition(NULL);
|
||||
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 = new (std::nothrow) uint8_t[SPI_FLASH_SEC_SIZE];
|
||||
if (!_buffer) {
|
||||
log_e("_buffer allocation failed");
|
||||
return false;
|
||||
}
|
||||
_size = size;
|
||||
_command = command;
|
||||
_md5.begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8_t cryptConfig, int cryptMode) {
|
||||
if (setCryptKey(cryptKey)) {
|
||||
if (setCryptMode(cryptMode)) {
|
||||
setCryptAddress(cryptAddress);
|
||||
setCryptConfig(cryptConfig);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UpdateClass::setCryptKey(const uint8_t *cryptKey) {
|
||||
if (!cryptKey) {
|
||||
if (_cryptKey) {
|
||||
delete[] _cryptKey;
|
||||
_cryptKey = 0;
|
||||
log_d("AES key unset");
|
||||
}
|
||||
return false; //key cleared, no key to decrypt with
|
||||
}
|
||||
//initialize
|
||||
if (!_cryptKey) {
|
||||
_cryptKey = new (std::nothrow) uint8_t[ENCRYPTED_KEY_SIZE];
|
||||
}
|
||||
if (!_cryptKey) {
|
||||
log_e("new failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(_cryptKey, cryptKey, ENCRYPTED_KEY_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::setCryptMode(const int cryptMode) {
|
||||
if (cryptMode >= U_AES_DECRYPT_NONE && cryptMode <= U_AES_DECRYPT_ON) {
|
||||
_cryptMode = cryptMode;
|
||||
} else {
|
||||
log_e("bad crypt mode argument %i", cryptMode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateClass::_abort(uint8_t err) {
|
||||
_reset();
|
||||
_error = err;
|
||||
}
|
||||
|
||||
void UpdateClass::abort() {
|
||||
_abort(UPDATE_ERROR_ABORT);
|
||||
}
|
||||
|
||||
void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) {
|
||||
memcpy(tweaked_key, _cryptKey, ENCRYPTED_KEY_SIZE);
|
||||
if (_cryptCfg == 0) {
|
||||
return; //no tweaking needed, use crypt key as-is
|
||||
}
|
||||
|
||||
const uint8_t pattern[] = {23, 23, 23, 14, 23, 23, 23, 12, 23, 23, 23, 10, 23, 23, 23, 8};
|
||||
int pattern_idx = 0;
|
||||
int key_idx = 0;
|
||||
int bit_len = 0;
|
||||
uint32_t tweak = 0;
|
||||
cryptAddress &= 0x00ffffe0; //bit 23-5
|
||||
cryptAddress <<= 8; //bit23 shifted to bit31(MSB)
|
||||
while (pattern_idx < sizeof(pattern)) {
|
||||
tweak = cryptAddress << (23 - pattern[pattern_idx]); //bit shift for small patterns
|
||||
// alternative to: tweak = rotl32(tweak,8 - bit_len);
|
||||
tweak = (tweak << (8 - bit_len)) | (tweak >> (24 + bit_len)); //rotate to line up with end of previous tweak bits
|
||||
bit_len += pattern[pattern_idx++] - 4; //add number of bits in next pattern(23-4 = 19bits = 23bit to 5bit)
|
||||
while (bit_len > 7) {
|
||||
tweaked_key[key_idx++] ^= tweak; //XOR byte
|
||||
// alternative to: tweak = rotl32(tweak, 8);
|
||||
tweak = (tweak << 8) | (tweak >> 24); //compiler should optimize to use rotate(fast)
|
||||
bit_len -= 8;
|
||||
}
|
||||
tweaked_key[key_idx] ^= tweak; //XOR remaining bits, will XOR zeros if no remaining bits
|
||||
}
|
||||
if (_cryptCfg == 0xf) {
|
||||
return; //return with fully tweaked key
|
||||
}
|
||||
|
||||
//some of tweaked key bits need to be restore back to crypt key bits
|
||||
const uint8_t cfg_bits[] = {67, 65, 63, 61};
|
||||
key_idx = 0;
|
||||
pattern_idx = 0;
|
||||
while (key_idx < ENCRYPTED_KEY_SIZE) {
|
||||
bit_len += cfg_bits[pattern_idx];
|
||||
if ((_cryptCfg & (1 << pattern_idx)) == 0) { //restore crypt key bits
|
||||
while (bit_len > 0) {
|
||||
if (bit_len > 7 || ((_cryptCfg & (2 << pattern_idx)) == 0)) { //restore a crypt key byte
|
||||
tweaked_key[key_idx] = _cryptKey[key_idx];
|
||||
} else { //MSBits restore crypt key bits, LSBits keep as tweaked bits
|
||||
tweaked_key[key_idx] &= (0xff >> bit_len);
|
||||
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (~(0xff >> bit_len)));
|
||||
}
|
||||
key_idx++;
|
||||
bit_len -= 8;
|
||||
}
|
||||
} else { //keep tweaked key bits
|
||||
while (bit_len > 0) {
|
||||
if (bit_len < 8 && ((_cryptCfg & (2 << pattern_idx)) == 0)) { //MSBits keep as tweaked bits, LSBits restore crypt key bits
|
||||
tweaked_key[key_idx] &= (~(0xff >> bit_len));
|
||||
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (0xff >> bit_len));
|
||||
}
|
||||
key_idx++;
|
||||
bit_len -= 8;
|
||||
}
|
||||
}
|
||||
pattern_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateClass::_decryptBuffer() {
|
||||
if (!_cryptKey) {
|
||||
log_w("AES key not set");
|
||||
return false;
|
||||
}
|
||||
if (_bufferLen % ENCRYPTED_BLOCK_SIZE != 0) {
|
||||
log_e("buffer size error");
|
||||
return false;
|
||||
}
|
||||
if (!_cryptBuffer) {
|
||||
_cryptBuffer = new (std::nothrow) uint8_t[ENCRYPTED_BLOCK_SIZE];
|
||||
}
|
||||
if (!_cryptBuffer) {
|
||||
log_e("new failed");
|
||||
return false;
|
||||
}
|
||||
uint8_t tweaked_key[ENCRYPTED_KEY_SIZE]; //tweaked crypt key
|
||||
int done = 0;
|
||||
|
||||
/*
|
||||
Mbedtls functions will be replaced with esp_aes functions when hardware acceleration is available
|
||||
|
||||
To Do:
|
||||
Replace mbedtls for the cases where there's no hardware acceleration
|
||||
*/
|
||||
|
||||
mbedtls_aes_context ctx; //initialize AES
|
||||
mbedtls_aes_init(&ctx);
|
||||
while ((_bufferLen - done) >= ENCRYPTED_BLOCK_SIZE) {
|
||||
for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) {
|
||||
_cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i] = _buffer[i + done]; //reverse order 16 bytes to decrypt
|
||||
}
|
||||
if (((_cryptAddress + _progress + done) % ENCRYPTED_TWEAK_BLOCK_SIZE) == 0 || done == 0) {
|
||||
_cryptKeyTweak(_cryptAddress + _progress + done, tweaked_key); //update tweaked crypt key
|
||||
if (mbedtls_aes_setkey_enc(&ctx, tweaked_key, 256)) {
|
||||
return false;
|
||||
}
|
||||
if (mbedtls_aes_setkey_dec(&ctx, tweaked_key, 256)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, _cryptBuffer, _cryptBuffer)) { //use MBEDTLS_AES_ENCRYPT to decrypt flash code
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) {
|
||||
_buffer[i + done] = _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i]; //reverse order 16 bytes from decrypt
|
||||
}
|
||||
done += ENCRYPTED_BLOCK_SIZE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::_writeBuffer() {
|
||||
//first bytes of loading image, check to see if loading image needs decrypting
|
||||
if (!_progress) {
|
||||
_cryptMode &= U_AES_DECRYPT_MODE_MASK;
|
||||
if ((_cryptMode == U_AES_DECRYPT_ON) || ((_command == U_FLASH) && (_cryptMode & U_AES_DECRYPT_AUTO) && (_buffer[0] != ESP_IMAGE_HEADER_MAGIC))) {
|
||||
_cryptMode |= U_AES_IMAGE_DECRYPTING_BIT; //set to decrypt the loading image
|
||||
log_d("Decrypting OTA Image");
|
||||
}
|
||||
}
|
||||
//check if data in buffer needs decrypting
|
||||
if (_cryptMode & U_AES_IMAGE_DECRYPTING_BIT) {
|
||||
if (!_decryptBuffer()) {
|
||||
_abort(UPDATE_ERROR_DECRYPT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//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 = new (std::nothrow) uint8_t[skip];
|
||||
if (!_skipBuffer) {
|
||||
log_e("_skipBuffer allocation failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(_skipBuffer, _buffer, skip);
|
||||
}
|
||||
if (!_progress && _progress_callback) {
|
||||
_progress_callback(0, _size);
|
||||
}
|
||||
size_t offset = _partition->address + _progress;
|
||||
bool block_erase =
|
||||
(_size - _progress >= SPI_FLASH_BLOCK_SIZE) && (offset % SPI_FLASH_BLOCK_SIZE == 0); // if it's the block boundary, than erase the whole block from here
|
||||
bool part_head_sectors =
|
||||
_partition->address % SPI_FLASH_BLOCK_SIZE
|
||||
&& offset < (_partition->address / SPI_FLASH_BLOCK_SIZE + 1) * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition heading block
|
||||
bool part_tail_sectors =
|
||||
offset >= (_partition->address + _size) / SPI_FLASH_BLOCK_SIZE * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition tailing block
|
||||
if (block_erase || part_head_sectors || part_tail_sectors) {
|
||||
if (!ESP.partitionEraseRange(_partition, _progress, block_erase ? SPI_FLASH_BLOCK_SIZE : SPI_FLASH_SEC_SIZE)) {
|
||||
_abort(UPDATE_ERROR_ERASE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// try to skip empty blocks on unencrypted partitions
|
||||
if ((_partition->encrypted || _chkDataInBlock(_buffer + skip / sizeof(uint32_t), _bufferLen - skip))
|
||||
&& !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 UpdateClass::_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 UpdateClass::_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 UpdateClass::setMD5(const char *expected_md5) {
|
||||
if (strlen(expected_md5) != 32) {
|
||||
return false;
|
||||
}
|
||||
_target_md5 = expected_md5;
|
||||
_target_md5.toLowerCase();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateClass::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 UpdateClass::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 UpdateClass::writeStream(Stream &data) {
|
||||
size_t written = 0;
|
||||
size_t toRead = 0;
|
||||
int timeout_failures = 0;
|
||||
|
||||
if (hasError() || !isRunning()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_command == U_FLASH && !_cryptMode) {
|
||||
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;
|
||||
|
||||
#if CONFIG_FREERTOS_UNICORE
|
||||
delay(1); // Fix solo WDT
|
||||
#endif
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
void UpdateClass::printError(Print &out) {
|
||||
out.println(_err2str(_error));
|
||||
}
|
||||
|
||||
const char *UpdateClass::errorString() {
|
||||
return _err2str(_error);
|
||||
}
|
||||
|
||||
bool UpdateClass::_chkDataInBlock(const uint8_t *data, size_t len) const {
|
||||
// check 32-bit aligned blocks only
|
||||
if (!len || len % sizeof(uint32_t)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t dwl = len / sizeof(uint32_t);
|
||||
|
||||
do {
|
||||
if (*(uint32_t *)data ^ 0xffffffff) { // for SPI NOR flash empty blocks are all one's, i.e. filled with 0xff byte
|
||||
return true;
|
||||
}
|
||||
|
||||
data += sizeof(uint32_t);
|
||||
} while (--dwl);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
|
||||
UpdateClass Update;
|
||||
#endif
|
||||
|
||||
#endif // defined(ESP32)
|
||||
491
UPnPClient.cpp.backup
Normal file
491
UPnPClient.cpp.backup
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
#include <HTTPClient.h>
|
||||
#include "HermitCrab.h"
|
||||
#include "UPnpClient.h"
|
||||
#include "Config.h"
|
||||
#include "WiFiHost.h"
|
||||
#define TAG_UPNP "UPnP"
|
||||
|
||||
|
||||
bool CUpnpClient::registerUPnP(uint32_t *pip, uint16_t *pport) {
|
||||
routerIP = *pip; // Gateway IP address
|
||||
publicPort = *pport; // host.m_nPublicPort = config.m_nPublicPort
|
||||
publicIP = 0UL;
|
||||
bool bSuccess = false;
|
||||
ESP_LOGI(TAG_UPNP,"UPnP %s(%D)\n", routerIP.toString().c_str(), publicPort);
|
||||
|
||||
// Step 1 - Discover UPnP service.
|
||||
if (discoverUPnP()) {
|
||||
// Step 2 - Check if mapping already exists
|
||||
if (!(bSuccess = requestPortMappingEntry())) {
|
||||
// Step 3 - Request new mapping
|
||||
bSuccess = requestPortForwarding();
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG_UPNP," UPnP discovery failed.");
|
||||
}
|
||||
|
||||
if (bSuccess) {
|
||||
// Extract external IP
|
||||
requestExternalIP();
|
||||
ESP_LOGI(TAG_UPNP," Public IP(Port): %s(%d)\n", publicIP.toString().c_str(), publicPort);
|
||||
|
||||
|
||||
// Extract external port assigned by UPnP
|
||||
// requestExternalPort();
|
||||
// ESP_LOGI(TAG_UPNP,"Assigned External Port: %d\n", publicPort);
|
||||
|
||||
*pport = publicPort; // External server port
|
||||
*pip = publicIP; // External IP address
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGI(TAG_UPNP," UPnP PortForwarding failed.");
|
||||
}
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
bool CUpnpClient::discoverUPnP() {
|
||||
//ESP_LOGI(TAG_UPNP,"\n\n** Sending SSDP discovery request...");
|
||||
WiFiUDP udp;
|
||||
bool ret = false;
|
||||
|
||||
// SSDP M-SEARCH Request
|
||||
const char *ssdpRequest =
|
||||
"M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"MAN: \"ssdp:discover\"\r\n"
|
||||
"MX: 3\r\n"
|
||||
"ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
|
||||
"\r\n";
|
||||
|
||||
udp.beginMulticast(SSDP_MULTICAST_IP, SSDP_PORT);
|
||||
udp.beginPacket(SSDP_MULTICAST_IP, SSDP_PORT);
|
||||
udp.write((const uint8_t *)ssdpRequest, strlen(ssdpRequest)); // Fix length
|
||||
udp.endPacket();
|
||||
|
||||
unsigned long startTime = millis();
|
||||
while (millis() - startTime < SEARCH_TIMEOUT) {
|
||||
int packetSize = udp.parsePacket();
|
||||
if (packetSize > 0) {
|
||||
udp.read(buffer, sizeof(buffer) - 1);
|
||||
buffer[packetSize] = '\0';
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"SSDP response received:");
|
||||
//ESP_LOGI(TAG_UPNP,buffer);
|
||||
|
||||
// Check if the response contains "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
||||
char *stField = strstr(buffer, "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1");
|
||||
if (stField) {
|
||||
// Extract router's UPnP service URL
|
||||
char *location = strstr(buffer, "LOCATION: ");
|
||||
if (location) {
|
||||
location += 9; // Move past "LOCATION: "
|
||||
while (*location == ' ') location++; // skip blanks
|
||||
char *end = strchr(location, '\r');
|
||||
if (end) {
|
||||
*end = '\0';
|
||||
//ESP_LOGI(TAG_UPNP,"Router UPnP URL: %s\n", location);
|
||||
routerLocation = String(location);
|
||||
|
||||
// Extract IP and Port from the Location URL
|
||||
int firstColonPos = routerLocation.indexOf(':'); // Find the first colon (for the protocol)
|
||||
int secondColonPos = routerLocation.indexOf(':', firstColonPos + 1); // Find the second colon (IP:PORT)
|
||||
|
||||
if (secondColonPos != -1) {
|
||||
routerIPString = routerLocation.substring(routerLocation.indexOf("://") + 3, secondColonPos); // Extract IP
|
||||
routerPort = routerLocation.substring(secondColonPos + 1, routerLocation.indexOf('/', secondColonPos)).toInt(); // Extract Port
|
||||
}
|
||||
|
||||
routerIP.fromString(routerIPString);
|
||||
//ESP_LOGI(TAG_UPNP,"Router UPnP IP(Port): %s(%d)\n", routerIP.toString().c_str(), routerPort);
|
||||
|
||||
String xml = fetchUPnPDescription(routerLocation);
|
||||
if (!xml.isEmpty()) {
|
||||
// ESP_LOGI(TAG_UPNP,xml);
|
||||
// ESP_LOGI(TAG_UPNP,"\n--- End Of XML ----\n");
|
||||
|
||||
// Parse the XML and get the controlURL
|
||||
// String parseXML(const String &xml);
|
||||
controlURL = parseXML(xml);
|
||||
if (!controlURL.isEmpty()) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
udp.stop();
|
||||
|
||||
if (!ret) {
|
||||
DPRINTLN("No SSDP response from UPnP device.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String CUpnpClient::fetchUPnPDescription(const String &location) {
|
||||
HTTPClient http;
|
||||
WiFiClient client;
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"Fetching UPnP XML from: \"%s\"\n", location.c_str());
|
||||
|
||||
http.begin(client, location);
|
||||
int httpCode = http.GET();
|
||||
|
||||
if (httpCode > 0) {
|
||||
//ESP_LOGI(TAG_UPNP,"HTTP Response Code: %d\n", httpCode);
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
String xmlContent = http.getString();
|
||||
http.end();
|
||||
return xmlContent;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//ESP_LOGI(TAG_UPNP,"HTTP Request failed, error: %s\n", http.errorToString(httpCode).c_str());
|
||||
}
|
||||
|
||||
http.end();
|
||||
return "";
|
||||
}
|
||||
|
||||
String CUpnpClient::parseXML(const String &xml) {
|
||||
// Locate the WANIPConnection service
|
||||
int servicePos = xml.indexOf("<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>");
|
||||
if (servicePos == -1) {
|
||||
ESP_LOGI(TAG_UPNP,"WANIPConnection service not found in XML.");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Find the start of the controlURL tag within the <service> block
|
||||
int controlStart = xml.indexOf("<controlURL>", servicePos);
|
||||
if (controlStart == -1) {
|
||||
ESP_LOGI(TAG_UPNP,"controlURL not found in service block.");
|
||||
return "";
|
||||
}
|
||||
controlStart += 12; // Move past "<controlURL>"
|
||||
|
||||
// Find the closing tag
|
||||
int controlEnd = xml.indexOf("</controlURL>", controlStart);
|
||||
if (controlEnd == -1) {
|
||||
ESP_LOGI(TAG_UPNP,"Malformed XML: Missing </controlURL>.");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Extract and clean the controlURL
|
||||
String controlURL = xml.substring(controlStart, controlEnd);
|
||||
controlURL.trim(); // Remove any spaces or newlines
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"Extracted controlURL: %s\n", controlURL.c_str());
|
||||
return controlURL;
|
||||
}
|
||||
|
||||
int CUpnpClient::sendSoapRequest(const char *request, char *response, size_t responseSize) {
|
||||
WiFiClient client;
|
||||
if (!client.connect(routerIP, routerPort)) { // UPnP uses port 1900 for SOAP requests
|
||||
ESP_LOGI(TAG_UPNP,"Failed to connect to router: %s\n", routerIP.toString().c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
client.print("POST /control?WANIPConn1 HTTP/1.1\r\n");
|
||||
client.print("Host: ");
|
||||
client.print(routerIP);
|
||||
client.print("\r\n");
|
||||
client.print("Content-Type: text/xml; charset=\"utf-8\"\r\n");
|
||||
client.print("SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetSpecificPortMappingEntry\"\r\n");
|
||||
client.print("Content-Length: ");
|
||||
client.print(strlen(request));
|
||||
client.print("\r\n\r\n");
|
||||
client.print(request);
|
||||
|
||||
unsigned long startMillis = millis();
|
||||
while (!client.available() && millis() - startMillis < 5000) {
|
||||
delay(10); // Wait for response
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while (client.available() && index < responseSize - 1) {
|
||||
response[index++] = client.read();
|
||||
}
|
||||
response[index] = '\0';
|
||||
|
||||
client.stop();
|
||||
return index;
|
||||
}
|
||||
|
||||
bool CUpnpClient::requestPortMappingEntry() {
|
||||
ESP_LOGI(TAG_UPNP,"** Requesting UPnP Port Mapping Entry...");
|
||||
char soapRequest[512];
|
||||
char mappingName[32];
|
||||
sprintf(mappingName, "HC_%04X", publicPort);
|
||||
|
||||
snprintf(soapRequest, sizeof(soapRequest),
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
|
||||
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
"<s:Body>"
|
||||
"<u:GetSpecificPortMappingEntry xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"
|
||||
"<NewRemoteHost></NewRemoteHost>"
|
||||
"<NewExternalPort>%d</NewExternalPort>"
|
||||
"<NewProtocol>TCP</NewProtocol>"
|
||||
"<NewInternalClient>%s</NewInternalClient>"
|
||||
"<NewPortMappingDescription>%s</NewPortMappingDescription>"
|
||||
"</u:GetSpecificPortMappingEntry>"
|
||||
"</s:Body>"
|
||||
"</s:Envelope>",
|
||||
publicPort,
|
||||
WiFi.localIP().toString().c_str(),
|
||||
mappingName);
|
||||
|
||||
char response[1024];
|
||||
int responseLen = sendSoapRequest(soapRequest, response, sizeof(response));
|
||||
|
||||
if (responseLen > 0) {
|
||||
if (strstr(response, mappingName)) {
|
||||
//ESP_LOGI(TAG_UPNP," Port mapping already exists.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG_UPNP," Port mapping not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CUpnpClient::requestPortForwarding() {
|
||||
// Sending port-forwarding request to the router's external IP or gateway
|
||||
ESP_LOGI(TAG_UPNP,"** Requesting UPnP Port Forwarding...");
|
||||
|
||||
if (controlURL.isEmpty()) {
|
||||
ESP_LOGI(TAG_UPNP,"Error: controlURL is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure controlURL is correctly formatted (handle absolute/relative cases)
|
||||
String postURL = controlURL;
|
||||
if (postURL.startsWith("http://") || postURL.startsWith("https://")) {
|
||||
int pathStart = postURL.indexOf('/', 8); // Skip "http://"
|
||||
if (pathStart != -1) {
|
||||
postURL = postURL.substring(pathStart); // Extract path only
|
||||
ESP_LOGI(TAG_UPNP,"PostURL: \"%s\"\n", postURL.c_str());
|
||||
} else {
|
||||
ESP_LOGI(TAG_UPNP,"Invalid controlURL format.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// XML body
|
||||
char xmlBody[800];
|
||||
snprintf(xmlBody, sizeof(xmlBody),
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
"<s:Body><u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"
|
||||
"<NewRemoteHost></NewRemoteHost>"
|
||||
"<NewExternalPort>%d</NewExternalPort>"
|
||||
"<NewProtocol>TCP</NewProtocol>"
|
||||
"<NewInternalPort>%d</NewInternalPort>"
|
||||
"<NewInternalClient>%s</NewInternalClient>"
|
||||
"<NewEnabled>1</NewEnabled>"
|
||||
"<NewPortMappingDescription>HC_%04X</NewPortMappingDescription>"
|
||||
"<NewLeaseDuration>0</NewLeaseDuration>"
|
||||
"</u:AddPortMapping></s:Body></s:Envelope>",
|
||||
publicPort, publicPort,
|
||||
WiFi.localIP().toString().c_str(),
|
||||
publicPort);
|
||||
//ESP_LOGI(TAG_UPNP,"XML Body: ");
|
||||
//ESP_LOGI(TAG_UPNP,xmlBody);
|
||||
|
||||
|
||||
// Calculate the correct content length
|
||||
int contentLength = strlen(xmlBody);
|
||||
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n"
|
||||
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
|
||||
"SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"\r\n"
|
||||
"%s", // Append XML body after headers
|
||||
controlURL.c_str(),
|
||||
routerIP.toString().c_str(), routerPort,
|
||||
contentLength,
|
||||
xmlBody);
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"==== HTTP Request ===\n\n%s\n\n--- End Of HTTP ---\n", buffer);
|
||||
|
||||
// Connect and send
|
||||
WiFiClient client;
|
||||
if (!client.connect(routerIP, routerPort)) {
|
||||
ESP_LOGI(TAG_UPNP,"Failed to connect to the router. %s:%d\n", routerIP.toString().c_str(), routerPort);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.print(buffer);
|
||||
|
||||
// Read response
|
||||
String response;
|
||||
uint32_t timeout = millis() + 5000;
|
||||
while (client.available() == 0) {
|
||||
if (millis() > timeout) {
|
||||
ESP_LOGI(TAG_UPNP,"Router did not respond to port mapping request.");
|
||||
client.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (client.available()) {
|
||||
response += client.readString();
|
||||
}
|
||||
client.stop();
|
||||
return true;
|
||||
};
|
||||
|
||||
bool CUpnpClient::requestExternalIP() {
|
||||
//ESP_LOGI(TAG_UPNP,"\n\nRequesting External IP via UPnP...");
|
||||
|
||||
// XML Request Body
|
||||
char xmlBody[300];
|
||||
snprintf(xmlBody, sizeof(xmlBody),
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
"<s:Body>"
|
||||
"<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>"
|
||||
"</s:Body>"
|
||||
"</s:Envelope>");
|
||||
|
||||
int contentLength = strlen(xmlBody);
|
||||
|
||||
// HTTP Request
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n"
|
||||
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
|
||||
"SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress\"\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"\r\n"
|
||||
"%s",
|
||||
controlURL.c_str(),
|
||||
routerIP.toString().c_str(), routerPort,
|
||||
contentLength,
|
||||
xmlBody);
|
||||
|
||||
// Connect and Send
|
||||
WiFiClient client;
|
||||
if (!client.connect(routerIP, routerPort)) {
|
||||
DPRINTLN("Failed to connect to router for External IP request.");
|
||||
return false;
|
||||
}
|
||||
|
||||
client.print(buffer);
|
||||
|
||||
// Read Response
|
||||
String response;
|
||||
uint32_t timeout = millis() + 5000;
|
||||
while (client.available() == 0) {
|
||||
if (millis() > timeout) {
|
||||
DPRINTLN("Router did not respond to External IP request.");
|
||||
client.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (client.available()) {
|
||||
response += client.readString();
|
||||
}
|
||||
client.stop();
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"External IP Response:");
|
||||
//ESP_LOGI(TAG_UPNP,response);
|
||||
|
||||
// Extract External IP from XML
|
||||
char *sz = (char *)(response.c_str());
|
||||
char *extIP = strstr(sz, "<NewExternalIPAddress>");
|
||||
if (extIP) {
|
||||
extIP += 22;
|
||||
char *end = strchr(extIP, '<');
|
||||
if (end) *end = '\0';
|
||||
|
||||
publicIP = IPAddress(extIP);
|
||||
DPRINTF("UPnP - External IP: %s\n", extIP);
|
||||
return true;
|
||||
}
|
||||
|
||||
DPRINTLN("Failed to extract External IP.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CUpnpClient::requestExternalPort() {
|
||||
//ESP_LOGI(TAG_UPNP,"\n\nRequesting External Port via UPnP...");
|
||||
|
||||
// XML Request Body for getting External Port
|
||||
char xmlBody[300];
|
||||
snprintf(xmlBody, sizeof(xmlBody),
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
"<s:Body>"
|
||||
"<u:GetExternalPort xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>"
|
||||
"</s:Body>"
|
||||
"</s:Envelope>");
|
||||
|
||||
int contentLength = strlen(xmlBody);
|
||||
|
||||
// HTTP Request to get External Port
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n"
|
||||
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
|
||||
"SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalPort\"\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"\r\n"
|
||||
"%s",
|
||||
controlURL.c_str(),
|
||||
routerIP.toString().c_str(), routerPort,
|
||||
contentLength,
|
||||
xmlBody);
|
||||
|
||||
// Connect and Send
|
||||
WiFiClient client;
|
||||
if (!client.connect(routerIP, routerPort)) {
|
||||
ESP_LOGI(TAG_UPNP,"Failed to connect to router for External Port request.");
|
||||
return false;
|
||||
}
|
||||
|
||||
client.print(buffer);
|
||||
|
||||
// Read Response
|
||||
String response;
|
||||
uint32_t timeout = millis() + 5000;
|
||||
while (client.available() == 0) {
|
||||
if (millis() > timeout) {
|
||||
ESP_LOGI(TAG_UPNP,"Router did not respond to External Port request.");
|
||||
client.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (client.available()) {
|
||||
response += client.readString();
|
||||
}
|
||||
client.stop();
|
||||
|
||||
//ESP_LOGI(TAG_UPNP,"External Port Response:");
|
||||
//ESP_LOGI(TAG_UPNP,response);
|
||||
|
||||
// Extract External Port from XML
|
||||
char *sz = (char *)(response.c_str());
|
||||
char *extPort = strstr(sz, "<NewExternalPort>");
|
||||
if (extPort) {
|
||||
extPort += 17;
|
||||
char *end = strchr(extPort, '<');
|
||||
if (end) *end = '\0';
|
||||
|
||||
publicPort = atoi(extPort);
|
||||
ESP_LOGI(TAG_UPNP,"External Port: %d\n", publicPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG_UPNP,"Failed to extract External Port.");
|
||||
return false;
|
||||
};
|
||||
170
src/firmware_download.php
Normal file
170
src/firmware_download.php
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
/**
|
||||
* [firmware_download.php] - CLEAN PRODUCTION VERSION
|
||||
* --------------------------------------------------------------------------------------------
|
||||
* PURPOSE:
|
||||
* - Serves firmware binaries to ESP32 clients using a "Stepped" update logic.
|
||||
* - Validates client identity via User-Agent and specific hardware headers.
|
||||
* - Scans the './firmware' directory for files matching: {Project}.{Chip}.{Version}.{SubVer}.bin
|
||||
* - Selects the next available version (lowest version that is higher than the client's current).
|
||||
* * WORKFLOW:
|
||||
* 1. Identity Check: Rejects any request not matching 'ESP32-http-Update' UA.
|
||||
* 2. Header Check: Validates MAC and system health headers from HCUpdate.cpp.
|
||||
* 3. Matching: Filters local files by Project Name and 'ESP32' chipset.
|
||||
* 4. Streaming: Delivers the binary with MD5 and Content-Length for ESP32 verification.
|
||||
* --------------------------------------------------------------------------------------------
|
||||
* Revision History:
|
||||
* 2026.04.08 - [RnD16] Stripped logging for production performance.
|
||||
* 2026.04.08 - [RnD17] Final production locking with detailed inline commentary.
|
||||
*/
|
||||
|
||||
header('Content-type: text/plain; charset=utf8', true);
|
||||
|
||||
// --- 1. CORE FUNCTIONS ---
|
||||
|
||||
/**
|
||||
* Validates existence and value of HTTP headers passed by the web server
|
||||
* Note: PHP converts 'X-Header-Name' to 'HTTP_X_HEADER_NAME'
|
||||
*/
|
||||
function check_header($name, $value = false) {
|
||||
if(!isset($_SERVER[$name])) return false;
|
||||
if($value && $_SERVER[$name] != $value) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the actual binary delivery to the MCU
|
||||
* Sends both standard HTTP headers and custom X-headers for HCUpdate logic
|
||||
*/
|
||||
function sendFile($path, $newVersion) {
|
||||
// =========================================================
|
||||
// MANDATORY HEADERS FOR CLIENT-SIDE HCUpdate.cpp DECISION
|
||||
// =========================================================
|
||||
header("X-New-Version: ".$newVersion);
|
||||
header("X-Update-Required: 1");
|
||||
header("version: ". $newVersion); // Legacy support header
|
||||
header("update: 1"); // Legacy support header
|
||||
// =========================================================
|
||||
|
||||
header($_SERVER["SERVER_PROTOCOL"].' 200 OK', true, 200);
|
||||
header('Content-Type: application/octet-stream', true);
|
||||
|
||||
// basename() ensures we don't leak the internal server folder structure
|
||||
header('Content-Disposition: attachment; filename='.basename($path));
|
||||
|
||||
// Critical for ESP32 Update.begin(size)
|
||||
header('Content-Length: '.filesize($path), true);
|
||||
|
||||
// Used by ESP32 to verify file integrity after download
|
||||
header('x-MD5: '.md5_file($path), true);
|
||||
|
||||
// Stream the actual bytes
|
||||
readfile($path);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardized exit for cases where no update is found or access is denied
|
||||
*/
|
||||
function stop_and_exit($headerCode = 200, $headerMsg = "") {
|
||||
// =========================================================
|
||||
// FORCE UPDATE STATE TO FALSE ON EXIT
|
||||
// =========================================================
|
||||
header("X-Update-Required: 0");
|
||||
header("update: 0");
|
||||
// =========================================================
|
||||
|
||||
if ($headerCode != 200) {
|
||||
header($_SERVER["SERVER_PROTOCOL"]." $headerCode $headerMsg", true, $headerCode);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
// --- 2. CONFIGURATION ---
|
||||
// Local directory where .bin files are stored
|
||||
$firmwareBaseDir = realpath(__DIR__ . '/../firmware') . DIRECTORY_SEPARATOR;
|
||||
|
||||
// --- 3. STRICT IDENTITY & HEADER VALIDATION ---
|
||||
|
||||
// 1. User-Agent must be exactly 'ESP32-http-Update'
|
||||
if(!check_header('HTTP_USER_AGENT', 'ESP32-http-Update')) {
|
||||
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden agent check', true, 403);
|
||||
echo "UserAgent: only for ESP32 updater!\n";
|
||||
exit();
|
||||
}
|
||||
|
||||
// 2. Mandatory system headers must be present (MAC, Size, MD5, ChipInfo)
|
||||
if( !check_header('HTTP_X_ESP32_STA_MAC') ||
|
||||
!check_header('HTTP_X_ESP32_SKETCH_SIZE') ||
|
||||
!check_header('HTTP_X_ESP32_SKETCH_MD5') ||
|
||||
!check_header('HTTP_X_ESP32_CHIP_SIZE') )
|
||||
{
|
||||
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden headercheck', true, 403);
|
||||
echo "Header: only for ESP32 updater!";
|
||||
exit();
|
||||
}
|
||||
|
||||
// --- 4. PROJECT & VERSION EXTRACTION ---
|
||||
if (!isset($_SERVER['HTTP_X_ESP_PROJECT']) || !isset($_SERVER['HTTP_X_ESP_VERSION'])) {
|
||||
stop_and_exit(400, "Bad Request: Missing Project/Version");
|
||||
}
|
||||
|
||||
$rawProj = trim($_SERVER['HTTP_X_ESP_PROJECT']);
|
||||
$rawVer = trim($_SERVER['HTTP_X_ESP_VERSION']);
|
||||
|
||||
// Sanitization: Ensure paths/logic aren't broken by special characters
|
||||
$targetProject = preg_replace("/[^a-zA-Z0-9]/", "-", $rawProj);
|
||||
$targetVersion = preg_replace("/[^0-9]/", "", $rawVer);
|
||||
|
||||
// --- 5. SCANNING LOGIC ---
|
||||
$availableUpdates = [];
|
||||
$serverNewestVersion = "0";
|
||||
|
||||
if (is_dir($firmwareBaseDir)) {
|
||||
// Filter out linux directory navigation dots
|
||||
$files = array_diff(scandir($firmwareBaseDir), array('.', '..'));
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Expected format: ProjectName.Chipset.Version.Subversion.bin
|
||||
$parts = explode('.', $file);
|
||||
if (count($parts) >= 5) {
|
||||
$fProjectRaw = trim($parts[0]);
|
||||
$fChipsetRaw = trim($parts[1]);
|
||||
|
||||
// Reconstruct version as numeric string for comparison (e.g., 20260408001)
|
||||
$fVersion = preg_replace("/[^0-9]/", "", $parts[2]) . preg_replace("/[^0-9]/", "", $parts[3]);
|
||||
|
||||
// Only consider files belonging to this project and the ESP32 chipset
|
||||
if (strcasecmp($fProjectRaw, $targetProject) === 0 && strcasecmp($fChipsetRaw, 'ESP32') === 0) {
|
||||
if ((float)$fVersion > (float)$serverNewestVersion) $serverNewestVersion = $fVersion;
|
||||
|
||||
// If file version > client version, add to potential candidates
|
||||
if ((float)$fVersion > (float)$targetVersion) {
|
||||
$availableUpdates[$fVersion] = $firmwareBaseDir . $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 6. FINAL DECISION ---
|
||||
if (!empty($availableUpdates)) {
|
||||
// Sort ascending to find the NEAREST (next) version for a stepped upgrade
|
||||
ksort($availableUpdates);
|
||||
$nextVer = array_key_first($availableUpdates);
|
||||
$targetFile = $availableUpdates[$nextVer];
|
||||
|
||||
sendFile($targetFile, $nextVer);
|
||||
} else {
|
||||
// =========================================================
|
||||
// NO UPDATE FOUND: RETURN 304 NOT MODIFIED
|
||||
// =========================================================
|
||||
header("X-Update-Required: 0");
|
||||
header("update: 0");
|
||||
header("X-New-Version: " . $rawVer);
|
||||
header("version: 000000000"); // Reset display for client UI if needed
|
||||
header($_SERVER["SERVER_PROTOCOL"]." 304 Not Modified", true, 304);
|
||||
exit();
|
||||
// =========================================================
|
||||
}
|
||||
?>
|
||||
Loading…
Reference in New Issue
Block a user