-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy pathesp32FOTA.hpp
405 lines (334 loc) · 16.4 KB
/
esp32FOTA.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/*
esp32 firmware OTA
Date: December 2018
Author: Chris Joyce <https://github.com/chrisjoyce911/esp32FOTA/esp32FOTA>
Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only)
Date: 2021-12-21
Author: Moritz Meintker <https://thinksilicon.de>
Remarks: Re-written/removed a bunch of functions around HTTPS. The library is
now URL-agnostic. This means if you provide an https://-URL it will
use the root_ca.pem (needs to be provided via PROGMEM/SPIFFS/LittleFS or SD)
to verify the server certificate and then download the ressource through an
encrypted connection unless you set the allow_insecure_https option.
Otherwise it will just use plain HTTP which will still offer to sign
your firmware image.
Date: 2022-09-12
Author: tobozo <https://github.com/tobozo>
Changes:
- Abstracted away filesystem
- Refactored some code blocks
- Added spiffs/littlefs/fatfs updatability
- Made crypto assets (pub key, rootca) loadable from multiple sources
Roadmap:
- Firmware/FlashFS update order (SPIFFS/LittleFS first or last?)
- Archive support for gz/targz formats
- firmware.gz + spiffs.gz in manifest
- bundle.tar.gz [ firmware + filesystem ] in manifest
- Update from Stream (e.g deported update via SD, http or gzupdater)
*/
#pragma once
#define esp32fota_h
extern "C" {
#include "semver/semver.h"
}
#include <map>
#include <WiFi.h>
// arduino-esp32 core 2.x => 3.x migration
#if __has_include(<NetworkClientSecure.h>)
#include <NetworkClientSecure.h>
#define ClientSecure NetworkClientSecure
#else
#include <WiFiClientSecure.h>
#define ClientSecure WiFiClientSecure
#endif
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <FS.h>
// inherit includes from sketch, detect SPIFFS first for legacy support
#if __has_include(<SPIFFS.h>) || defined _SPIFFS_H_
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "Using SPIFFS for certificate validation"
#endif
#include <SPIFFS.h>
#define FOTA_FS &SPIFFS
#elif __has_include(<LittleFS.h>) || defined _LiffleFS_H_
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "Using LittleFS for certificate validation"
#endif
#include <LittleFS.h>
#define FOTA_FS &LittleFS
#elif __has_include(<SD.h>) || defined _SD_H_
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "Using SD for certificate validation"
#endif
#include <SD.h>
#define FOTA_FS &SD
#elif __has_include(<SD_MMC.h>) || defined _SD_MMC_H_
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "Using SD_MMC for certificate validation"
#endif
#include <SD_MMC.h>
#define FOTA_FS &SD_MMC
#elif defined _LIFFLEFS_H_ // older externally linked, hard to identify and unsupported versions of SPIFFS
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "this version of LittleFS is unsupported, use #include <LittleFS.h> instead, if using platformio add LittleFS(esp32)@^2.0.0 to lib_deps"
#endif
#elif __has_include(<PSRamFS.h>) || defined _PSRAMFS_H_
#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
#pragma message "Using PSRamFS for certificate validation"
#endif
#include <PSRamFS.h>
#define FOTA_FS &PSRamFS
#else
//#if !defined(DISABLE_ALL_LIBRARY_WARNINGS)
// #pragma message "No filesystem provided, certificate validation will be unavailable (hint: include SD, SPIFFS or LittleFS before including this library)"
//#endif
#define FOTA_FS nullptr
#endif
#if __has_include(<flashz.hpp>)
#pragma message "Using FlashZ as Update agent"
#include <flashz.hpp>
#define F_Compression "zlib"
#define F_hasZlib() true
#define F_Update FlashZ::getInstance()
#define F_UpdateEnd() (mode_z ? F_Update.endz() : F_Update.end())
#define F_abort() if (mode_z) F_Update.abortz(); else F_Update.abort()
#define F_writeStream() (mode_z ? F_Update.writezStream(*_stream, updateSize) : F_Update.writeStream(*_stream))
// #define DEBUG_ESP32_FLASHZ
#if !defined DEBUG_ESP32_FLASHZ
#define F_isZlibStream() (_stream->peek() == ZLIB_HEADER && ((partition == U_SPIFFS && _flashFileSystemUrl.indexOf("zz")>-1) || (partition == U_FLASH && _firmwareUrl.indexOf("zz")>-1)))
#define F_canBegin() (mode_z ? F_Update.beginz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition))
#else
__attribute__((unused)) static bool F_canBegin_cb(bool mode_z, int updateSize, int partition) { // implement debug here
return (mode_z ? F_Update.beginz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition));
}
__attribute__((unused)) static bool F_isZlibStream_cb( Stream* stream, int partition, String flashFileSystemUrl, String firmwareUrl ) { // implement debug here
return (stream->peek() == ZLIB_HEADER && ((partition == U_SPIFFS && flashFileSystemUrl.indexOf("zz")>-1) || (partition == U_FLASH && firmwareUrl.indexOf("zz")>-1)));
}
#define F_isZlibStream() F_isZlibStream_cb( _stream, partition, _flashFileSystemUrl, _firmwareUrl )
#define F_canBegin() F_canBegin_cb(mode_z, updateSize, partition)
#endif
#elif __has_include("ESP32-targz.h")
#pragma message "Using GzUpdateClass as Update agent"
#include <ESP32-targz.h>
#define F_Compression "gzip"
#define F_hasZlib() true
#define F_Update GzUpdateClass::getInstance()
#define F_UpdateEnd() (mode_z ? F_Update.endgz() : F_Update.end())
#define F_abort() if (mode_z) F_Update.abortgz(); else F_Update.abort()
#define F_writeStream() (mode_z ? F_Update.writeGzStream(*_stream, updateSize) : F_Update.writeStream(*_stream))
// #define DEBUG_ESP32_TARGZ
#if !defined DEBUG_ESP32_TARGZ
#define F_isZlibStream() (_stream->peek() == 0x1f && ((partition == U_SPIFFS && _flashFileSystemUrl.indexOf("gz")>-1) || (partition == U_FLASH && _firmwareUrl.indexOf("gz")>-1)) )
#define F_canBegin() (mode_z ? F_Update.begingz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition))
#else
__attribute__((unused)) static bool F_canBegin_cb(bool mode_z, int updateSize, int partition) { // implement debug here
return (mode_z ? F_Update.begingz(UPDATE_SIZE_UNKNOWN, partition) : F_Update.begin(updateSize, partition));
}
__attribute__((unused)) static bool F_isZlibStream_cb( Stream* stream, int partition, String flashFileSystemUrl, String firmwareUrl ) { // implement debug here
return (stream->peek() == 0x1f && ((partition == U_SPIFFS && flashFileSystemUrl.indexOf("gz")>-1) || (partition == U_FLASH && firmwareUrl.indexOf("gz")>-1)) );
}
#define F_isZlibStream() F_isZlibStream_cb( _stream, partition, _flashFileSystemUrl, _firmwareUrl )
#define F_canBegin() F_canBegin_cb(mode_z, updateSize, partition)
#endif
#else
#include <Update.h>
#define F_Compression "none"
#define F_Update Update
#define F_hasZlib() false
#define F_isZlibStream() false
#define F_canBegin() F_Update.begin(updateSize, partition)
#define F_UpdateEnd() F_Update.end()
#define F_abort() F_Update.abort()
#define F_writeStream() F_Update.writeStream(*_stream);
#endif
#define FW_SIGNATURE_LENGTH 512
struct SemverClass
{
public:
SemverClass( const char* version );
SemverClass( int major, int minor=0, int patch=0 );
~SemverClass() { semver_free(&_ver); }
semver_t* ver();
private:
semver_t _ver = semver_t();
};
// Filesystem/memory helper for signature check and pem validation.
// This is abstracted away to allow storage alternatives such as
// PROGMEM, SD, SPIFFS, LittleFS or FatFS
// Intended to be used by esp32FOTA.setPubKey() and esp32FOTA.setRootCA()
class CryptoAsset
{
public:
virtual size_t size() = 0;
virtual const char* get() = 0;
};
class CryptoFileAsset : public CryptoAsset
{
public:
CryptoFileAsset( const char* _path, fs::FS* _fs ) : path(_path), fs(_fs), contents(""), len(0) { }
size_t size();
const char* get() { return contents.c_str(); }
private:
const char* path;
fs::FS* fs;
std::string contents;
size_t len;
bool fs_read_file();
};
class CryptoMemAsset : public CryptoAsset
{
public:
CryptoMemAsset( const char* _name, const char* _bytes, size_t _len ) : name(_name), bytes(_bytes), len(_len) { }
size_t size() { return len; };
const char* get() { return bytes; }
private:
const char* name;
const char* bytes;
size_t len;
};
struct FOTAConfig_t
{
char* name { nullptr };
char* manifest_url { nullptr };
SemverClass sem {0};
bool check_sig { false };
bool unsafe { false };
bool use_device_id { false };
CryptoAsset* root_ca { nullptr };
CryptoAsset* pub_key { nullptr };
size_t signature_len {FW_SIGNATURE_LENGTH};
bool allow_reuse { true };
bool use_http10 { false }; // Use HTTP 1.0 (WARNING: setting to 'true' disables chunked transfers)
FOTAConfig_t() = default;
};
enum FOTAStreamType_t
{
FOTA_HTTP_STREAM,
FOTA_FILE_STREAM,
FOTA_SERIAL_STREAM
};
// Main Class
class esp32FOTA
{
public:
esp32FOTA();
~esp32FOTA();
esp32FOTA( FOTAConfig_t cfg );
esp32FOTA(const char* firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false );
esp32FOTA(const String &firwmareType, int firwmareVersion, bool validate = false, bool allow_insecure_https = false )
: esp32FOTA(firwmareType.c_str(), firwmareVersion, validate, allow_insecure_https){};
esp32FOTA(const char* firwmareType, const char* firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false );
esp32FOTA(const String &firwmareType, const String &firmwareSemanticVersion, bool validate = false, bool allow_insecure_https = false )
: esp32FOTA(firwmareType.c_str(), firmwareSemanticVersion.c_str(), validate, allow_insecure_https){};
template <typename T> void setPubKey( T* asset ) { _cfg.pub_key = (CryptoAsset*)asset; _cfg.check_sig = true; }
template <typename T> void setRootCA( T* asset ) { _cfg.root_ca = (CryptoAsset*)asset; _cfg.unsafe = false; }
bool forceUpdate(const char* firmwareHost, uint16_t firmwarePort, const char* firmwarePath, bool validate );
bool forceUpdate(const char* firmwareURL, bool validate );
bool forceUpdate(bool validate );
bool forceUpdateSPIFFS(const char* firmwareURL, bool validate );
void handle();
bool execOTA();
bool execSPIFFSOTA();
bool execOTA( int partition, bool restart_after = true );
bool execHTTPcheck();
void useDeviceId( bool use=true ) { _cfg.use_device_id = use; }
// config setter
void setConfig( FOTAConfig_t cfg );
void printConfig( FOTAConfig_t *cfg=nullptr );
// Manually specify the manifest url, this is provided as a transition between legagy and new config system
void setManifestURL( const char* manifest_url ) { setString( &_cfg.manifest_url, manifest_url ); }
void setManifestURL( const String &manifest_url ) { setManifestURL( manifest_url.c_str() ); }
// use this to set "Authorization: Basic" or other specific headers to be sent with the queries
void setExtraHTTPHeader( String name, String value ) { extraHTTPHeaders[name] = value; }
// set the signature len
void setSignatureLen( size_t len );
// /!\ Only use this to change filesystem for **default** RootCA and PubKey paths.
// Otherwise use setPubKey() and setRootCA()
void setCertFileSystem( fs::FS *cert_filesystem = nullptr );
// this is passed to Update.onProgress()
typedef std::function<void(size_t,size_t)> ProgressCallback_cb; // size_t progress, size_t size
void setProgressCb(ProgressCallback_cb fn) { onOTAProgress = fn; } // callback setter
// when Update.begin() returned false
typedef std::function<void(int)> UpdateBeginFail_cb; // int partition (U_FLASH or U_SPIFFS)
void setUpdateBeginFailCb(UpdateBeginFail_cb fn) { onUpdateBeginFail = fn; } // callback setter
// after Update.end() and before validate_sig()
typedef std::function<void(int)> UpdateEnd_cb; // int partition (U_FLASH or U_SPIFFS)
void setUpdateEndCb(UpdateEnd_cb fn) { onUpdateEnd = fn; } // callback setter
// validate_sig() error handling, mixed situations
typedef std::function<void(int,int)> UpdateCheckFail_cb; // int partition (U_FLASH or U_SPIFFS), int error_code
void setUpdateCheckFailCb(UpdateCheckFail_cb fn) { onUpdateCheckFail = fn; } // callback setter
// update successful
typedef std::function<void(int,bool)> UpdateFinished_cb; // int partition (U_FLASH or U_SPIFFS), bool restart_after
void setUpdateFinishedCb(UpdateFinished_cb fn) { onUpdateFinished = fn; } // callback setter
// stream getter
typedef std::function<int64_t(esp32FOTA*,int)> getStream_cb; // esp32FOTA* this, int partition (U_FLASH or U_SPIFFS), returns stream size
void setStreamGetter( getStream_cb fn ) { getStream = fn; } // callback setter
// stream ender
typedef std::function<void(esp32FOTA*)> endStream_cb; // esp32FOTA* this
void setStreamEnder( endStream_cb fn ) { endStream = fn; } // callback setter
// connection check
typedef std::function<bool()> isConnected_cb; //
void setStatusChecker( isConnected_cb fn ) { isConnected = fn; } // callback setter
// updating from a File or from Serial?
void setStreamType( FOTAStreamType_t stream_type ) { _stream_type = stream_type; }
void setStreamTimeout( uint32_t timeout ) { _stream_timeout = timeout; }
const char* getManifestURL() { return _manifestUrl.c_str(); }
const char* getFirmwareURL() { return _firmwareUrl.c_str(); }
const char* getFlashFS_URL() { return _flashFileSystemUrl.c_str(); }
const char* getPath(int part) { return part==U_SPIFFS ? getFlashFS_URL() : getFirmwareURL(); }
bool zlibSupported() { return mode_z; }
int getPayloadVersion();
void getPayloadVersion(char * version_string);
FOTAConfig_t getConfig() { return _cfg; };
FOTAStreamType_t getStreamType() { return _stream_type; }
HTTPClient* getHTTPCLient() { return &_http; }
ClientSecure* getWiFiClient() { return &_client; }
fs::File* getFotaFilePtr() { return &_file; }
Stream* getFotaStreamPtr() { return _stream; }
fs::FS* getFotaFS() { return _fs; }
// internals but need to be exposed to the callbacks
bool setupHTTP( const char* url );
void setFotaStream( Stream* stream ) { _stream = stream; }
//[[deprecated("Use setManifestURL( String ) or cfg.manifest_url with setConfig( FOTAConfig_t )")]] String checkURL = "";
//[[deprecated("Use cfg.use_device_id with setConfig( FOTAConfig_t )")]] bool useDeviceID = false;
private:
HTTPClient _http;
ClientSecure _client;
Stream *_stream;
fs::File _file;
bool mode_z = F_hasZlib();
FOTAStreamType_t _stream_type = FOTA_HTTP_STREAM; // defaults to HTTP
uint32_t _stream_timeout = 10000; // max wait for stream->available()
void setupStream();
void stopStream();
void setString( char **dest, const char* src ); // mem allocator
FOTAConfig_t _cfg;
SemverClass _payload_sem = SemverClass(0,0,0);
String _manifestUrl;
String _firmwareUrl;
String _flashFileSystemUrl;
fs::FS *_fs = FOTA_FS; // default filesystem for certificate validation
// custom callbacks provided by user
ProgressCallback_cb onOTAProgress; // this is passed to Update.onProgress()
UpdateBeginFail_cb onUpdateBeginFail; // when Update.begin() returned false
UpdateEnd_cb onUpdateEnd; // after Update.end() and before validate_sig()
UpdateCheckFail_cb onUpdateCheckFail; // validate_sig() error handling, mixed situations
UpdateFinished_cb onUpdateFinished; // update successful
getStream_cb getStream; // optional stream getter, defaults to http.getStreamPtr()
endStream_cb endStream; // optional stream closer, defaults to http.end()
isConnected_cb isConnected; // optional connection checker, defaults to WiFi.status()==WL_CONNECTED
std::map<String,String> extraHTTPHeaders; // this holds the extra http headers defined by the user
String getDeviceID();
bool checkJSONManifest(JsonVariant JSONDocument);
void debugSemVer( const char* label, semver_t* version );
void getPartition( int update_partition );
bool validate_sig( const esp_partition_t* partition, unsigned char *signature, uint32_t firmware_size );
// temporary partition holder for signature check operations
const esp_partition_t* _target_partition = nullptr;
// This is kept for legacy behaviour, use setPubKey() and setRootCA() with
// CryptoMemAsset ot CryptoFileAsset instead
void setupCryptoAssets();
const char* rsa_key_pub_default_path = "/rsa_key.pub";
const char* root_ca_pem_default_path = "/root_ca.pem";
};